Feature/magic link - #7
Merged
Merged
Conversation
No new table — magic-link tokens are single-use, expiring, hashed tokens tied to a user, exactly what VerificationStore (already used for email-change confirmation) already models. PurposeMagicLink is a separate value from PurposeEmailVerify even though both are 'click a link in your email': the Purpose check on read is what stops a leaked/guessed email-verification link from ever being replayed as a login link, or vice versa. Also adds a magic_link_requested audit event type.
Deliberately a separate interface from EmailSender rather than a new method added to it — EmailSender already shipped in an earlier release, and adding a required method to an existing interface would break every consuming app's existing implementation at compile time. The two are also genuinely different concerns for the app to word differently: 'confirm your new email' reads nothing like 'click to log in,' and EmailSender.SendVerification has no way to signal which one it's sending.
Pulls the 'check confirmed second-factor methods, then either pause with *ErrSecondFactorRequired or finish the login' logic out of Login into a standalone completePrimaryAuth, shared by any primary authentication path — magic-link login (landing in the next few commits) reuses it verbatim rather than reimplementing the same check slightly differently, which is exactly the kind of drift that could let a new login method accidentally bypass the second-factor gate. Pure extraction — Login's own behavior is unchanged.
RequestMagicLink logs in an existing account only — it never creates one. Returns nil for a nonexistent email exactly as it would for a real one, and never calls the sender in that case, to avoid leaking which emails are registered; a genuine delivery failure for an existing account still propagates, since that's an operational concern distinct from enumeration. CompleteMagicLink marks the token used immediately after validation, before any second-factor check or session creation, so a link can never be replayed even if something later in the call fails. It routes through the same completePrimaryAuth gate password login uses — an account with TOTP/a passkey enrolled pauses here exactly as it would after a correct password. magicLinkTTL is fixed at 15 minutes, not configurable — same reasoning as mfaPendingTTL: a passwordless login link is a bearer credential for the account it's mailed to, and a tuning knob here invites widening it well past what 'click the link you just got' actually needs.
Covers: sending only for existing accounts and never revealing which emails aren't registered, single-use enforcement, expiry, a wrong-purpose token (e.g. email-change) correctly rejected as a login token, and an account with TOTP enrolled correctly pausing for a second factor on completion instead of logging straight in.
- Config.MagicLinkSender (optional, notify.MagicLinkSender); requires Config.Verifications to also be set (validated in New). - New facade functions: RequestMagicLink, CompleteMagicLink, each returning cryden.ErrMagicLinkNotConfigured if called without Config.MagicLinkSender set.
Also fixes a stale 'not in v2' line that still listed WebAuthn and was about to incorrectly list magic links too, now that both are built; notes passwordless-primary passkey login as the planned fast-follow this and WebAuthn's shared plumbing sets up.
Runnable end-to-end check with no database dependency: go run ./cmd/smoketest/magic-link. Walks request-for-nonexistent-email (silently returns nil, sender never called), request-and-complete for a real account, single-use enforcement, a garbage token, and — using a real generated TOTP code, not a stub — an account with TOTP enrolled correctly pausing on *auth.ErrSecondFactorRequired instead of logging straight in.
Step 6 (enrolling TOTP to verify the second-factor pause) called cryden.EnrollTOTP against an engine built without Config.TOTP or Config.EncryptionKey set, so it failed immediately with ErrTOTPNotConfigured before ever reaching the actual check this step exists to verify.
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.
No description provided.