@samveys/auth
Session and API-token guards, OIDC sign-in with PKCE, policies
This list was read from version 1.0.2; run pnpm sync to refresh it.
import { auth, authenticate, authOf, authorize, definePolicy, guarded, … } from '@samveys/auth'
Function (14)
auth
Function
The `@samveys/auth` plugin. Registers the named middleware `auth` (the default guard) and `auth:<guard>` for each guard, plus the lazy `ctx.auth`. The session and OIDC guards need the `@samveys/session` plugin and its middleware before `auth` (`use: ['session', 'auth']`); without it they fail with SVS_E090. A token-only API does not need the session plugin. ```ts auth({ guards: { web: sessionGuard({ provider }), api: tokenGuard({ provider }) }, default: 'web' }) ```
function auth(options: AuthOptions): import("@samveys/core").Plugin<AuthOptions>;
Exported from packages/auth/src/plugin.ts
authenticate
Function
Middleware that authenticates the request with `guard`. On success `ctx.auth` is set; otherwise the request ends with SVS_E093 (401). Put `session` before it when the guard reads the session. @param name Name shown in `ctx.auth.guard`. Default: the guard's kind.
function authenticate(guard: Guard, name?: string): Middleware;
Exported from packages/auth/src/middleware.ts
authOf
Function
`ctx.auth`, or SVS_E093 when no `auth` middleware ran for this request.
function authOf(ctx: HttpContext): AuthContext;
Exported from packages/auth/src/errors.ts
authorize
Function
Checks `policy[action]` for the caller of `ctx` and throws SVS_E094 (403) unless it returns `true`. No signed-in user is SVS_E093; an unknown action, a rule that returns anything but `true`, and a rule that throws all deny (a broken rule must never let a request through).
function authorize<P extends Policy>(ctx: HttpContext, policy: P, action: keyof P & string, resource?: unknown): Promise<void>;
Exported from packages/auth/src/policy.ts
definePolicy
Function
Declares the rules for one kind of resource. A rule gets the signed-in user and the resource and returns `true` to allow. ```ts const postPolicy = definePolicy({ update: (user: User, post: Post) => user.id === post.userId }) ```
function definePolicy<const P extends Readonly<Record<string, PolicyRule>>>(rules: P): P;
Exported from packages/auth/src/policy.ts
guarded
Function
Wraps a route handler so the policy check runs immediately before it, whatever middleware the route has: a route that forgot the `auth` middleware is still refused (SVS_E093), and a signed-in user the policy does not allow gets SVS_E094. The resource is loaded first, so a rule can compare it with the user. ```ts router.put('/posts/:id', guarded(postPolicy, 'update', (ctx) => posts.find(ctx.params.id), (ctx, post) => save(post))) ```
function guarded<P extends Policy, R, T, C extends HttpContext = HttpContext>(policy: P, action: keyof P & string, loadResource: (ctx: C) => R | Promise<R>, handler: (ctx: C, resource: R) => T | Promise<T>): (ctx: C) => Promise<T>;
Exported from packages/auth/src/policy.ts
oidcGuard
Function
Sign-in with an OpenID Connect provider through the authorization code flow with PKCE (S256). `redirectToProvider()` sends the browser to the provider with a fresh `state`, `nonce` and PKCE challenge, all remembered in the session. `handleCallback()` checks `state`, exchanges the code (sending the PKCE verifier), validates the ID token — signature (RS256 or ES256 from the provider's JWKS), `iss`, `aud`, `exp`, `iat`, `nonce` — maps the claims to a user and signs the user in with a new session id. Anything wrong is SVS_E095, with the precise reason in the error's `meta.reason` for logs. Needs the `session` middleware on both routes. Works with any compliant provider (Google Workspace, Entra ID, Keycloak): only the issuer URL differs.
function oidcGuard<U extends AuthUser = AuthUser>(options: OidcGuardOptions<U>): OidcGuard<U>;
Exported from packages/auth/src/oidc-guard.ts
ormUserProvider
Function
A {@link UserProvider} over an `@samveys/orm` model: `findById` is `Model.find(id)`. Token and OIDC flows hand ids over as strings; the ORM casts them to the key column. ```ts const users = ormUserProvider(User) ```
function ormUserProvider<U extends AuthUser = AuthUser>(model: FindableModel, options?: OrmUserProviderOptions<U>): UserProvider<U>;
Exported from packages/auth/src/providers.ts
passwordAuth
Function
Password sign-in with Argon2id from core. Hashes are upgraded on login when the cost parameters changed. The same error is thrown for an unknown email and a wrong password.
function passwordAuth<U extends PasswordUser>(options: PasswordAuthOptions<U>): {
Exported from packages/auth/src/password.ts
passwordResets
Function
Password reset tokens: 32 random bytes shown once (in the mail link), stored as SHA-256, valid once and for `ttlMs`. Creating a new token invalidates older ones for the same email.
function passwordResets(options: PasswordResetOptions): {
Exported from packages/auth/src/password.ts
requireAbility
Function
Middleware that refuses (SVS_E094, 403) unless `ctx.auth.can(ability)`. Runs after an authenticating middleware; with none it answers SVS_E093 instead, so a misordered route fails closed.
function requireAbility(ability: string): Middleware;
Exported from packages/auth/src/middleware.ts
sessionGuard
Function
Guard for browser users: the signed-in user's id lives in the server-side session. Needs the `session` middleware before `auth`. `login()` regenerates the session id, so an id an attacker planted before sign-in is worthless afterwards (session fixation). A session whose user was deleted counts as signed out.
function sessionGuard<U extends AuthUser = AuthUser>(options: SessionGuardOptions<U>): SessionGuard<U>;
Exported from packages/auth/src/session-guard.ts
tokenGuard
Function
Guard for API clients: `Authorization: Bearer svs_<id>.<secret>`. Tokens live in `samveys_api_tokens(id, user_id, name, token_hash, abilities, last_used_at, expires_at, created_at)`, created on first use. Only a SHA-256 of the secret is stored, so a leaked table cannot be replayed, and the hash is compared in constant time. Unknown, malformed, expired and orphaned tokens are all the same answer (unauthenticated), so a client cannot tell them apart.
function tokenGuard<U extends AuthUser = AuthUser>(options: TokenGuardOptions<U>): TokenGuard<U>;
Exported from packages/auth/src/token-guard.ts
writeAuthScaffold
Function
Writes the auth scaffolding under `rootDir`; refuses to overwrite any existing file (SVS_E099).
function writeAuthScaffold(rootDir: string): readonly string[];
Exported from packages/auth/src/make-auth.ts
Value (1)
AUTH_FILES
Value
Files written by `svs make:auth`: a login form, a reset-request form and a reset form, the controller with routes, and a migration. The forms meet WCAG 2.2 SC 3.3.8 "Accessible Authentication (Minimum)": no cognitive test, and paste and password managers keep working — so: - real `<label for>` on every field; - `autocomplete="username"` / `"current-password"` / `"new-password"` so managers fill them; - `type="email"` and `type="password"` (no custom widgets); - no `onpaste`, `oncopy` or `ondrop` handlers, no `maxlength` on passwords; - a "show password" button instead of a confirm-password field.
const AUTH_FILES: Readonly<Record<string, string>>;
Exported from packages/auth/src/make-auth.ts
Interface (24)
ApiTokenInfo
Interface
Metadata of the API token that authenticated a request. The secret is never available again.
interface ApiTokenInfo {
readonly id: string;
readonly name: string;
readonly abilities: readonly string[];
readonly expiresAt: number | undefined;
}
Exported from packages/auth/src/contracts.ts
AuthContext
Interface
`ctx.auth`: the authenticated caller of this request.
interface AuthContext {
readonly user: AuthUser;
/** The guard name from the plugin config, e.g. `web` or `api`. */
readonly guard: string;
/** Present when the request was authenticated by an API token. */
readonly token?: ApiTokenInfo;
/** True when the token (or, for session users, the `abilities` function of the guard) grants `ability`. `*` grants all. */
can(ability: string): boolean;
}
Exported from packages/auth/src/contracts.ts
AuthOptions
Interface
No documentation comment.
interface AuthOptions {
/** Named guards. Each is available as the middleware `auth:<name>`. */
readonly guards: Readonly<Record<string, Guard>>;
/** The guard behind the plain `auth` middleware. Must be a key of `guards`. */
readonly default: string;
}
Exported from packages/auth/src/contracts.ts
AuthUser
Interface
The smallest user the framework needs. Your user model has more fields; policies see all of them.
interface AuthUser {
readonly id: string | number;
}
Exported from packages/auth/src/contracts.ts
CreatedToken
Interface
No documentation comment.
interface CreatedToken {
/** `svs_<id>.<secret>`. Shown once: only a SHA-256 of the secret is stored. */
readonly plainText: string;
readonly token: ApiTokenInfo;
}
Exported from packages/auth/src/contracts.ts
CreateTokenOptions
Interface
No documentation comment.
interface CreateTokenOptions {
/** A label for the token list, e.g. `ci-deploy`. */
readonly name: string;
/** What the token may do, e.g. `['server:update']`. Use `['*']` for everything. */
readonly abilities: readonly string[];
/** Epoch milliseconds, or a Date. Default: never expires. */
readonly expiresAt?: number | Date;
}
Exported from packages/auth/src/contracts.ts
FindableModel
Interface
The part of an `@samveys/orm` model class the provider uses: `Model.find(id)`.
interface FindableModel {
find(id: string | number | bigint): Promise<unknown>;
}
Exported from packages/auth/src/providers.ts
Guard
Interface
Something that can identify the caller.
interface Guard {
readonly kind: 'session' | 'token' | 'oidc' | (string & {});
/** `undefined` means "not authenticated": the middleware then answers SVS_E093. */
authenticate(ctx: HttpContext): Promise<GuardResult | undefined>;
/** Called by the plugin at boot so a guard can use the app's database without being given one. */
attach?(dependencies: GuardDependencies): void;
}
Exported from packages/auth/src/contracts.ts
GuardDependencies
Interface
No documentation comment.
interface GuardDependencies {
/** The app's `'db'` binding. */
readonly database: () => Database;
Exported from packages/auth/src/contracts.ts
GuardResult
Interface
What a guard returns for an authenticated request.
interface GuardResult {
readonly user: AuthUser;
readonly abilities: readonly string[];
readonly token?: ApiTokenInfo;
}
Exported from packages/auth/src/contracts.ts
IdTokenClaims
Interface
Claims of a validated OIDC ID token.
interface IdTokenClaims {
readonly iss: string;
readonly sub: string;
readonly aud: string | readonly string[];
readonly exp: number;
readonly iat?: number;
readonly nonce?: string;
readonly email?: string;
readonly email_verified?: boolean;
readonly name?: string;
readonly [claim: string]: unknown;
}
Exported from packages/auth/src/contracts.ts
OidcCallbackResult
Interface
No documentation comment.
interface OidcCallbackResult<U extends AuthUser = AuthUser> {
readonly user: U;
readonly claims: IdTokenClaims;
}
Exported from packages/auth/src/contracts.ts
OidcGuard
Interface
No documentation comment.
interface OidcGuard<U extends AuthUser = AuthUser> extends Guard {
readonly kind: 'oidc';
/** 302 to the provider's authorization endpoint, after storing `state`, `nonce` and the PKCE verifier in the session. */
redirectToProvider(ctx: HttpContext): Promise<Response>;
/** Validates the callback and signs the user in (new session id). Any failure is SVS_E095. */
handleCallback(ctx: HttpContext): Promise<OidcCallbackResult<U>>;
logout(ctx: HttpContext): Promise<void>;
}
Exported from packages/auth/src/contracts.ts
OidcGuardOptions
Interface
No documentation comment.
interface OidcGuardOptions<U extends AuthUser = AuthUser> {
/** The issuer URL; `/.well-known/openid-configuration` is appended. Must match the `issuer` in the discovery document exactly. */
readonly issuer: string;
readonly clientId: string;
readonly clientSecret?: string;
readonly redirectUri: string;
/** Default `['openid', 'profile', 'email']`. */
readonly scopes?: readonly string[];
/** Needs `findOrCreateFromClaims`. */
readonly provider: UserProvider<U> & Required<Pick<UserProvider<U>, 'findOrCreateFromClaims'>>;
…
Show the whole declaration (16 lines)
interface OidcGuardOptions<U extends AuthUser = AuthUser> {
/** The issuer URL; `/.well-known/openid-configuration` is appended. Must match the `issuer` in the discovery document exactly. */
readonly issuer: string;
readonly clientId: string;
readonly clientSecret?: string;
readonly redirectUri: string;
/** Default `['openid', 'profile', 'email']`. */
readonly scopes?: readonly string[];
/** Needs `findOrCreateFromClaims`. */
readonly provider: UserProvider<U> & Required<Pick<UserProvider<U>, 'findOrCreateFromClaims'>>;
…interface OidcGuardOptions<U extends AuthUser = AuthUser> {
/** The issuer URL; `/.well-known/openid-configuration` is appended. Must match the `issuer` in the discovery document exactly. */
readonly issuer: string;
readonly clientId: string;
readonly clientSecret?: string;
readonly redirectUri: string;
/** Default `['openid', 'profile', 'email']`. */
readonly scopes?: readonly string[];
/** Needs `findOrCreateFromClaims`. */
readonly provider: UserProvider<U> & Required<Pick<UserProvider<U>, 'findOrCreateFromClaims'>>;
/** Signs the user in after the callback. Default: a session guard over the same provider. */
readonly sessionGuard?: SessionGuard<U>;
/** For tests or a proxy. Default `globalThis.fetch`. */
readonly fetch?: typeof fetch;
/** Clock in epoch milliseconds, for tests. */
readonly now?: () => number;
Exported from packages/auth/src/contracts.ts
OrmUserProviderOptions
Interface
No documentation comment.
interface OrmUserProviderOptions<U extends AuthUser> {
readonly findByCredentials?: UserProvider<U>['findByCredentials'];
/** Required for OIDC sign-in: look the user up by `claims.sub` or `claims.email`, creating it on first sign-in. */
readonly findOrCreateFromClaims?: (claims: IdTokenClaims) => Promise<U>;
Exported from packages/auth/src/providers.ts
PasswordAuthOptions
Interface
No documentation comment.
interface PasswordAuthOptions<U extends PasswordUser> {
readonly provider: PasswordProvider<U>;
readonly session: SessionGuard<U>;
/** Default: core's Argon2id hasher. */
readonly hasher?: Hasher;
}
Exported from packages/auth/src/password.ts
PasswordProvider
Interface
No documentation comment.
interface PasswordProvider<U extends PasswordUser> {
findByEmail(email: string): Promise<U | undefined | null>;
/** Called when the stored hash used weaker parameters than today's: store the new one. */
updatePasswordHash(user: U, hash: string): Promise<void>;
}
Exported from packages/auth/src/password.ts
PasswordResetOptions
Interface
No documentation comment.
interface PasswordResetOptions {
readonly database: () => Database;
Exported from packages/auth/src/password.ts
PasswordUser
Interface
A user with a stored Argon2id password hash.
interface PasswordUser extends AuthUser {
readonly email: string;
readonly passwordHash: string;
}
Exported from packages/auth/src/password.ts
SessionGuard
Interface
No documentation comment.
interface SessionGuard<U extends AuthUser = AuthUser> extends Guard {
readonly kind: 'session';
/** Stores the user id in the session and regenerates the session id (stops session fixation). */
login(ctx: HttpContext, user: U): Promise<void>;
/** Invalidates the session. */
logout(ctx: HttpContext): Promise<void>;
}
Exported from packages/auth/src/contracts.ts
SessionGuardOptions
Interface
No documentation comment.
interface SessionGuardOptions<U extends AuthUser = AuthUser> {
readonly provider: UserProvider<U>;
/** Session key for the user id. Default `auth.user_id`. */
readonly sessionKey?: string;
/** Abilities of a signed-in user, for `ctx.auth.can()` and `requireAbility()`. Default: none (fail closed). */
readonly abilities?: (user: U) => readonly string[];
Exported from packages/auth/src/contracts.ts
TokenGuard
Interface
No documentation comment.
interface TokenGuard<U extends AuthUser = AuthUser> extends Guard {
readonly kind: 'token';
createToken(user: U, options: CreateTokenOptions): Promise<CreatedToken>;
/** Deletes one token by id. Returns whether it existed. */
revokeToken(id: string): Promise<boolean>;
/** Deletes every token of a user. Returns how many. */
revokeAllTokens(user: AuthUser): Promise<number>;
}
Exported from packages/auth/src/contracts.ts
TokenGuardOptions
Interface
No documentation comment.
interface TokenGuardOptions<U extends AuthUser = AuthUser> {
readonly provider: UserProvider<U>;
/** Default: the app's `'db'` binding. */
readonly database?: DatabaseSource;
/** Default `samveys_api_tokens`. */
readonly table?: string;
/** Clock in epoch milliseconds, for tests. */
readonly now?: () => number;
Exported from packages/auth/src/contracts.ts
UserProvider
Interface
Where users come from.
interface UserProvider<U extends AuthUser = AuthUser> {
/** `undefined` (or `null`) when the user no longer exists: the request is then unauthenticated. */
findById(id: string | number): Promise<U | undefined | null>;
/** For password sign-in forms. Not used by the built-in guards. */
findByCredentials?(input: Readonly<Record<string, unknown>>): Promise<U | undefined | null>;
/** OIDC: map validated claims to a user, creating one on first sign-in. */
findOrCreateFromClaims?(claims: IdTokenClaims): Promise<U>;
}
Exported from packages/auth/src/contracts.ts
Type (3)
DatabaseSource
Type
A database connection, or a function that returns one (the plugin supplies `container.make('db')`).
type DatabaseSource = Database | (() => Database);
Exported from packages/auth/src/contracts.ts
Policy
Type
A set of rules by action name, made with `definePolicy()`.
type Policy = Readonly<Record<string, PolicyRule>>;
Exported from packages/auth/src/contracts.ts
PolicyRule
Type
One rule of a policy. Returns exactly `true` to allow; anything else denies.
type PolicyRule<U = any, R = any> = (user: U, resource: R) => boolean | Promise<boolean>;
Exported from packages/auth/src/contracts.ts