import { type Handle, type RequestEvent } from '@sveltejs/kit'; import type { AuthConfig } from '../types.js'; import type { Repositories } from './adapters/types.js'; import { type PublicRoute } from './public-routes.js'; export type { PublicRoute } from './public-routes.js'; export interface AuthHandleOptions { config: AuthConfig; repos: Repositories; /** * Routes exempt from the auth guard. A string entry is a pathname * **prefix** (`startsWith`); `{ path, exact: true }` exempts that pathname * alone (see {@link PublicRoute}). * * **The list REPLACES the defaults; it does not extend them.** The defaults * are {@link DEFAULT_PUBLIC_ROUTES}, exported so an app that only wants to * add its own public pages can spread them: * `[...DEFAULT_PUBLIC_ROUTES, '/pricing']`. * * Dropping `'/api/auth/'` locks out the app's own sign-in: every auth * endpoint is then guarded, so an unauthenticated `POST /api/auth/login` * gets `401 not_authenticated` instead of a session. Replacing wholesale is * the right mode only for a handle scoped to routes that mount no auth * endpoints at all. * * A prefix grants more than its spelling suggests: `'/pricing'` also exempts * `/pricing-admin` and `/pricing/internal`, and a bare `'/'` exempts the * entire app — the handle warns about that one at construction. The landing * page alone is `{ path: '/', exact: true }`. A list held in a variable * first needs `as const` or the annotation `PublicRoute[]`: TypeScript * otherwise widens `exact: true` to `boolean` and the assignment is a type * error. An inline list needs nothing. * * Read once, at construction: mutating the array afterwards does not move * the guard. * * @default DEFAULT_PUBLIC_ROUTES */ publicRoutes?: readonly PublicRoute[]; /** * Allow unauthenticated SvelteKit Remote Functions * (`kit.experimental.remoteFunctions`) to pass the route guard. * * Remote functions can't be guarded by `publicRoutes` — a caller controls the * pathname the guard sees, via either of two transports: * - `/_app/remote/…` (query / command / JS-enhanced form): SvelteKit rewrites * `event.url.pathname` from the client-controlled `x-sveltekit-pathname` * header before this hook runs. * - the no-JS `
` fallback: dispatched through the * page pipeline from the `/remote` search param, decoupled from the * pathname, with `event.isRemoteRequest` left `false`. * * Either way a spoofed public route (e.g. `/auth/login`) would slip an * unauthenticated remote call past a path-only check, so the guard * default-denies (`401`) both — keyed on the unspoofable * `event.isRemoteRequest` and, for the fallback, a `POST` carrying a truthy * `/remote` action param. * * Set this to `true` only if your app deliberately exposes public remote * functions — you are then responsible for authorizing each remote function * yourself (check `event.locals.user` inside it). Authenticated remote * requests are unaffected either way. * * @default false */ allowUnauthenticatedRemote?: boolean; /** * The package's own CSRF gate, step 1 of the hook. The cookie and header * knobs live on `config.csrf`; this is the exemption. */ csrf?: { /** * Routes the hook handles as **cookieless**: machine endpoints whose * callers send no `Origin` header and hold no session — a cron runner * with a secret header, an OAuth token endpoint, an API-key route. For a * matching request the hook reads no cookie and writes none: no Origin * gate, no session hydration (`locals.user` is `null` even beside a valid * session cookie), no refresh rotation, no CSRF cookie, no route guard. * The response gets the security headers and nothing else. * * Cookieless describes the hook, not the request: the cookie still * arrives, and a route that reads `event.cookies` itself keeps working * with the CSRF gate off — which is how every handler this package ships * resolves its user (`requireSessionUser`), so a list entry covering * `/api/auth/` is refused at construction. **Never exempt a * cookie-authorised route**; exempt only routes that authenticate every * request without a cookie (bearer token, secret header, PKCE). The * predicate form cannot be checked at construction — that rule is yours * to keep there. * * Same vocabulary as {@link publicRoutes}: a string is a pathname prefix, * `{ path, exact: true }` that pathname alone, matched against the * requested pathname (`event.url.pathname`) — a `reroute` hook changes * which route is resolved, not this. A bare `'/'` is refused, because * nothing would be left on; `exempt: () => true` is the deliberate * spelling. Or a synchronous predicate over the event for callers a path * does not identify — `(e) => e.request.headers.has('x-cron-secret')`. * Only a literal `true` exempts, so an async predicate (a Promise) and a * truthy non-boolean exempt nothing; a throw fails the request. * Remote-function requests are never exempt on either transport — their * pathname is client-controlled and their transport is * cookie-authenticated by construction — and the predicate is not * consulted for them. * * SvelteKit's kernel CSRF gate runs before any hook and is unaffected: a * form-encoded cross-origin POST still needs `kit.csrf.trustedOrigins: * ['*']` in a built app. docs/AUTH.md → Machine callers. * * @default nothing is exempt */ exempt?: readonly PublicRoute[] | ((event: RequestEvent) => boolean); }; } /** * The route prefixes `createAuthHandle` exempts from the guard when * `publicRoutes` is omitted. Spread it to extend rather than replace (see * {@link AuthHandleOptions.publicRoutes}). Frozen because it is exported: one * array backs every handle that omits the option, so a `push` into it would * widen the guard for all of them at once. */ export declare const DEFAULT_PUBLIC_ROUTES: readonly string[]; export declare function createAuthHandle(options: AuthHandleOptions): Handle;