Noizu.MCP.Auth.Server.Store.Ecto (Noizu MCP v0.1.6)

Copy Markdown View Source

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) — an Ecto.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 by Noizu.MCP.Auth.Server.config/1. revoke_access_token/2 raises 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.