69 lines
3.4 KiB
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;
|