@dotpress/auth 2.0.0-alpha.4 → 2.0.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/context.ts","../src/auth.ts","../src/crypto.ts","../src/csrf.ts","../src/rate-limit.ts","../src/middlewares.ts","../src/plugin.ts"],"sourcesContent":["import { declareContextKey } from '@dotpress/core'\nimport type { AuthSession, AuthUser } from './types.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Authenticated user (`authPlugin`). Use `requireAuth` to require it. */\n user: AuthUser | undefined\n /** Access token data of the authenticated user (`authPlugin`). */\n auth: AuthSession | undefined\n }\n}\n\n// Accessing these keys without `authPlugin` throws an explicit error.\ndeclareContextKey('user', 'authPlugin')\ndeclareContextKey('auth', 'authPlugin')\n","import type {\n DotpressRequest,\n DotpressResponse,\n PluginCors,\n} from '@dotpress/core'\nimport bcrypt from 'bcryptjs'\nimport {\n CompactSign,\n SignJWT,\n calculateJwkThumbprint,\n compactVerify,\n decodeProtectedHeader,\n errors,\n exportJWK,\n importPKCS8,\n importSPKI,\n jwtVerify,\n type JWK,\n} from 'jose'\nimport { sha256, toHex } from './crypto.js'\nimport { isCookieAuthenticationAllowed } from './csrf.js'\nimport { memoryRateLimitStore } from './rate-limit.js'\nimport type {\n AuthInitOptions,\n AuthSession,\n AuthSettings,\n AuthUser,\n CookieSettings,\n CreatedAccessToken,\n CreatedRefreshToken,\n IssuedTokens,\n LoginThrottleSettings,\n LogoutScope,\n RateLimit,\n RefreshTokenData,\n ResolvedAuthSettings,\n ResolvedCookieSettings,\n ResolvedLoginThrottleSettings,\n ResolvedSessionSettings,\n RevokedSession,\n SessionSettings,\n TokenPair,\n TokenSubject,\n} from './types.js'\n\nconst ALGORITHM = 'RS256'\n/** Media type of the access tokens (RFC 9068). */\nconst ACCESS_TOKEN_TYPE = 'at+jwt'\nconst DEFAULT_IDLE_TIMEOUT = 14 * 24 * 60 * 60\n\nexport type AuthErrorCode =\n | 'INVALID_ACCESS_TOKEN'\n | 'INVALID_REFRESH_TOKEN'\n | 'EXPIRED_REFRESH_TOKEN'\n /** A used refresh token was presented again: its session is revoked. */\n | 'REFRESH_TOKEN_REUSED'\n /** Valid access token of a revoked session (`isSessionRevoked()`). */\n | 'SESSION_REVOKED'\n\n/**\n * Error raised by the token helpers.\n */\nexport class AuthError extends Error {\n constructor(\n readonly code: AuthErrorCode,\n options?: ErrorOptions\n ) {\n super(code, options)\n this.name = 'AuthError'\n }\n}\n\nexport interface Auth {\n readonly settings: ResolvedAuthSettings\n /**\n * Imports the keys. Fails with an explicit message on invalid keys.\n * `authPlugin()` calls it at startup, with the browser path of its routes.\n */\n init(options?: AuthInitOptions): Promise<void>\n generateAccessToken(subject: TokenSubject): Promise<CreatedAccessToken>\n generateRefreshToken(): Promise<CreatedRefreshToken>\n /** Generates and stores a token pair, starting a session. */\n generateTokensForUser(subject: TokenSubject): Promise<IssuedTokens>\n /**\n * Rotates a token pair. The access token may be expired, but must be\n * signed by the key pair and match the refresh token.\n *\n * A refresh token can only be used once. Within\n * `refreshTokenReuseInterval` after its first use, it may be presented\n * again (concurrent refreshes, retries after a lost response): a new pair\n * is issued in the same session. After, presenting it again means it was\n * probably stolen: the whole session (token family) is revoked, and\n * `REFRESH_TOKEN_REUSED` is thrown.\n *\n * Throws `EXPIRED_REFRESH_TOKEN` when the session reached its inactivity\n * or absolute timeout, and `INVALID_REFRESH_TOKEN` when\n * `findUserById()` no longer returns the user (the session is revoked).\n */\n refreshTokens(\n tokens: TokenPair\n ): Promise<IssuedTokens & { userId: string; user: AuthUser }>\n /**\n * Verifies a valid (not expired) access token. With\n * `DataProvider.isSessionRevoked()`, throws `SESSION_REVOKED` for the\n * tokens of a revoked session.\n */\n verifyAccessToken(accessToken: string): Promise<AuthSession>\n /**\n * Revokes the session of a refresh token (logout). The token may be\n * used, revoked or expired: it still designates its session. Tokens\n * stored without family (before V2) revoke every session of the user.\n *\n * With `scope: 'all'`, revokes every session of the user, on every\n * device. The token must then be active (neither used, revoked nor\n * expired).\n *\n * Returns `undefined`, without revoking anything, for an unknown token,\n * or an inactive token with `scope: 'all'`.\n */\n revokeSession(\n refreshToken: string,\n options?: { scope?: LogoutScope }\n ): Promise<RevokedSession | undefined>\n /**\n * End of a session, when an absolute timeout applies to it (computed\n * from the `auth_time` claim).\n */\n getSessionExpiresAt(session: AuthSession): Date | undefined\n /**\n * Public keys as a JSON Web Key Set (RFC 7517): the active key first,\n * then the additional keys. Other services can verify the tokens with\n * it (`createRemoteJWKSet()` of jose).\n */\n getJwks(): Promise<{ keys: JWK[] }>\n /** Roles of a user, from the `getUserRoles` setting. */\n getUserRoles(user: AuthUser): readonly string[]\n /**\n * Sets the token cookies (cookie mode only). They expire with the\n * refresh token (`refreshTokenExpiresAt` of issued tokens), or after the\n * inactivity timeout.\n */\n setAuthCookies(\n res: DotpressResponse,\n tokens: TokenPair & { refreshTokenExpiresAt?: Date },\n expiresInSeconds?: number\n ): void\n clearAuthCookies(res: DotpressResponse): void\n /**\n * Whether a request may be authenticated by the cookies: protection\n * against cross-site request forgery (see `cookies.trustedOrigins`).\n * `authPlugin()` checks it on its routes and before reading the access\n * token cookie.\n */\n isCookieAuthenticationAllowed(req: DotpressRequest): Promise<boolean>\n /**\n * Hashes a password with bcrypt. Defaults to `passwordHashRounds`.\n * Throws a `RangeError` for passwords longer than 72 bytes (see\n * `isPasswordTooLong()`).\n */\n hashPassword(password: string, saltRounds?: number): Promise<string>\n /** Whether a bcrypt hash has another cost than `passwordHashRounds`. */\n passwordNeedsRehash(passwordHash: string): boolean\n /**\n * Hashes a verified password with `passwordHashRounds`, to upgrade its\n * stored hash. Unlike `hashPassword()`, it accepts passwords longer than\n * 72 bytes, created before the check.\n */\n rehashPassword(password: string): Promise<string>\n /**\n * Verifies a password against its bcrypt hash. Without valid hash\n * (unknown user, account without password), it compares the password\n * with a dummy hash and returns `false`: the answer takes the same time,\n * and does not reveal whether the account exists.\n */\n verifyPassword(\n password: string,\n passwordHash: string | null | undefined\n ): Promise<boolean>\n}\n\nconst DEFAULT_COOKIE_SETTINGS: ResolvedCookieSettings = {\n enabled: false,\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n path: '/',\n refreshTokenPath: '/auth',\n refreshTokenSameSite: 'strict',\n trustedOrigins: [],\n accessTokenName: 'dotpress_access_token',\n refreshTokenName: 'dotpress_refresh_token',\n accessTokenInCookie: false,\n domain: undefined,\n}\n\nconst defaultUserRoles = (user: AuthUser): readonly string[] => {\n const { roles, role } = user as { roles?: unknown; role?: unknown }\n if (Array.isArray(roles)) {\n return roles.filter((value): value is string => typeof value === 'string')\n }\n return typeof role === 'string' ? [role] : []\n}\n\nconst checkSession = (session: SessionSettings | undefined, name: string) => {\n for (const key of ['idleTimeout', 'absoluteTimeout'] as const) {\n const value = session?.[key]\n if (value !== undefined && (!Number.isFinite(value) || value <= 0)) {\n throw new RangeError(\n `Invalid ${name}.${key}: expected a positive number of seconds.`\n )\n }\n }\n}\n\nconst DEFAULT_LOGIN_LIMITS = {\n usernameAndIp: { max: 5, window: 60 },\n username: { max: 100, window: 60 * 60 },\n} as const\n\nconst resolveLoginThrottle = (\n input: LoginThrottleSettings | false | undefined\n): ResolvedLoginThrottleSettings | undefined => {\n if (input === false) return undefined\n\n const limit = (\n name: keyof typeof DEFAULT_LOGIN_LIMITS\n ): RateLimit | undefined => {\n const value = input?.limits?.[name]\n if (value === false) return undefined\n const resolved = value ?? DEFAULT_LOGIN_LIMITS[name]\n if (\n !Number.isInteger(resolved.max) ||\n resolved.max < 1 ||\n !Number.isFinite(resolved.window) ||\n resolved.window <= 0\n ) {\n throw new RangeError(\n `Invalid loginThrottle.limits.${name}: expected a positive integer max and a positive window, in seconds.`\n )\n }\n return { max: resolved.max, window: resolved.window }\n }\n\n const store = input?.store ?? memoryRateLimitStore()\n if (\n typeof store.increment !== 'function' ||\n typeof store.reset !== 'function'\n ) {\n throw new TypeError(\n 'Invalid loginThrottle.store: expected increment() and reset() methods.'\n )\n }\n\n return {\n store,\n limits: {\n usernameAndIp: limit('usernameAndIp'),\n username: limit('username'),\n },\n }\n}\n\nconst resolveSettings = (settings: AuthSettings): ResolvedAuthSettings => {\n if (!settings?.keys?.secretKey || !settings.keys.publicKey) {\n throw new TypeError(\n 'createAuth() requires keys.secretKey and keys.publicKey.'\n )\n }\n if (!settings.dataProvider) {\n throw new TypeError('createAuth() requires a dataProvider.')\n }\n if (typeof settings.issuer !== 'string' || settings.issuer === '') {\n throw new TypeError(\n 'createAuth() requires an issuer (iss claim), such as the URL of the API.'\n )\n }\n if (settings.audience !== undefined && settings.audience === '') {\n throw new TypeError('Invalid audience: expected a non-empty string.')\n }\n const clients = settings.clients\n if (\n clients &&\n (!Array.isArray(clients.allowed) ||\n clients.allowed.length === 0 ||\n clients.allowed.some((id) => typeof id !== 'string' || id === ''))\n ) {\n throw new TypeError(\n 'Invalid clients.allowed: expected a non-empty list of client identifiers.'\n )\n }\n const rounds = settings.passwordHashRounds\n if (\n rounds !== undefined &&\n (!Number.isInteger(rounds) || rounds < 4 || rounds > 31)\n ) {\n throw new RangeError(\n 'Invalid passwordHashRounds: expected an integer between 4 and 31.'\n )\n }\n if ('refreshTokenTTLInDays' in settings) {\n throw new TypeError(\n 'refreshTokenTTLInDays is replaced by session.idleTimeout, in seconds.'\n )\n }\n for (const origin of settings.cookies?.trustedOrigins ?? []) {\n let valid = false\n try {\n valid = new URL(origin).origin === origin\n } catch {\n // Invalid URL.\n }\n if (!valid) {\n throw new TypeError(\n `Invalid cookies.trustedOrigins: \"${origin}\" is not an origin (expected a scheme and a host, such as https://app.example.com, without path or trailing slash).`\n )\n }\n }\n checkSession(settings.session, 'session')\n for (const [clientId, session] of Object.entries(clients?.sessions ?? {})) {\n if (!clients?.allowed.includes(clientId)) {\n throw new TypeError(\n `Invalid clients.sessions: \"${clientId}\" is not in clients.allowed.`\n )\n }\n checkSession(session, `clients.sessions.${clientId}`)\n }\n const reuseInterval = settings.refreshTokenReuseInterval\n if (\n reuseInterval !== undefined &&\n (!Number.isFinite(reuseInterval) || reuseInterval < 0)\n ) {\n throw new RangeError(\n 'Invalid refreshTokenReuseInterval: expected a non-negative number of seconds.'\n )\n }\n\n return {\n keys: settings.keys,\n dataProvider: settings.dataProvider,\n accessTokenTTL: settings.accessTokenTTL ?? 900,\n session: {\n idleTimeout: settings.session?.idleTimeout ?? DEFAULT_IDLE_TIMEOUT,\n absoluteTimeout: settings.session?.absoluteTimeout,\n },\n issuer: settings.issuer,\n audience: settings.audience ?? settings.issuer,\n cookies: resolveCookies(settings.cookies),\n clients: settings.clients,\n onLoginFailed: settings.onLoginFailed,\n onLoginSuccess: settings.onLoginSuccess,\n loginThrottle: resolveLoginThrottle(settings.loginThrottle),\n beforeLogin: settings.beforeLogin,\n passwordHashRounds: settings.passwordHashRounds ?? 12,\n usernameClaim: settings.usernameClaim ?? false,\n refreshTokenReuseInterval: settings.refreshTokenReuseInterval ?? 10,\n onRefreshTokenReuse: settings.onRefreshTokenReuse,\n getUserRoles: settings.getUserRoles ?? defaultUserRoles,\n }\n}\n\n/**\n * Name of a cookie. Like Auth.js, the default names get the strongest\n * prefix the attributes allow: `__Host-` (secure, no domain, path `/`)\n * cannot be overwritten by a subdomain or an insecure page; `__Secure-`\n * (secure) cannot be overwritten by an insecure page.\n */\nconst cookieName = (\n setting: 'accessTokenName' | 'refreshTokenName',\n name: string | undefined,\n base: string,\n attributes: { secure: boolean; domain: string | undefined; path: string }\n) => {\n const { secure, domain, path } = attributes\n const hostAllowed = secure && domain === undefined && path === '/'\n if (name === undefined) {\n if (!secure) return base\n return hostAllowed ? `__Host-${base}` : `__Secure-${base}`\n }\n\n // Browsers silently reject a prefixed cookie without its requirements.\n const lowerName = name.toLowerCase()\n if (lowerName.startsWith('__host-') && !hostAllowed) {\n throw new TypeError(\n `Invalid cookies.${setting}: a __Host- cookie must be secure, without domain, and with path \"/\".`\n )\n }\n if (lowerName.startsWith('__secure-') && !secure) {\n throw new TypeError(\n `Invalid cookies.${setting}: a __Secure- cookie must be secure.`\n )\n }\n return name\n}\n\nconst resolveCookies = (\n settings: CookieSettings = {}\n): ResolvedCookieSettings => {\n const merged: ResolvedCookieSettings = { ...DEFAULT_COOKIE_SETTINGS }\n for (const [key, value] of Object.entries(settings)) {\n // An explicit `undefined` keeps the default.\n if (value !== undefined) Object.assign(merged, { [key]: value })\n }\n for (const key of ['path', 'refreshTokenPath'] as const) {\n if (!merged[key].startsWith('/')) {\n throw new TypeError(\n `Invalid cookies.${key}: expected a path starting with \"/\".`\n )\n }\n }\n const { secure, domain } = merged\n\n return {\n ...merged,\n refreshTokenSameSite:\n settings.refreshTokenSameSite ??\n (settings.sameSite === 'none' ? 'none' : 'strict'),\n accessTokenName: cookieName(\n 'accessTokenName',\n settings.accessTokenName,\n 'dotpress_access_token',\n { secure, domain, path: merged.path }\n ),\n refreshTokenName: cookieName(\n 'refreshTokenName',\n settings.refreshTokenName,\n 'dotpress_refresh_token',\n { secure, domain, path: merged.refreshTokenPath }\n ),\n }\n}\n\nconst PKCS1_PUBLIC_KEY_ERROR =\n 'PKCS#1 keys (\"BEGIN RSA PUBLIC KEY\") are not supported, use SPKI (\"BEGIN PUBLIC KEY\"). Convert it with: openssl rsa -RSAPublicKey_in -in public.pem -pubout -out public.spki.pem'\n\n/** Public key accepted to verify tokens. */\ninterface VerificationKey {\n kid: string\n key: CryptoKey\n jwk: JWK\n}\n\nconst loadKey = async (kind: string, load: () => Promise<CryptoKey>) => {\n try {\n return await load()\n } catch (error) {\n throw new TypeError(\n `Invalid auth ${kind}: expected an RSA PEM key (${(error as Error).message}).`,\n { cause: error }\n )\n }\n}\n\nconst loadPublicKey = async (\n kind: string,\n pem: string,\n kid: string | undefined\n): Promise<VerificationKey> => {\n if (pem.includes('BEGIN RSA PUBLIC KEY')) {\n throw new TypeError(`Invalid auth ${kind}: ${PKCS1_PUBLIC_KEY_ERROR}`)\n }\n const key = await loadKey(kind, () =>\n importSPKI(pem, ALGORITHM, { extractable: true })\n )\n const { kty, n, e } = await exportJWK(key)\n const jwk: JWK = { kty, n, e }\n return { kid: kid ?? (await calculateJwkThumbprint(jwk)), key, jwk }\n}\n\nconst importKeys = async ({\n secretKey,\n publicKey,\n kid,\n additionalPublicKeys = [],\n}: ResolvedAuthSettings['keys']) => {\n if (secretKey.includes('BEGIN RSA PRIVATE KEY')) {\n throw new TypeError(\n 'Invalid auth secretKey: PKCS#1 keys (\"BEGIN RSA PRIVATE KEY\") are not supported, use PKCS#8 (\"BEGIN PRIVATE KEY\"). Convert it with: openssl pkcs8 -topk8 -nocrypt -in private.pem -out private.pkcs8.pem'\n )\n }\n\n const privateKey = await loadKey('secretKey', () =>\n importPKCS8(secretKey, ALGORITHM)\n )\n const active = await loadPublicKey('publicKey', publicKey, kid)\n\n // A public key that does not match the private key would reject every\n // token: fail at startup instead.\n try {\n const probe = await new CompactSign(new Uint8Array([1]))\n .setProtectedHeader({ alg: ALGORITHM })\n .sign(privateKey)\n await compactVerify(probe, active.key)\n } catch (error) {\n throw new TypeError(\n 'Invalid auth keys: publicKey does not match secretKey.',\n { cause: error }\n )\n }\n\n const verificationKeys = [active]\n for (const [index, entry] of additionalPublicKeys.entries()) {\n const { publicKey: pem, kid: entryKid } =\n typeof entry === 'string' ? { publicKey: entry, kid: undefined } : entry\n const loaded = await loadPublicKey(\n `additionalPublicKeys[${index}]`,\n pem,\n entryKid\n )\n const existing = verificationKeys.find((item) => item.kid === loaded.kid)\n if (!existing) {\n verificationKeys.push(loaded)\n } else if (\n existing.jwk.n !== loaded.jwk.n ||\n existing.jwk.e !== loaded.jwk.e\n ) {\n throw new TypeError(\n `Invalid auth keys: kid \"${loaded.kid}\" is used by several keys.`\n )\n }\n }\n\n return { privateKey, kid: active.kid, verificationKeys }\n}\n\n/**\n * Verifies a token with the key designated by its `kid` header, chosen\n * among the configured keys only. Tokens without `kid` (issued by V1) are\n * tried with every key.\n */\nconst verifyWithKeys = async <T>(\n token: string,\n verificationKeys: readonly VerificationKey[],\n verify: (key: CryptoKey) => Promise<T>\n): Promise<T> => {\n const { kid } = decodeProtectedHeader(token)\n const candidates =\n kid === undefined\n ? verificationKeys\n : verificationKeys.filter((item) => item.kid === kid)\n if (candidates.length === 0) {\n throw new errors.JWKSNoMatchingKey(`Unknown key id: ${String(kid)}`)\n }\n\n let lastError: unknown\n for (const candidate of candidates) {\n try {\n return await verify(candidate.key)\n } catch (error) {\n // Only a signature mismatch means another key may match.\n if (!(error instanceof errors.JWSSignatureVerificationFailed)) throw error\n lastError = error\n }\n }\n throw lastError\n}\n\n/** bcrypt hash format: `$2a$`, `$2b$` or `$2y$`, cost, salt and hash. */\nconst BCRYPT_HASH = /^\\$2[aby]\\$\\d{2}\\$[./A-Za-z0-9]{53}$/\n\nconst nowInSeconds = () => Math.floor(Date.now() / 1000)\n\nconst toSession = (\n claims: Record<string, unknown>,\n roles: readonly string[]\n): AuthSession => {\n if (typeof claims.sub !== 'string' || claims.sub === '') {\n throw new AuthError('INVALID_ACCESS_TOKEN')\n }\n return {\n userId: claims.sub,\n username: typeof claims.username === 'string' ? claims.username : undefined,\n tokenId: typeof claims.jti === 'string' ? claims.jti : '',\n sessionId: typeof claims.sid === 'string' ? claims.sid : undefined,\n clientId:\n typeof claims.client_id === 'string' ? claims.client_id : undefined,\n roles,\n claims,\n }\n}\n\n/**\n * Creates the authentication service. It works without an app (scripts,\n * workers), and is plugged into an app with `authPlugin()`.\n *\n * @example\n * const auth = createAuth({ keys, dataProvider })\n * await createApp({ adapter, plugins: [authPlugin(auth)] })\n */\nexport const createAuth = (input: AuthSettings): Auth => {\n const settings = resolveSettings(input)\n let keys: ReturnType<typeof importKeys> | undefined\n\n const getKeys = () => {\n keys ??= importKeys(settings.keys).catch((error: unknown) => {\n // Allows a later call to retry, and to report the error again.\n keys = undefined\n throw error\n })\n return keys\n }\n\n let dummyHash: Promise<string> | undefined\n /** Hash of a random password, with the configured cost. */\n const getDummyHash = () => {\n dummyHash ??= bcrypt.hash(\n toHex(crypto.getRandomValues(new Uint8Array(16))),\n settings.passwordHashRounds\n )\n return dummyHash\n }\n\n /** Session lifetimes of a client. */\n const sessionPolicy = (\n clientId: string | undefined\n ): ResolvedSessionSettings => {\n const override =\n clientId === undefined\n ? undefined\n : settings.clients?.sessions?.[clientId]\n if (!override) return settings.session\n return {\n idleTimeout: override.idleTimeout ?? settings.session.idleTimeout,\n absoluteTimeout:\n 'absoluteTimeout' in override\n ? override.absoluteTimeout\n : settings.session.absoluteTimeout,\n }\n }\n\n /** End of a session started at `authTime`, in seconds, if bounded. */\n const sessionEnd = (authTime: number, clientId: string | undefined) => {\n const { absoluteTimeout } = sessionPolicy(clientId)\n return absoluteTimeout === undefined\n ? undefined\n : authTime + absoluteTimeout\n }\n\n const signAccessToken = async (claims: {\n sub: string\n username?: string\n auth_time: number\n client_id?: string\n sid?: string\n }): Promise<CreatedAccessToken> => {\n const { privateKey, kid } = await getKeys()\n const jwtId = crypto.randomUUID()\n const issuedAt = nowInSeconds()\n // An access token never outlives its session.\n const expiresAt = Math.min(\n issuedAt + settings.accessTokenTTL,\n sessionEnd(claims.auth_time, claims.client_id) ?? Infinity\n )\n\n const token = await new SignJWT({\n // Personal data: written on demand only.\n ...(settings.usernameClaim &&\n claims.username && { username: claims.username }),\n auth_time: claims.auth_time,\n ...(claims.sid !== undefined && { sid: claims.sid }),\n ...(claims.client_id !== undefined && { client_id: claims.client_id }),\n })\n .setProtectedHeader({ alg: ALGORITHM, typ: ACCESS_TOKEN_TYPE, kid })\n .setSubject(claims.sub)\n .setIssuer(settings.issuer)\n .setAudience(settings.audience)\n .setJti(jwtId)\n .setIssuedAt(issuedAt)\n .setExpirationTime(expiresAt)\n .sign(privateKey)\n\n return { jwtId, accessToken: token, expiresAt: new Date(expiresAt * 1000) }\n }\n\n const generateRefreshToken = async (): Promise<CreatedRefreshToken> => {\n const token = toHex(crypto.getRandomValues(new Uint8Array(128)))\n return { token, tokenHash: await sha256(token) }\n }\n\n /**\n * Signs an access token and stores a new refresh token. The refresh\n * token expires after the inactivity timeout, or at the end of the\n * session.\n */\n const issueTokens = async (\n claims: {\n sub: string\n username?: string\n auth_time: number\n client_id?: string\n },\n familyId: string\n ): Promise<IssuedTokens> => {\n // The session identifier (`sid`) is the token family.\n const access = await signAccessToken({ ...claims, sid: familyId })\n const refreshToken = await generateRefreshToken()\n const end = sessionEnd(claims.auth_time, claims.client_id)\n const refreshTokenExpiresAt = new Date(\n Math.min(\n Date.now() + sessionPolicy(claims.client_id).idleTimeout * 1000,\n end === undefined ? Infinity : end * 1000\n )\n )\n\n await settings.dataProvider.storeRefreshToken({\n ...refreshToken,\n userId: claims.sub,\n jwtId: access.jwtId,\n familyId,\n expiresAt: refreshTokenExpiresAt,\n })\n\n return {\n accessToken: access.accessToken,\n refreshToken: refreshToken.token,\n accessTokenExpiresAt: access.expiresAt,\n refreshTokenExpiresAt,\n sessionExpiresAt: end === undefined ? undefined : new Date(end * 1000),\n }\n }\n\n /**\n * Handles a used refresh token presented again. Within the reuse\n * interval (counted from its first use), it is a concurrent refresh or a\n * retry after a lost response: returns, and a new token pair is issued in\n * the same session. After, the token was probably stolen: the session is\n * revoked.\n */\n const checkReuse = async (stored: RefreshTokenData): Promise<void> => {\n const usedAt = stored.usedAt?.getTime()\n if (\n usedAt !== undefined &&\n Date.now() - usedAt < settings.refreshTokenReuseInterval * 1000\n ) {\n return\n }\n\n const familyId = stored.familyId ?? undefined\n if (familyId) {\n await settings.dataProvider.revokeTokenFamily(familyId)\n } else {\n // Tokens stored without family: revoke every session of the user.\n await settings.dataProvider.revokeTokens(stored.userId)\n }\n\n try {\n await settings.onRefreshTokenReuse?.({\n userId: stored.userId,\n familyId,\n jwtId: stored.jwtId,\n })\n } catch {\n // The session is revoked; a failing notification must not hide it.\n }\n\n throw new AuthError('REFRESH_TOKEN_REUSED')\n }\n\n /** Whether the `clients` setting accepts a token client. */\n const isClientAccepted = (clientId: string | undefined) => {\n const { clients } = settings\n if (!clients) return true\n return clientId === undefined\n ? !clients.required\n : clients.allowed.includes(clientId)\n }\n\n /** Claims of a new session, for trusted code (login, scripts). */\n const sessionClaims = ({ userId, username, clientId }: TokenSubject) => {\n if (!isClientAccepted(clientId)) {\n throw new TypeError(\n clientId === undefined\n ? 'A clientId is required by the clients setting.'\n : `Unknown client \"${clientId}\": add it to clients.allowed.`\n )\n }\n return {\n sub: userId,\n username,\n auth_time: nowInSeconds(),\n client_id: clientId,\n }\n }\n\n const generateAccessToken = async (subject: TokenSubject) =>\n signAccessToken(sessionClaims(subject))\n\n let routesPath: string | undefined\n let cors: PluginCors | undefined\n\n /** Trusted origins: `cookies.trustedOrigins`, else the CORS origins. */\n const isTrustedOrigin = (req: DotpressRequest) => (origin: string) => {\n if (input.cookies?.trustedOrigins !== undefined || !cors?.credentials) {\n return settings.cookies.trustedOrigins.includes(origin)\n }\n return cors.isOriginAllowed(req)\n }\n\n /**\n * Sets the path of the refresh token cookie to the browser path of the\n * auth routes, unless `cookies.refreshTokenPath` is set.\n */\n const bindRoutesPath = (path: string) => {\n if (typeof path !== 'string' || !path.startsWith('/')) {\n throw new TypeError(\n `Invalid routesPath: expected a path starting with \"/\", received ${JSON.stringify(path)}.`\n )\n }\n if (routesPath !== undefined && routesPath !== path) {\n throw new Error(\n `This auth service already serves routes under \"${routesPath}\": create one auth service per app to serve them under \"${path}\".`\n )\n }\n routesPath = path\n if (input.cookies?.refreshTokenPath === undefined) {\n settings.cookies = resolveCookies({\n ...input.cookies,\n refreshTokenPath: path,\n })\n }\n }\n\n const cookieOptions = () => {\n const { httpOnly, secure, sameSite, path, domain } = settings.cookies\n return { httpOnly, secure, sameSite, path, domain }\n }\n\n return {\n settings,\n\n isCookieAuthenticationAllowed: (req) =>\n isCookieAuthenticationAllowed(req, isTrustedOrigin(req)),\n\n init: async (options = {}) => {\n if (options.routesPath !== undefined) bindRoutesPath(options.routesPath)\n if (options.cors !== undefined) cors = options.cors\n // The dummy hash is computed now: otherwise, the first login of an\n // unknown user would take longer.\n await Promise.all([getKeys(), getDummyHash()])\n },\n\n generateAccessToken,\n\n generateRefreshToken,\n\n async generateTokensForUser(subject) {\n return issueTokens(sessionClaims(subject), crypto.randomUUID())\n },\n\n async refreshTokens({ accessToken, refreshToken }) {\n const { verificationKeys } = await getKeys()\n\n let claims: Record<string, unknown>\n let legacy: boolean\n try {\n // Signature only: the access token is usually expired here, and\n // `iss`, `aud` and `typ` are not checked, so that tokens issued\n // by V1 (or before a configuration change) are migrated. The\n // stored refresh token proves the session.\n const { payload, protectedHeader } = await verifyWithKeys(\n accessToken,\n verificationKeys,\n (key) => compactVerify(accessToken, key, { algorithms: [ALGORITHM] })\n )\n claims = JSON.parse(new TextDecoder().decode(payload)) as Record<\n string,\n unknown\n >\n legacy = protectedHeader.typ !== ACCESS_TOKEN_TYPE\n } catch (error) {\n throw new AuthError('INVALID_REFRESH_TOKEN', { cause: error })\n }\n\n const userId = typeof claims.sub === 'string' ? claims.sub : ''\n const clientId =\n typeof claims.client_id === 'string'\n ? claims.client_id\n : // V1 tokens carried the client in `aud`.\n legacy &&\n typeof claims.aud === 'string' &&\n settings.clients?.allowed.includes(claims.aud)\n ? claims.aud\n : undefined\n const refreshTokenHash = await sha256(refreshToken)\n const stored =\n await settings.dataProvider.findRefreshToken(refreshTokenHash)\n\n // Unknown token, or token presented with an access token it was not\n // issued with: nothing proves possession of the session.\n if (\n !userId ||\n !stored ||\n stored.userId !== userId ||\n stored.jwtId !== claims.jti ||\n stored.revokedAt ||\n // Client removed from the configuration: the session ends.\n !isClientAccepted(clientId)\n ) {\n throw new AuthError('INVALID_REFRESH_TOKEN')\n }\n\n if (stored.isUsed) await checkReuse(stored)\n\n // Start of the session, kept across rotations. Without it, a\n // session cannot be bounded: with an absolute timeout, it ends.\n const claimedAuthTime =\n typeof claims.auth_time === 'number' ? claims.auth_time : undefined\n if (\n claimedAuthTime === undefined &&\n sessionPolicy(clientId).absoluteTimeout !== undefined\n ) {\n throw new AuthError('EXPIRED_REFRESH_TOKEN')\n }\n const authTime = claimedAuthTime ?? nowInSeconds()\n const end = sessionEnd(authTime, clientId)\n\n // Inactivity timeout (refresh token expiry) and absolute timeout.\n if (\n stored.expiresAt.getTime() <= Date.now() ||\n (end !== undefined && end * 1000 <= Date.now())\n ) {\n throw new AuthError('EXPIRED_REFRESH_TOKEN')\n }\n\n // Deleted or disabled account: the session ends.\n const user = await settings.dataProvider.findUserById(userId)\n if (!user) {\n if (stored.familyId) {\n await settings.dataProvider.revokeTokenFamily(stored.familyId)\n } else {\n await settings.dataProvider.revokeTokens(userId)\n }\n throw new AuthError('INVALID_REFRESH_TOKEN')\n }\n\n const issued = await issueTokens(\n {\n sub: userId,\n username:\n typeof claims.username === 'string' ? claims.username : undefined,\n auth_time: authTime,\n client_id: clientId,\n },\n // Tokens stored without family start one, derived from the token\n // so that concurrent rotations land in the same family.\n stored.familyId || stored.jwtId\n )\n // A token reused within the interval keeps its first use date: the\n // interval cannot be extended by presenting it again.\n if (!stored.isUsed) {\n await settings.dataProvider.markRefreshTokenAsUsed(\n refreshTokenHash,\n new Date()\n )\n }\n\n return { userId, user, ...issued }\n },\n\n async verifyAccessToken(accessToken) {\n const { verificationKeys } = await getKeys()\n try {\n const { payload } = await verifyWithKeys(\n accessToken,\n verificationKeys,\n (key) =>\n jwtVerify(accessToken, key, {\n algorithms: [ALGORITHM],\n typ: ACCESS_TOKEN_TYPE,\n issuer: settings.issuer,\n audience: settings.audience,\n })\n )\n const session = toSession(payload as Record<string, unknown>, [])\n if (!isClientAccepted(session.clientId)) {\n throw new AuthError('INVALID_ACCESS_TOKEN')\n }\n const { isSessionRevoked } = settings.dataProvider\n if (\n session.sessionId !== undefined &&\n isSessionRevoked &&\n (await isSessionRevoked.call(\n settings.dataProvider,\n session.sessionId\n ))\n ) {\n throw new AuthError('SESSION_REVOKED')\n }\n return session\n } catch (error) {\n if (error instanceof AuthError) throw error\n throw new AuthError('INVALID_ACCESS_TOKEN', { cause: error })\n }\n },\n\n async revokeSession(refreshToken, { scope = 'current' } = {}) {\n const { dataProvider } = settings\n const stored = await dataProvider.findRefreshToken(\n await sha256(refreshToken)\n )\n if (!stored) return undefined\n const sessionId = stored.familyId || undefined\n\n if (scope === 'all') {\n // Logging out every device requires a token that still works.\n if (\n stored.revokedAt ||\n stored.isUsed ||\n stored.expiresAt.getTime() <= Date.now()\n ) {\n return undefined\n }\n await dataProvider.revokeTokens(stored.userId)\n } else if (sessionId) {\n await dataProvider.revokeTokenFamily(sessionId)\n } else {\n // Token stored without family: its session cannot be singled out.\n await dataProvider.revokeTokens(stored.userId)\n }\n\n return { userId: stored.userId, sessionId }\n },\n\n async getJwks() {\n const { verificationKeys } = await getKeys()\n return {\n keys: verificationKeys.map(({ kid, jwk }) => ({\n ...jwk,\n kid,\n alg: ALGORITHM,\n use: 'sig',\n })),\n }\n },\n\n getSessionExpiresAt(session) {\n const authTime = session.claims.auth_time\n if (typeof authTime !== 'number') return undefined\n const end = sessionEnd(authTime, session.clientId)\n return end === undefined ? undefined : new Date(end * 1000)\n },\n\n getUserRoles: (user) => settings.getUserRoles(user),\n\n setAuthCookies(res, tokens, expiresInSeconds) {\n const cookies = settings.cookies\n if (!cookies.enabled) return\n\n // Both cookies live as long as the refresh token: refreshing requires\n // the access token, even expired (its claims and signature).\n const expiresAt = tokens.refreshTokenExpiresAt?.getTime()\n const options = {\n ...cookieOptions(),\n maxAge:\n expiresInSeconds ??\n (expiresAt === undefined\n ? settings.session.idleTimeout\n : Math.max(0, Math.ceil((expiresAt - Date.now()) / 1000))),\n }\n\n res.setCookie(cookies.refreshTokenName, tokens.refreshToken, {\n ...options,\n path: cookies.refreshTokenPath,\n sameSite: cookies.refreshTokenSameSite,\n })\n if (cookies.accessTokenInCookie) {\n res.setCookie(cookies.accessTokenName, tokens.accessToken, options)\n }\n },\n\n clearAuthCookies(res) {\n const cookies = settings.cookies\n if (!cookies.enabled) return\n\n // A cookie is only cleared with its path.\n res.clearCookie(cookies.refreshTokenName, {\n ...cookieOptions(),\n path: cookies.refreshTokenPath,\n })\n if (cookies.accessTokenInCookie) {\n res.clearCookie(cookies.accessTokenName, cookieOptions())\n }\n },\n\n hashPassword: (password, saltRounds = settings.passwordHashRounds) =>\n generatePasswordHash(password, saltRounds),\n\n passwordNeedsRehash: (passwordHash) =>\n BCRYPT_HASH.test(passwordHash) &&\n bcrypt.getRounds(passwordHash) !== settings.passwordHashRounds,\n\n // No length check: the password was verified, and a longer V1 password\n // is truncated the same way.\n rehashPassword: (password) =>\n bcrypt.hash(password, settings.passwordHashRounds),\n\n async verifyPassword(password, passwordHash) {\n if (typeof passwordHash === 'string' && BCRYPT_HASH.test(passwordHash)) {\n return bcrypt.compare(password, passwordHash)\n }\n await bcrypt.compare(password, await getDummyHash())\n return false\n },\n }\n}\n\n/** Longest password bcrypt reads, in UTF-8 bytes. */\nexport const MAX_PASSWORD_BYTES = 72\n\n/**\n * Whether a password exceeds the 72 bytes (UTF-8) read by bcrypt. Check\n * it in the sign-up and password change schemas: `hashPassword()` rejects\n * these passwords instead of silently truncating them.\n *\n * @example\n * password: z.string().min(12).refine((p) => !isPasswordTooLong(p), 'Password too long')\n */\nexport const isPasswordTooLong = (password: string): boolean =>\n bcrypt.truncates(password)\n\n/**\n * Hashes a password with bcrypt (cost 12 by default). Throws a\n * `RangeError` for passwords longer than 72 bytes (see\n * `isPasswordTooLong()`).\n */\nexport const generatePasswordHash = async (\n password: string,\n saltRounds = 12\n): Promise<string> => {\n if (isPasswordTooLong(password)) {\n throw new RangeError(\n `Password too long: bcrypt reads ${MAX_PASSWORD_BYTES} bytes at most (UTF-8). Check it with isPasswordTooLong().`\n )\n }\n return bcrypt.hash(password, saltRounds)\n}\n","export const toHex = (bytes: Uint8Array) =>\n Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n\n/** Hexadecimal SHA-256 hash of a string. */\nexport const sha256 = async (value: string) =>\n toHex(\n new Uint8Array(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))\n )\n )\n","import type { DotpressRequest } from '@dotpress/core'\n\n/** Methods that must not change state: never cross-site request forgeries. */\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])\n\nconst hostOf = (url: string | undefined) => {\n if (url === undefined) return undefined\n try {\n return new URL(url).host\n } catch {\n return undefined\n }\n}\n\n/**\n * Whether a request may be authenticated by cookies, which the browser\n * attaches to every request, including the ones forged by another site\n * (CSRF). Relies on the headers set by the browser (Fetch Metadata, then\n * `Origin` for older browsers), like the OWASP recommendations and Go's\n * `http.CrossOriginProtection`:\n *\n * - safe methods (`GET`, `HEAD`, `OPTIONS`) are allowed;\n * - a trusted `Origin` is allowed;\n * - with `Sec-Fetch-Site`, only `same-origin` and `none` (typed URL,\n * bookmark) are allowed: `same-site` covers the other subdomains;\n * - otherwise, an `Origin` is allowed when its host is the API host;\n * - a request with neither header does not come from a browser: allowed.\n */\nexport const isCookieAuthenticationAllowed = async (\n req: DotpressRequest,\n isTrustedOrigin: (origin: string) => boolean | Promise<boolean>\n): Promise<boolean> => {\n if (SAFE_METHODS.has(req.method)) return true\n\n const origin = req.header('origin')\n if (origin !== undefined && (await isTrustedOrigin(origin))) return true\n\n const site = req.header('sec-fetch-site')\n if (site !== undefined) return site === 'same-origin' || site === 'none'\n\n if (origin === undefined) return true\n const originHost = hostOf(origin)\n return (\n originHost !== undefined &&\n originHost === (req.header('host') ?? hostOf(req.url))\n )\n}\n","/** State of a rate limit counter. */\nexport interface RateLimitState {\n /** Number of hits in the current window, including the last one. */\n count: number\n /** End of the current window. */\n resetAt: Date\n}\n\n/**\n * Storage of rate limit counters (fixed windows). Use a shared store\n * (Redis, database) when the app runs on several instances.\n */\nexport interface RateLimitStore {\n /**\n * Atomically increments the counter of a key and returns its state. A\n * missing or expired counter starts a new window of `windowSeconds`.\n */\n increment(key: string, windowSeconds: number): Promise<RateLimitState>\n /** Deletes the counter of a key. */\n reset(key: string): Promise<void>\n}\n\nexport interface MemoryRateLimitStoreOptions {\n /**\n * Maximum number of counters kept in memory: expired counters are purged\n * first, then the oldest ones. Defaults to 10 000.\n */\n maxKeys?: number\n}\n\n/**\n * In-memory rate limit store, for a single instance. With several\n * instances, each one counts on its own: use a shared store.\n */\nexport const memoryRateLimitStore = (\n options: MemoryRateLimitStoreOptions = {}\n): RateLimitStore => {\n const maxKeys = options.maxKeys ?? 10_000\n if (!Number.isInteger(maxKeys) || maxKeys < 1) {\n throw new RangeError('Invalid maxKeys: expected a positive integer.')\n }\n const counters = new Map<string, RateLimitState>()\n\n const makeRoom = (now: number) => {\n if (counters.size < maxKeys) return\n for (const [key, state] of counters) {\n if (state.resetAt.getTime() <= now) counters.delete(key)\n }\n // Map iteration follows insertion order: the oldest windows first.\n for (const key of counters.keys()) {\n if (counters.size < maxKeys) break\n counters.delete(key)\n }\n }\n\n return {\n async increment(key, windowSeconds) {\n const now = Date.now()\n const current = counters.get(key)\n if (current && current.resetAt.getTime() > now) {\n current.count += 1\n return { ...current }\n }\n\n counters.delete(key)\n makeRoom(now)\n const state = { count: 1, resetAt: new Date(now + windowSeconds * 1000) }\n counters.set(key, state)\n return { ...state }\n },\n\n async reset(key) {\n counters.delete(key)\n },\n }\n}\n","import {\n defineMiddleware,\n forbiddenError,\n unauthorizedError,\n} from '@dotpress/core'\nimport type { AuthSession, AuthUser } from './types.js'\n\n/** Documents the routes using the guards as secured by an access token. */\nconst SECURED = { security: ['bearerAuth'] }\n\n/** Context guaranteed by `requireAuth`, `requireRole()` and `requireClient()`. */\nexport interface AuthenticatedContext {\n user: AuthUser\n auth: AuthSession\n}\n\n/**\n * Requires an authenticated user (`401 UNAUTHORIZED` otherwise). In the\n * handlers of the routes using it, `ctx.user` and `ctx.auth` are defined.\n */\nexport const requireAuth = defineMiddleware<AuthenticatedContext>(\n (ctx) => (ctx.user && ctx.auth ? undefined : unauthorizedError()),\n SECURED\n)\n\n/**\n * Requires an authenticated user with at least one of the roles\n * (`401 UNAUTHORIZED` without user, `403 FORBIDDEN` without role). Roles\n * come from the `getUserRoles` setting of `createAuth()`.\n */\nexport const requireRole = (...roles: [string, ...string[]]) => {\n if (roles.length === 0) {\n throw new TypeError('requireRole() expects at least one role.')\n }\n\n return defineMiddleware<AuthenticatedContext>((ctx) => {\n if (!ctx.user || !ctx.auth) return unauthorizedError()\n const granted = ctx.auth.roles\n return roles.some((role) => granted.includes(role))\n ? undefined\n : forbiddenError()\n }, SECURED)\n}\n\n/**\n * Requires an access token issued for one of the clients (`401\n * UNAUTHORIZED` without user, `403 FORBIDDEN` for another client). For\n * example, keeps the tokens of a mobile app away from the backoffice\n * routes. It does not prove which application sends the request: combine\n * it with `requireRole()`.\n */\nexport const requireClient = (...clientIds: [string, ...string[]]) => {\n if (clientIds.length === 0) {\n throw new TypeError('requireClient() expects at least one client.')\n }\n\n return defineMiddleware<AuthenticatedContext>((ctx) => {\n if (!ctx.user || !ctx.auth) return unauthorizedError()\n const { clientId } = ctx.auth\n return clientId !== undefined && clientIds.includes(clientId)\n ? undefined\n : forbiddenError()\n }, SECURED)\n}\n","import {\n definePlugin,\n forbiddenError,\n internalError,\n tooManyRequestsError,\n unauthorizedError,\n type DotpressRequest,\n type Middleware,\n type RouteGroup,\n} from '@dotpress/core'\nimport { AuthError, type Auth } from './auth.js'\nimport { sha256 } from './crypto.js'\nimport { requireAuth } from './middlewares.js'\nimport type {\n IssuedTokens,\n LoginFailedEvent,\n ResolvedCookieSettings,\n ResolvedLoginThrottleSettings,\n TokenPair,\n} from './types.js'\nimport './context.js'\n\nexport interface AuthPluginOptions {\n /** Prefix of the authentication routes. Defaults to `/auth`. */\n prefix?: string\n /** Registers the authentication routes. Defaults to `true`. */\n routes?: boolean\n /**\n * Publishes the public keys at `GET /.well-known/jwks.json`, for other\n * services verifying the tokens. Defaults to `false`.\n */\n jwks?: boolean\n}\n\n/** Security schemes of the plugin, for documentation generators. */\nexport const AUTH_SECURITY_SCHEMES = {\n bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },\n} as const\n\nconst LOCAL_IPS = new Set([\n '127.0.0.1',\n 'localhost',\n '::1',\n '::ffff',\n '::ffff:127.0.0.1',\n])\n\n/** Client IP address for login events (empty for local requests). */\nconst clientIp = (req: DotpressRequest) =>\n req.ip && !LOCAL_IPS.has(req.ip) ? req.ip : ''\n\nconst BEARER = /^bearer +(\\S+) *$/i\n\nconst bodyField = (body: unknown, key: string): string | undefined => {\n if (typeof body !== 'object' || body === null) return undefined\n const value = (body as Record<string, unknown>)[key]\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\n/**\n * Access token of a request, and where it was found. The request body is\n * never read: an app route may receive an `accessToken` field of its own\n * (a third-party token, for example), and RFC 6750 discourages it.\n */\nconst findAccessToken = (\n req: DotpressRequest,\n cookies: ResolvedCookieSettings\n): { token: string; source: 'header' | 'cookie' } | undefined => {\n // Authentication schemes are case-insensitive (RFC 7235).\n const bearer = BEARER.exec(req.header('authorization') ?? '')\n if (bearer) return { token: bearer[1]!, source: 'header' }\n\n if (cookies.enabled && cookies.accessTokenInCookie) {\n const cookie = req.cookies[cookies.accessTokenName]\n if (cookie) return { token: cookie, source: 'cookie' }\n }\n\n return undefined\n}\n\n/**\n * Access token of a request: `Authorization: Bearer` header, then cookie\n * (when enabled). The request body is never read.\n */\nexport const extractAccessToken = (\n req: DotpressRequest,\n cookies: ResolvedCookieSettings\n): string | undefined => findAccessToken(req, cookies)?.token\n\n/**\n * Counters of the login throttle for an attempt. The username is\n * normalized (letter case, spaces), and the keys are hashed: bounded size,\n * and no personal data in the store.\n */\nconst loginThrottleKeys = async (\n throttle: ResolvedLoginThrottleSettings,\n username: string,\n ipAddress: string\n) => {\n const normalized = username.trim().toLowerCase()\n const { usernameAndIp, username: perUsername } = throttle.limits\n return {\n usernameAndIp: usernameAndIp && {\n key: `dotpress:login:username-ip:${await sha256(`${normalized}\\n${ipAddress}`)}`,\n limit: usernameAndIp,\n },\n username: perUsername && {\n key: `dotpress:login:username:${await sha256(normalized)}`,\n limit: perUsername,\n },\n }\n}\n\n/** Response of the cookie routes to a cross-origin request. */\nconst crossOriginError = () => forbiddenError('CROSS_ORIGIN_REQUEST')\n\n/**\n * Token pair sent to the refresh route: refresh token cookie first, then\n * body fields.\n */\nconst extractTokens = (\n req: DotpressRequest,\n body: unknown,\n cookies: ResolvedCookieSettings\n): (TokenPair & { mode: 'cookie' | 'body' }) | undefined => {\n const cookieRefreshToken = req.cookies[cookies.refreshTokenName]\n\n if (cookieRefreshToken) {\n const accessToken = cookies.accessTokenInCookie\n ? req.cookies[cookies.accessTokenName]\n : bodyField(body, 'accessToken')\n return accessToken\n ? { accessToken, refreshToken: cookieRefreshToken, mode: 'cookie' }\n : undefined\n }\n\n const accessToken = bodyField(body, 'accessToken')\n const refreshToken = bodyField(body, 'refreshToken')\n return accessToken && refreshToken\n ? { accessToken, refreshToken, mode: 'body' }\n : undefined\n}\n\nconst refreshErrorResponse = (error: unknown, fallback: string) => {\n if (error instanceof AuthError) {\n if (error.code === 'EXPIRED_REFRESH_TOKEN') {\n return unauthorizedError('EXPIRED_REFRESH_TOKEN')\n }\n return unauthorizedError('INVALID_REFRESH_TOKEN')\n }\n return internalError(fallback)\n}\n\nconst registerRoutes = (group: RouteGroup, auth: Auth) => {\n const { settings } = auth\n const { dataProvider, cookies } = settings\n\n /**\n * Expiration fields of the responses: access token, and session when an\n * absolute timeout applies.\n */\n const expiration = (accessTokenExpiresAt: Date, sessionExpiresAt?: Date) => ({\n expiresIn: Math.max(\n 0,\n Math.ceil((accessTokenExpiresAt.getTime() - Date.now()) / 1000)\n ),\n expiresAt: accessTokenExpiresAt.toISOString(),\n ...(sessionExpiresAt && {\n sessionExpiresAt: sessionExpiresAt.toISOString(),\n }),\n })\n const issuedExpiration = (tokens: IssuedTokens) =>\n expiration(tokens.accessTokenExpiresAt, tokens.sessionExpiresAt)\n\n /**\n * Tokens returned in the response body. Tokens set as cookies are never\n * returned, so that the JavaScript of the page cannot read them: the\n * refresh token always is a cookie in cookie mode, the access token only\n * with `accessTokenInCookie`.\n */\n const bodyTokens = (\n tokens: TokenPair,\n inCookies: boolean\n ): Partial<TokenPair> => {\n if (!inCookies) {\n return {\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n }\n }\n return cookies.accessTokenInCookie\n ? {}\n : { accessToken: tokens.accessToken }\n }\n\n const { clients } = settings\n\n const loginFailed = async (event: LoginFailedEvent) => {\n await settings.onLoginFailed?.(event)\n return unauthorizedError('INVALID_LOGIN')\n }\n\n const meta = { tags: ['auth'] }\n /** Cookie routes refuse requests from other origins. */\n const crossOrigin =\n 'Cross-origin request in cookie mode (`CROSS_ORIGIN_REQUEST`, see cookies.trustedOrigins).'\n\n group.defineRoute({\n method: 'post',\n path: '/login',\n meta: {\n ...meta,\n summary: 'Log in with a username and a password',\n responses: {\n 401: 'Invalid credentials (`INVALID_LOGIN`).',\n 429: 'Too many failed attempts (`TOO_MANY_LOGIN_ATTEMPTS`), with a `Retry-After` header.',\n },\n },\n schema: (z) => ({\n body: z.object({\n username: z.string(),\n password: z.string(),\n // One of the allowed clients when the clients setting is defined,\n // otherwise any string, only reported in the login events.\n clientId: !clients\n ? z.string().optional()\n : clients.required\n ? z.enum(clients.allowed as [string, ...string[]])\n : z.enum(clients.allowed as [string, ...string[]]).optional(),\n mode: z.enum(['body', 'cookie']).optional(),\n }),\n response: z.object({\n userId: z.string(),\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n expiresIn: z.number(),\n expiresAt: z.string(),\n sessionExpiresAt: z.string().optional(),\n }),\n }),\n handler: async ({ req, res, logger }) => {\n const { username, password, clientId, mode } = req.body\n const event = {\n username,\n clientId: clientId ?? '',\n ipAddress: clientIp(req),\n }\n\n // Brute force protection, before any password check: the attempt\n // is counted first (atomic increment), so that parallel attempts\n // cannot pass before the counter is updated.\n const throttle = settings.loginThrottle\n const counters =\n throttle &&\n (await loginThrottleKeys(throttle, username, event.ipAddress))\n if (throttle && counters) {\n let retryAfter = 0\n for (const counter of [counters.usernameAndIp, counters.username]) {\n if (!counter) continue\n const state = await throttle.store.increment(\n counter.key,\n counter.limit.window\n )\n if (state.count > counter.limit.max) {\n retryAfter = Math.max(\n retryAfter,\n Math.ceil((state.resetAt.getTime() - Date.now()) / 1000),\n 1\n )\n }\n }\n if (retryAfter > 0) {\n await settings.onLoginFailed?.({\n ...event,\n errorType: 'too_many_attempts',\n })\n res.setHeader('retry-after', String(retryAfter))\n return tooManyRequestsError('TOO_MANY_LOGIN_ATTEMPTS')\n }\n }\n\n const rejected = await settings.beforeLogin?.({ ...event, req })\n if (rejected) return rejected\n\n const user = await dataProvider.findUserIdentifiers(username)\n // The password is checked even for unknown users, against a dummy\n // hash: both failures take the same time.\n const passwordValid = await auth.verifyPassword(\n password,\n user?.passwordHash\n )\n if (!user) {\n return loginFailed({ ...event, errorType: 'invalid_username' })\n }\n\n if (!passwordValid) {\n return loginFailed({ ...event, errorType: 'invalid_password' })\n }\n\n if (\n clientId !== undefined &&\n clients?.isUserRoleAllowed &&\n !clients.isUserRoleAllowed(user.role ?? '', clientId)\n ) {\n return loginFailed({ ...event, errorType: 'invalid_role' })\n }\n\n const tokens = await auth.generateTokensForUser({\n userId: user.userId,\n username,\n // Without clients setting, the clientId is only reported in the\n // login events: an unchecked value is not written in the token.\n clientId: clients ? clientId : undefined,\n })\n\n // A successful login clears the counter of this client only: a\n // distributed attack on the account keeps being counted.\n if (throttle && counters?.usernameAndIp) {\n await throttle.store.reset(counters.usernameAndIp.key)\n }\n\n await settings.onLoginSuccess?.({ ...event, userId: user.userId })\n\n // Upgrades a hash with another cost (passwordHashRounds raised).\n if (\n dataProvider.updatePasswordHash &&\n user.passwordHash &&\n auth.passwordNeedsRehash(user.passwordHash)\n ) {\n try {\n await dataProvider.updatePasswordHash(\n user.userId,\n await auth.rehashPassword(password)\n )\n } catch (error) {\n logger.warn(\n error instanceof Error ? error : { error },\n 'Cannot update the password hash'\n )\n }\n }\n\n const inCookies = mode === 'cookie' && cookies.enabled\n if (inCookies) auth.setAuthCookies(res, tokens)\n\n return {\n userId: user.userId,\n ...bodyTokens(tokens, inCookies),\n ...issuedExpiration(tokens),\n }\n },\n })\n\n group.defineRoute({\n method: 'post',\n path: '/refresh',\n meta: {\n ...meta,\n summary: 'Rotate a token pair',\n responses: {\n 401: 'Missing, invalid, expired or reused tokens.',\n 403: crossOrigin,\n },\n },\n schema: (z) => ({\n body: z\n .object({\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n })\n .optional(),\n response: z.object({\n userId: z.string(),\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n expiresIn: z.number(),\n expiresAt: z.string(),\n sessionExpiresAt: z.string().optional(),\n }),\n }),\n handler: async ({ req, res }) => {\n const extracted = extractTokens(req, req.body, cookies)\n if (!extracted) return unauthorizedError('NO_TOKENS_PROVIDED')\n if (\n extracted.mode === 'cookie' &&\n !(await auth.isCookieAuthenticationAllowed(req))\n ) {\n return crossOriginError()\n }\n\n try {\n const { userId, ...tokens } = await auth.refreshTokens(extracted)\n const inCookies = extracted.mode === 'cookie' && cookies.enabled\n if (inCookies) auth.setAuthCookies(res, tokens)\n return {\n userId,\n ...bodyTokens(tokens, inCookies),\n ...issuedExpiration(tokens),\n }\n } catch (error) {\n return refreshErrorResponse(error, 'Can not refresh tokens.')\n }\n },\n })\n\n group.defineRoute({\n path: '/me',\n meta: { ...meta, summary: 'Authenticated user' },\n middlewares: [requireAuth],\n handler: ({ user }) => user,\n })\n\n group.defineRoute({\n method: 'post',\n path: '/restore',\n meta: {\n ...meta,\n summary: 'Restore a session from the auth cookies',\n responses: {\n 401: 'Missing or invalid auth cookies.',\n 403: crossOrigin,\n },\n },\n schema: (z) => ({\n response: z.object({\n userId: z.string(),\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n expiresIn: z.number(),\n expiresAt: z.string(),\n sessionExpiresAt: z.string().optional(),\n user: z.unknown(),\n }),\n }),\n handler: async ({ req, res }) => {\n if (!cookies.enabled) return unauthorizedError('COOKIES_NOT_ENABLED')\n if (!cookies.accessTokenInCookie) {\n return unauthorizedError('ACCESS_TOKEN_NOT_IN_COOKIE')\n }\n\n const refreshToken = req.cookies[cookies.refreshTokenName]\n const accessToken = req.cookies[cookies.accessTokenName]\n if (!refreshToken) return unauthorizedError('NO_REFRESH_TOKEN')\n if (!accessToken) return unauthorizedError('NO_ACCESS_TOKEN')\n if (!(await auth.isCookieAuthenticationAllowed(req))) {\n return crossOriginError()\n }\n\n // A valid access token restores the session as is.\n try {\n const session = await auth.verifyAccessToken(accessToken)\n const { userId } = session\n const user = await dataProvider.findUserById(userId)\n // The tokens stay in their cookies.\n if (user) {\n return {\n userId,\n ...expiration(\n new Date(Number(session.claims.exp) * 1000),\n auth.getSessionExpiresAt(session)\n ),\n user,\n }\n }\n } catch {\n // Expired or invalid: try the refresh token.\n }\n\n try {\n const { userId, user, ...tokens } = await auth.refreshTokens({\n accessToken,\n refreshToken,\n })\n auth.setAuthCookies(res, tokens)\n\n return { userId, ...issuedExpiration(tokens), user }\n } catch (error) {\n return refreshErrorResponse(error, 'Cannot restore session.')\n }\n },\n })\n\n group.defineRoute({\n method: 'post',\n path: '/logout',\n meta: {\n ...meta,\n summary: 'Revoke the current session, or every session of the user',\n responses: {\n 401: '`scope: \"all\"` without a valid refresh token or access token.',\n 403: crossOrigin,\n },\n },\n schema: (z) => ({\n body: z\n .object({\n refreshToken: z.string().optional(),\n scope: z.enum(['current', 'all']).optional(),\n })\n .optional(),\n }),\n // The session is proven by the refresh token (cookie, then body), or\n // by the access token: an expired access token does not prevent a\n // logout.\n handler: async ({ req, res, auth: session }) => {\n const scope = req.body?.scope ?? 'current'\n const cookieRefreshToken = cookies.enabled\n ? req.cookies[cookies.refreshTokenName]\n : undefined\n if (\n cookieRefreshToken &&\n !(await auth.isCookieAuthenticationAllowed(req))\n ) {\n return crossOriginError()\n }\n\n const refreshToken = cookieRefreshToken || req.body?.refreshToken\n const revoked = refreshToken\n ? await auth.revokeSession(refreshToken, { scope })\n : undefined\n\n if (!revoked && session) {\n if (scope === 'all') {\n await dataProvider.revokeTokens(session.userId)\n } else if (session.sessionId) {\n await dataProvider.revokeTokenFamily(session.sessionId)\n }\n } else if (!revoked && scope === 'all') {\n // Logging out every device requires proving the identity.\n return unauthorizedError()\n }\n\n // Idempotent (RFC 7009): an unknown or missing token still logs the\n // client out.\n auth.clearAuthCookies(res)\n return { success: true }\n },\n })\n}\n\n/** Group prefix as the router stores it: `/auth/` gives `/auth`. */\nconst normalizePrefix = (prefix: string) =>\n `/${prefix.split('/').filter(Boolean).join('/')}`\n\n/**\n * Plugs an authentication service into an app:\n *\n * - reads the access token of every request, and sets `ctx.user` and\n * `ctx.auth` when it is valid;\n * - registers the `POST /auth/login`, `POST /auth/refresh`,\n * `GET /auth/me`, `POST /auth/restore` and `POST /auth/logout` routes.\n */\nexport const authPlugin = (auth: Auth, options: AuthPluginOptions = {}) => {\n const prefix = options.prefix ?? '/auth'\n\n return definePlugin({\n name: 'auth',\n auth,\n securitySchemes: AUTH_SECURITY_SCHEMES,\n async setup(api) {\n // Fails at startup on invalid keys. The refresh token cookie is only\n // sent to the auth routes: its path is their browser path.\n await auth.init({\n ...(options.routes === false\n ? {}\n : { routesPath: api.publicPath(normalizePrefix(prefix)) }),\n ...(api.cors ? { cors: api.cors } : {}),\n })\n\n api.decorateContext('user', () => undefined)\n api.decorateContext('auth', () => undefined)\n\n // Requests whose bearer token was rejected (expired, revoked…).\n const rejectedTokens = new WeakSet<object>()\n\n const authenticate: Middleware = async (ctx) => {\n const { cookies } = auth.settings\n const found = findAccessToken(ctx.req, cookies)\n if (!found) return\n const { token } = found\n\n // Cross-site request forgery: the browser attached the cookie to a\n // request from another origin. The request stays anonymous.\n if (\n found.source === 'cookie' &&\n !(await auth.isCookieAuthenticationAllowed(ctx.req))\n ) {\n ctx.logger.warn(\n { origin: ctx.req.header('origin') },\n 'Access token cookie ignored: cross-origin request (see cookies.trustedOrigins)'\n )\n return\n }\n\n try {\n const session = await auth.verifyAccessToken(token)\n const user = await auth.settings.dataProvider.findUserById(\n session.userId\n )\n if (!user) {\n if (found.source === 'header') rejectedTokens.add(ctx)\n return\n }\n\n ctx.user = user\n ctx.auth = { ...session, roles: auth.getUserRoles(user) }\n } catch (error) {\n if (found.source === 'header') rejectedTokens.add(ctx)\n ctx.logger.warn(\n error instanceof Error ? error : { error },\n 'Invalid access token'\n )\n }\n }\n\n api.addGlobalMiddleware(authenticate)\n\n // Challenge of the 401 responses (RFC 7235, RFC 6750), unless one was\n // set by the app or another plugin.\n api.hooks.onResponse(({ response, ctx }) => {\n if (response.status !== 401) return\n const has = Object.keys(response.headers).some(\n (name) => name.toLowerCase() === 'www-authenticate'\n )\n if (has) return\n return {\n ...response,\n headers: {\n ...response.headers,\n 'www-authenticate':\n ctx && rejectedTokens.has(ctx)\n ? 'Bearer error=\"invalid_token\"'\n : 'Bearer',\n },\n }\n })\n\n if (options.jwks) {\n api.addRoute({\n path: '/.well-known/jwks.json',\n meta: { tags: ['auth'], summary: 'Public keys of the access tokens' },\n handler: async ({ res }) => {\n // Short cache: verifiers pick up a rotation within minutes.\n res.setHeader('cache-control', 'public, max-age=300')\n return auth.getJwks()\n },\n })\n }\n\n if (options.routes !== false) {\n registerRoutes(api.addRouteGroup(prefix), auth)\n }\n },\n })\n}\n"],"mappings":"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","names":["forbiddenError","unauthorizedError","forbiddenError","accessToken","unauthorizedError"]}
1
+ {"version":3,"sources":["../src/context.ts","../src/auth.ts","../src/crypto.ts","../src/csrf.ts","../src/middlewares.ts","../src/plugin.ts"],"sourcesContent":["import { declareContextKey } from '@dotpress/core'\nimport type { AuthSession, AuthUser } from './types.js'\n\ndeclare module '@dotpress/core' {\n interface RequestContext {\n /** Authenticated user (`authPlugin`). Use `requireAuth` to require it. */\n user: AuthUser | undefined\n /** Access token data of the authenticated user (`authPlugin`). */\n auth: AuthSession | undefined\n }\n}\n\n// Accessing these keys without `authPlugin` throws an explicit error.\ndeclareContextKey('user', 'authPlugin')\ndeclareContextKey('auth', 'authPlugin')\n","import {\n createMemoryRateLimitStore,\n parseDuration,\n type DotpressRequest,\n type DotpressResponse,\n type PluginCors,\n} from '@dotpress/core'\nimport bcrypt from 'bcryptjs'\nimport {\n CompactSign,\n SignJWT,\n calculateJwkThumbprint,\n compactVerify,\n decodeProtectedHeader,\n errors,\n exportJWK,\n importPKCS8,\n importSPKI,\n jwtVerify,\n type JWK,\n} from 'jose'\nimport { sha256, toHex } from './crypto.js'\nimport { isCookieAuthenticationAllowed } from './csrf.js'\nimport type {\n AuthInitOptions,\n AuthSession,\n AuthOptions,\n AuthUser,\n AuthCookieOptions,\n ClientOptions,\n KeyPairOptions,\n CreatedAccessToken,\n CreatedRefreshToken,\n IssuedTokens,\n LoginThrottleOptions,\n LogoutScope,\n RateLimit,\n RefreshTokenData,\n ResolvedRateLimit,\n ResolvedAuthOptions,\n ResolvedCookieOptions,\n ResolvedLoginThrottleOptions,\n ResolvedSessionOptions,\n RevokedSession,\n SessionOptions,\n TokenPair,\n TokenSubject,\n} from './types.js'\n\nconst ALGORITHM = 'RS256'\n/** Media type of the access tokens (RFC 9068). */\nconst ACCESS_TOKEN_TYPE = 'at+jwt'\nconst DEFAULT_IDLE_TIMEOUT = 14 * 24 * 60 * 60\n\nexport type AuthErrorCode =\n | 'INVALID_ACCESS_TOKEN'\n | 'INVALID_REFRESH_TOKEN'\n | 'EXPIRED_REFRESH_TOKEN'\n /** A used refresh token was presented again: its session is revoked. */\n | 'REFRESH_TOKEN_REUSED'\n /** Valid access token of a revoked session (`isSessionRevoked()`). */\n | 'SESSION_REVOKED'\n\n/**\n * Error raised by the token helpers.\n */\nexport class AuthError extends Error {\n constructor(\n readonly code: AuthErrorCode,\n options?: ErrorOptions\n ) {\n super(code, options)\n this.name = 'AuthError'\n }\n}\n\nexport interface Auth {\n /** Generates and stores a token pair, starting a session. */\n generateTokensForUser(subject: TokenSubject): Promise<IssuedTokens>\n /**\n * Rotates a token pair. The access token may be expired, but must be\n * signed by the key pair and match the refresh token.\n *\n * A refresh token can only be used once. Within\n * `refreshTokenReuseInterval` after its first use, it may be presented\n * again (concurrent refreshes, retries after a lost response): a new pair\n * is issued in the same session. After, presenting it again means it was\n * probably stolen: the whole session is revoked, and\n * `REFRESH_TOKEN_REUSED` is thrown.\n *\n * Throws `EXPIRED_REFRESH_TOKEN` when the session reached its inactivity\n * or absolute timeout, and `INVALID_REFRESH_TOKEN` when\n * `findUserById()` no longer returns the user (the session is revoked).\n */\n refreshTokens(\n tokens: TokenPair\n ): Promise<IssuedTokens & { userId: string; user: AuthUser }>\n /**\n * Verifies a valid (not expired) access token. With\n * `DataProvider.isSessionRevoked()`, throws `SESSION_REVOKED` for the\n * tokens of a revoked session.\n */\n verifyAccessToken(accessToken: string): Promise<AuthSession>\n /**\n * Revokes the session of a refresh token (logout). The token may be\n * used, revoked or expired: it still designates its session. Tokens\n * stored before sessions (V1) revoke every session of the user.\n *\n * With `scope: 'all'`, revokes every session of the user, on every\n * device. The token must then be active (neither used, revoked nor\n * expired).\n *\n * Returns `undefined`, without revoking anything, for an unknown token,\n * or an inactive token with `scope: 'all'`.\n */\n revokeSession(\n refreshToken: string,\n options?: { scope?: LogoutScope }\n ): Promise<RevokedSession | undefined>\n /**\n * Revokes every session of a user, on every device: after a password\n * change, or when an account is disabled. Their refresh tokens stop\n * working at once; their access tokens too with\n * `DataProvider.isSessionRevoked()`, else at their expiry.\n */\n revokeUserSessions(userId: string): Promise<void>\n /**\n * End of a session, when an absolute timeout applies to it (computed\n * from the `auth_time` claim).\n */\n getSessionExpiresAt(session: AuthSession): Date | undefined\n /**\n * Public keys as a JSON Web Key Set (RFC 7517): the active key first,\n * then the additional keys. Other services can verify the tokens with\n * it (`createRemoteJWKSet()` of jose).\n */\n getJwks(): Promise<{ keys: JWK[] }>\n /** Roles of a user, from the `getUserRoles` setting. */\n getUserRoles(user: AuthUser): readonly string[]\n /**\n * Sets the token cookies (cookie mode only). They expire with the\n * refresh token (`refreshTokenExpiresAt` of issued tokens), or after the\n * inactivity timeout.\n */\n setAuthCookies(\n res: DotpressResponse,\n tokens: TokenPair & { refreshTokenExpiresAt?: Date }\n ): void\n clearAuthCookies(res: DotpressResponse): void\n /**\n * Whether a request may be authenticated by the cookies: protection\n * against cross-site request forgery (see `cookies.trustedOrigins`).\n * `authPlugin()` checks it on its routes and before reading the access\n * token cookie.\n */\n isCookieAuthenticationAllowed(req: DotpressRequest): Promise<boolean>\n /**\n * Hashes a password with bcrypt, with `passwordHashRounds`. Throws a\n * `RangeError` for passwords longer than 72 bytes (see\n * `isPasswordTooLong()`).\n */\n hashPassword(password: string): Promise<string>\n /** Whether a bcrypt hash has another cost than `passwordHashRounds`. */\n passwordNeedsRehash(passwordHash: string): boolean\n /**\n * Hashes a verified password with `passwordHashRounds`, to upgrade its\n * stored hash. Unlike `hashPassword()`, it accepts passwords longer than\n * 72 bytes, created before the check.\n */\n rehashPassword(password: string): Promise<string>\n /**\n * Verifies a password against its bcrypt hash. Without valid hash\n * (unknown user, account without password), it compares the password\n * with a dummy hash and returns `false`: the answer takes the same time,\n * and does not reveal whether the account exists.\n */\n verifyPassword(\n password: string,\n passwordHash: string | null | undefined\n ): Promise<boolean>\n}\n\n/** What `authPlugin()` needs of an auth service, besides its methods. */\nexport interface AuthInternals {\n readonly settings: ResolvedAuthOptions\n /**\n * Imports the keys. Fails with an explicit message on invalid keys.\n * `authPlugin()` calls it at startup, with the browser path of its routes.\n */\n init(options?: AuthInitOptions): Promise<void>\n}\n\nconst internals = new WeakMap<Auth, AuthInternals>()\n\n/**\n * The internals of a service created by `createAuth()`, or `undefined` for\n * any other value.\n */\nexport const internalsOf = (auth: unknown): AuthInternals | undefined =>\n typeof auth === 'object' && auth !== null\n ? internals.get(auth as Auth)\n : undefined\n\n/** A duration option, in whole seconds, at least `min`. */\nconst wholeSeconds = (label: string, value: unknown, min: number) => {\n const result = Math.round(\n parseDuration(value, `createAuth(): ${label}`) / 1000\n )\n if (result < min) {\n throw new RangeError(\n `createAuth(): ${label} must be at least ${min} second${min === 1 ? '' : 's'}.`\n )\n }\n return result\n}\n\n/** Throws a `TypeError` for an unknown key of `options`. */\nconst assertKnown = (\n options: unknown,\n known: readonly string[],\n prefix = ''\n): void => {\n if (options === undefined) return\n if (typeof options !== 'object' || options === null) {\n throw new TypeError(\n `createAuth(): ${prefix ? prefix.slice(0, -1) : 'options'} must be an object.`\n )\n }\n for (const key of Object.keys(options)) {\n if (!known.includes(key)) {\n throw new TypeError(`createAuth(): unknown option \"${prefix}${key}\".`)\n }\n }\n}\n\nconst OPTIONS = [\n 'keys',\n 'dataProvider',\n 'accessTokenTTL',\n 'session',\n 'issuer',\n 'audience',\n 'cookies',\n 'clients',\n 'onLoginFailed',\n 'onLoginSuccess',\n 'loginThrottle',\n 'beforeLogin',\n 'passwordHashRounds',\n 'usernameClaim',\n 'refreshTokenReuseInterval',\n 'onRefreshTokenReuse',\n 'getUserRoles',\n] satisfies (keyof AuthOptions)[]\n\nconst KEY_OPTIONS = [\n 'privateKey',\n 'publicKey',\n 'kid',\n 'additionalPublicKeys',\n] satisfies (keyof KeyPairOptions)[]\n\nconst SESSION_OPTIONS = [\n 'idleTimeout',\n 'absoluteTimeout',\n] satisfies (keyof SessionOptions)[]\n\nconst CLIENT_OPTIONS = [\n 'allowed',\n 'required',\n 'isUserRoleAllowed',\n 'sessions',\n] satisfies (keyof ClientOptions)[]\n\nconst DEFAULT_COOKIE_SETTINGS: ResolvedCookieOptions = {\n enabled: false,\n httpOnly: true,\n secure: true,\n sameSite: 'lax',\n path: '/',\n refreshTokenPath: '/auth',\n refreshTokenSameSite: 'strict',\n trustedOrigins: [],\n accessTokenName: 'dotpress_access_token',\n refreshTokenName: 'dotpress_refresh_token',\n accessTokenInCookie: false,\n domain: undefined,\n}\n\nconst defaultUserRoles = (user: AuthUser): readonly string[] => {\n const { roles, role } = user as { roles?: unknown; role?: unknown }\n if (Array.isArray(roles)) {\n return roles.filter((value): value is string => typeof value === 'string')\n }\n return typeof role === 'string' ? [role] : []\n}\n\n/** Session lifetimes in seconds; missing keys stay missing. */\nconst resolveSession = (\n session: SessionOptions | undefined,\n name: string\n): Partial<ResolvedSessionOptions> => {\n assertKnown(session, SESSION_OPTIONS, `${name}.`)\n const resolved: Partial<ResolvedSessionOptions> = {}\n for (const key of SESSION_OPTIONS) {\n if (session === undefined || !(key in session)) continue\n const value = session[key]\n // An explicit `absoluteTimeout: undefined` removes the global limit.\n resolved[key] =\n value === undefined ? undefined : wholeSeconds(`${name}.${key}`, value, 1)\n }\n return resolved\n}\n\nconst DEFAULT_LOGIN_LIMITS = {\n usernameAndIp: { max: 5, window: 60 },\n username: { max: 100, window: 60 * 60 },\n} as const\n\nconst resolveLoginThrottle = (\n input: LoginThrottleOptions | false | undefined\n): ResolvedLoginThrottleOptions | undefined => {\n if (input === false) return undefined\n\n assertKnown(input, ['store', 'limits'], 'loginThrottle.')\n assertKnown(\n input?.limits,\n ['usernameAndIp', 'username'],\n 'loginThrottle.limits.'\n )\n\n const limit = (\n name: keyof typeof DEFAULT_LOGIN_LIMITS\n ): ResolvedRateLimit | undefined => {\n const value = input?.limits?.[name]\n if (value === false) return undefined\n const resolved: RateLimit = value ?? DEFAULT_LOGIN_LIMITS[name]\n assertKnown(\n resolved,\n ['max', 'window'] satisfies (keyof RateLimit)[],\n `loginThrottle.limits.${name}.`\n )\n if (!Number.isInteger(resolved.max) || resolved.max < 1) {\n throw new RangeError(\n `createAuth(): loginThrottle.limits.${name}.max must be a positive integer.`\n )\n }\n return {\n max: resolved.max,\n window: wholeSeconds(\n `loginThrottle.limits.${name}.window`,\n resolved.window,\n 1\n ),\n }\n }\n\n const store = input?.store ?? createMemoryRateLimitStore()\n if (\n typeof store.increment !== 'function' ||\n typeof store.reset !== 'function'\n ) {\n throw new TypeError(\n 'createAuth(): loginThrottle.store must have increment() and reset() methods.'\n )\n }\n\n return {\n store,\n limits: {\n usernameAndIp: limit('usernameAndIp'),\n username: limit('username'),\n },\n }\n}\n\nconst resolveSettings = (settings: AuthOptions): ResolvedAuthOptions => {\n if (typeof settings !== 'object' || settings === null) {\n throw new TypeError('createAuth(): expected an object of options.')\n }\n if ('refreshTokenTTLInDays' in settings) {\n throw new TypeError(\n 'createAuth(): refreshTokenTTLInDays is replaced by session.idleTimeout.'\n )\n }\n if (\n typeof settings.keys === 'object' &&\n settings.keys !== null &&\n 'secretKey' in settings.keys\n ) {\n throw new TypeError(\n 'createAuth(): keys.secretKey is renamed keys.privateKey.'\n )\n }\n assertKnown(settings, OPTIONS)\n assertKnown(settings.keys, KEY_OPTIONS, 'keys.')\n for (const [index, entry] of (\n settings.keys?.additionalPublicKeys ?? []\n ).entries()) {\n if (typeof entry !== 'string') {\n assertKnown(\n entry,\n ['publicKey', 'kid'],\n `keys.additionalPublicKeys[${index}].`\n )\n }\n }\n assertKnown(settings.clients, CLIENT_OPTIONS, 'clients.')\n assertKnown(\n settings.cookies,\n Object.keys(DEFAULT_COOKIE_SETTINGS),\n 'cookies.'\n )\n if (!settings?.keys?.privateKey || !settings.keys.publicKey) {\n throw new TypeError(\n 'createAuth(): keys.privateKey and keys.publicKey are required.'\n )\n }\n if (!settings.dataProvider) {\n throw new TypeError('createAuth(): dataProvider is required.')\n }\n if (typeof settings.issuer !== 'string' || settings.issuer === '') {\n throw new TypeError(\n 'createAuth(): issuer is required (the iss claim), such as the URL of the API.'\n )\n }\n if (settings.audience !== undefined && settings.audience === '') {\n throw new TypeError('createAuth(): audience must be a non-empty string.')\n }\n const clients = settings.clients\n if (\n clients &&\n (!Array.isArray(clients.allowed) ||\n clients.allowed.length === 0 ||\n clients.allowed.some((id) => typeof id !== 'string' || id === ''))\n ) {\n throw new TypeError(\n 'createAuth(): clients.allowed must be a non-empty list of client identifiers.'\n )\n }\n const rounds = settings.passwordHashRounds\n if (\n rounds !== undefined &&\n (!Number.isInteger(rounds) || rounds < 4 || rounds > 31)\n ) {\n throw new RangeError(\n 'createAuth(): passwordHashRounds must be an integer between 4 and 31.'\n )\n }\n for (const origin of settings.cookies?.trustedOrigins ?? []) {\n let valid = false\n try {\n valid = new URL(origin).origin === origin\n } catch {\n // Invalid URL.\n }\n if (!valid) {\n throw new TypeError(\n `createAuth(): cookies.trustedOrigins: \"${origin}\" is not an origin (expected a scheme and a host, such as https://app.example.com, without path or trailing slash).`\n )\n }\n }\n const session = resolveSession(settings.session, 'session')\n const clientSessions: Record<string, Partial<ResolvedSessionOptions>> = {}\n for (const [clientId, override] of Object.entries(clients?.sessions ?? {})) {\n if (!clients?.allowed.includes(clientId)) {\n throw new TypeError(\n `createAuth(): clients.sessions: \"${clientId}\" is not in clients.allowed.`\n )\n }\n clientSessions[clientId] = resolveSession(\n override,\n `clients.sessions.${clientId}`\n )\n }\n const optional = (\n label: 'accessTokenTTL' | 'refreshTokenReuseInterval',\n fallback: number,\n min: number\n ) =>\n settings[label] === undefined\n ? fallback\n : wholeSeconds(label, settings[label], min)\n\n return {\n keys: settings.keys,\n dataProvider: settings.dataProvider,\n accessTokenTTL: optional('accessTokenTTL', 900, 1),\n session: {\n idleTimeout: session.idleTimeout ?? DEFAULT_IDLE_TIMEOUT,\n absoluteTimeout: session.absoluteTimeout,\n },\n clientSessions,\n issuer: settings.issuer,\n audience: settings.audience ?? settings.issuer,\n cookies: resolveCookies(settings.cookies),\n clients: settings.clients,\n onLoginFailed: settings.onLoginFailed,\n onLoginSuccess: settings.onLoginSuccess,\n loginThrottle: resolveLoginThrottle(settings.loginThrottle),\n beforeLogin: settings.beforeLogin,\n passwordHashRounds: settings.passwordHashRounds ?? 12,\n usernameClaim: settings.usernameClaim ?? false,\n refreshTokenReuseInterval: optional('refreshTokenReuseInterval', 10, 0),\n onRefreshTokenReuse: settings.onRefreshTokenReuse,\n getUserRoles: settings.getUserRoles ?? defaultUserRoles,\n }\n}\n\n/**\n * Name of a cookie. Like Auth.js, the default names get the strongest\n * prefix the attributes allow: `__Host-` (secure, no domain, path `/`)\n * cannot be overwritten by a subdomain or an insecure page; `__Secure-`\n * (secure) cannot be overwritten by an insecure page.\n */\nconst cookieName = (\n setting: 'accessTokenName' | 'refreshTokenName',\n name: string | undefined,\n base: string,\n attributes: { secure: boolean; domain: string | undefined; path: string }\n) => {\n const { secure, domain, path } = attributes\n const hostAllowed = secure && domain === undefined && path === '/'\n if (name === undefined) {\n if (!secure) return base\n return hostAllowed ? `__Host-${base}` : `__Secure-${base}`\n }\n\n // Browsers silently reject a prefixed cookie without its requirements.\n const lowerName = name.toLowerCase()\n if (lowerName.startsWith('__host-') && !hostAllowed) {\n throw new TypeError(\n `createAuth(): cookies.${setting}: a __Host- cookie must be secure, without domain, and with path \"/\".`\n )\n }\n if (lowerName.startsWith('__secure-') && !secure) {\n throw new TypeError(\n `createAuth(): cookies.${setting}: a __Secure- cookie must be secure.`\n )\n }\n return name\n}\n\nconst resolveCookies = (\n settings: AuthCookieOptions = {}\n): ResolvedCookieOptions => {\n const merged: ResolvedCookieOptions = { ...DEFAULT_COOKIE_SETTINGS }\n for (const [key, value] of Object.entries(settings)) {\n // An explicit `undefined` keeps the default.\n if (value !== undefined) Object.assign(merged, { [key]: value })\n }\n for (const key of ['path', 'refreshTokenPath'] as const) {\n if (!merged[key].startsWith('/')) {\n throw new TypeError(`createAuth(): cookies.${key} must start with \"/\".`)\n }\n }\n const { secure, domain } = merged\n\n return {\n ...merged,\n refreshTokenSameSite:\n settings.refreshTokenSameSite ??\n (settings.sameSite === 'none' ? 'none' : 'strict'),\n accessTokenName: cookieName(\n 'accessTokenName',\n settings.accessTokenName,\n 'dotpress_access_token',\n { secure, domain, path: merged.path }\n ),\n refreshTokenName: cookieName(\n 'refreshTokenName',\n settings.refreshTokenName,\n 'dotpress_refresh_token',\n { secure, domain, path: merged.refreshTokenPath }\n ),\n }\n}\n\nconst PKCS1_PUBLIC_KEY_ERROR =\n 'PKCS#1 keys (\"BEGIN RSA PUBLIC KEY\") are not supported, use SPKI (\"BEGIN PUBLIC KEY\"). Convert it with: openssl rsa -RSAPublicKey_in -in public.pem -pubout -out public.spki.pem'\n\n/** Public key accepted to verify tokens. */\ninterface VerificationKey {\n kid: string\n key: CryptoKey\n jwk: JWK\n}\n\nconst loadKey = async (kind: string, load: () => Promise<CryptoKey>) => {\n try {\n return await load()\n } catch (error) {\n throw new TypeError(\n `createAuth(): keys.${kind} must be an RSA PEM key (${(error as Error).message}).`,\n { cause: error }\n )\n }\n}\n\nconst loadPublicKey = async (\n kind: string,\n pem: string,\n kid: string | undefined\n): Promise<VerificationKey> => {\n if (pem.includes('BEGIN RSA PUBLIC KEY')) {\n throw new TypeError(`createAuth(): keys.${kind}: ${PKCS1_PUBLIC_KEY_ERROR}`)\n }\n const key = await loadKey(kind, () =>\n importSPKI(pem, ALGORITHM, { extractable: true })\n )\n const { kty, n, e } = await exportJWK(key)\n const jwk: JWK = { kty, n, e }\n return { kid: kid ?? (await calculateJwkThumbprint(jwk)), key, jwk }\n}\n\nconst importKeys = async ({\n privateKey,\n publicKey,\n kid,\n additionalPublicKeys = [],\n}: ResolvedAuthOptions['keys']) => {\n if (privateKey.includes('BEGIN RSA PRIVATE KEY')) {\n throw new TypeError(\n 'createAuth(): keys.privateKey: PKCS#1 keys (\"BEGIN RSA PRIVATE KEY\") are not supported, use PKCS#8 (\"BEGIN PRIVATE KEY\"). Convert it with: openssl pkcs8 -topk8 -nocrypt -in private.pem -out private.pkcs8.pem'\n )\n }\n\n const signingKey = await loadKey('privateKey', () =>\n importPKCS8(privateKey, ALGORITHM)\n )\n const active = await loadPublicKey('publicKey', publicKey, kid)\n\n // A public key that does not match the private key would reject every\n // token: fail at startup instead.\n try {\n const probe = await new CompactSign(new Uint8Array([1]))\n .setProtectedHeader({ alg: ALGORITHM })\n .sign(signingKey)\n await compactVerify(probe, active.key)\n } catch (error) {\n throw new TypeError(\n 'createAuth(): keys.publicKey does not match keys.privateKey.',\n { cause: error }\n )\n }\n\n const verificationKeys = [active]\n for (const [index, entry] of additionalPublicKeys.entries()) {\n const { publicKey: pem, kid: entryKid } =\n typeof entry === 'string' ? { publicKey: entry, kid: undefined } : entry\n const loaded = await loadPublicKey(\n `additionalPublicKeys[${index}]`,\n pem,\n entryKid\n )\n const existing = verificationKeys.find((item) => item.kid === loaded.kid)\n if (!existing) {\n verificationKeys.push(loaded)\n } else if (\n existing.jwk.n !== loaded.jwk.n ||\n existing.jwk.e !== loaded.jwk.e\n ) {\n throw new TypeError(\n `createAuth(): keys: kid \"${loaded.kid}\" is used by several keys.`\n )\n }\n }\n\n return { signingKey, kid: active.kid, verificationKeys }\n}\n\n/**\n * Verifies a token with the key designated by its `kid` header, chosen\n * among the configured keys only. Tokens without `kid` (issued by V1) are\n * tried with every key.\n */\nconst verifyWithKeys = async <T>(\n token: string,\n verificationKeys: readonly VerificationKey[],\n verify: (key: CryptoKey) => Promise<T>\n): Promise<T> => {\n const { kid } = decodeProtectedHeader(token)\n const candidates =\n kid === undefined\n ? verificationKeys\n : verificationKeys.filter((item) => item.kid === kid)\n if (candidates.length === 0) {\n throw new errors.JWKSNoMatchingKey(`Unknown key id: ${String(kid)}`)\n }\n\n let lastError: unknown\n for (const candidate of candidates) {\n try {\n return await verify(candidate.key)\n } catch (error) {\n // Only a signature mismatch means another key may match.\n if (!(error instanceof errors.JWSSignatureVerificationFailed)) throw error\n lastError = error\n }\n }\n throw lastError\n}\n\n/** bcrypt hash format: `$2a$`, `$2b$` or `$2y$`, cost, salt and hash. */\nconst BCRYPT_HASH = /^\\$2[aby]\\$\\d{2}\\$[./A-Za-z0-9]{53}$/\n\nconst nowInSeconds = () => Math.floor(Date.now() / 1000)\n\nconst toSession = (\n claims: Record<string, unknown>,\n roles: readonly string[]\n): AuthSession => {\n if (\n typeof claims.sub !== 'string' ||\n claims.sub === '' ||\n typeof claims.sid !== 'string' ||\n claims.sid === ''\n ) {\n throw new AuthError('INVALID_ACCESS_TOKEN')\n }\n return {\n userId: claims.sub,\n username: typeof claims.username === 'string' ? claims.username : undefined,\n accessTokenId: typeof claims.jti === 'string' ? claims.jti : '',\n sessionId: claims.sid,\n clientId:\n typeof claims.client_id === 'string' ? claims.client_id : undefined,\n roles,\n claims,\n }\n}\n\n/**\n * Creates the authentication service. It works without an app (scripts,\n * workers), and is plugged into an app with `authPlugin()`.\n *\n * @example\n * const auth = createAuth({ keys, dataProvider })\n * await createApp({ adapter, plugins: [authPlugin(auth)] })\n */\nexport const createAuth = (input: AuthOptions): Auth => {\n const settings = resolveSettings(input)\n let keys: ReturnType<typeof importKeys> | undefined\n\n const getKeys = () => {\n keys ??= importKeys(settings.keys).catch((error: unknown) => {\n // Allows a later call to retry, and to report the error again.\n keys = undefined\n throw error\n })\n return keys\n }\n\n let dummyHash: Promise<string> | undefined\n /** Hash of a random password, with the configured cost. */\n const getDummyHash = () => {\n dummyHash ??= bcrypt.hash(\n toHex(crypto.getRandomValues(new Uint8Array(16))),\n settings.passwordHashRounds\n )\n return dummyHash\n }\n\n /** Session lifetimes of a client. */\n const sessionPolicy = (\n clientId: string | undefined\n ): ResolvedSessionOptions => {\n const override =\n clientId === undefined ? undefined : settings.clientSessions[clientId]\n if (!override) return settings.session\n return {\n idleTimeout: override.idleTimeout ?? settings.session.idleTimeout,\n absoluteTimeout:\n 'absoluteTimeout' in override\n ? override.absoluteTimeout\n : settings.session.absoluteTimeout,\n }\n }\n\n /** End of a session started at `authTime`, in seconds, if bounded. */\n const sessionEnd = (authTime: number, clientId: string | undefined) => {\n const { absoluteTimeout } = sessionPolicy(clientId)\n return absoluteTimeout === undefined\n ? undefined\n : authTime + absoluteTimeout\n }\n\n const signAccessToken = async (claims: {\n sub: string\n username?: string\n auth_time: number\n client_id?: string\n sid: string\n }): Promise<CreatedAccessToken> => {\n const { signingKey, kid } = await getKeys()\n const accessTokenId = crypto.randomUUID()\n const issuedAt = nowInSeconds()\n // An access token never outlives its session.\n const expiresAt = Math.min(\n issuedAt + settings.accessTokenTTL,\n sessionEnd(claims.auth_time, claims.client_id) ?? Infinity\n )\n\n const token = await new SignJWT({\n // Personal data: written on demand only.\n ...(settings.usernameClaim &&\n claims.username && { username: claims.username }),\n auth_time: claims.auth_time,\n sid: claims.sid,\n ...(claims.client_id !== undefined && { client_id: claims.client_id }),\n })\n .setProtectedHeader({ alg: ALGORITHM, typ: ACCESS_TOKEN_TYPE, kid })\n .setSubject(claims.sub)\n .setIssuer(settings.issuer)\n .setAudience(settings.audience)\n .setJti(accessTokenId)\n .setIssuedAt(issuedAt)\n .setExpirationTime(expiresAt)\n .sign(signingKey)\n\n return {\n accessTokenId,\n accessToken: token,\n expiresAt: new Date(expiresAt * 1000),\n }\n }\n\n const generateRefreshToken = async (): Promise<CreatedRefreshToken> => {\n const token = toHex(crypto.getRandomValues(new Uint8Array(128)))\n return { token, tokenHash: await sha256(token) }\n }\n\n /**\n * Signs an access token and stores a new refresh token. The refresh\n * token expires after the inactivity timeout, or at the end of the\n * session.\n */\n const issueTokens = async (\n claims: {\n sub: string\n username?: string\n auth_time: number\n client_id?: string\n },\n sessionId: string\n ): Promise<IssuedTokens> => {\n // Every token of a session shares its identifier (`sid`).\n const access = await signAccessToken({ ...claims, sid: sessionId })\n const refreshToken = await generateRefreshToken()\n const end = sessionEnd(claims.auth_time, claims.client_id)\n const refreshTokenExpiresAt = new Date(\n Math.min(\n Date.now() + sessionPolicy(claims.client_id).idleTimeout * 1000,\n end === undefined ? Infinity : end * 1000\n )\n )\n\n await settings.dataProvider.storeRefreshToken({\n tokenHash: refreshToken.tokenHash,\n userId: claims.sub,\n accessTokenId: access.accessTokenId,\n sessionId,\n expiresAt: refreshTokenExpiresAt,\n })\n\n return {\n accessToken: access.accessToken,\n refreshToken: refreshToken.token,\n accessTokenExpiresAt: access.expiresAt,\n refreshTokenExpiresAt,\n sessionExpiresAt: end === undefined ? undefined : new Date(end * 1000),\n }\n }\n\n /**\n * Handles a used refresh token presented again. Within the reuse\n * interval (counted from its first use), it is a concurrent refresh or a\n * retry after a lost response: returns, and a new token pair is issued in\n * the same session. After, the token was probably stolen: the session is\n * revoked.\n */\n const checkReuse = async (stored: RefreshTokenData): Promise<void> => {\n const usedAt = stored.usedAt?.getTime()\n if (\n usedAt !== undefined &&\n Date.now() - usedAt < settings.refreshTokenReuseInterval * 1000\n ) {\n return\n }\n\n const sessionId = stored.sessionId ?? undefined\n if (sessionId) {\n await settings.dataProvider.revokeSessionTokens(sessionId)\n } else {\n // Tokens stored before sessions (V1): revoke every session of the user.\n await settings.dataProvider.revokeUserTokens(stored.userId)\n }\n\n try {\n await settings.onRefreshTokenReuse?.({\n userId: stored.userId,\n sessionId,\n accessTokenId: stored.accessTokenId,\n })\n } catch {\n // The session is revoked; a failing notification must not hide it.\n }\n\n throw new AuthError('REFRESH_TOKEN_REUSED')\n }\n\n /** Whether the `clients` setting accepts a token client. */\n const isClientAccepted = (clientId: string | undefined) => {\n const { clients } = settings\n if (!clients) return true\n return clientId === undefined\n ? !clients.required\n : clients.allowed.includes(clientId)\n }\n\n /** Claims of a new session, for trusted code (login, scripts). */\n const sessionClaims = ({ userId, username, clientId }: TokenSubject) => {\n if (!isClientAccepted(clientId)) {\n throw new TypeError(\n clientId === undefined\n ? 'A clientId is required by the clients setting.'\n : `Unknown client \"${clientId}\": add it to clients.allowed.`\n )\n }\n return {\n sub: userId,\n username,\n auth_time: nowInSeconds(),\n client_id: clientId,\n }\n }\n\n let routesPath: string | undefined\n let cors: PluginCors | undefined\n\n /** Trusted origins: `cookies.trustedOrigins`, else the CORS origins. */\n const isTrustedOrigin = (req: DotpressRequest) => (origin: string) => {\n if (input.cookies?.trustedOrigins !== undefined || !cors?.credentials) {\n return settings.cookies.trustedOrigins.includes(origin)\n }\n return cors.isOriginAllowed(req)\n }\n\n /**\n * Sets the path of the refresh token cookie to the browser path of the\n * auth routes, unless `cookies.refreshTokenPath` is set.\n */\n const bindRoutesPath = (path: string) => {\n if (typeof path !== 'string' || !path.startsWith('/')) {\n throw new TypeError(\n `authPlugin(): the routes path must start with \"/\", received ${JSON.stringify(path)}.`\n )\n }\n if (routesPath !== undefined && routesPath !== path) {\n throw new Error(\n `authPlugin(): this auth service already serves routes under \"${routesPath}\": create one auth service per app to serve them under \"${path}\".`\n )\n }\n routesPath = path\n if (input.cookies?.refreshTokenPath === undefined) {\n settings.cookies = resolveCookies({\n ...input.cookies,\n refreshTokenPath: path,\n })\n }\n }\n\n const cookieOptions = () => {\n const { httpOnly, secure, sameSite, path, domain } = settings.cookies\n return { httpOnly, secure, sameSite, path, domain }\n }\n\n const auth: Auth = {\n isCookieAuthenticationAllowed: (req) =>\n isCookieAuthenticationAllowed(req, isTrustedOrigin(req)),\n\n async generateTokensForUser(subject) {\n return issueTokens(sessionClaims(subject), crypto.randomUUID())\n },\n\n async refreshTokens({ accessToken, refreshToken }) {\n const { verificationKeys } = await getKeys()\n\n let claims: Record<string, unknown>\n let legacy: boolean\n try {\n // Signature only: the access token is usually expired here, and\n // `iss`, `aud` and `typ` are not checked, so that tokens issued\n // by V1 (or before a configuration change) are migrated. The\n // stored refresh token proves the session.\n const { payload, protectedHeader } = await verifyWithKeys(\n accessToken,\n verificationKeys,\n (key) => compactVerify(accessToken, key, { algorithms: [ALGORITHM] })\n )\n claims = JSON.parse(new TextDecoder().decode(payload)) as Record<\n string,\n unknown\n >\n legacy = protectedHeader.typ !== ACCESS_TOKEN_TYPE\n } catch (error) {\n throw new AuthError('INVALID_REFRESH_TOKEN', { cause: error })\n }\n\n const userId = typeof claims.sub === 'string' ? claims.sub : ''\n const clientId =\n typeof claims.client_id === 'string'\n ? claims.client_id\n : // V1 tokens carried the client in `aud`.\n legacy &&\n typeof claims.aud === 'string' &&\n settings.clients?.allowed.includes(claims.aud)\n ? claims.aud\n : undefined\n const refreshTokenHash = await sha256(refreshToken)\n const stored =\n await settings.dataProvider.findRefreshToken(refreshTokenHash)\n\n // Unknown token, or token presented with an access token it was not\n // issued with: nothing proves possession of the session.\n if (\n !userId ||\n !stored ||\n stored.userId !== userId ||\n stored.accessTokenId !== claims.jti ||\n stored.revokedAt ||\n // Client removed from the configuration: the session ends.\n !isClientAccepted(clientId)\n ) {\n throw new AuthError('INVALID_REFRESH_TOKEN')\n }\n\n if (stored.isUsed) await checkReuse(stored)\n\n // Start of the session, kept across rotations. Without it, a\n // session cannot be bounded: with an absolute timeout, it ends.\n const claimedAuthTime =\n typeof claims.auth_time === 'number' ? claims.auth_time : undefined\n if (\n claimedAuthTime === undefined &&\n sessionPolicy(clientId).absoluteTimeout !== undefined\n ) {\n throw new AuthError('EXPIRED_REFRESH_TOKEN')\n }\n const authTime = claimedAuthTime ?? nowInSeconds()\n const end = sessionEnd(authTime, clientId)\n\n // Inactivity timeout (refresh token expiry) and absolute timeout.\n if (\n stored.expiresAt.getTime() <= Date.now() ||\n (end !== undefined && end * 1000 <= Date.now())\n ) {\n throw new AuthError('EXPIRED_REFRESH_TOKEN')\n }\n\n // Deleted or disabled account: the session ends.\n const user = await settings.dataProvider.findUserById(userId)\n if (!user) {\n if (stored.sessionId) {\n await settings.dataProvider.revokeSessionTokens(stored.sessionId)\n } else {\n await settings.dataProvider.revokeUserTokens(userId)\n }\n throw new AuthError('INVALID_REFRESH_TOKEN')\n }\n\n const issued = await issueTokens(\n {\n sub: userId,\n username:\n typeof claims.username === 'string' ? claims.username : undefined,\n auth_time: authTime,\n client_id: clientId,\n },\n // Tokens stored before sessions (V1) start one, derived from the\n // token so that concurrent rotations land in the same session.\n stored.sessionId || stored.accessTokenId\n )\n // A token reused within the interval keeps its first use date: the\n // interval cannot be extended by presenting it again.\n if (!stored.isUsed) {\n await settings.dataProvider.markRefreshTokenAsUsed(\n refreshTokenHash,\n new Date()\n )\n }\n\n return { userId, user, ...issued }\n },\n\n async verifyAccessToken(accessToken) {\n const { verificationKeys } = await getKeys()\n try {\n const { payload } = await verifyWithKeys(\n accessToken,\n verificationKeys,\n (key) =>\n jwtVerify(accessToken, key, {\n algorithms: [ALGORITHM],\n typ: ACCESS_TOKEN_TYPE,\n issuer: settings.issuer,\n audience: settings.audience,\n })\n )\n const session = toSession(payload as Record<string, unknown>, [])\n if (!isClientAccepted(session.clientId)) {\n throw new AuthError('INVALID_ACCESS_TOKEN')\n }\n const { isSessionRevoked } = settings.dataProvider\n if (\n session.sessionId !== undefined &&\n isSessionRevoked &&\n (await isSessionRevoked.call(\n settings.dataProvider,\n session.sessionId\n ))\n ) {\n throw new AuthError('SESSION_REVOKED')\n }\n return session\n } catch (error) {\n if (error instanceof AuthError) throw error\n throw new AuthError('INVALID_ACCESS_TOKEN', { cause: error })\n }\n },\n\n async revokeSession(refreshToken, { scope = 'current' } = {}) {\n const { dataProvider } = settings\n const stored = await dataProvider.findRefreshToken(\n await sha256(refreshToken)\n )\n if (!stored) return undefined\n const sessionId = stored.sessionId || undefined\n\n if (scope === 'all') {\n // Logging out every device requires a token that still works.\n if (\n stored.revokedAt ||\n stored.isUsed ||\n stored.expiresAt.getTime() <= Date.now()\n ) {\n return undefined\n }\n await dataProvider.revokeUserTokens(stored.userId)\n } else if (sessionId) {\n await dataProvider.revokeSessionTokens(sessionId)\n } else {\n // Token stored before sessions (V1): its session cannot be singled out.\n await dataProvider.revokeUserTokens(stored.userId)\n }\n\n return { userId: stored.userId, sessionId }\n },\n\n async revokeUserSessions(userId) {\n if (typeof userId !== 'string' || userId === '') {\n throw new TypeError(\n 'revokeUserSessions(): userId must be a non-empty string.'\n )\n }\n await settings.dataProvider.revokeUserTokens(userId)\n },\n\n async getJwks() {\n const { verificationKeys } = await getKeys()\n return {\n keys: verificationKeys.map(({ kid, jwk }) => ({\n ...jwk,\n kid,\n alg: ALGORITHM,\n use: 'sig',\n })),\n }\n },\n\n getSessionExpiresAt(session) {\n const authTime = session.claims.auth_time\n if (typeof authTime !== 'number') return undefined\n const end = sessionEnd(authTime, session.clientId)\n return end === undefined ? undefined : new Date(end * 1000)\n },\n\n getUserRoles: (user) => settings.getUserRoles(user),\n\n setAuthCookies(res, tokens) {\n const cookies = settings.cookies\n if (!cookies.enabled) return\n\n // Both cookies live as long as the refresh token: refreshing requires\n // the access token, even expired (its claims and signature).\n const expiresAt = tokens.refreshTokenExpiresAt?.getTime()\n const options = {\n ...cookieOptions(),\n maxAge:\n expiresAt === undefined\n ? settings.session.idleTimeout\n : Math.max(0, Math.ceil((expiresAt - Date.now()) / 1000)),\n }\n\n res.setCookie(cookies.refreshTokenName, tokens.refreshToken, {\n ...options,\n path: cookies.refreshTokenPath,\n sameSite: cookies.refreshTokenSameSite,\n })\n if (cookies.accessTokenInCookie) {\n res.setCookie(cookies.accessTokenName, tokens.accessToken, options)\n }\n },\n\n clearAuthCookies(res) {\n const cookies = settings.cookies\n if (!cookies.enabled) return\n\n // A cookie is only cleared with its path.\n res.clearCookie(cookies.refreshTokenName, {\n ...cookieOptions(),\n path: cookies.refreshTokenPath,\n })\n if (cookies.accessTokenInCookie) {\n res.clearCookie(cookies.accessTokenName, cookieOptions())\n }\n },\n\n hashPassword: (password) =>\n generatePasswordHash(password, { rounds: settings.passwordHashRounds }),\n\n passwordNeedsRehash: (passwordHash) =>\n BCRYPT_HASH.test(passwordHash) &&\n bcrypt.getRounds(passwordHash) !== settings.passwordHashRounds,\n\n // No length check: the password was verified, and a longer V1 password\n // is truncated the same way.\n rehashPassword: (password) =>\n bcrypt.hash(password, settings.passwordHashRounds),\n\n async verifyPassword(password, passwordHash) {\n if (typeof passwordHash === 'string' && BCRYPT_HASH.test(passwordHash)) {\n return bcrypt.compare(password, passwordHash)\n }\n await bcrypt.compare(password, await getDummyHash())\n return false\n },\n }\n\n internals.set(auth, {\n settings,\n init: async (options = {}) => {\n if (options.routesPath !== undefined) bindRoutesPath(options.routesPath)\n if (options.cors !== undefined) cors = options.cors\n // The dummy hash is computed now: otherwise, the first login of an\n // unknown user would take longer.\n await Promise.all([getKeys(), getDummyHash()])\n },\n })\n return auth\n}\n\n/** Longest password bcrypt reads, in UTF-8 bytes. */\nconst MAX_PASSWORD_BYTES = 72\n\n/**\n * Whether a password exceeds the 72 bytes (UTF-8) read by bcrypt. Check\n * it in the sign-up and password change schemas: `hashPassword()` rejects\n * these passwords instead of silently truncating them.\n *\n * @example\n * password: z.string().min(12).refine((p) => !isPasswordTooLong(p), 'Password too long')\n */\nexport const isPasswordTooLong = (password: string): boolean =>\n bcrypt.truncates(password)\n\nexport interface PasswordHashOptions {\n /** bcrypt cost, from 4 to 31. Defaults to 12. */\n rounds?: number\n}\n\n/**\n * Hashes a password with bcrypt, out of an auth service (seeds, scripts).\n * Throws a `RangeError` for passwords longer than 72 bytes (see\n * `isPasswordTooLong()`).\n */\nexport const generatePasswordHash = async (\n password: string,\n options: PasswordHashOptions = {}\n): Promise<string> => {\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('generatePasswordHash(): options must be an object.')\n }\n for (const key of Object.keys(options)) {\n if (key !== 'rounds') {\n throw new TypeError(`generatePasswordHash(): unknown option \"${key}\".`)\n }\n }\n const { rounds = 12 } = options\n if (!Number.isInteger(rounds) || rounds < 4 || rounds > 31) {\n throw new RangeError(\n 'generatePasswordHash(): rounds must be an integer between 4 and 31.'\n )\n }\n if (isPasswordTooLong(password)) {\n throw new RangeError(\n `Password too long: bcrypt reads ${MAX_PASSWORD_BYTES} bytes at most (UTF-8). Check it with isPasswordTooLong().`\n )\n }\n return bcrypt.hash(password, rounds)\n}\n","export const toHex = (bytes: Uint8Array) =>\n Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')\n\n/** Hexadecimal SHA-256 hash of a string. */\nexport const sha256 = async (value: string) =>\n toHex(\n new Uint8Array(\n await crypto.subtle.digest('SHA-256', new TextEncoder().encode(value))\n )\n )\n","import type { DotpressRequest } from '@dotpress/core'\n\n/** Methods that must not change state: never cross-site request forgeries. */\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS'])\n\nconst hostOf = (url: string | undefined) => {\n if (url === undefined) return undefined\n try {\n return new URL(url).host\n } catch {\n return undefined\n }\n}\n\n/**\n * Whether a request may be authenticated by cookies, which the browser\n * attaches to every request, including the ones forged by another site\n * (CSRF). Relies on the headers set by the browser (Fetch Metadata, then\n * `Origin` for older browsers), like the OWASP recommendations and Go's\n * `http.CrossOriginProtection`:\n *\n * - safe methods (`GET`, `HEAD`, `OPTIONS`) are allowed;\n * - a trusted `Origin` is allowed;\n * - with `Sec-Fetch-Site`, only `same-origin` and `none` (typed URL,\n * bookmark) are allowed: `same-site` covers the other subdomains;\n * - otherwise, an `Origin` is allowed when its host is the API host;\n * - a request with neither header does not come from a browser: allowed.\n */\nexport const isCookieAuthenticationAllowed = async (\n req: DotpressRequest,\n isTrustedOrigin: (origin: string) => boolean | Promise<boolean>\n): Promise<boolean> => {\n if (SAFE_METHODS.has(req.method)) return true\n\n const origin = req.header('origin')\n if (origin !== undefined && (await isTrustedOrigin(origin))) return true\n\n const site = req.header('sec-fetch-site')\n if (site !== undefined) return site === 'same-origin' || site === 'none'\n\n if (origin === undefined) return true\n const originHost = hostOf(origin)\n return (\n originHost !== undefined &&\n originHost === (req.header('host') ?? hostOf(req.url))\n )\n}\n","import {\n defineMiddleware,\n forbiddenError,\n unauthorizedError,\n} from '@dotpress/core'\nimport type { AuthSession, AuthUser } from './types.js'\n\n/** Documents the routes using the guards as secured by an access token. */\nconst SECURED = { security: ['bearerAuth'] }\n\n/** Context guaranteed by `requireAuth`, `requireRole()` and `requireClient()`. */\nexport interface AuthenticatedContext {\n user: AuthUser\n auth: AuthSession\n}\n\n/**\n * Requires an authenticated user (`401 UNAUTHORIZED` otherwise). In the\n * handlers of the routes using it, `ctx.user` and `ctx.auth` are defined.\n */\nexport const requireAuth = defineMiddleware<AuthenticatedContext>(\n (ctx) => (ctx.user && ctx.auth ? undefined : unauthorizedError()),\n SECURED\n)\n\n/**\n * Requires an authenticated user with at least one of the roles\n * (`401 UNAUTHORIZED` without user, `403 FORBIDDEN` without role). Roles\n * come from the `getUserRoles` setting of `createAuth()`.\n */\nexport const requireRole = (...roles: [string, ...string[]]) => {\n if (roles.length === 0) {\n throw new TypeError('requireRole(): expected at least one role.')\n }\n\n return defineMiddleware<AuthenticatedContext>((ctx) => {\n if (!ctx.user || !ctx.auth) return unauthorizedError()\n const granted = ctx.auth.roles\n return roles.some((role) => granted.includes(role))\n ? undefined\n : forbiddenError()\n }, SECURED)\n}\n\n/**\n * Requires an access token issued for one of the clients (`401\n * UNAUTHORIZED` without user, `403 FORBIDDEN` for another client). For\n * example, keeps the tokens of a mobile app away from the backoffice\n * routes. It does not prove which application sends the request: combine\n * it with `requireRole()`.\n */\nexport const requireClient = (...clientIds: [string, ...string[]]) => {\n if (clientIds.length === 0) {\n throw new TypeError('requireClient(): expected at least one client.')\n }\n\n return defineMiddleware<AuthenticatedContext>((ctx) => {\n if (!ctx.user || !ctx.auth) return unauthorizedError()\n const { clientId } = ctx.auth\n return clientId !== undefined && clientIds.includes(clientId)\n ? undefined\n : forbiddenError()\n }, SECURED)\n}\n","import {\n definePlugin,\n forbiddenError,\n internalError,\n tooManyRequestsError,\n unauthorizedError,\n type DotpressRequest,\n type Middleware,\n type RouteGroup,\n} from '@dotpress/core'\nimport { AuthError, internalsOf, type Auth } from './auth.js'\nimport { sha256 } from './crypto.js'\nimport { requireAuth } from './middlewares.js'\nimport type {\n IssuedTokens,\n LoginFailedEvent,\n ResolvedAuthOptions,\n ResolvedCookieOptions,\n ResolvedLoginThrottleOptions,\n TokenPair,\n} from './types.js'\nimport './context.js'\n\nexport interface AuthPluginOptions {\n /** Prefix of the authentication routes. Defaults to `/auth`. */\n prefix?: string\n /** Registers the authentication routes. Defaults to `true`. */\n routes?: boolean\n /**\n * Publishes the public keys at `GET /.well-known/jwks.json`, for other\n * services verifying the tokens. Defaults to `false`.\n */\n jwks?: boolean\n}\n\nconst PLUGIN_OPTIONS: readonly string[] = [\n 'prefix',\n 'routes',\n 'jwks',\n] satisfies (keyof AuthPluginOptions)[]\n\n/** Security schemes of the plugin, for documentation generators. */\nexport const AUTH_SECURITY_SCHEMES = {\n bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },\n} as const\n\nconst LOCAL_IPS = new Set([\n '127.0.0.1',\n 'localhost',\n '::1',\n '::ffff',\n '::ffff:127.0.0.1',\n])\n\n/** Client IP address for login events (empty for local requests). */\nconst clientIp = (req: DotpressRequest) =>\n req.ip && !LOCAL_IPS.has(req.ip) ? req.ip : ''\n\nconst BEARER = /^bearer +(\\S+) *$/i\n\nconst bodyField = (body: unknown, key: string): string | undefined => {\n if (typeof body !== 'object' || body === null) return undefined\n const value = (body as Record<string, unknown>)[key]\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\n/**\n * Access token of a request, and where it was found. The request body is\n * never read: an app route may receive an `accessToken` field of its own\n * (a third-party token, for example), and RFC 6750 discourages it.\n */\nconst findAccessToken = (\n req: DotpressRequest,\n cookies: ResolvedCookieOptions\n): { token: string; source: 'header' | 'cookie' } | undefined => {\n // Authentication schemes are case-insensitive (RFC 7235).\n const bearer = BEARER.exec(req.header('authorization') ?? '')\n if (bearer) return { token: bearer[1]!, source: 'header' }\n\n if (cookies.enabled && cookies.accessTokenInCookie) {\n const cookie = req.cookies[cookies.accessTokenName]\n if (cookie) return { token: cookie, source: 'cookie' }\n }\n\n return undefined\n}\n\n/**\n * Counters of the login throttle for an attempt. The username is\n * normalized (letter case, spaces), and the keys are hashed: bounded size,\n * and no personal data in the store.\n */\nconst loginThrottleKeys = async (\n throttle: ResolvedLoginThrottleOptions,\n username: string,\n ipAddress: string\n) => {\n const normalized = username.trim().toLowerCase()\n const { usernameAndIp, username: perUsername } = throttle.limits\n return {\n usernameAndIp: usernameAndIp && {\n key: `dotpress:login:username-ip:${await sha256(`${normalized}\\n${ipAddress}`)}`,\n limit: usernameAndIp,\n },\n username: perUsername && {\n key: `dotpress:login:username:${await sha256(normalized)}`,\n limit: perUsername,\n },\n }\n}\n\n/** Response of the cookie routes to a cross-origin request. */\nconst crossOriginError = () => forbiddenError('CROSS_ORIGIN_REQUEST')\n\n/**\n * Token pair sent to the refresh route: refresh token cookie first, then\n * body fields.\n */\nconst extractTokens = (\n req: DotpressRequest,\n body: unknown,\n cookies: ResolvedCookieOptions\n): (TokenPair & { mode: 'cookie' | 'body' }) | undefined => {\n const cookieRefreshToken = req.cookies[cookies.refreshTokenName]\n\n if (cookieRefreshToken) {\n const accessToken = cookies.accessTokenInCookie\n ? req.cookies[cookies.accessTokenName]\n : bodyField(body, 'accessToken')\n return accessToken\n ? { accessToken, refreshToken: cookieRefreshToken, mode: 'cookie' }\n : undefined\n }\n\n const accessToken = bodyField(body, 'accessToken')\n const refreshToken = bodyField(body, 'refreshToken')\n return accessToken && refreshToken\n ? { accessToken, refreshToken, mode: 'body' }\n : undefined\n}\n\nconst refreshErrorResponse = (error: unknown, fallback: string) => {\n if (error instanceof AuthError) {\n if (error.code === 'EXPIRED_REFRESH_TOKEN') {\n return unauthorizedError('EXPIRED_REFRESH_TOKEN')\n }\n return unauthorizedError('INVALID_REFRESH_TOKEN')\n }\n return internalError(fallback)\n}\n\nconst registerRoutes = (\n group: RouteGroup,\n auth: Auth,\n settings: ResolvedAuthOptions\n) => {\n const { dataProvider, cookies } = settings\n\n /**\n * Expiration fields of the responses: access token, and session when an\n * absolute timeout applies.\n */\n const expiration = (accessTokenExpiresAt: Date, sessionExpiresAt?: Date) => ({\n expiresIn: Math.max(\n 0,\n Math.ceil((accessTokenExpiresAt.getTime() - Date.now()) / 1000)\n ),\n expiresAt: accessTokenExpiresAt.toISOString(),\n ...(sessionExpiresAt && {\n sessionExpiresAt: sessionExpiresAt.toISOString(),\n }),\n })\n const issuedExpiration = (tokens: IssuedTokens) =>\n expiration(tokens.accessTokenExpiresAt, tokens.sessionExpiresAt)\n\n /**\n * Tokens returned in the response body. Tokens set as cookies are never\n * returned, so that the JavaScript of the page cannot read them: the\n * refresh token always is a cookie in cookie mode, the access token only\n * with `accessTokenInCookie`.\n */\n const bodyTokens = (\n tokens: TokenPair,\n inCookies: boolean\n ): Partial<TokenPair> => {\n if (!inCookies) {\n return {\n accessToken: tokens.accessToken,\n refreshToken: tokens.refreshToken,\n }\n }\n return cookies.accessTokenInCookie\n ? {}\n : { accessToken: tokens.accessToken }\n }\n\n const { clients } = settings\n\n const loginFailed = async (event: LoginFailedEvent) => {\n await settings.onLoginFailed?.(event)\n return unauthorizedError('INVALID_LOGIN')\n }\n\n const meta = { tags: ['auth'] }\n /** Cookie routes refuse requests from other origins. */\n const crossOrigin =\n 'Cross-origin request in cookie mode (`CROSS_ORIGIN_REQUEST`, see cookies.trustedOrigins).'\n\n group.defineRoute({\n method: 'post',\n path: '/login',\n meta: {\n ...meta,\n summary: 'Log in with a username and a password',\n responses: {\n 401: 'Invalid credentials (`INVALID_LOGIN`).',\n 429: 'Too many failed attempts (`TOO_MANY_LOGIN_ATTEMPTS`), with a `Retry-After` header.',\n },\n },\n schema: (z) => ({\n body: z.object({\n username: z.string(),\n password: z.string(),\n // One of the allowed clients when the clients setting is defined,\n // otherwise any string, only reported in the login events.\n clientId: !clients\n ? z.string().optional()\n : clients.required\n ? z.enum(clients.allowed as [string, ...string[]])\n : z.enum(clients.allowed as [string, ...string[]]).optional(),\n mode: z.enum(['body', 'cookie']).optional(),\n }),\n response: z.object({\n userId: z.string(),\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n expiresIn: z.number(),\n expiresAt: z.string(),\n sessionExpiresAt: z.string().optional(),\n }),\n }),\n handler: async ({ req, res, logger }) => {\n const { username, password, clientId, mode } = req.body\n const event = {\n username,\n clientId: clientId ?? '',\n ipAddress: clientIp(req),\n }\n\n // Brute force protection, before any password check: the attempt\n // is counted first (atomic increment), so that parallel attempts\n // cannot pass before the counter is updated.\n const throttle = settings.loginThrottle\n const counters =\n throttle &&\n (await loginThrottleKeys(throttle, username, event.ipAddress))\n if (throttle && counters) {\n let retryAfter = 0\n for (const counter of [counters.usernameAndIp, counters.username]) {\n if (!counter) continue\n const state = await throttle.store.increment(\n counter.key,\n counter.limit.window\n )\n if (state.count > counter.limit.max) {\n retryAfter = Math.max(\n retryAfter,\n Math.ceil((state.resetAt.getTime() - Date.now()) / 1000),\n 1\n )\n }\n }\n if (retryAfter > 0) {\n await settings.onLoginFailed?.({\n ...event,\n errorType: 'too_many_attempts',\n })\n res.setHeader('retry-after', String(retryAfter))\n return tooManyRequestsError('TOO_MANY_LOGIN_ATTEMPTS')\n }\n }\n\n const rejected = await settings.beforeLogin?.({ ...event, req })\n if (rejected) return rejected\n\n const user = await dataProvider.findUserIdentifiers(username)\n // The password is checked even for unknown users, against a dummy\n // hash: both failures take the same time.\n const passwordValid = await auth.verifyPassword(\n password,\n user?.passwordHash\n )\n if (!user) {\n return loginFailed({ ...event, errorType: 'invalid_username' })\n }\n\n if (!passwordValid) {\n return loginFailed({ ...event, errorType: 'invalid_password' })\n }\n\n if (\n clientId !== undefined &&\n clients?.isUserRoleAllowed &&\n !clients.isUserRoleAllowed({\n userId: user.userId,\n role: user.role,\n clientId,\n })\n ) {\n return loginFailed({ ...event, errorType: 'invalid_role' })\n }\n\n const tokens = await auth.generateTokensForUser({\n userId: user.userId,\n username,\n // Without clients setting, the clientId is only reported in the\n // login events: an unchecked value is not written in the token.\n clientId: clients ? clientId : undefined,\n })\n\n // A successful login clears the counter of this client only: a\n // distributed attack on the account keeps being counted.\n if (throttle && counters?.usernameAndIp) {\n await throttle.store.reset(counters.usernameAndIp.key)\n }\n\n await settings.onLoginSuccess?.({ ...event, userId: user.userId })\n\n // Upgrades a hash with another cost (passwordHashRounds raised).\n if (\n dataProvider.updatePasswordHash &&\n user.passwordHash &&\n auth.passwordNeedsRehash(user.passwordHash)\n ) {\n try {\n await dataProvider.updatePasswordHash(\n user.userId,\n await auth.rehashPassword(password)\n )\n } catch (error) {\n logger.warn(\n error instanceof Error ? error : { error },\n 'Cannot update the password hash'\n )\n }\n }\n\n const inCookies = mode === 'cookie' && cookies.enabled\n if (inCookies) auth.setAuthCookies(res, tokens)\n\n return {\n userId: user.userId,\n ...bodyTokens(tokens, inCookies),\n ...issuedExpiration(tokens),\n }\n },\n })\n\n group.defineRoute({\n method: 'post',\n path: '/refresh',\n meta: {\n ...meta,\n summary: 'Rotate a token pair',\n responses: {\n 401: 'Missing, invalid, expired or reused tokens.',\n 403: crossOrigin,\n },\n },\n schema: (z) => ({\n body: z\n .object({\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n })\n .optional(),\n response: z.object({\n userId: z.string(),\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n expiresIn: z.number(),\n expiresAt: z.string(),\n sessionExpiresAt: z.string().optional(),\n }),\n }),\n handler: async ({ req, res }) => {\n const extracted = extractTokens(req, req.body, cookies)\n if (!extracted) return unauthorizedError('NO_TOKENS_PROVIDED')\n if (\n extracted.mode === 'cookie' &&\n !(await auth.isCookieAuthenticationAllowed(req))\n ) {\n return crossOriginError()\n }\n\n try {\n const { userId, ...tokens } = await auth.refreshTokens(extracted)\n const inCookies = extracted.mode === 'cookie' && cookies.enabled\n if (inCookies) auth.setAuthCookies(res, tokens)\n return {\n userId,\n ...bodyTokens(tokens, inCookies),\n ...issuedExpiration(tokens),\n }\n } catch (error) {\n return refreshErrorResponse(error, 'Can not refresh tokens.')\n }\n },\n })\n\n group.defineRoute({\n path: '/me',\n meta: { ...meta, summary: 'Authenticated user' },\n middlewares: [requireAuth],\n handler: ({ user }) => user,\n })\n\n group.defineRoute({\n method: 'post',\n path: '/restore',\n meta: {\n ...meta,\n summary: 'Restore a session from the auth cookies',\n responses: {\n 401: 'Missing or invalid auth cookies.',\n 403: crossOrigin,\n },\n },\n schema: (z) => ({\n response: z.object({\n userId: z.string(),\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n expiresIn: z.number(),\n expiresAt: z.string(),\n sessionExpiresAt: z.string().optional(),\n user: z.unknown(),\n }),\n }),\n handler: async ({ req, res }) => {\n if (!cookies.enabled) return unauthorizedError('COOKIES_NOT_ENABLED')\n if (!cookies.accessTokenInCookie) {\n return unauthorizedError('ACCESS_TOKEN_NOT_IN_COOKIE')\n }\n\n const refreshToken = req.cookies[cookies.refreshTokenName]\n const accessToken = req.cookies[cookies.accessTokenName]\n if (!refreshToken) return unauthorizedError('NO_REFRESH_TOKEN')\n if (!accessToken) return unauthorizedError('NO_ACCESS_TOKEN')\n if (!(await auth.isCookieAuthenticationAllowed(req))) {\n return crossOriginError()\n }\n\n // A valid access token restores the session as is.\n try {\n const session = await auth.verifyAccessToken(accessToken)\n const { userId } = session\n const user = await dataProvider.findUserById(userId)\n // The tokens stay in their cookies.\n if (user) {\n return {\n userId,\n ...expiration(\n new Date(Number(session.claims.exp) * 1000),\n auth.getSessionExpiresAt(session)\n ),\n user,\n }\n }\n } catch {\n // Expired or invalid: try the refresh token.\n }\n\n try {\n const { userId, user, ...tokens } = await auth.refreshTokens({\n accessToken,\n refreshToken,\n })\n auth.setAuthCookies(res, tokens)\n\n return { userId, ...issuedExpiration(tokens), user }\n } catch (error) {\n return refreshErrorResponse(error, 'Cannot restore session.')\n }\n },\n })\n\n group.defineRoute({\n method: 'post',\n path: '/logout',\n meta: {\n ...meta,\n summary: 'Revoke the current session, or every session of the user',\n responses: {\n 401: '`scope: \"all\"` without a valid refresh token or access token.',\n 403: crossOrigin,\n },\n },\n schema: (z) => ({\n body: z\n .object({\n refreshToken: z.string().optional(),\n scope: z.enum(['current', 'all']).optional(),\n })\n .optional(),\n }),\n // The session is proven by the refresh token (cookie, then body), or\n // by the access token: an expired access token does not prevent a\n // logout.\n handler: async ({ req, res, auth: session }) => {\n const scope = req.body?.scope ?? 'current'\n const cookieRefreshToken = cookies.enabled\n ? req.cookies[cookies.refreshTokenName]\n : undefined\n if (\n cookieRefreshToken &&\n !(await auth.isCookieAuthenticationAllowed(req))\n ) {\n return crossOriginError()\n }\n\n const refreshToken = cookieRefreshToken || req.body?.refreshToken\n const revoked = refreshToken\n ? await auth.revokeSession(refreshToken, { scope })\n : undefined\n\n if (!revoked && session) {\n if (scope === 'all') {\n await dataProvider.revokeUserTokens(session.userId)\n } else if (session.sessionId) {\n await dataProvider.revokeSessionTokens(session.sessionId)\n }\n } else if (!revoked && scope === 'all') {\n // Logging out every device requires proving the identity.\n return unauthorizedError()\n }\n\n // Idempotent (RFC 7009): an unknown or missing token still logs the\n // client out.\n auth.clearAuthCookies(res)\n return { success: true }\n },\n })\n}\n\n/** Group prefix as the router stores it: `/auth/` gives `/auth`. */\nconst normalizePrefix = (prefix: string) =>\n `/${prefix.split('/').filter(Boolean).join('/')}`\n\n/**\n * Plugs an authentication service into an app:\n *\n * - reads the access token of every request, and sets `ctx.user` and\n * `ctx.auth` when it is valid;\n * - registers the `POST /auth/login`, `POST /auth/refresh`,\n * `GET /auth/me`, `POST /auth/restore` and `POST /auth/logout` routes.\n */\nexport const authPlugin = (auth: Auth, options: AuthPluginOptions = {}) => {\n const internal = internalsOf(auth)\n if (!internal) {\n throw new TypeError(\n 'authPlugin(): expected an auth service created with createAuth().'\n )\n }\n if (typeof options !== 'object' || options === null) {\n throw new TypeError('authPlugin(): options must be an object.')\n }\n for (const key of Object.keys(options)) {\n if (!PLUGIN_OPTIONS.includes(key)) {\n throw new TypeError(`authPlugin(): unknown option \"${key}\".`)\n }\n }\n const { settings } = internal\n const prefix = options.prefix ?? '/auth'\n\n return definePlugin({\n name: 'auth',\n auth,\n securitySchemes: AUTH_SECURITY_SCHEMES,\n async setup(api) {\n // Fails at startup on invalid keys. The refresh token cookie is only\n // sent to the auth routes: its path is their browser path.\n await internal.init({\n ...(options.routes === false\n ? {}\n : { routesPath: api.publicPath(normalizePrefix(prefix)) }),\n ...(api.cors ? { cors: api.cors } : {}),\n })\n\n api.decorateContext('user', () => undefined)\n api.decorateContext('auth', () => undefined)\n\n // Requests whose bearer token was rejected (expired, revoked…).\n const rejectedTokens = new WeakSet<object>()\n\n const authenticate: Middleware = async (ctx) => {\n const found = findAccessToken(ctx.req, settings.cookies)\n if (!found) return\n const { token } = found\n\n // Cross-site request forgery: the browser attached the cookie to a\n // request from another origin. The request stays anonymous.\n if (\n found.source === 'cookie' &&\n !(await auth.isCookieAuthenticationAllowed(ctx.req))\n ) {\n ctx.logger.warn(\n { origin: ctx.req.header('origin') },\n 'Access token cookie ignored: cross-origin request (see cookies.trustedOrigins)'\n )\n return\n }\n\n try {\n const session = await auth.verifyAccessToken(token)\n const user = await settings.dataProvider.findUserById(session.userId)\n if (!user) {\n if (found.source === 'header') rejectedTokens.add(ctx)\n return\n }\n\n ctx.user = user\n ctx.auth = { ...session, roles: auth.getUserRoles(user) }\n } catch (error) {\n if (found.source === 'header') rejectedTokens.add(ctx)\n ctx.logger.warn(\n error instanceof Error ? error : { error },\n 'Invalid access token'\n )\n }\n }\n\n api.addGlobalMiddleware(authenticate)\n\n // Challenge of the 401 responses (RFC 7235, RFC 6750), unless one was\n // set by the app or another plugin.\n api.hooks.onResponse(({ response, ctx }) => {\n if (response.status !== 401) return\n const has = Object.keys(response.headers).some(\n (name) => name.toLowerCase() === 'www-authenticate'\n )\n if (has) return\n return {\n ...response,\n headers: {\n ...response.headers,\n 'www-authenticate':\n ctx && rejectedTokens.has(ctx)\n ? 'Bearer error=\"invalid_token\"'\n : 'Bearer',\n },\n }\n })\n\n if (options.jwks) {\n api.defineRoute({\n path: '/.well-known/jwks.json',\n meta: { tags: ['auth'], summary: 'Public keys of the access tokens' },\n handler: async ({ res }) => {\n // Short cache: verifiers pick up a rotation within minutes.\n res.setHeader('cache-control', 'public, max-age=300')\n return auth.getJwks()\n },\n })\n }\n\n if (options.routes !== false) {\n registerRoutes(api.createGroup(prefix), auth, settings)\n }\n },\n })\n}\n"],"mappings":"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","names":["forbiddenError","unauthorizedError","forbiddenError","accessToken","unauthorizedError"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotpress/auth",
3
- "version": "2.0.0-alpha.4",
3
+ "version": "2.0.0-alpha.5",
4
4
  "description": "JWT authentication plugin for Dotpress",
5
5
  "author": "Renaud Guyon",
6
6
  "license": "MIT",
@@ -29,7 +29,7 @@
29
29
  "node": ">=22.12"
30
30
  },
31
31
  "peerDependencies": {
32
- "@dotpress/core": "^2.0.0-alpha.4",
32
+ "@dotpress/core": "^2.0.0-alpha.5",
33
33
  "zod": "^4.6.0"
34
34
  },
35
35
  "dependencies": {
@@ -37,8 +37,8 @@
37
37
  "jose": "^6.2.15"
38
38
  },
39
39
  "devDependencies": {
40
- "@dotpress/core": "2.0.0-alpha.4",
41
- "@dotpress/testing": "2.0.0-alpha.4",
40
+ "@dotpress/core": "2.0.0-alpha.5",
41
+ "@dotpress/testing": "2.0.0-alpha.5",
42
42
  "@types/jsonwebtoken": "^9.0.10",
43
43
  "jsonwebtoken": "^9.0.3",
44
44
  "zod": "^4.6.7"