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).
import * as Main from "gizweb:auth/main";Types
Site
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
function principalOf(p: Person): Security.PrincipalThe principal of a person, for the Gate.
install
function install(db: Data.Db): bool throws Model.RejectionThe 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
function signUp(sys: System, db: Data.Db, email: string, name: string, password: string, now: i64): Person throws Model.RejectionA 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
function insertPerson(db: Data.Db, person: Person): Person throws Model.RejectionAdds 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
function signIn(sys: System, db: Data.Db, email: string, password: string): Person throws Model.RejectionThe 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
function startSession(sys: System, db: Data.Db, person: Person, label: string, now: i64): string throws Model.RejectionA new session of PERSON on the device LABEL: the token for its cookie.
Effects: fail, read, write, console, io.
current
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
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
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
function signOut(db: Data.Db, token: string, now: i64): bool throws Model.RejectionSigns out one session by its token: this device only.
Effects: fail, read, write, console, io.
signOutEverywhere
function signOutEverywhere(db: Data.Db, person: string, now: i64): bool throws Model.RejectionSigns PERSON out of every device, and of every older token.
Effects: fail, read, write, console, io.
linkFor
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.
signInWithLink
function signInWithLink(db: Data.Db, raw: string, now: i64): Person throws Model.RejectionThe person of a magic link (purpose "login"), who is then proven to own the address.
Effects: fail, read, write, console, io.
verifyMailbox
function verifyMailbox(db: Data.Db, raw: string, now: i64): bool throws Model.RejectionProves the mailbox of the verify link RAW.
Effects: fail, read, write, console, io.
linkOpen
function linkOpen(db: Data.Db, raw: string, purpose: string, now: i64): bool throws Model.RejectionWhether 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
function resetPassword(sys: System, db: Data.Db, raw: string, password: string, now: i64): Person throws Model.RejectionSets 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
function newApiToken(sys: System, db: Data.Db, person: string, name: string, expires: i64, now: i64): string throws Model.RejectionA 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
function revokeApiToken(db: Data.Db, person: string, digest: string, now: i64): bool throws Model.RejectionEnds 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
function devicesOf(db: Data.Db, person: string, now: i64): Vec<Session> throws Model.RejectionThe sessions of PERSON open at NOW, for a devices list.
Effects: fail, read, write, console, io.
revokeDevice
function revokeDevice(db: Data.Db, person: string, digest: string, now: i64): bool throws Model.RejectionEnds one of PERSON's sessions by its digest (a devices list's row), at NOW.
Effects: fail, read, write, console, io.
tokensOf
function tokensOf(db: Data.Db, person: string): Vec<ApiToken> throws Model.RejectionPERSON's API tokens, newest first.
Effects: fail, read, write, console, io.
addPasskey
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.RejectionKeeps the passkey that the registration ceremony ATTESTATION answers (see Passkeys.register) for PERSON, named NAME.
Effects: fail, read, write, console, io.
signInWithPasskey
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.RejectionThe 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
function signInWithIdentity(sys: System, db: Data.Db, id: Identity, now: i64): Person throws Model.RejectionThe 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
function ssoProvider(connection: string): stringThe provider of an enterprise SSO connection: each connection is a provider of its own, sso:${connection}.
Effects: fail.
linkSso
function linkSso(db: Data.Db, connection: string, subject: string, person: string, email: string, active: bool, profile: string, now: i64): bool throws Model.RejectionRecords 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
function linksOf(db: Data.Db, person: string): Vec<ProviderLink> throws Model.RejectionThe provider links of PERSON, in the order they were made.
Effects: fail, read, write, console, io.
carriedRow
function carriedRow(refreshToken: string, person: string, now: i64, expires: i64): ImportedSessionThe 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
function importCarried(db: Data.Db, refreshToken: string, person: string, now: i64, expires: i64): bool throws Model.RejectionRecords a carried refresh session of PERSON, from its cookie REFRESHTOKEN, made at NOW and good until EXPIRES.
Effects: fail, read, write, console, io.
adoptRefresh
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
function oidcToken(sys: System, db: Data.Db, issuer: Oidc.OidcIssuer, req: Oidc.OidcToken, now: i64): string throws Model.RejectionThe 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
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
function importTotp(db: Data.Db, person: string, secret: string, backup: string, on: bool, now: i64): bool throws Model.RejectionKeeps PERSON's two-factor sign-in as it was at the move (see Totp.importFor).
Effects: fail, read, write, console, io.
importPasskey
function importPasskey(db: Data.Db, person: string, name: string, credential: Passkeys.Credential, now: i64): bool throws Model.RejectionKeeps a passkey of PERSON as it was at the move (see Passkey.add).
Effects: fail, read, write, console, io.
person
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
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
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
const API_PREFIX = "t."The prefix of an API token. A token without it is a session (opaque, "s.") or gizstack's signed form.
COOKIE
const COOKIE = Sessions.COOKIEThe 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.