Skip to content

feat(wabe): add magic link#332

Merged
coratgerl merged 2 commits into
mainfrom
feat-magic-link
Jun 6, 2026
Merged

feat(wabe): add magic link#332
coratgerl merged 2 commits into
mainfrom
feat-magic-link

Conversation

@coratgerl

@coratgerl coratgerl commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Added magic link (email OTP) authentication for passwordless sign-in and registration with 6-digit verification codes
    • Implemented challenge-based authentication flow with configurable OTP expiration and maximum verification attempt limits
  • Documentation

    • Added comprehensive documentation for magic link authentication setup and usage
    • Updated feature list to specify supported authentication methods

@coratgerl coratgerl self-assigned this Jun 6, 2026
@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Worried about impact? Review this PR in Change Stack to explore blast radius before you approve or request changes.

Review Change Stack

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a5a65139-44fd-4f16-8fa6-8cad19df9090

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds passwordless email OTP (magic link) authentication to Wabe. It includes type contracts supporting challenge-based flows, cryptographic OTP generation and verification, challenge storage and lifecycle management, provider implementations for sign-in/sign-up/verification, resolver integration, hook infrastructure to skip duplicate sign-up hooks, default authentication wiring, comprehensive integration tests, and documentation.

Changes

Magic Link Authentication Implementation

Layer / File(s) Summary
Type contracts and authentication configuration
src/authentication/interface.ts
Extended OnVerifyChallengeOptions to require challengeToken, added optional challengeToken to sign-in/sign-up results, added challengeStorage configuration, extended security config for OTP TTL and max attempts, and introduced MagicLinkIntent enum.
OTP cryptography: generation, hashing, and verification
src/authentication/magicLinkOtp.ts, src/authentication/magicLinkOtp.test.ts
Implemented production-randomized 6-digit OTP generation and constant-time HMAC-SHA256 verification, with unit tests validating format, hash computation, and edge cases.
Challenge storage schema and persistence
src/schema/Schema.ts, src/authentication/magicLinkChallenge.ts
Added _MagicLinkChallenge database model with unique token index, and helper functions for email normalization, user lookup, and configuration-aware TTL/max-attempts.
Challenge lifecycle: creation and consumption
src/authentication/magicLinkChallenge.ts
Implemented challenge creation with OTP hashing and deletion of prior challenges, and consumption with mutex-protected verification, attempt tracking, and expiry validation.
Magic Link provider: request, sign-in, sign-up, and verification
src/authentication/providers/MagicLink.ts
Implemented requestMagicLinkOtp for rate-limited OTP delivery, MagicLink provider for sign-in (dummy challenge for unknowns) and sign-up (duplicate check and real challenge), and MagicLinkChallenge provider for verification with conditional user creation.
Email template and configuration
src/email/interface.ts, src/email/templates/magicLink.ts
Added optional magicLink template to email interface and implemented HTML email template with styled OTP code block.
Rate-limit attempt registration
src/authentication/security.ts
Extracted reusable registerRateLimitAttempt function; registerRateLimitFailure now delegates to it.
Sign-up resolver: dynamic provider selection and challenge handling
src/authentication/resolvers/signUpWithResolver.ts
Modified to select provider dynamically, call provider.onSignUp, handle optional challengeToken with early return, and set _skipAuthenticationSignUpHook: true to avoid duplicate hook calls.
Sign-in resolver: challenge token early-return
src/authentication/resolvers/signInWithResolver.ts
Added handling for optional challengeToken from provider.onSignIn with early return that nulls access/refresh/user/srp tokens.
Verify challenge resolver: provider-managed storage
src/authentication/resolvers/verifyChallenge.ts
Updated to read challengeStorage configuration and conditionally skip consumeMfaChallenge when provider manages storage directly.
Database and hook infrastructure for sign-up skipping
src/database/interface.ts, src/database/DatabaseController.ts, src/hooks/...
Threaded _skipAuthenticationSignUpHook flag through CreateObjectOptionsDatabaseControllerinitializeHookHookObject, and added conditional skip guard in authentication sign-up hook.
GraphQL schema: enums, mutations, and integration
src/schema/Schema.ts
Added MagicLinkIntent enum, updated signUpWith/verifyChallenge field definitions, integrated _MagicLinkChallenge class into defaults.
Default authentication wiring
src/authentication/defaultAuthentication.ts, src/authentication/providers/index.ts
Added magicLinkChallenge and magicLink to default methods, re-exported MagicLink provider.
Integration tests
src/authentication/magicLink.inte.test.ts
Comprehensive end-to-end tests validating user creation after verification, challenge token flow, non-enumeration, replay protection, attempt limits, TTL expiry, and disabled-signup behavior.
Sign-up resolver tests
src/authentication/resolvers/signUpWithResolver.test.ts
Refactored to use shared mutation constant and added spy-based tests verifying EmailPassword.onSignUp invocation and password hashing.
Existing provider test updates
src/authentication/providers/EmailOTP.test.ts, src/authentication/providers/QRCodeOTP.test.ts, src/authentication/resolvers/verifyChallenge.test.ts
Updated to include explicit challengeToken parameter in challenge verification test calls and added beforeEach mock cleanup.
Documentation and configuration
docs/documentation/authentication/magicLink.md, docs/documentation/authentication/defaultMethods.md, rspress.config.ts, README.md
Added comprehensive feature documentation with configuration/frontend guidance, updated sidebar navigation, and enumerated supported auth methods.
Cleanup
src/mutex/MutexController.ts
Removed unused DevWabeTypes import.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • palixir/wabe#299: Challenge-token and provider-managed challenge storage updates align with this PR's MFA security enhancements to verifyChallengeResolver and challenge consumption patterns.

Poem

🐰 A magic link hops into sight,
No passwords needed, just OTP light,
Six digits dance in the email stream,
Passwordless auth—a fuzzy dream!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(wabe): add magic link' directly and clearly summarizes the main change—the addition of magic link authentication functionality.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-magic-link

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coratgerl

Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/wabe-documentation/docs/documentation/authentication/magicLink.md (1)

14-14: 💤 Low value

Consider splitting this sentence for clarity.

The sentence correctly describes non-enumeration behavior but packs multiple ideas (same response, no email sent, use sign-up instead, dual rationale). Splitting into 2–3 shorter sentences would improve readability.

✏️ Suggested rewording
-For `signInWith`, an email that is not registered yet receives the same API response (`challengeToken`, null tokens) but **no email is sent**. Use `signUpWith` to register a new address. This avoids sending OTP messages to people who never signed up while keeping responses neutral for enumeration.
+For `signInWith`, an email that is not registered yet receives the same API response (`challengeToken`, null tokens) but **no email is sent**. To register a new address, use `signUpWith` instead. This design prevents sending OTP messages to addresses that never signed up while keeping responses neutral to avoid user enumeration.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/wabe-documentation/docs/documentation/authentication/magicLink.md`
at line 14, Split the long sentence into two or three clearer sentences: state
first that signInWith returns the same API response (challengeToken and null
tokens) for unregistered emails while not sending any email, and then add a
separate sentence advising to use signUpWith to register new addresses; mention
the non-enumeration rationale (avoid sending OTPs to non-subscribers while
keeping responses neutral) in a short follow-up sentence if needed. Ensure the
text references signInWith, signUpWith, and challengeToken so readers can easily
map behavior to the API symbols.
packages/wabe/src/email/templates/magicLink.ts (1)

1-30: 💤 Low value

Hardcoded validity duration may not match configured TTL.

The template states "valid for 15 minutes" (line 22), but magicLinkOtpTtlMs is configurable via AuthenticationSecurityConfig. If an operator changes the TTL, the email text would be misleading.

Consider either accepting the TTL as a parameter or documenting that the default template assumes the default TTL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/wabe/src/email/templates/magicLink.ts` around lines 1 - 30, The
email template magicLinkTemplate currently hardcodes "valid for 15 minutes"
which can diverge from AuthenticationSecurityConfig.magicLinkOtpTtlMs; change
magicLinkTemplate to accept a TTL (milliseconds) parameter or a precomputed
minutes value and interpolate the computed minutes into the sentence instead of
the hardcoded "15 minutes"; update callers that invoke magicLinkTemplate (where
OTPs are generated/sent) to pass AuthenticationSecurityConfig.magicLinkOtpTtlMs
(or its minutes conversion) so the displayed validity matches the configured
TTL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/wabe/src/authentication/magicLink.inte.test.ts`:
- Around line 302-311: The test "should reject invalid OTP" uses a hardcoded OTP
value of '000000' which could legitimately be generated as a valid OTP in
test/dev mode, causing the test to fail unpredictably. Instead of using a
hardcoded invalid OTP, capture the valid OTP that was generated during the
signUpMagicLink call and create an invalid OTP dynamically by modifying the
valid one (for example, by incrementing or changing one of its digits). Pass
this dynamically generated invalid OTP to the verifyMagicLink function call to
ensure the test always uses a value guaranteed to be different from the actual
generated OTP.

---

Nitpick comments:
In `@packages/wabe-documentation/docs/documentation/authentication/magicLink.md`:
- Line 14: Split the long sentence into two or three clearer sentences: state
first that signInWith returns the same API response (challengeToken and null
tokens) for unregistered emails while not sending any email, and then add a
separate sentence advising to use signUpWith to register new addresses; mention
the non-enumeration rationale (avoid sending OTPs to non-subscribers while
keeping responses neutral) in a short follow-up sentence if needed. Ensure the
text references signInWith, signUpWith, and challengeToken so readers can easily
map behavior to the API symbols.

In `@packages/wabe/src/email/templates/magicLink.ts`:
- Around line 1-30: The email template magicLinkTemplate currently hardcodes
"valid for 15 minutes" which can diverge from
AuthenticationSecurityConfig.magicLinkOtpTtlMs; change magicLinkTemplate to
accept a TTL (milliseconds) parameter or a precomputed minutes value and
interpolate the computed minutes into the sentence instead of the hardcoded "15
minutes"; update callers that invoke magicLinkTemplate (where OTPs are
generated/sent) to pass AuthenticationSecurityConfig.magicLinkOtpTtlMs (or its
minutes conversion) so the displayed validity matches the configured TTL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d35ccf70-d4f0-4d2b-bf15-619c45dd1130

📥 Commits

Reviewing files that changed from the base of the PR and between d805062 and 5666a79.

⛔ Files ignored due to path filters (2)
  • packages/wabe/generated/schema.graphql is excluded by !**/generated/**
  • packages/wabe/generated/wabe.ts is excluded by !**/generated/**
📒 Files selected for processing (29)
  • packages/wabe-documentation/docs/documentation/authentication/defaultMethods.md
  • packages/wabe-documentation/docs/documentation/authentication/magicLink.md
  • packages/wabe-documentation/rspress.config.ts
  • packages/wabe/README.md
  • packages/wabe/src/authentication/defaultAuthentication.ts
  • packages/wabe/src/authentication/interface.ts
  • packages/wabe/src/authentication/magicLink.inte.test.ts
  • packages/wabe/src/authentication/magicLinkChallenge.ts
  • packages/wabe/src/authentication/magicLinkOtp.test.ts
  • packages/wabe/src/authentication/magicLinkOtp.ts
  • packages/wabe/src/authentication/providers/EmailOTP.test.ts
  • packages/wabe/src/authentication/providers/MagicLink.ts
  • packages/wabe/src/authentication/providers/QRCodeOTP.test.ts
  • packages/wabe/src/authentication/providers/index.ts
  • packages/wabe/src/authentication/resolvers/signInWithResolver.ts
  • packages/wabe/src/authentication/resolvers/signUpWithResolver.test.ts
  • packages/wabe/src/authentication/resolvers/signUpWithResolver.ts
  • packages/wabe/src/authentication/resolvers/verifyChallenge.test.ts
  • packages/wabe/src/authentication/resolvers/verifyChallenge.ts
  • packages/wabe/src/authentication/security.ts
  • packages/wabe/src/database/DatabaseController.ts
  • packages/wabe/src/database/interface.ts
  • packages/wabe/src/email/interface.ts
  • packages/wabe/src/email/templates/magicLink.ts
  • packages/wabe/src/hooks/HookObject.ts
  • packages/wabe/src/hooks/authentication.ts
  • packages/wabe/src/hooks/index.ts
  • packages/wabe/src/mutex/MutexController.ts
  • packages/wabe/src/schema/Schema.ts
💤 Files with no reviewable changes (1)
  • packages/wabe/src/mutex/MutexController.ts

Comment thread packages/wabe/src/authentication/magicLink.inte.test.ts
@coratgerl coratgerl merged commit 3b36a45 into main Jun 6, 2026
3 checks passed
@coratgerl coratgerl deleted the feat-magic-link branch June 6, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant