textmachine/platform/internal/pgstore/queries/identity.sql

69 lines
3.4 KiB
SQL

-- Identity, login states and the login journal.
-- name: PutLoginState :exec
insert into auth_states (state_sha256, provider, issuer, nonce, code_verifier, return_to, start_id, created_at, expires_at)
values (sqlc.arg(state_sha256), sqlc.arg(provider), sqlc.arg(issuer), sqlc.arg(nonce),
sqlc.arg(code_verifier), sqlc.arg(return_to), sqlc.arg(start_id), sqlc.arg(created_at),
sqlc.arg(expires_at));
-- name: TakeLoginState :one
-- Deleting and returning in ONE statement is what makes it single-use under concurrency: a second
-- callback with the same state deletes nothing and gets nothing, with no window between the check
-- and the removal.
delete from auth_states
where state_sha256 = sqlc.arg(state_sha256) and expires_at > sqlc.arg(now)
returning provider, issuer, nonce, code_verifier, return_to, start_id, created_at, expires_at;
-- name: DeleteExpiredLoginStates :execrows
delete from auth_states where expires_at <= sqlc.arg(now);
-- name: DeleteOldLoginEvents :execrows
-- /auth/callback writes a row on every refusal and needs no credential to do it, so a journal that
-- only grows is a liability rather than an audit.
delete from login_events where at < sqlc.arg(before);
-- name: UserByIdentity :one
-- Resolves a provider's subject to the account it belongs to, WITHOUT creating one.
select user_id from identities
where provider = sqlc.arg(provider) and subject = sqlc.arg(subject);
-- name: LockIdentity :one
select user_id from identities
where provider = sqlc.arg(provider) and subject = sqlc.arg(subject) for update;
-- name: RefreshIdentity :exec
-- The address is refreshed only when the provider says it is verified — an unverified one is kept
-- on the identity and never promoted to the account.
update identities set email = sqlc.narg(email), email_verified = sqlc.arg(email_verified),
last_login_at = sqlc.arg(now)
where provider = sqlc.arg(provider) and subject = sqlc.arg(subject);
-- name: RefreshAccountEmail :exec
-- The cast keeps the parameter non-null: users.email IS nullable, but this path is reached only for
-- a VERIFIED, non-empty address, and a *string here would invite a nil the branch cannot produce.
update users set email = sqlc.arg(email)::text where id = sqlc.arg(id);
-- name: CreateUser :exec
insert into users (id, email, created_at)
values (sqlc.arg(id), sqlc.narg(email), sqlc.arg(created_at));
-- name: CreateIdentity :execrows
-- `do nothing` rather than a bare insert: two first logins of one brand-new identity can race, and
-- the loser must see the row the winner inserted rather than a unique violation.
insert into identities (provider, subject, user_id, email, email_verified, created_at, last_login_at)
values (sqlc.arg(provider), sqlc.arg(subject), sqlc.arg(user_id), sqlc.narg(email),
sqlc.arg(email_verified), sqlc.arg(now), sqlc.arg(now))
on conflict (provider, subject) do nothing;
-- name: RevokeUserSessions :execrows
update sessions set revoked_at = sqlc.arg(now)::timestamptz
where user_id = sqlc.arg(user_id) and revoked_at is null;
-- name: RecordLogin :exec
insert into login_events (user_id, provider, outcome, reason, ip_prefix, client, at)
values (sqlc.narg(user_id), sqlc.arg(provider), sqlc.arg(outcome), sqlc.arg(reason),
sqlc.arg(ip_prefix), sqlc.arg(client), sqlc.arg(at));
-- name: RecentLogins :many
select provider, outcome, reason, ip_prefix, client, at
from login_events where user_id = sqlc.arg(user_id)::text order by at desc limit sqlc.arg(lim)::bigint;