Postgres Noizu.MCP.Auth.Server.Store adapter, in raw SQL.
store: {Noizu.MCP.Auth.Server.Store.Ecto, repo: MyApp.Repo}No Ecto schemas, deliberately
The library defines no schema modules and no migrations. A schema is a claim
about a table the host owns; two apps with slightly different columns would
both be broken by one library change, and a mix ecto.gen.migration here
would collide with a host that uses Liquibase (both of ours do). What ships
instead is priv/liquibase/noizu_mcp_oauth.yaml, which the host copies into
its own changelog directory and owns from then on. This adapter speaks to
those tables through Ecto.Adapters.SQL.query/4 and nothing else.
Options
:repo(required) — anEcto.Repo:prefix— Postgres schema qualifier, e.g."mcp":timeout— query timeout, default 5s:subject_type—:text(default) or:uuid:track_access_tokens— set for you byNoizu.MCP.Auth.Server.config/1.revoke_access_token/2raises when it is absent rather than reporting a revocation it did not perform; see that function.
:subject_type
subject is opaque text to this library — it is whatever the upstream
callback returns, which may be an email or any other identifier — and the
shipped template declares it text. The template's optional subject_fk
changeSets change it to uuid so it can reference users(id). Postgres will
not accept a string bind param for a uuid column, so a host that
uncommented those changeSets must say so:
store: {Store.Ecto, repo: MyApp.Repo, subject_type: :uuid}Values stay strings on both sides of the boundary either way; this option only
tells the adapter how to bind them. Leaving it :text against a uuid column
fails loudly on the first write rather than corrupting anything.
Atomicity
take_authorization_code/2 is a single
UPDATE … SET used_at = now() WHERE code_hash = $1 AND used_at IS NULL RETURNING *.
Two concurrent redemptions both reach the row; exactly one updates it; the
loser's zero-row result is then disambiguated by a follow-up SELECT, which
tells "never existed" from "already used". The latter is a replay and
revokes the refresh family.
rotate_refresh_token/3 is the same shape — a guarded UPDATE … WHERE rotated_at IS NULL AND revoked_at IS NULL RETURNING *, with the new row
inserted in the same transaction — so one refresh token cannot be exchanged
twice, and a second attempt is reported as a replay rather than as a miss.