Fix KYB org members being sent to identity verification on Rewards - #1952
Merged
Conversation
The rewards route guarded itself on GET /api/liveness-checkpoints/status, which reports whether the caller has passed a check β not whether they need one. Only the API can answer that, and only per grant: grants belonging to an org with a KYB record are exempt, since their withdrawers are authorized by the company record rather than by a personal identity check. The guard therefore redirected exempt members away before any grants endpoint was called. Because those members have no personal KYC by design, the checkpoint flow then bounced them on to /wave/kyc-required, leaving them unable to reach Rewards at all and looking at an apparent demand to verify their identity a second time. Drop the pre-gate and react to the API's refusal instead. The grants endpoints already respond with liveness_checkpoint_required, and call.ts already translates that into LivenessCheckpointRequiredError; nothing was catching it. Handling it at the three call sites also removes a round trip from the page load, which awaited parent() and so serialized behind the status call, and makes the single-grant route exact, since the API decides that one per grant rather than per user.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
A member of an org with a KYB record opens
/wave/rewards, and instead of their rewards they land on/wave/kyc-requiredβ apparently being asked to verify their identity a second time, with no way through.Why
rewards/+layout.tsguarded the whole section onGET /api/liveness-checkpoints/status:That endpoint reports whether the caller has passed a check. It does not report whether they need one β it evaluates the user alone and never looks at a grant. Only the API can answer that question, and only per grant: grants belonging to an org with a KYB record are exempt, because their withdrawers are authorized by the company record rather than by a personal identity check.
So the guard redirected exempt members away before any grants endpoint was ever called. The API would have served their grants perfectly happily. The layout's own comment asserted the premise that made this look safe β "The API refuses the grants endpoints outright in that case" β which is only true for grants that aren't exempt.
It then compounded:
checkpoint/+layout.tssees an unsatisfied checkpoint, checks for personal KYC, and finds none β which is correct and expected for a KYB withdrawer β so it forwards them to/wave/kyc-required. Hence the dead end, and hence the "why do I need to KYC again?".rewards/+page.sveltewas already KYB-aware and would have rendered correctly. It just never got the chance.The fix
Stop pre-gating; let the API's refusal do the routing. The grants endpoints already respond with
liveness_checkpoint_required, andcall.tsalready turns that intoLivenessCheckpointRequiredErrorβ nothing was catching it. Its own doc comment already described the intended handling.rewards/+layout.ts.handleCheckpointRequired(err, backTo)tolib/utils/wave/liveness.tsβ redirects into the challenge flow for that one error, re-throws everything else.rewards/+page.tsandrewards/[grantId]/+page.ts, which also inherit thedepends('wave:liveness-checkpoint')the layout carried.openWithdrawalFlow, for a check falling due between page load and click.Net β2 lines. Three things improve as a side effect:
parent(), so the status call was serialized ahead of the grants fetch rather than running alongside it.kyc-requireddead end resolves itself, since/wave/checkpointis now only reached after a genuine refusal.No backend change.
/api/liveness-checkpoints/statusstays what it is β the checkpoint flow itself asks exactly that question. It was only ever wrong as a stand-in for "do I need one".Testing
npm run checkβ 0 errors, 177 warnings, unchanged from before.eslintandprettier --checkclean on the touched files.Not covered by an automated test: the gating logic itself is tested on the backend, but this lived entirely in the frontend route guard, which is how it got through.
Intended behaviour, called out to save a reviewer the detour
A user holding both a personal grant and an exempt org grant is gated on the whole list, org grant included. That is correct and not something this PR should change: the personal grant requires personal KYC on its own terms, and an approved KYC satisfies the checkpoint directly through the
recent_kycpath, so there is no additional step and no dead end. The org grant is simply gated alongside for that window.The bug this PR fixes is specifically the case where every grant is exempt, and the user was gated anyway.
Follow-up, not in this PR
/wave/checkpointas a KYB withdrawer still dead-ends atkyc-required. Nothing routes there anymore, so it takes a deliberate URL entry.