diff --git a/web/cypress/e2e/create-account.cy.ts b/web/cypress/e2e/create-account.cy.ts index 667690f9a..16ef3b1ef 100644 --- a/web/cypress/e2e/create-account.cy.ts +++ b/web/cypress/e2e/create-account.cy.ts @@ -195,4 +195,69 @@ describe('Create Account Page', () => { cy.contains('Already have an account?'); cy.contains('Sign in'); }); + + it('Shows email verification message when awaiting_verification is true', () => { + const testUser = { + username: `testuser${Date.now()}`, + email: `test${Date.now()}@example.com`, + password: 'validpassword123', + }; + + // Setup account creation with awaiting_verification response + cy.intercept('POST', '/api/v1/user/', { + statusCode: 200, + body: {awaiting_verification: true}, + }).as('createUserAwaitingVerification'); + + // Setup signin intercept to verify it's NOT called + cy.intercept('POST', '/api/v1/signin', (req) => { + throw new Error('Signin should not be called when awaiting verification'); + }).as('signinShouldNotBeCalled'); + + cy.visit('/createaccount'); + + // Fill form with valid data + cy.get('#username').type(testUser.username); + cy.get('#email').type(testUser.email); + cy.get('#password').type(testUser.password); + cy.get('#confirm-password').type(testUser.password); + + // Submit form + cy.get('button[type=submit]').click(); + + // Wait for create user API call + cy.wait('@createUserAwaitingVerification').then((interception) => { + expect(interception.request.body).to.deep.equal({ + username: testUser.username, + email: testUser.email, + password: testUser.password, + }); + }); + + // Should show verification message + cy.get('[data-testid="awaiting-verification-alert"]').should('be.visible'); + cy.contains( + 'Thank you for registering! We have sent you an activation email.', + ); + cy.contains('verify your email address').should('be.visible'); + + // Form should be hidden (check visibility, not existence since display:none keeps elements in DOM) + cy.get('#username').should('not.be.visible'); + cy.get('#email').should('not.be.visible'); + cy.get('#password').should('not.be.visible'); + + // Should not redirect to organization page + cy.url().should('include', '/createaccount'); + cy.url().should('not.include', '/organization'); + + // Should show sign in link (it's outside the form when awaiting verification) + // Find the visible link that's not inside a hidden form + cy.get('a[href="/signin"]').should('be.visible'); + // The text should also be visible (rendered outside the hidden form) + cy.contains('body', 'Already have an account?').should('be.visible'); + + // Verify no auto-login was attempted - signin API should not be called + // If signin was attempted, it would have thrown an error from the intercept + cy.wait(500); // Small wait to ensure no signin API call is made + }); }); diff --git a/web/src/hooks/UseCreateAccount.ts b/web/src/hooks/UseCreateAccount.ts index 53a1f7186..767213b66 100644 --- a/web/src/hooks/UseCreateAccount.ts +++ b/web/src/hooks/UseCreateAccount.ts @@ -24,11 +24,17 @@ export function useCreateAccount() { try { // Create the user account - await createUser(username, password, email); + const response = await createUser(username, password, email); // Clear CSRF token after account creation (session state changed) GlobalAuthState.csrfToken = null; + // Check if email verification is required + if (response.awaiting_verification === true) { + // Email verification required, return success but indicate verification is needed + return {success: true, awaitingVerification: true}; + } + // Auto-login after successful account creation try { const loginResponse = await loginUser(username, password); diff --git a/web/src/resources/UserResource.ts b/web/src/resources/UserResource.ts index e853f2c37..1e7e27e49 100644 --- a/web/src/resources/UserResource.ts +++ b/web/src/resources/UserResource.ts @@ -122,11 +122,16 @@ export interface CreateUserRequest { email: string; } +export interface CreateUserResponse { + awaiting_verification?: boolean; + [key: string]: any; // Allow other fields from the API response +} + export async function createUser( username: string, password: string, email: string, -) { +): Promise { const response = await axios.post('/api/v1/user/', { username, password, diff --git a/web/src/routes/CreateAccount/CreateAccount.tsx b/web/src/routes/CreateAccount/CreateAccount.tsx index 05d619309..18dc43e5f 100644 --- a/web/src/routes/CreateAccount/CreateAccount.tsx +++ b/web/src/routes/CreateAccount/CreateAccount.tsx @@ -22,6 +22,7 @@ export function CreateAccount() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [confirmPassword, setConfirmPassword] = useState(''); + const [awaitingVerification, setAwaitingVerification] = useState(false); const {createAccountWithAutoLogin, isLoading, error, setError} = useCreateAccount(); @@ -75,7 +76,10 @@ export function CreateAccount() { return; } - await createAccountWithAutoLogin(username, password, email); + const result = await createAccountWithAutoLogin(username, password, email); + if (result.success && result.awaitingVerification) { + setAwaitingVerification(true); + } }; const errMessage = ( @@ -89,166 +93,194 @@ export function CreateAccount() { ); const createAccountForm = ( -
- - setUsername(v)} - validated={validateUsername(username)} - /> - - - - ) : undefined - } - > - Username must be at least 3 characters and contain only letters, - numbers, hyphens, underscores, and periods - - - - - - - setEmail(v)} - validated={validateEmail(email)} - /> - - - - ) : undefined - } - > - Please enter a valid email address - - - - - - - setPassword(v)} - validated={validatePassword(password)} - /> - - - - ) : undefined - } - > - Password must be at least 8 characters long - - - - - - - setConfirmPassword(v)} - validated={validateConfirmPassword(confirmPassword)} - /> - - - - ) : undefined - } - > - Passwords must match - - - - - - {error && errMessage} - - - - + Thank you for registering! We have sent you an activation email. You + must verify your email address before you can + continue. + + )} - + + + setUsername(v)} + validated={validateUsername(username)} + /> + + + + ) : undefined + } + > + Username must be at least 3 characters and contain only letters, + numbers, hyphens, underscores, and periods + + + + + + + setEmail(v)} + validated={validateEmail(email)} + /> + + + + ) : undefined + } + > + Please enter a valid email address + + + + + + + setPassword(v)} + validated={validatePassword(password)} + /> + + + + ) : undefined + } + > + Password must be at least 8 characters long + + + + + + + setConfirmPassword(v)} + validated={validateConfirmPassword(confirmPassword)} + /> + + + + ) : undefined + } + > + Passwords must match + + + + + + {error && errMessage} + + + + + + +
+ Already have an account?{' '} + + Sign in + +
+
+ + + {awaitingVerification && (
Already have an account?{' '}
-
- + )} + ); return (