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 revoked jti. Idempotent via INSERT ... 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

  • IsRevoked is membership-only — no expires_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. jti is a unique fosite UUID, so a stale expired row can't collide with a fresh token.
  • No RevokedToken model 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/
Edited by Smriti Garg

Merge request reports

Loading
Loading