fix(setup): record email unverified so onboarding works without SMTP
At bootstrap there is no SMTP, so the old /setup flow was unreachable: it requested an emailed OTP that could never arrive. Setup now records the Owner's email address unverified (no OTP round-trip) and requires a passkey, deferring SMTP configuration to a later Settings page. Setup completes on email-recorded + passkey-enrolled, and the lockdown lifts on the passkey, not on email_verified: a passkey is the Owner's only pre-SMTP login credential (email-OTP login refuses admin accounts). The record-email endpoint (POST /account/email) now clears email_verified in the same write. Only VerifyEmailOTP, which proves control of the address, may set that flag; recording a fresh unproven address must never leave a stale email_verified=true asserting a proof the user never gave. The change strictly tightens the invariant, so no existing reader breaks. Remove the dead ErrEmailTaken path and its documented 409: no migration puts a unique index on users.email and the codebase does not enforce email uniqueness, so the unique-violation branch was unreachable and the 409 an impossible response. The /setup route (Setup.tsx, setEmail helper, setup i18n copy) is rewritten to match: record-email, mandatory passkey, no skip-for-now. The SMTP settings page and post-setup configure-SMTP nudge are deferred.
This commit is contained in:
12 files changed
+328
-155
No files matched your search
@@ -104,7 +104,11 @@ func (a *API) handleSetupRedeem(w http.ResponseWriter, r *http.Request) {
|
||||
"email": u.Email,
|
||||
"email_verified": u.EmailVerified,
|
||||
"has_passkey": hasPasskey,
|
||||
"setup_required": !u.EmailVerified || !hasPasskey,
|
||||
// Setup completes on email recorded + passkey enrolled. NOT email_verified:
|
||||
// the bootstrap has no SMTP, so the Owner's address is stored unverified and a
|
||||
// later Settings/SMTP flow verifies it. Passkey is the Owner's only pre-SMTP
|
||||
// login credential, so it — not email verification — is the durable gate.
|
||||
"setup_required": u.Email == "" || !hasPasskey,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -131,6 +135,10 @@ func (a *API) handleSetupStatus(w http.ResponseWriter, r *http.Request) {
|
||||
"email": u.Email,
|
||||
"email_verified": u.EmailVerified,
|
||||
"has_passkey": hasPasskey,
|
||||
"setup_required": !u.EmailVerified || !hasPasskey,
|
||||
// Setup completes on email recorded + passkey enrolled. NOT email_verified:
|
||||
// the bootstrap has no SMTP, so the Owner's address is stored unverified and a
|
||||
// later Settings/SMTP flow verifies it. Passkey is the Owner's only pre-SMTP
|
||||
// login credential, so it — not email verification — is the durable gate.
|
||||
"setup_required": u.Email == "" || !hasPasskey,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user