Skip to content

fix(web): read the Didit callback status from the provider, not the client - #1068

Closed
rudrasatani13 wants to merge 1 commit into
kindfi-org:developfrom
rudrasatani13:fix/1022-didit-callback-status
Closed

rudrasatani13 wants to merge 1 commit into
kindfi-org:developfrom
rudrasatani13:fix/1022-didit-callback-status

Conversation

@rudrasatani13

Copy link
Copy Markdown

Closes #1022

Two browser-facing paths persisted a KYC status the browser chose.

The callback route accepted { verificationSessionId, status } and stored status directly. Worse, app/(routes)/profile/page.tsx read searchParams.status and stored that — so /profile?kyc=completed&verificationSessionId=<any own session>&status=Approved was enough to mark a user approved. Because applyDiditStatusUpdate calls activatePollarIfApproved, that also reached wallet activation, which is the "self-approval and unintended Pollar wallet activation" the issue names.

The change

One shared path, lib/kyc/refresh-session-status.ts, now used by both entry points:

  1. Look up the session and check ownership. findDiditSessionBySessionId, then record.userId === userId. A session that belongs to someone else is reported exactly like one that does not exist — both are 403 Unknown verification session — so neither route can be used to probe for session ids.
  2. Read the status from Didit. getDiditSessionStatus is the only source; no request field reaches applyDiditStatusUpdate.
  3. Write nothing if the provider is unreachable. The response is 202 with reason: 'provider_unavailable'; the webhook and check-status remain the trusted paths and will apply the real status when Didit is reachable again.

The request body is now { verificationSessionId } only, the route's validator rejects anything without it and ignores status entirely, and both profile dashboards stop sending it. The two dashboards also stopped showing a status toast derived from the query string — it was cosmetic, but it was the same browser-supplied value presented as a result.

Acceptance criteria

Criterion Where
Reject callback session ids that do not belong to the authenticated user ownership check in the helper, 403; test "writes nothing for a session that belongs to another user"
Do not persist browser-supplied KYC status values status removed from the body type, the validator, both clients, and the profile page
Read status from Didit, or rely on trusted paths getDiditSessionStatus is the only writer input; provider failure writes nothing
Prevent self-approval and unintended Pollar activation no path reaches applyDiditStatusUpdate with a client value, so activatePollarIfApproved can only be reached on a provider-reported approval

Verification

cd apps/web && bun test test/kyc-callback-status.test.ts   # 5 pass, 0 fail
cd apps/web && bun test                                    # same 62 pre-existing failures as develop, no new ones
bunx biome check <the six changed files>                   # no new findings

The five cases are: applies what Didit reports; another user's session writes nothing; unknown session writes nothing; provider unreachable writes nothing; and a declined session is stored as declined rather than as whatever the caller wanted.

Notes

  • Two pre-existing warnings remain in profile-dashboard-v2.tsx (unused useRouter import, unused parameter) — they are in the repository's existing lint output, not introduced here, and I left them rather than mixing a cleanup into a security fix.
  • The same file has a pre-existing req.ip type error inside the withRateLimit identifier, unchanged by this PR.
  • The Didit webhook signature path is untouched, and there is no migration.

@almanax-ai

almanax-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Plan expired

Your subscription has expired. Please renew your subscription to continue using CI/CD integration and other features.

@vercel

vercel Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@rudrasatani13 is attempting to deploy a commit to the kindfi Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 30 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0ae066bd-b610-4da2-bbd9-15362fa9b4cb

📥 Commits

Reviewing files that changed from the base of the PR and between 8b94f0a and 0d3eb5f.

📒 Files selected for processing (6)
  • apps/web/app/(routes)/profile/page.tsx
  • apps/web/app/api/kyc/didit/callback/route.ts
  • apps/web/components/sections/profile/dashboard/profile-dashboard-v2.tsx
  • apps/web/components/sections/profile/profile-dashboard.tsx
  • apps/web/lib/kyc/refresh-session-status.ts
  • apps/web/test/kyc-callback-status.test.ts

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.

@rudrasatani13

Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

Prevent client-controlled Didit callback status updates

2 participants