Fix email verification dead ends, sync seeded account passwords#18
Merged
Conversation
- Show an editable, prefilled email field on the verify screen instead of relying on hidden state, so users can always supply the email the verification code was sent to - Mark the account email as verified when a password reset completes, since following the emailed reset link proves mailbox ownership; previously an unverified user stayed locked out (403) after a reset - Make the seed sync an existing demo/admin account password to the configured DEMO_PASSWORD/ADMIN_PASSWORD so documented credentials always work
Contributor
There was a problem hiding this comment.
Pull request overview
This PR addresses “dead end” states in the email verification flow, ensures password reset unblocks previously-unverified accounts, and makes seeded demo/admin accounts’ credentials consistent with configured values.
Changes:
- Frontend: In verify mode, render an editable, prefilled Email Address field and remove reliance on hidden “pending verification email” state.
- Backend: Mark
emailVerified: truewhen completing a password reset. - Ops/Seed: When seeding an existing demo/admin user, sync the stored password to the configured password when it differs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| frontend/src/components/auth/LoginPage.tsx | Removes hidden verification-email state; adds visible email field in verify mode and updates verify/resend logic. |
| frontend/src/tests/components/LoginPage.test.tsx | Adds verify-mode tests covering prefill/edit/submit/cleared-email recovery/resend behavior. |
| backend/src/services/auth.service.ts | Updates password-reset flow to also mark the user as email-verified. |
| backend/src/tests/services/auth.service.test.ts | Adds assertion ensuring reset sets emailVerified: true. |
| backend/prisma/seed.ts | Extends seed reconciliation to optionally update existing seeded users’ passwords when mismatched. |
Comments suppressed due to low confidence (2)
frontend/src/components/auth/LoginPage.tsx:51
- The registration-success effect no longer references
email, but it’s still included in the dependency array. This causes unnecessary effect re-evaluations and makes the dependency list misleading now thatpendingVerificationEmailhas been removed.
useEffect(() => {
if (registrationSuccess) {
setSuccessMessage('Account created. Enter the verification code sent to your email.');
setMode('verify');
setPassword('');
setConfirmPassword('');
setVerificationCode('');
clearRegistrationSuccess();
}
}, [registrationSuccess, clearRegistrationSuccess, email]);
frontend/src/components/auth/LoginPage.tsx:104
- When an unverified user attempts to log in, the UI switches to verify mode but leaves the password in component state. If the user navigates back to login/register, the password field can be repopulated with the prior value, which is unnecessary and increases the chance of accidental disclosure (e.g., shared screen).
if (mode === 'login' && err?.message === 'Email verification required') {
clearError();
setVerificationCode('');
setSuccessMessage('Enter the verification code sent to your email.');
setMode('verify');
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
195
to
201
| <Typography variant="body2" color="text.secondary"> | ||
| {mode === 'login' | ||
| ? 'Sign in to start working on your models.' | ||
| : mode === 'register' | ||
| ? 'Use your email and password to get started.' | ||
| : `Enter the 6-digit code sent to ${pendingVerificationEmail || email}.`} | ||
| : `Enter the 6-digit code sent to ${email.trim() || 'your email'}.`} | ||
| </Typography> |
Comment on lines
25
to
34
| if (existing) { | ||
| const updates: { role?: UserRole; emailVerified?: boolean } = {}; | ||
| const updates: { role?: UserRole; emailVerified?: boolean; password?: string } = {}; | ||
| if (existing.role !== role) updates.role = role; | ||
| if (!existing.emailVerified) updates.emailVerified = true; | ||
| // Keep the stored password in sync with the configured one, so the | ||
| // documented demo/admin credentials always work even if the account | ||
| // predates the seed or its password was changed in the app. | ||
| const passwordMatches = await bcrypt.compare(password, existing.password); | ||
| if (!passwordMatches) updates.password = await bcrypt.hash(password, 12); | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
resetPasswordnever setemailVerified, so an unverified account stayed locked out with 403 "Email verification required" even after a successful password reset, and each login attempt mailed yet another code.DEMO_PASSWORD.Changes
pendingVerificationEmailstate).emailVerified: true(the emailed link proves mailbox ownership).Testing
tsc --noEmitclean on both packages.