Skip to content

[LFXV2-1250] Validate id_token_social.sub before linking social identity#28

Merged
mauriciozanettisalomao merged 4 commits intolinuxfoundation:mainfrom
mauriciozanettisalomao:feat/lfxv2-1250-validate-id-token-social-before-link
Mar 18, 2026
Merged

[LFXV2-1250] Validate id_token_social.sub before linking social identity#28
mauriciozanettisalomao merged 4 commits intolinuxfoundation:mainfrom
mauriciozanettisalomao:feat/lfxv2-1250-validate-id-token-social-before-link

Conversation

@mauriciozanettisalomao
Copy link
Contributor

@mauriciozanettisalomao mauriciozanettisalomao commented Mar 17, 2026

Overview

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-1250

Adds a defense-in-depth guard against merging two LFID database users during social identity linking (LFXV2-1250).

When a social provider is already connected to a different LFID, Auth0 returns the sub of the existing database user (auth0|...) instead of the social identity's own sub (e.g. google-oauth2|...). If the service proceeds with the link in this state, it merges two LFID database users — effectively locking the secondary account's owner out with no current recovery path.

The solution adds ValidateLinkRequest to the IdentityLinker port. The Auth0 implementation rejects the request early if id_token_social.sub starts with auth0|. Mock and Authelia implementations are no-op stubs to be completed in follow-up tickets.

How it works

The guard is inserted in the LinkIdentity handler before any Auth0 Management API call:

Unmarshal → ValidateLinkRequest (JWT sub prefix check) → MetadataLookup → LinkIdentity

ValidateLinkRequest extracts the sub claim from IdentityToken without signature verification (the Management API rejects tampered tokens at link time) and rejects if the prefix is auth0|. Valid social prefixes (google-oauth2|, github|, linkedin|, etc.) pass through.

Changes

  • internal/domain/port/user.go — adds ValidateLinkRequest to IdentityLinker interface
  • internal/infrastructure/auth0/user.go — Auth0 implementation: extracts sub, rejects auth0| prefix
  • internal/service/message_handler.go — calls ValidateLinkRequest before MetadataLookup
  • internal/infrastructure/mock/user.go — no-op stub
  • internal/infrastructure/authelia/user.go — no-op stub
  • internal/infrastructure/auth0/identity_test.go — unit tests for ValidateLinkRequest
  • internal/service/message_handler_test.go — unit tests for the guard in the handler

Test Evidence

Error path — auth0| sub rejected

$ nats req --server nats://lfx-platform-nats.lfx.svc.cluster.local:4222 \
  "lfx.auth-service.user_identity.link" \
  '{
    "user": { "auth_token": "..." },
    "link_with": {
      "identity_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdXRoMHxmYWtlLWRiLXVzZXIiLCJlbWFpbCI6InRlc3RAZXhhbXBsZS5jb20iLCJpYXQiOjE3MDAwMDAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
    }
  }'

11:22:38 Sending request on "lfx.auth-service.user_identity.link"
11:22:38 Received with rtt 3.197538ms
{"success":false,"error":"the provided identity token belongs to an existing LFID account and cannot be linked"}

Social token, link proceeds

{"time":"2026-03-17T14:24:45.526Z","level":"DEBUG","msg":"API call successful","method":"POST","status_code":201,"description":"link identity to user","subject":"lfx.auth-service.user_identity.link"}
{"time":"2026-03-17T14:24:45.526Z","level":"DEBUG","msg":"identity linked successfully","user_id":"aut****","status_code":201,"subject":"lfx.auth-service.user_identity.link"}

… tests

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-1250

Assisted by [Claude Code](https://claude.ai/code)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
@mauriciozanettisalomao mauriciozanettisalomao requested a review from a team as a code owner March 17, 2026 14:36
Copilot AI review requested due to automatic review settings March 17, 2026 14:36
@coderabbitai
Copy link

coderabbitai bot commented Mar 17, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 73be5dea-4ab6-48d6-a352-4425ffd9ae64

📥 Commits

Reviewing files that changed from the base of the PR and between 1c9d3ed and 9f245c2.

📒 Files selected for processing (6)
  • internal/domain/port/user.go
  • internal/infrastructure/auth0/user.go
  • internal/infrastructure/authelia/user.go
  • internal/infrastructure/mock/user.go
  • internal/service/message_handler.go
  • internal/service/message_handler_test.go

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

Adds a pre-validation step to identity linking: IdentityLinker gains ValidateLinkRequest, the message handler calls it before linking, auth0 implements real validation (rejects auth0| subjects), authelia and mock provide no-op validators, and tests cover the validation behavior.

Changes

Cohort / File(s) Summary
Interface Definition
internal/domain/port/user.go
Added ValidateLinkRequest(ctx context.Context, request *model.LinkIdentity) error to IdentityLinker.
Auth0 Provider & Tests
internal/infrastructure/auth0/user.go, internal/infrastructure/auth0/identity_test.go
Implemented ValidateLinkRequest to check nil request, empty/malformed token, extract subject, and reject `auth0
Other Providers (no-op)
internal/infrastructure/authelia/user.go, internal/infrastructure/mock/user.go
Added no-op ValidateLinkRequest implementations that log and return nil.
Service Integration & Tests
internal/service/message_handler.go, internal/service/message_handler_test.go
Message handler calls ValidateLinkRequest before LinkIdentity; tests added for validation failure blocking, successful flow, and invalid JSON handling.

Sequence Diagram

sequenceDiagram
    participant Client
    participant MsgHandler as Message Handler
    participant IdentityLinker as Identity Linker
    participant TokenExtractor as Token Extractor
    participant DB as Database

    Client->>MsgHandler: LinkIdentity(request)
    MsgHandler->>IdentityLinker: ValidateLinkRequest(ctx, request)

    alt Validation fails
        IdentityLinker->>TokenExtractor: Extract subject from identity token
        TokenExtractor-->>IdentityLinker: Error or subject
        IdentityLinker-->>MsgHandler: validation error
        MsgHandler-->>Client: Validation error response
    else Validation succeeds
        IdentityLinker-->>MsgHandler: nil (validation passed)
        MsgHandler->>IdentityLinker: LinkIdentity(ctx, request)
        IdentityLinker->>DB: Link identity record
        DB-->>IdentityLinker: Success
        IdentityLinker-->>MsgHandler: Link result
        MsgHandler-->>Client: Success response
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: adding validation of the social identity token subject before linking, which is the primary defense-in-depth guard introduced in this PR.
Description check ✅ Passed The description is detailed and directly related to the changeset, explaining the security issue, the solution, how it works, all changed files, and providing test evidence.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link

@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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/service/message_handler_test.go`:
- Around line 1207-1215: The table-driven tests define expectLinkerCalled but
never assert it, causing false-positive passes; update the test loop that
iterates over tests to assert that the mockIdentityLinker observed a
LinkIdentity invocation matches expectLinkerCalled (e.g., check a boolean field
or call count on mockIdentityLinker) after the handler runs and before finishing
the subtest; apply the same assertion to the second similar table-driven test
block (the one around the other occurrence) so both use expectLinkerCalled to
verify LinkIdentity was actually invoked.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ec1c2f4f-c291-4d1b-9922-253ccb9ade74

📥 Commits

Reviewing files that changed from the base of the PR and between da0c7ea and 1342a3d.

📒 Files selected for processing (7)
  • internal/domain/port/user.go
  • internal/infrastructure/auth0/identity_test.go
  • internal/infrastructure/auth0/user.go
  • internal/infrastructure/authelia/user.go
  • internal/infrastructure/mock/user.go
  • internal/service/message_handler.go
  • internal/service/message_handler_test.go

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a validation step to the identity-linking flow to prevent linking when the provided identity token represents an existing LFID (Auth0 database) account, and updates the IdentityLinker port + implementations accordingly.

Changes:

  • Add ValidateLinkRequest to port.IdentityLinker and call it from messageHandlerOrchestrator.LinkIdentity before user metadata lookup/linking.
  • Implement ValidateLinkRequest for Auth0 (reject sub values prefixed with auth0|) and no-op implementations for Authelia and mock.
  • Add tests covering the new validation behavior (Auth0 unit test + service orchestrator test).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/service/message_handler.go Calls ValidateLinkRequest before proceeding with metadata lookup and linking.
internal/service/message_handler_test.go Adds an orchestrator-level test harness with a mock identity linker for validation gating behavior.
internal/infrastructure/mock/user.go Adds a no-op ValidateLinkRequest to satisfy the updated IdentityLinker interface.
internal/infrastructure/authelia/user.go Adds a no-op ValidateLinkRequest to satisfy the updated IdentityLinker interface.
internal/infrastructure/auth0/user.go Adds Auth0-specific ValidateLinkRequest logic based on extracted JWT subject.
internal/infrastructure/auth0/identity_test.go Adds unit tests for Auth0 ValidateLinkRequest.
internal/domain/port/user.go Extends IdentityLinker with the new ValidateLinkRequest method.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

…sage handler tests

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-1250

Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
Copy link

@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.

♻️ Duplicate comments (1)
internal/service/message_handler_test.go (1)

1207-1276: ⚠️ Potential issue | 🟡 Minor

Add explicit LinkIdentity invocation assertions in table tests.

These cases validate response payloads, but they still don’t assert that LinkIdentity was actually invoked on success. This can allow false-positive passes if orchestration regresses. Please track/verify call state per case.

Suggested test hardening
 	tests := []struct {
 		name          string
 		messageData   []byte
 		linker        *mockIdentityLinker
 		reader        *mockUserServiceReader
 		expectSuccess bool
 		expectError   string
+		expectLinkerCalled bool
 	}{
 		{
 			name:        "validate guard blocks database user token",
@@
 			expectSuccess: false,
 			expectError:   "the provided identity token belongs to an existing LFID account and cannot be linked",
+			expectLinkerCalled: false,
 		},
 		{
 			name:        "validate guard passes for social token",
@@
 			expectSuccess: true,
+			expectLinkerCalled: true,
 		},
 		{
 			name:          "invalid json returns error",
@@
 			expectSuccess: false,
+			expectLinkerCalled: false,
 		},
 	}
@@
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			linkerCalled := false
+			origLink := tt.linker.linkIdentityFunc
+			tt.linker.linkIdentityFunc = func(ctx context.Context, req *model.LinkIdentity) error {
+				linkerCalled = true
+				if origLink != nil {
+					return origLink(ctx, req)
+				}
+				return nil
+			}
+
 			orchestrator := NewMessageHandlerOrchestrator(
 				WithIdentityLinkerForMessageHandler(tt.linker),
 				WithUserReaderForMessageHandler(tt.reader),
 			)
@@
 			if tt.expectError != "" && response.Error != tt.expectError {
 				t.Errorf("error = %q, want %q", response.Error, tt.expectError)
 			}
+			if linkerCalled != tt.expectLinkerCalled {
+				t.Errorf("LinkIdentity called = %v, want %v", linkerCalled, tt.expectLinkerCalled)
+			}
 		})
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/message_handler_test.go` around lines 1207 - 1276, The table
tests currently verify the response payload but don't assert that the identity
linking path was executed; update the mockIdentityLinker instances in each test
case to track invocation (e.g., add a called flag or counter in the mock and set
it inside linkIdentityFunc), then after calling orchestrator.LinkIdentity(...)
assert that linkIdentity was invoked when expectSuccess is true and not invoked
when the validateLinkRequestFunc returns a validation error; use the existing
mockIdentityLinker and its linkIdentityFunc/validateLinkRequestFunc hooks to set
and check the flag immediately after unmarshalling the UserDataResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@internal/service/message_handler_test.go`:
- Around line 1207-1276: The table tests currently verify the response payload
but don't assert that the identity linking path was executed; update the
mockIdentityLinker instances in each test case to track invocation (e.g., add a
called flag or counter in the mock and set it inside linkIdentityFunc), then
after calling orchestrator.LinkIdentity(...) assert that linkIdentity was
invoked when expectSuccess is true and not invoked when the
validateLinkRequestFunc returns a validation error; use the existing
mockIdentityLinker and its linkIdentityFunc/validateLinkRequestFunc hooks to set
and check the flag immediately after unmarshalling the UserDataResponse.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3a8de7e8-877d-4256-8798-1557b60a2689

📥 Commits

Reviewing files that changed from the base of the PR and between 1342a3d and 4df6a0a.

📒 Files selected for processing (2)
  • internal/infrastructure/auth0/identity_test.go
  • internal/service/message_handler_test.go

…counts

Jira Ticket: https://linuxfoundation.atlassian.net/browse/LFXV2-1250

Assisted by [Claude Code](https://claude.ai/code)

Signed-off-by: Mauricio Zanetti Salomao <mauriciozanetti86@gmail.com>
@mauriciozanettisalomao mauriciozanettisalomao merged commit d291c44 into linuxfoundation:main Mar 18, 2026
3 of 6 checks passed
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.

3 participants