GizStack
View .mdOpen in ChatGPTOpen in Claude

gizweb:auth/main

Auth: who is asking, for every gizweb app. It owns the people, their sessions, teams and roles, proofs of a mailbox, API tokens and (with the passkey and OAuth modules) the other ways in. An app asks current for the person of a request, and the Gate judges with their team grants (Gate.grantOf): pages, tools, agents and APIs share that one rule. The rows are the app's own: every function takes the Data.Db of the thread that serves the request (see records.giz).

gizlang
import * as Main from "gizweb:auth/main";

Types

Site

gizlang
type Site<C> = { prefix: string; home: string; origin: string; title: string; password: bool; secret: bytes; db: (context: C) => Data.Db; sys: (context: C) => System; shell: (context: C) => App.Shell; mail: (context: C, to: string, link: string) => bool; }

What the sign-in routes need of an app. PREFIX is where they are served ("/auth"), HOME where a signed-in person goes, ORIGIN the app's own address (a mailed link starts with it, never with the request's Host, which a client chooses). PASSWORD turns on the password field; without it a person signs in by a mailed link only. MAIL sends LINK to the address TO and says whether it was sent. The other functions read the app's context: its records (this thread's Data.Db), its System and its page shell.

Opaque: create and use it through this module's functions.

Functions

principalOf

gizlang
function principalOf(p: Person): Security.Principal

The principal of a person, for the Gate.

install

gizlang
function install(db: Data.Db): bool throws Model.Rejection

The tables of this module, made when missing. Their changes are additive: an existing table keeps its rows. Call it once on the app's records before it serves.

Effects: fail, read, write, console, io.

signUp

gizlang
function signUp(sys: System, db: Data.Db, email: string, name: string, password: string, now: i64): Person throws Model.Rejection

A new account of EMAIL named NAME. A password is optional: "" makes an account for a passkey or a link.

Effects: fail, read, write, console, io.

insertPerson

gizlang
function insertPerson(db: Data.Db, person: Person): Person throws Model.Rejection

Adds PERSON with the id it has (an importer's account: its principal id, its bcrypt hash, its verified time). The address is kept lowercase, and an address that is taken is refused.

Effects: fail, read, write, console, io.

signIn

gizlang
function signIn(sys: System, db: Data.Db, email: string, password: string): Person throws Model.Rejection

The person of EMAIL with PASSWORD. Its failure is SIGN_IN_FAILED, whichever part was wrong. A bcrypt password that an import brought is stored again as this module stores passwords, at its first use.

Effects: fail, read, write, console, io.

startSession

gizlang
function startSession(sys: System, db: Data.Db, person: Person, label: string, now: i64): string throws Model.Rejection

A new session of PERSON on the device LABEL: the token for its cookie.

Effects: fail, read, write, console, io.

current

gizlang
function current(db: Data.Db, secret: bytes, request: Http.Request, now: i64): Result<Option<Person>, Model.Rejection>

The person a request is signed in as at NOW, or undefined. A session row, an API token, or gizstack's older signed token (valid until 30 days after it was issued and not after a sign-out everywhere).

Effects: fail, read, write, console, io.

personOf

gizlang
function personOf(db: Data.Db, secret: bytes, token: string, now: i64): Result<Option<Person>, Model.Rejection>

The person of TOKEN at NOW (as current reads it from a request), or undefined.

Effects: fail, read, write, console, io.

personById

gizlang
function personById(db: Data.Db, id: string): Result<Option<Person>, Model.Rejection>

The person with the id ID, or undefined.

Effects: fail, read, write, console, io.

signOut

gizlang
function signOut(db: Data.Db, token: string, now: i64): bool throws Model.Rejection

Signs out one session by its token: this device only.

Effects: fail, read, write, console, io.

signOutEverywhere

gizlang
function signOutEverywhere(db: Data.Db, person: string, now: i64): bool throws Model.Rejection

Signs PERSON out of every device, and of every older token.

Effects: fail, read, write, console, io.

linkFor

gizlang
function linkFor(sys: System, db: Data.Db, email: string, purpose: string, now: i64): Result<Option<string>, Model.Rejection>

The raw token of a new proof of PURPOSE ("verify", "reset" or "login") for EMAIL, when an account has it. The caller mails the link whatever the answer, so an address is never revealed by the reply.

Effects: fail, read, write, console, io.

gizlang
function signInWithLink(db: Data.Db, raw: string, now: i64): Person throws Model.Rejection

The person of a magic link (purpose "login"), who is then proven to own the address.

Effects: fail, read, write, console, io.

verifyMailbox

gizlang
function verifyMailbox(db: Data.Db, raw: string, now: i64): bool throws Model.Rejection

Proves the mailbox of the verify link RAW.

Effects: fail, read, write, console, io.

linkOpen

gizlang
function linkOpen(db: Data.Db, raw: string, purpose: string, now: i64): bool throws Model.Rejection

Whether RAW is a live link of PURPOSE at NOW, without using it: what a page checks before it shows the form the link opens.

Effects: fail, read, write, console, io.

resetPassword

gizlang
function resetPassword(sys: System, db: Data.Db, raw: string, password: string, now: i64): Person throws Model.Rejection

Sets the password of the reset link RAW to PASSWORD and gives the person, to sign in. Every session of the person ends with it. A dead link is refused before the password is hashed, which is slow on purpose.

Effects: fail, read, write, console, io.

newApiToken

gizlang
function newApiToken(sys: System, db: Data.Db, person: string, name: string, expires: i64, now: i64): string throws Model.Rejection

A new API token of PERSON named NAME, ending at EXPIRES (ms, 0 never). Returned once; only its digest is stored.

Effects: fail, read, write, console, io.

revokeApiToken

gizlang
function revokeApiToken(db: Data.Db, person: string, digest: string, now: i64): bool throws Model.Rejection

Ends one of PERSON's API tokens at NOW. DIGEST is the token's digest as tokensOf lists it, so a page never holds a secret.

Effects: fail, read, write, console, io.

devicesOf

gizlang
function devicesOf(db: Data.Db, person: string, now: i64): Vec<Session> throws Model.Rejection

The sessions of PERSON open at NOW, for a devices list.

Effects: fail, read, write, console, io.

revokeDevice

gizlang
function revokeDevice(db: Data.Db, person: string, digest: string, now: i64): bool throws Model.Rejection

Ends one of PERSON's sessions by its digest (a devices list's row), at NOW.

Effects: fail, read, write, console, io.

tokensOf

gizlang
function tokensOf(db: Data.Db, person: string): Vec<ApiToken> throws Model.Rejection

PERSON's API tokens, newest first.

Effects: fail, read, write, console, io.

addPasskey

gizlang
function addPasskey(secret: bytes, db: Data.Db, person: string, name: string, rpId: string, origin: string, challengeText: string, json: bytes, attestation: bytes, now: i64): bool throws Model.Rejection

Keeps the passkey that the registration ceremony ATTESTATION answers (see Passkeys.register) for PERSON, named NAME.

Effects: fail, read, write, console, io.

signInWithPasskey

gizlang
function signInWithPasskey(secret: bytes, db: Data.Db, rpId: string, origin: string, challengeText: string, id: string, json: bytes, data: bytes, signature: bytes, now: i64): Person throws Model.Rejection

The person who signs in with the passkey ID, once its assertion (DATA, SIGNATURE, over the client data JSON) answers the challenge. The counter it reports is kept, so a copy of the credential is refused.

Effects: fail, read, write, console, io.

signInWithIdentity

gizlang
function signInWithIdentity(sys: System, db: Data.Db, id: Identity, now: i64): Person throws Model.Rejection

The person of a sign-in with a provider's IDENTITY, at NOW. A linked subject is its person. Otherwise the verified address decides: a verified account of it is linked and signed in; an unverified one is taken over, as the mailbox owner (its password and its sessions end); a new address makes an account. The link is kept for the next sign-in.

Effects: fail, read, write, console, io.

ssoProvider

gizlang
function ssoProvider(connection: string): string

The provider of an enterprise SSO connection: each connection is a provider of its own, sso:${connection}.

Effects: fail.

linkSso

gizlang
function linkSso(db: Data.Db, connection: string, subject: string, person: string, email: string, active: bool, profile: string, now: i64): bool throws Model.Rejection

Records what the directory of CONNECTION says about SUBJECT: its PERSON, whether it may sign in (ACTIVE) and its PROFILE (JSON text, "" when none). A sync calls it for each user it reads; the row is updated when it exists. A link that is not active refuses sign-in, so a user the directory removed cannot sign in with it.

Effects: fail, read, write, console, io.

linksOf

gizlang
function linksOf(db: Data.Db, person: string): Vec<ProviderLink> throws Model.Rejection

The provider links of PERSON, in the order they were made.

Effects: fail, read, write, console, io.

carriedRow

gizlang
function carriedRow(refreshToken: string, person: string, now: i64, expires: i64): ImportedSession

The row that carries a refresh session of the earlier system to this app: its cookie is kept as its digest, its person is PERSON, the row was made at NOW and the cookie expires at EXPIRES. The importer stores the row, never the cookie.

Effects: fail.

importCarried

gizlang
function importCarried(db: Data.Db, refreshToken: string, person: string, now: i64, expires: i64): bool throws Model.Rejection

Records a carried refresh session of PERSON, from its cookie REFRESHTOKEN, made at NOW and good until EXPIRES.

Effects: fail, read, write, console, io.

adoptRefresh

gizlang
function adoptRefresh(sys: System, db: Data.Db, refreshToken: string, now: i64): Result<Option<Sessions.Carried>, Model.Rejection>

The session of a refreshToken cookie at NOW, once (see Sessions.adoptRefresh). The caller sets the new session cookie and clears refreshToken when this gives a session.

Effects: fail, read, write, console, io.

oidcToken

gizlang
function oidcToken(sys: System, db: Data.Db, issuer: Oidc.OidcIssuer, req: Oidc.OidcToken, now: i64): string throws Model.Rejection

The token response of a token request (see Oidc.grant): the ID token, an access token (an API token of the person, for ACCESS_LIFE) and the refresh token that replaces the one used.

Effects: fail, read, write, console, io.

oidcUserinfo

gizlang
function oidcUserinfo(db: Data.Db, token: string, now: i64): Result<Option<string>, Model.Rejection>

The userinfo claims of an access token from oidcToken, at NOW; undefined when it is not a live one.

Effects: fail, read, write, console, io.

importTotp

gizlang
function importTotp(db: Data.Db, person: string, secret: string, backup: string, on: bool, now: i64): bool throws Model.Rejection

Keeps PERSON's two-factor sign-in as it was at the move (see Totp.importFor).

Effects: fail, read, write, console, io.

importPasskey

gizlang
function importPasskey(db: Data.Db, person: string, name: string, credential: Passkeys.Credential, now: i64): bool throws Model.Rejection

Keeps a passkey of PERSON as it was at the move (see Passkey.add).

Effects: fail, read, write, console, io.

person

gizlang
function person<C>(site: Site<C>, context: C, request: Http.Request, now: i64): Option<Person>

The person of REQUEST at NOW (a session cookie or an API token), or undefined: what a route checks before it serves.

routes

gizlang
function routes<C>(site: Site<C>): Vec<Http.Route<App.Handle<C>>>

The sign-in of an app in one call: routes.push(...Auth.routes(site)) serves PREFIX/sign-in (the page, and the password sign-in when PASSWORD is on), PREFIX/link (a mailed one-use link, which also makes the account of a new address) and PREFIX/sign-out. Call Auth.install on the database once first.

Constants

SIGN_IN_FAILED

gizlang
const SIGN_IN_FAILED = "The email or the password is wrong."

The answer of every sign-in that fails: the same for an unknown address and a wrong password.

API_PREFIX

gizlang
const API_PREFIX = "t."

The prefix of an API token. A token without it is a session (opaque, "s.") or gizstack's signed form.

gizlang
const COOKIE = Sessions.COOKIE

The cookie that holds a session, and the parts of the identity an app uses on its own: teams and their Gate, the provider sign-in, and the passkey challenge.