Feat(oauth): add revoked tokens denylist DAL
What does this MR do and why?
Adds revokedtokens — the in-process data access layer (DAL) for the token revocation store, backed by the revoked_tokens denylist table introduced in !386 (merged) (merged). Part of the Token Management epic (gitlab-org&22564); this implements the "store + DAL" scope of gitlab-org/gitlab#602674 (closed).
The IAM service issues stateless JWTs that cannot be revoked before their exp; this store records revoked tokens so they can be rejected early. This MR is the persistence layer only — it has no runtime caller yet. The write path (revoke endpoint, gitlab-org/gitlab#602673 (closed)) and read path (introspection, gitlab-org/gitlab#603631 (closed)) are separate MRs that will consume this DAL.
The package mirrors the existing accesstokens / refreshtokens DAL layout and exposes two operations:
Revoke(jti, expiresAt)— records a revokedjti. Idempotent viaINSERT ... ON CONFLICT (jti) DO NOTHING, so re-revoking is a no-op with no error (matches RFC 7009's idempotent revoke).IsRevoked(jti)— denylist membership check (SELECT EXISTS(... WHERE jti = $1)).
Plus the sqlc source (queries.sql), the generated sqlcgen package, the sqlc.yaml entry, and integration tests.
Design notes
IsRevokedis membership-only — noexpires_at > now()guard. Expiry is the JWT validator's responsibility and is always checked before the revocation lookup; folding an expiry check in here would introduce a DB-vs-validator clock-skew window near expiry where a revoked token could slip through.jtiis a unique fosite UUID, so a stale expired row can't collide with a fresh token.- No
RevokedTokenmodel struct is generated (omit_unused_structs) because no query returns a row.
Out of scope / follow-up
- Self-cleaning sweep (deleting rows past
expires_at) is deferred to a follow-up, along with the Yugabyte range-sharded index the range delete needs. Design under discussion on gitlab-org/gitlab#602674 (closed).
Testing
Integration tests (//go:build integration) cover Revoke (insert + idempotent re-revoke) and IsRevoked (true for a revoked jti, false for unknown). Verified green against both Postgres and Yugabyte:
go test -tags=integration ./auth/oauth/server/revokedtokens/
ENV=development-l1 go test -tags=integration ./auth/oauth/server/revokedtokens/Related
- Issue: gitlab-org/gitlab#602674 (closed)
- Epic: gitlab-org&22564
- Table migration: !386 (merged)