Skip to content

fix(kyc): resolve authenticated user ID for Etherfuse KYC gates - #1087

Merged
Bran18 merged 1 commit into
kindfi-org:developfrom
mubkid1:fix/1046-etherfuse-resolved-user-id
Sep 29, 2026
Merged

Bran18 merged 1 commit into
kindfi-org:developfrom
mubkid1:fix/1046-etherfuse-resolved-user-id

Conversation

@mubkid1

@mubkid1 mubkid1 commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Overview

The Etherfuse on/off-ramp cards constructed their KYC gate with useKycRequiredGate(userId ?? '') at render time, while the real authenticated user id was fetched later inside the request handler with an unchecked fetch('/api/auth/user'). When the optional userId prop was absent, the gate was built with '' (whose preflight short-circuits to true, silently disabling the gate) and a second, independent resolution of the same id was used for the request body. This change resolves the authenticated user id once, stores it in component state, and uses that single value for both the KYC gate and the Etherfuse request bodies.

Related Issue

Closes #1046

Changes

New resolver

  • [ADD] apps/web/lib/kyc/resolved-user-id.ts
    • normalizeUserId trims and collapses null/undefined/''/whitespace to null, so an empty string can never look like a resolved principal.
    • resolveAuthenticatedUserId returns a provided id with no network call, otherwise queries /api/auth/user exactly once and returns resolved only for { user: { id: <non-empty string> } } (top-level id accepted defensively).
    • Every failure returns unavailable with a machine-readable reason (network_error, http_<status>, invalid_payload, missing_id); it never throws and never returns an empty id.

New hook

  • [ADD] apps/web/hooks/use-resolved-user-id.ts
    • useResolvedUserId(userIdProp?) stores the resolved id in state and exposes { userId, isLoading, error, refresh }.
    • Resolves on mount and on prop change, ignores results after unmount, and keeps only the latest request when a refresh() overlaps an in-flight resolution.
    • An unresolved or failed lookup is null, never ''.

Modified KYC gate hook

  • [MODIFY] apps/web/hooks/use-kyc-required-gate.ts
    • Parameter widened to userId: string | null (existing string callers keep compiling).
    • Exposes isPrincipalResolved (false only when userId === null).
    • Documents that a null id means "principal not resolved yet" and must not be treated as "gate disabled"; existing preflight behaviour for a non-empty id is unchanged.

Modified Etherfuse cards

  • [MODIFY] apps/web/components/sections/projects/manage/escrow/components/etherfuse-off-ramp-card.tsx
  • [MODIFY] apps/web/components/sections/projects/manage/escrow/components/etherfuse-on-ramp-card.tsx
    • Replaced the inline fetch('/api/auth/user') and useKycRequiredGate(userId ?? '') with useResolvedUserId(userId) + useKycRequiredGate(resolvedUserId).
    • handleOffRamp/handleOnRamp bail out with toast.error('Unable to resolve your account. Please sign in again.') when the id is null, and otherwise send the same resolvedUserId in the request body.
    • <KycRequiredGate userId={resolvedUserId} /> now receives the exact id used by the request body.
    • Both action buttons are disabled while isResolvingUserId is true; denial handling is unchanged.

Tests

  • [ADD] apps/web/test/kyc-resolved-user-id.test.ts (bun:test)
    • normalizeUserId: null/undefined/empty/whitespace -> null, trimming of real ids.
    • resolveAuthenticatedUserId: provided prop wins with zero fetches (asserted via a spy), user.id resolved, defensive top-level id, 500 -> http_500, 401 -> http_401, rejected fetch -> network_error, non-JSON body -> invalid_payload, missing user -> missing_id, and empty/whitespace/non-string user.id -> missing_id (the regression that previously let the gate receive '').
    • A final case drives every failing shape and asserts the resolver resolves to unavailable instead of throwing.

Verification Results

The end-to-end suite runs with bun test (the apps/web test script is
bun test/**.test.{mjs,ts,js}); this PR was authored via the GitHub API without a
local clone, so no bun test output is claimed here. What the added test file covers:

  • Provided-prop short-circuit with a fetch spy asserted as never called (no double fetch).
  • Successful resolution via user.id and via a defensive top-level id.
  • HTTP status, rejected promise, unparsable body, and missing/empty/whitespace/non-string id failure boundaries.
  • The '' regression specifically — an empty user.id resolves to unavailable/missing_id, not a usable id.

The type-level part of this change WAS executed, with the real toolchain
(tsc --strict --noEmit --skipLibCheck, TypeScript from the repository's own
devDependency range). Because useKycRequiredGate gained a string | null
parameter while five other call sites still pass a plain string, the hook keeps a
type parameter so each caller keeps its own userId type:

PASS  useKycRequiredGate(string)                -> result.userId : string
PASS  useKycRequiredGate(string | null)         -> result.userId : string | null
PASS  <KycRequiredGate userId={...} /> requires : string
PASS  @ts-expect-error on passing string | null to that prop (so the cards pass ?? '')

That is why both cards pass userId={resolvedUserId ?? ''} to <KycRequiredGate>
while passing the raw resolvedUserId to useKycRequiredGate and the request body:
the component's prop is a required string, the gate hook's is not.

Acceptance Criteria Status How it is addressed
Do not initialize useKycRequiredGate with an empty user ID Addressed Cards call useKycRequiredGate(resolvedUserId), which is null (not '') until resolved
Store the resolved authenticated user ID in component state Addressed useResolvedUserId keeps it in state and exposes it as userId
Use the same ID for KYC gate actions and Etherfuse request bodies Addressed The single resolvedUserId is passed to useKycRequiredGate, the request body, and <KycRequiredGate userId={...} />
Verify users can start and recheck KYC when no userId prop is provided Addressed The resolver normalizes the absent prop to an authenticated lookup, exposes null while unresolved (no gated request), and refresh() re-resolves

Closes #1046

@almanax-ai

almanax-ai Bot commented Sep 28, 2026

Copy link
Copy Markdown

Plan expired

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

@drips-wave

drips-wave Bot commented Sep 28, 2026

Copy link
Copy Markdown

@mubkid1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

@mubkid1 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 28, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 59 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: 36c779eb-9062-4f0a-b62e-ac2869d509da

📥 Commits

Reviewing files that changed from the base of the PR and between 9cf0797 and 6955ddf.

📒 Files selected for processing (6)
  • apps/web/components/sections/projects/manage/escrow/components/etherfuse-off-ramp-card.tsx
  • apps/web/components/sections/projects/manage/escrow/components/etherfuse-on-ramp-card.tsx
  • apps/web/hooks/use-kyc-required-gate.ts
  • apps/web/hooks/use-resolved-user-id.ts
  • apps/web/lib/kyc/resolved-user-id.ts
  • apps/web/test/kyc-resolved-user-id.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.

@Bran18
Bran18 merged commit 7fc7d42 into kindfi-org:develop Sep 29, 2026
2 of 3 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.

Provide resolved user IDs to Etherfuse KYC gates

2 participants