fix(auth): harden device-auth poll (atomic consume + blocked re-check)#4328
Draft
markijbema wants to merge 2 commits into
Draft
fix(auth): harden device-auth poll (atomic consume + blocked re-check)#4328markijbema wants to merge 2 commits into
markijbema wants to merge 2 commits into
Conversation
pollDeviceAuthRequest read the approved request, minted a token, then issued an unconditional 'expired' update — so two concurrent polls could each mint a long-lived API token from one approval. Replace it with a guarded compare-and-swap that flips 'approved' -> 'expired' and returns the row only to the winning caller; losers normalize to 'expired'. The token is minted only after the row is consumed, so at most one token is issued per approval.
…(Finding 2) Authorization was only checked when the user approved the device request; the later poll minted a ~5-year API token without re-checking. A user could approve while in good standing, get blocked, then poll and receive a fresh token still accepted by pepper-only downstream services. Re-validate the user at mint time (blocked_reason or isUserBlacklistedByDomain) and return 'denied' instead of minting. The approval is already consumed, so a blocked user cannot retry the code into a token.
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.
Summary
Follow-up to #4314 (block rotates pepper). That PR was Workstream B; this is Workstream A — server-side hardening of the device-authorization poll path in
apps/web/src/lib/device-auth/device-auth.ts. It closes two findings from the device-auth review:pollDeviceAuthRequestread the approved request, minted a token, then issued an unconditionalstatus = 'expired'update. Two concurrent polls could each pass the read and each mint a long-lived API token from one approval. Replaced with a guarded compare-and-swap (UPDATE ... SET status='expired' WHERE code = ? AND status='approved' RETURNING kilo_user_id); the token is minted only by the caller that won the swap, and losers normalize toexpired.blocked_reasonandisUserBlacklistedByDomain, returningdeniedinstead of minting. Because the approval is consumed first, a blocked user can't retry the code into a token.No schema change, no migration. The poll API route already maps
denied-> 403 andexpired-> 410, so no route change was needed.Verification
apps/web/src/lib/device-auth/device-auth.test.ts(24 tests pass), including two new cases:expired);deniedwith no token, and a retry returnsexpired.ifas the blocked-reason check; I did not add a dedicated test for it becausegetBlacklistedDomainsis Redis-cached (60s) and not reliably seedable in a unit test.Visual Changes
N/A
Reviewer Notes
deniedwhile the row is set toexpiredby the consume; both are terminal/non-retryable, anddenied(403) is the more informative signal to the polling device.