Files
clinch/db/migrate/20260719000005_add_device_code_replay_tracking.rb
T
Dan MilneandClaude Opus 4.8 71ee301dd2 Device flow: replay-revocation, user_code CSPRNG, /device rate limit
More device-flow security-review fixes.

Replay-revocation on device_code redemption (was: destroy on use):
- Redemption now marks the code redeemed_at and links the issued access/refresh
  tokens back to it (new oidc_device_code_id FKs, on_delete: :nullify) instead of
  destroying the row. A replayed redeemed code is detected as reuse — revoking
  every descended token and returning a distinguishable "already been used"
  invalid_grant rather than the generic "Invalid device_code" — mirroring the
  authorization-code reuse semantics (RFC 6749 §4.1.2). The device_code FK is
  carried forward across refresh rotation so revocation reaches the whole chain.
  Redeemed codes are reaped by the existing expiry cleanup sweep.

user_code CSPRNG (RFC 8628 §6.1):
- The 8-char user_code is a credential (type it + Approve mints tokens), so draw
  it from SecureRandom instead of Ruby's global Mersenne Twister (Array#sample).

Rate-limit the /device verification endpoint (RFC 8628 §5.1):
- Add the app's standard 10/min limit on show + verify so a signed-in user can't
  brute-force the short code space to deny or hijack a pending authorization.

Also carried in this commit (other agent's working-tree change):
- Give each OidcController rate_limit a distinct name: so frequent device polling
  on the token endpoint no longer shares one counter with (and 429s) unrelated
  token/refresh/revoke/introspect calls.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016Q4ATZHoMCWqvSpE2yYoie
2026-07-19 13:38:40 +10:00

21 lines
1.0 KiB
Ruby

class AddDeviceCodeReplayTracking < ActiveRecord::Migration[8.1]
# Replay-revocation for the device grant, mirroring the authorization-code path.
#
# The device flow previously destroy!ed the code on redemption, so a replayed
# redeemed code was indistinguishable from an unknown one and the tokens it
# minted could not be revoked. Instead we now mark the code redeemed_at and link
# the issued tokens back to it, so a second redemption is detected as reuse and
# every descended token is revoked (RFC 6749 §4.1.2 reuse semantics).
#
# on_delete: :nullify matches the oidc_authorization_code FK: the cleanup job can
# still delete expired device codes without orphaning or destroying live tokens.
def change
add_column :oidc_device_codes, :redeemed_at, :datetime
add_reference :oidc_access_tokens, :oidc_device_code, null: true,
foreign_key: {on_delete: :nullify}
add_reference :oidc_refresh_tokens, :oidc_device_code, null: true,
foreign_key: {on_delete: :nullify}
end
end