import type { RequestHandler } from '@sveltejs/kit'; import type { AuthLocale } from '../../i18n/keys.js'; import type { AuthUser } from '../../types.js'; import type { AuthDeps } from '../deps.js'; export interface InvitationHandlerOptions { /** * Authorize the caller to manage invitations (create / list / revoke). * Required and fail-closed by design: there is no default, because an open * invitation endpoint would let any authenticated user mint invitations — * and the register handler's account-enumeration defense holds ONLY while * invitations stay admin-minted (see `register.ts` / docs/AUTH.md). Receives * the sanitized authenticated user; return `true` to allow. The package has * no role model of its own, so you decide what "may invite" means, e.g. * `authorize: (user) => user.role === 'admin'`. */ authorize: (user: AuthUser) => boolean | Promise; /** * Roles assignable through an invitation. The submitted role must be one of * these — without an allow-list a crafted request could invite a user at a * higher privilege than the UI offers (privilege escalation). Mirror the * `roles` prop you pass to ``. */ roles: R[]; /** * Build the invitation email sent when the client requests it (the * `sendEmail` flag). Defaults to a localized template (`config.email.locale`) * linking to `${appUrl}/auth/register?token=&email=`. * Receives the resolved context — `from`, `appName`, and the `t` bundle — so a * custom builder can reuse or override them. Return `{ subject, html, text? }` * (optionally a `from` to override the configured sender). * * The `url` carries the one-time invitation token: it IS the credential, so a * custom builder must put it in the message and must not log it. */ inviteEmail?: (ctx: { email: string; role: R; url: string; from?: string; appName: string; t: AuthLocale; }) => { subject: string; html: string; text?: string; from?: string; }; /** * How long an invitation stays redeemable, in milliseconds. * @default 7 days * * Deliberately far longer than the one-hour password-reset window: a reset is * a response to something the user just did, while an invitation has to reach * a person who may be on holiday. Before #149 there was no window at all — * an invitation stayed open from the moment it was minted until someone used * it, which is the interval the address-only gate left exploitable. * * Shorten it if invitations are handed over synchronously. */ invitationTtlMs?: number; } /** * Admin-facing invitation CRUD: the server half of ``. * Returns three handlers — mount `POST` + `GET` on `/api/invitations` and * `DELETE` on `/api/invitations/[id]`: * * ```ts * const invitations = createInvitationHandlers(deps, { * authorize: (user) => user.role === 'admin', * roles: ['member', 'admin'] * }); * // src/routes/api/invitations/+server.ts * export const POST = invitations.POST; * export const GET = invitations.GET; * // src/routes/api/invitations/[id]/+server.ts * export const DELETE = invitations.DELETE; * ``` * * CSRF is enforced by `createAuthHandle` (the mutating requests carry the * double-submit token / Origin check), so the handlers don't repeat it. They * resolve the caller from the session cookie directly (not `locals.user`), so * authorization is unaffected by a `transformUser` hook reshaping locals. */ export declare function createInvitationHandlers(deps: AuthDeps, options: InvitationHandlerOptions): { POST: RequestHandler; GET: RequestHandler; DELETE: RequestHandler; };