Skip to content

Route OTP registration, Magic Link registration, Magic Link authentication through authnprovider#3012

Open
ThumulaPerera wants to merge 2 commits into
thunder-id:mainfrom
ThumulaPerera:improve-magic-link-to-go-through-authnprovider
Open

Route OTP registration, Magic Link registration, Magic Link authentication through authnprovider#3012
ThumulaPerera wants to merge 2 commits into
thunder-id:mainfrom
ThumulaPerera:improve-magic-link-to-go-through-authnprovider

Conversation

@ThumulaPerera
Copy link
Copy Markdown
Member

@ThumulaPerera ThumulaPerera commented May 26, 2026

Purpose

This PR incorporates 3 main changes

  1. Route OTP registration through authnprovider
  2. Route Magic Link registration through authnprovider
  3. Route Magic Link authentications through authnprovider

Approach

For 1 above :

  • OTP service:
    Merged the separate VerifyOTP (verify-only) and Authenticate (verify + resolve user) methods into a single Authenticate that returns an OTPAuthnResult struct. When OTP verification succeeds but no local user exists, it returns {InternalEntity: nil, VerifiedIdentifiers: {"mobileNumber": recipient}} instead of an error. This lets callers distinguish "valid OTP, no user yet" from actual failures without inspecting error codes.
  • Authn provider:
    Updated authenticateWithOTP to handle the new result shape — nil entity triggers an early return with IsExistingUser: false and the verified identifiers, matching how federated auth already works. Also extracted a shared buildAttributesResponse helper and fixed the existing-user path to include attribute values (IsAttributeValuesIncluded: true).
  • SMS OTP executor:
    Registration flow now calls authnProvider.AuthenticateUser instead of otpService.VerifyOTP directly, so registration and login both go through the same provider abstraction.

For 2 and 3 above :

  • Magic link service:
    Renamed VerifyMagicLink → Authenticate, introduced MagicLinkAuthnResult struct. User-not-found is now a successful result (InternalEntity: nil + VerifiedIdentifiers carrying the proven email/attribute) instead of an error. Token verification logic is extracted into a private verifyTokenAndExtractSubject helper.
  • Authn provider:
    Wired magicLinkService as a dependency through the initialization chain. Added authenticateWithMagicLink in the provider and registered the "magiclink" credential key in resolveCredentials — following the same entity-ID-or-early-return pattern as OTP and federated.
  • Magic link executor:
    Verification now calls authnProvider.AuthenticateUser instead of magicLinkService.VerifyMagicLink directly. The old validateMagicLinkToken (which mixed auth concerns with flow concerns) is split: authentication moves to the provider, and flow-specific checks (execution-ID binding, JTI replay detection) stay in the executor as validateFlowClaims.

Related Issues

Related PRs

  • N/A

Checklist

  • Followed the contribution guidelines.
  • Manual test round performed and verified.
  • Documentation provided. (Add links if there are any)
    • Ran Vale and fixed all errors and warnings
  • Tests provided. (Add links if there are any)
    • Unit Tests
    • Integration Tests
  • Breaking changes. (Fill if applicable)
    • Breaking changes section filled.
    • breaking change label added.

Security checks

  • Followed secure coding standards in WSO2 Secure Coding Guidelines
  • Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets.

Summary by CodeRabbit

  • New Features

    • Magic link authentication added and integrated into authentication flows.
    • OTP and magic link flows now support registration: verified identifiers and user attributes are returned even when no local account exists, enabling smoother self-registration.
  • Refactor

    • Authentication services and provider flows now return richer result objects to represent both resolved users and verified-but-unresolved identifiers, and use the centralized authn provider for verification.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

📝 Walkthrough

Walkthrough

This PR refactors authentication services and flows to support a unified result model where verified identifiers are returned even when no local user is found. Magic link and OTP services now return result types that combine either an internal entity or verified identifiers. All authentication is delegated through the authn provider manager instead of direct service calls.

Changes

Authentication Result Model & Provider Integration

Layer / File(s) Summary
Magic Link Service Refactoring
backend/internal/authn/magiclink/service.go, backend/internal/authn/magiclink/service_test.go
MagicLinkAuthnResult introduced to carry either a resolved entity (InternalEntity) or verified identifier data (VerifiedIdentifiers). Interface method changed from VerifyMagicLink to Authenticate. Implementation refactored with separated JWT verification and claim-decoding helpers; "user not found" returns a successful result with verified identifiers. Tests updated from TestVerifyMagicLink* to TestAuthenticate* coverage.
OTP Service Refactoring
backend/internal/authn/otp/service.go, backend/internal/authn/otp/service_test.go
OTPAuthnResult struct introduced; Authenticate now returns *OTPAuthnResult. Handler treats "user not found" during OTP verification as a successful result with verified mobile number in VerifiedIdentifiers. Tests updated to assert InternalEntity and VerifiedIdentifiers.
Authn Provider Manager Integration
backend/internal/authnprovider/manager/init.go, backend/internal/authnprovider/manager/manager.go, backend/internal/authnprovider/manager/manager_test.go
Manager initialization now accepts magicLinkService and forwards it into provider initialization. AuthenticateUser sets provider data for non-existing users using only attributes and isAttributeValuesIncluded. Tests updated to expect provider data with included attributes.
Default Authn Provider Implementation
backend/internal/authnprovider/provider/default_authn_provider.go, backend/internal/authnprovider/provider/default_authn_provider_test.go
Default provider extended with magicLinkService and authenticateWithMagicLink. Credential resolution recognizes "magiclink". OTP and federated flows return early AuthnResult with attribute values when InternalEntity is nil. buildAttributesResponse helper added. Tests expanded to cover OTP and Magic Link credential paths and attribute assertions.
Authn Provider Initialization
backend/internal/authnprovider/provider/init.go
InitializeAuthnProvider and initializeDefaultAuthnProvider updated to accept and inject magicLinkService into default provider construction.
Magic Link Executor Delegation
backend/internal/flow/executor/magic_link_executor.go, backend/internal/flow/executor/magic_link_executor_test.go
Magic link executor now depends on the authn provider manager and delegates authentication to it; JWT flow-claim validation moved to validateFlowClaims. Verify/registration flows and tests were updated to use AuthenticateUser and new claim-validation tests were added.
SMS Auth Executor Refactoring
backend/internal/flow/executor/sms_auth_executor.go, backend/internal/flow/executor/sms_auth_executor_test.go
SMS executor now authenticates via authnProvider.AuthenticateUser with OTP credentials. Registration flows detect IsExistingUser and fail with "User already exists" message. Tests added for registration with and without existing user.
Service Manager Wiring
backend/cmd/server/servicemanager.go
Service manager updated to pass magicLinkService into InitializeAuthnProviderManager.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Suggested labels

breaking change

Suggested reviewers

  • darshanasbg
  • ThaminduDilshan
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Route OTP registration, Magic Link registration, Magic Link authentication through authnprovider' is clear, specific, and directly summarizes the main changes: refactoring three authentication flows (OTP registration, Magic Link registration, Magic Link authentication) to route through a centralized authnprovider component.
Description check ✅ Passed The description comprehensively covers Purpose (three main changes clearly enumerated), Approach (detailed explanation of OTP service changes, authn provider updates, executor refactoring for both OTP and Magic Link), related issues/PRs, and a completed checklist with unit tests and manual testing confirmed. Documentation section is marked incomplete, but non-critical given other completeness.
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

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
backend/internal/authnprovider/provider/default_authn_provider_test.go (1)

300-392: ⚡ Quick win

Add direct unit tests for the new magiclink credential branch.

This suite covers OTP well, but the newly added authenticateWithMagicLink path is untested here (success, non-existing user, invalid payload, and service error mapping). Please add targeted tests for credentials["magiclink"] routing and outcomes.

🤖 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 `@backend/internal/authnprovider/provider/default_authn_provider_test.go`
around lines 300 - 392, Add unit tests mirroring the existing OTP tests but
targeting the magiclink credential branch: create tests in
DefaultAuthnProviderTestSuite that call provider.Authenticate with
credentials["magiclink"] payloads and assert correct behavior for (1) existing
user (mock magiclink service to return InternalEntity with ID and ensure
suite.mockService.GetEntity is called and result.IsExistingUser true), (2)
non-existing user (magiclink returns nil InternalEntity plus VerifiedIdentifiers
and ensure AttributesResponse populated and IsExistingUser false), (3) invalid
payload (pass non-map magiclink value and expect ErrorCodeInvalidRequest), and
(4) service error mapping (mock magiclink to return an error and assert
Authenticate returns authnprovidercm.ErrorCodeAuthenticationFailed); reference
newDefaultAuthnProvider, Authenticate, and authenticateWithMagicLink flow when
adding these tests.
🤖 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.

Nitpick comments:
In `@backend/internal/authnprovider/provider/default_authn_provider_test.go`:
- Around line 300-392: Add unit tests mirroring the existing OTP tests but
targeting the magiclink credential branch: create tests in
DefaultAuthnProviderTestSuite that call provider.Authenticate with
credentials["magiclink"] payloads and assert correct behavior for (1) existing
user (mock magiclink service to return InternalEntity with ID and ensure
suite.mockService.GetEntity is called and result.IsExistingUser true), (2)
non-existing user (magiclink returns nil InternalEntity plus VerifiedIdentifiers
and ensure AttributesResponse populated and IsExistingUser false), (3) invalid
payload (pass non-map magiclink value and expect ErrorCodeInvalidRequest), and
(4) service error mapping (mock magiclink to return an error and assert
Authenticate returns authnprovidercm.ErrorCodeAuthenticationFailed); reference
newDefaultAuthnProvider, Authenticate, and authenticateWithMagicLink flow when
adding these tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aa7b34a8-b525-4f90-9660-bf494d12bada

📥 Commits

Reviewing files that changed from the base of the PR and between 4dbd0c6 and f0a5a7d.

⛔ Files ignored due to path filters (2)
  • backend/tests/mocks/authn/magiclinkmock/MagicLinkAuthnServiceInterface_mock.go is excluded by !**/*_mock.go
  • backend/tests/mocks/authn/otpmock/OTPAuthnServiceInterface_mock.go is excluded by !**/*_mock.go
📒 Files selected for processing (16)
  • backend/cmd/server/servicemanager.go
  • backend/internal/authn/magiclink/service.go
  • backend/internal/authn/magiclink/service_test.go
  • backend/internal/authn/otp/service.go
  • backend/internal/authn/otp/service_test.go
  • backend/internal/authnprovider/manager/init.go
  • backend/internal/authnprovider/manager/manager.go
  • backend/internal/authnprovider/manager/manager_test.go
  • backend/internal/authnprovider/provider/default_authn_provider.go
  • backend/internal/authnprovider/provider/default_authn_provider_test.go
  • backend/internal/authnprovider/provider/init.go
  • backend/internal/flow/executor/init.go
  • backend/internal/flow/executor/magic_link_executor.go
  • backend/internal/flow/executor/magic_link_executor_test.go
  • backend/internal/flow/executor/sms_auth_executor.go
  • backend/internal/flow/executor/sms_auth_executor_test.go

@codecov
Copy link
Copy Markdown

codecov Bot commented May 26, 2026

Codecov Report

❌ Patch coverage is 95.67901% with 7 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...kend/internal/flow/executor/magic_link_executor.go 85.36% 5 Missing and 1 partial ⚠️
backend/internal/authn/magiclink/service.go 96.42% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@ThumulaPerera ThumulaPerera force-pushed the improve-magic-link-to-go-through-authnprovider branch from f0a5a7d to 2dd0b98 Compare May 26, 2026 08:12
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 (1)
backend/internal/flow/executor/magic_link_executor_test.go (1)

536-544: ⚡ Quick win

Tighten AuthenticateUser argument assertions in verify-mode tests

Using mock.Anything for nearly all AuthenticateUser args makes these tests pass even if credential type/payload wiring regresses. Prefer mock.MatchedBy for the key fields (e.g., magic-link credential type/token/destination attribute) in at least one success and one failure-path test.

🤖 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 `@backend/internal/flow/executor/magic_link_executor_test.go` around lines 536
- 544, The test currently stubs mockAuthnProvider.AuthenticateUser with
mock.Anything for almost all args, allowing regressions in magic-link wiring;
update the AuthenticateUser expectations in magic_link_executor_test.go to use
mock.MatchedBy for the credential-related parameters (e.g., credential type
equals magic-link, and the payload contains the expected token and destination)
when setting up mockAuthnProvider.On(...).Return(...) for both a representative
success case (the existing success stub that returns AuthnBasicResult) and at
least one failure-path test so the mock asserts the magic-link credential
type/token/destination are passed through correctly.
🤖 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 `@backend/internal/authn/magiclink/service.go`:
- Around line 146-156: The current handling in the block after
s.resolveUserFromSubject converts common.ErrorUserNotFound into a successful
MagicLinkAuthnResult even when subjectAttribute is empty; change the logic so
that if resolveErr != nil and resolveErr.Code == common.ErrorUserNotFound.Code,
you only return a successful MagicLinkAuthnResult when subjectAttribute is
non-empty (i.e., login-style identifier lookup), but when subjectAttribute is
empty (local user lookup) propagate/return resolveErr instead of building a
MagicLinkAuthnResult; update the branch around
resolveUserFromSubject/subjectAttribute/MagicLinkAuthnResult so executeVerify
receives an error for missing users rather than a nil UserID.

---

Nitpick comments:
In `@backend/internal/flow/executor/magic_link_executor_test.go`:
- Around line 536-544: The test currently stubs
mockAuthnProvider.AuthenticateUser with mock.Anything for almost all args,
allowing regressions in magic-link wiring; update the AuthenticateUser
expectations in magic_link_executor_test.go to use mock.MatchedBy for the
credential-related parameters (e.g., credential type equals magic-link, and the
payload contains the expected token and destination) when setting up
mockAuthnProvider.On(...).Return(...) for both a representative success case
(the existing success stub that returns AuthnBasicResult) and at least one
failure-path test so the mock asserts the magic-link credential
type/token/destination are passed through correctly.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a439af35-a0a7-4f3c-b0ba-44c67ea13d70

📥 Commits

Reviewing files that changed from the base of the PR and between f0a5a7d and 2dd0b98.

⛔ Files ignored due to path filters (1)
  • backend/tests/mocks/authn/magiclinkmock/MagicLinkAuthnServiceInterface_mock.go is excluded by !**/*_mock.go
📒 Files selected for processing (10)
  • backend/cmd/server/servicemanager.go
  • backend/internal/authn/magiclink/service.go
  • backend/internal/authn/magiclink/service_test.go
  • backend/internal/authnprovider/manager/init.go
  • backend/internal/authnprovider/provider/default_authn_provider.go
  • backend/internal/authnprovider/provider/default_authn_provider_test.go
  • backend/internal/authnprovider/provider/init.go
  • backend/internal/flow/executor/init.go
  • backend/internal/flow/executor/magic_link_executor.go
  • backend/internal/flow/executor/magic_link_executor_test.go

Comment thread backend/internal/authn/magiclink/service.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant