@warlock.js/auth 5.22.1 → 5.23.1
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/CHANGELOG.md +358 -342
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/types.d.mts +1 -10
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/index.d.mts +2 -2
- package/esm/services/auth.service.d.mts +7 -7
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/auth.service.mjs.map +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { AppleProviderConfig, AuthProvider, DiscordProviderConfig, FacebookProviderConfig, GitHubProviderConfig, GoogleProviderConfig, LinkedInProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig, XProviderConfig } from "./providers.mjs";
|
|
2
|
-
import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig,
|
|
2
|
+
import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair } from "./types.mjs";
|
|
3
3
|
import { Authenticable } from "./auth-contract.mjs";
|
|
4
4
|
import { RequestUser } from "./request-locals.mjs";
|
|
@@ -428,20 +428,11 @@ type SetAuthCookieOptions = {
|
|
|
428
428
|
*/
|
|
429
429
|
maxAge?: number;
|
|
430
430
|
};
|
|
431
|
-
/**
|
|
432
|
-
* The cookie surface the auth cookie helpers write through. Core's `Response`
|
|
433
|
-
* satisfies it, and so does a page action's buffered `ActionResponse` from
|
|
434
|
-
* `@warlock.js/web`, so the same helpers work in controllers and page actions.
|
|
435
|
-
*/
|
|
436
|
-
type CookieWriter = {
|
|
437
|
-
cookie(name: string, value: string, options?: Record<string, unknown>): unknown;
|
|
438
|
-
clearCookie(name: string, options?: Record<string, unknown>): unknown;
|
|
439
|
-
};
|
|
440
431
|
/** Per-call overrides for {@link AuthService.clearAuthCookie}. */
|
|
441
432
|
type ClearAuthCookieOptions = {
|
|
442
433
|
/** Cookie name; defaults to `auth.cookie.name` (package default `"access_token"`). */name?: string; /** Cookie `Path`; defaults to `auth.cookie.path` (package default `"/"`). Must match the `Path` the cookie was set with. */
|
|
443
434
|
path?: string;
|
|
444
435
|
};
|
|
445
436
|
//#endregion
|
|
446
|
-
export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig,
|
|
437
|
+
export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair };
|
|
447
438
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"sourcesContent":["import { type ChildModel } from \"@warlock.js/cascade\";\r\nimport { type Algorithm } from \"fast-jwt\";\r\nimport type { AccessToken } from \"../models/access-token\";\r\nimport type { Auth } from \"../models/auth.model\";\r\nimport type { OneTimeToken } from \"../models/one-time-token\";\r\nimport type { OtpConfig, PasskeysConfig, ProvidersConfig } from \"./providers\";\r\nimport type { RefreshToken } from \"../models/refresh-token\";\r\n\r\n/**\r\n * Symbol to indicate no expiration for tokens\r\n * Use this when you explicitly want tokens to never expire\r\n *\r\n * @example\r\n * ```typescript\r\n * // src/config/auth.ts\r\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\r\n *\r\n * const authConfigurations: AuthConfigurations = {\r\n * accessToken: {\r\n * secret: env(\"JWT_SECRET\"),\r\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\r\n * },\r\n * };\r\n *\r\n * export default authConfigurations;\r\n * ```\r\n */\r\nexport const NO_EXPIRATION = \"100y\";\r\n\r\n/**\r\n * Behavior when logout is called without a refresh token\r\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\r\n * - \"error\": Return an error requiring the refresh token\r\n */\r\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\r\n\r\n/** The single request credential source an auth middleware instance accepts. */\r\nexport type TokenFrom = \"header\" | `cookie:${string}`;\r\n\r\n/** One explicitly named credential location accepted by auth middleware. */\r\nexport interface AuthCredentialDescriptor {\r\n source: \"header\" | \"cookie\";\r\n key?: string;\r\n}\r\n\r\n/** Cookie credential and bounded duplicate window for automatic renewal. */\r\nexport interface AuthRefreshCredentialDescriptor extends AuthCredentialDescriptor {\r\n /** Milliseconds; the service clamps this to the 0–10 second range (default 5 seconds). */\r\n overlapMs?: number;\r\n}\r\n\r\n/** Per-route authentication behavior for the object middleware overload. */\r\nexport interface AuthMiddlewareOptions extends Partial<AuthCredentialDescriptor> {\r\n optional?: boolean;\r\n /**\r\n * Accept both a header and a cookie credential (at most one of each).\r\n * Precedence is fixed and independent of array order: a present\r\n * `Authorization` header wins, an invalid header rejects and never falls back\r\n * to the cookie, and renewal plus the CSRF Origin check apply only when the\r\n * cookie is the credential actually used. Cannot be combined with `source`.\r\n */\r\n sources?: AuthCredentialDescriptor[];\r\n /** Opt-in automatic renewal credential. Cookie sources only. */\r\n refresh?: AuthRefreshCredentialDescriptor;\r\n redirect?: { to: string; returnUrlParam?: string };\r\n}\r\n\r\n/** Application policy that decides whether a resolved user may authenticate. */\r\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\r\n\r\n/** Password credentials plus the application-defined identity fields. */\r\nexport type AuthCredentials = Record<string, unknown> & {\r\n password: string;\r\n};\r\n\r\n/**\r\n * Access-token configuration.\r\n *\r\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\r\n * backward-compatible shim); one of the two is required at runtime.\r\n */\r\nexport type AccessTokenConfig = {\r\n /**\r\n * Override the persisted access-token model — extend {@link AccessToken} to\r\n * add columns (e.g. a tenant key) or rename them, then register the subclass\r\n * here. Defaults to the package's `AccessToken`.\r\n */\r\n model?: typeof AccessToken;\r\n /**\r\n * Secret used to sign access tokens.\r\n */\r\n secret?: string;\r\n /**\r\n * JWT algorithm.\r\n * @default \"HS256\"\r\n */\r\n algorithm?: Algorithm;\r\n /**\r\n * Access-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\r\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\r\n * first token issue: each of them would otherwise sign a token with no `exp`\r\n * claim at all.\r\n * @example \"1h\" or NO_EXPIRATION\r\n * @default \"1h\"\r\n */\r\n expiresIn?: string;\r\n};\r\n\r\n/**\r\n * Refresh-token configuration.\r\n */\r\nexport type RefreshTokenConfig = {\r\n /**\r\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\r\n * Defaults to the package's `RefreshToken`.\r\n */\r\n model?: typeof RefreshToken;\r\n /**\r\n * Enable refresh tokens.\r\n * @default true\r\n */\r\n enabled?: boolean;\r\n /**\r\n * Separate secret for refresh tokens (recommended for security). Falls back\r\n * to the access-token secret when omitted.\r\n */\r\n secret?: string;\r\n /**\r\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds; an unusable value throws naming this key on issue\r\n * (see {@link AccessTokenConfig.expiresIn}).\r\n * @example \"7d\" or \"1w\"\r\n * @default \"7d\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * Rotate the refresh token on each use (old token revoked, replay detected).\r\n * @default true\r\n */\r\n rotation?: boolean;\r\n /**\r\n * Maximum active refresh tokens per user; the oldest are revoked past it.\r\n * @default 5\r\n */\r\n maxPerUser?: number;\r\n /**\r\n * Behavior when logout is called without a refresh token.\r\n * @default \"revoke-all\"\r\n */\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n};\r\n\r\n/**\r\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\r\n * deprecation warning.\r\n *\r\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\r\n */\r\nexport type LegacyJwtConfig = {\r\n secret?: string;\r\n algorithm?: Algorithm;\r\n expiresIn?: string;\r\n refresh?: {\r\n secret?: string;\r\n enabled?: boolean;\r\n expiresIn?: string;\r\n rotation?: boolean;\r\n maxPerUser?: number;\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n };\r\n};\r\n\r\n/**\r\n * Page-route auth-failure behavior. When a guarded PAGE route (a React-SSR page,\r\n * `route.isPage`) rejects a logged-out browser, this decides whether it redirects\r\n * to a login page instead of returning the API-style JSON 401.\r\n *\r\n * Read at runtime through `authConfig.pageAuth` (`auth.pageAuth.*`). API routes\r\n * always keep the JSON 401 contract regardless of this setting.\r\n */\r\nexport type PageAuthConfig = {\r\n /**\r\n * Login path a guarded page route redirects a logged-out browser to (e.g.\r\n * `\"/login\"`). Unset ⇒ page-route auth failures keep the JSON 401, so the\r\n * behavior is opt-in and backward-compatible.\r\n */\r\n loginPath?: string;\r\n /**\r\n * Query parameter the redirect carries the originally-requested path in, so\r\n * the login flow can send the user back where they were headed.\r\n * @default \"returnUrl\"\r\n */\r\n returnUrlParam?: string;\r\n};\r\n\r\n/**\r\n * Data every email-verification / password-reset notification receives.\r\n */\r\nexport type OneTimeTokenNotificationData = {\r\n /** The raw token — the only place it exists; only its SHA-256 hash is stored. */\r\n token: string;\r\n /** When the token stops working. */\r\n expiresAt: Date;\r\n /** The link built by `auth.verification.url` / `auth.passwordReset.url`, when configured. */\r\n url?: string;\r\n};\r\n\r\n/**\r\n * Anything auth can deliver a token through — a `defineNotification(...)`\r\n * result satisfies it, as does any object with a compatible `send`.\r\n */\r\nexport type AuthNotification = {\r\n send: (to: Auth, data: OneTimeTokenNotificationData) => unknown;\r\n};\r\n\r\n/** Builds the link a token notification points at. */\r\nexport type OneTimeTokenUrlBuilder = (token: string, user: Auth) => string;\r\n\r\n/** Persists a new plaintext password for a user during a password reset. */\r\nexport type PasswordSetter = (user: Auth, plainPassword: string) => void | Promise<void>;\r\n\r\n/**\r\n * Email-verification configuration (`auth.verification`).\r\n */\r\nexport type EmailVerificationConfig = {\r\n /**\r\n * Token lifetime — a positive `ms` duration string.\r\n * @default \"24h\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * User attribute stamped with the verification `Date`. Declare it in the\r\n * user schema (e.g. `emailVerifiedAt: v.date().optional()`).\r\n * @default \"emailVerifiedAt\"\r\n */\r\n field?: string;\r\n /** Replace the default verification email. */\r\n notification?: AuthNotification;\r\n /** Build the verification link passed to the notification as `url`. */\r\n url?: OneTimeTokenUrlBuilder;\r\n};\r\n\r\n/**\r\n * Password-reset configuration (`auth.passwordReset`).\r\n */\r\nexport type PasswordResetConfig = {\r\n /**\r\n * Token lifetime — a positive `ms` duration string.\r\n * @default \"60m\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * User attribute `requestPasswordReset` looks the account up by.\r\n * @default \"email\"\r\n */\r\n identifierField?: string;\r\n /** Replace the default reset email. */\r\n notification?: AuthNotification;\r\n /** Build the reset link passed to the notification as `url`. */\r\n url?: OneTimeTokenUrlBuilder;\r\n /**\r\n * Persist the new password yourself. By default auth saves a hash made by\r\n * the same `hashPassword` login verifies against, and falls back to the\r\n * plaintext when the model hashes on save (`useHashedPassword()`), checking\r\n * the stored value with `verifyPassword` either way.\r\n */\r\n setPassword?: PasswordSetter;\r\n};\r\n\r\n/**\r\n * One-time token storage configuration (`auth.oneTimeToken`).\r\n */\r\nexport type OneTimeTokenConfig = {\r\n /** Override the persisted model — extend {@link OneTimeToken}. */\r\n model?: typeof OneTimeToken;\r\n};\r\n\r\nexport type AuthConfigurations = {\r\n /**\r\n * Define all user types — maps a user-type slug to its `Auth` model class so\r\n * tokens and the middleware can resolve the right model.\r\n */\r\n userType: {\r\n [userType: string]: ChildModel<Auth>;\r\n };\r\n /** Default user type used by `authMiddleware()` and options-only overloads. */\r\n defaultUserType?: string;\r\n /**\r\n * Decide whether a resolved user may authenticate or receive new tokens.\r\n * @default () => true\r\n */\r\n canAuthenticate?: CanAuthenticate;\r\n /**\r\n * Access-token configuration (secret, expiry, algorithm, model override).\r\n */\r\n accessToken?: AccessTokenConfig;\r\n /**\r\n * Refresh-token configuration (rotation, lifetime, cap, model override).\r\n */\r\n refreshToken?: RefreshTokenConfig;\r\n /**\r\n * Page-route auth-failure behavior — set `loginPath` to redirect a logged-out\r\n * browser on a guarded PAGE route to a login page instead of the JSON 401.\r\n * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.\r\n */\r\n pageAuth?: PageAuthConfig;\r\n /**\r\n * Cookie auth session config — read by `authService.setAuthCookie` /\r\n * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own\r\n * defaults (`name: \"access_token\"`, `path: \"/\"`).\r\n */\r\n cookie?: CookieAuthConfig;\r\n /**\r\n * CSRF Origin-check config for cookie-authenticated unsafe-method requests.\r\n */\r\n csrf?: CsrfConfig;\r\n /**\r\n * Email verification — token lifetime, verified field, notification override.\r\n */\r\n verification?: EmailVerificationConfig;\r\n /**\r\n * Password reset — token lifetime, lookup field, notification and password-writer overrides.\r\n */\r\n passwordReset?: PasswordResetConfig;\r\n /**\r\n * One-time token (verification / reset) storage — model override.\r\n */\r\n oneTimeToken?: OneTimeTokenConfig;\r\n /**\r\n * Redirect login providers (`google`, `custom`) and account-linking policy.\r\n */\r\n providers?: ProvidersConfig;\r\n /**\r\n * Passkey (WebAuthn) relying-party settings. Requires `@simplewebauthn/server`.\r\n */\r\n passkeys?: PasskeysConfig;\r\n /**\r\n * Phone one-time-code login — channel, lifetime, attempts, delivery.\r\n */\r\n otp?: OtpConfig;\r\n /**\r\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\r\n */\r\n jwt?: LegacyJwtConfig;\r\n};\r\n\r\nexport type AccessTokenOutput = {\r\n /**\r\n * JWT Token\r\n */\r\n token: string;\r\n /**\r\n * Exprie time in ISO format UTC time\r\n */\r\n expiresAt: string;\r\n};\r\n\r\n/**\r\n * Token pair returned after login or token refresh\r\n */\r\nexport type TokenPair = {\r\n /**\r\n * JWT access token (short-lived)\r\n */\r\n accessToken: AccessTokenOutput;\r\n /**\r\n * JWT refresh token (long-lived)\r\n */\r\n refreshToken?: AccessTokenOutput;\r\n};\r\n\r\n/**\r\n * Device information for session tracking\r\n */\r\nexport type DeviceInfo = {\r\n /**\r\n * User agent string from request\r\n */\r\n userAgent?: string;\r\n /**\r\n * Client IP address\r\n */\r\n ip?: string;\r\n /**\r\n * Optional device identifier\r\n */\r\n deviceId?: string;\r\n /**\r\n * Token family ID (for rotation tracking)\r\n * @internal\r\n */\r\n familyId?: string;\r\n /**\r\n * Access token payload\r\n */\r\n payload?: Record<string, unknown>;\r\n};\r\n\r\nexport type LoginResult<UserType extends Auth> = {\r\n user: UserType;\r\n tokens: TokenPair;\r\n};\r\n\r\n/**\r\n * Configuration for the cookie an app opts into via `authService.setAuthCookie`\r\n * / `clearAuthCookie` and reads back with `authMiddleware([], \"cookie:<name>\")`.\r\n * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT\r\n * configurable here — they come from core's `secureCookieDefaults()`, the\r\n * same floor every `response.cookie()` call gets, so a cookie auth session can\r\n * never be weakened to a laxer policy than the framework default.\r\n */\r\nexport type CookieAuthConfig = {\r\n /**\r\n * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a\r\n * `cookie:<name>` token source must match to read it back.\r\n * @default \"access_token\"\r\n */\r\n name?: string;\r\n /**\r\n * Cookie name the session helpers write and clear for the refresh token.\r\n * @default \"refresh_token\"\r\n */\r\n refreshName?: string;\r\n /**\r\n * Cookie `Path` attribute.\r\n * @default \"/\"\r\n */\r\n path?: string;\r\n};\r\n\r\n/**\r\n * CSRF Origin-check configuration. Read via\r\n * `authConfig.csrf.allowedOrigins()`.\r\n */\r\nexport type CsrfConfig = {\r\n /**\r\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\r\n * in addition to the request's own origin.\r\n * @default []\r\n */\r\n allowedOrigins?: string[];\r\n};\r\n\r\n/**\r\n * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted\r\n * falls back to `auth.cookie.*` config, then the package default. Cookie\r\n * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —\r\n * see {@link CookieAuthConfig}.\r\n */\r\nexport type SetAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). */\r\n path?: string;\r\n /**\r\n * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would\r\n * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a\r\n * bare token string with no `maxAge` produces a session cookie (cleared\r\n * when the browser closes).\r\n */\r\n maxAge?: number;\r\n};\r\n\r\n/**\n * The cookie surface the auth cookie helpers write through. Core's `Response`\n * satisfies it, and so does a page action's buffered `ActionResponse` from\n * `@warlock.js/web`, so the same helpers work in controllers and page actions.\n */\nexport type CookieWriter = {\n cookie(name: string, value: string, options?: Record<string, unknown>): unknown;\n clearCookie(name: string, options?: Record<string, unknown>): unknown;\n};\n\n/** Per-call overrides for {@link AuthService.clearAuthCookie}. */\r\nexport type ClearAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). Must match the `Path` the cookie was set with. */\r\n path?: string;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,gBAAgB"}
|
|
1
|
+
{"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"sourcesContent":["import { type ChildModel } from \"@warlock.js/cascade\";\r\nimport { type Algorithm } from \"fast-jwt\";\r\nimport type { AccessToken } from \"../models/access-token\";\r\nimport type { Auth } from \"../models/auth.model\";\r\nimport type { OneTimeToken } from \"../models/one-time-token\";\r\nimport type { OtpConfig, PasskeysConfig, ProvidersConfig } from \"./providers\";\r\nimport type { RefreshToken } from \"../models/refresh-token\";\r\n\r\n/**\r\n * Symbol to indicate no expiration for tokens\r\n * Use this when you explicitly want tokens to never expire\r\n *\r\n * @example\r\n * ```typescript\r\n * // src/config/auth.ts\r\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\r\n *\r\n * const authConfigurations: AuthConfigurations = {\r\n * accessToken: {\r\n * secret: env(\"JWT_SECRET\"),\r\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\r\n * },\r\n * };\r\n *\r\n * export default authConfigurations;\r\n * ```\r\n */\r\nexport const NO_EXPIRATION = \"100y\";\r\n\r\n/**\r\n * Behavior when logout is called without a refresh token\r\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\r\n * - \"error\": Return an error requiring the refresh token\r\n */\r\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\r\n\r\n/** The single request credential source an auth middleware instance accepts. */\r\nexport type TokenFrom = \"header\" | `cookie:${string}`;\r\n\r\n/** One explicitly named credential location accepted by auth middleware. */\r\nexport interface AuthCredentialDescriptor {\r\n source: \"header\" | \"cookie\";\r\n key?: string;\r\n}\r\n\r\n/** Cookie credential and bounded duplicate window for automatic renewal. */\r\nexport interface AuthRefreshCredentialDescriptor extends AuthCredentialDescriptor {\r\n /** Milliseconds; the service clamps this to the 0–10 second range (default 5 seconds). */\r\n overlapMs?: number;\r\n}\r\n\r\n/** Per-route authentication behavior for the object middleware overload. */\r\nexport interface AuthMiddlewareOptions extends Partial<AuthCredentialDescriptor> {\r\n optional?: boolean;\r\n /**\r\n * Accept both a header and a cookie credential (at most one of each).\r\n * Precedence is fixed and independent of array order: a present\r\n * `Authorization` header wins, an invalid header rejects and never falls back\r\n * to the cookie, and renewal plus the CSRF Origin check apply only when the\r\n * cookie is the credential actually used. Cannot be combined with `source`.\r\n */\r\n sources?: AuthCredentialDescriptor[];\r\n /** Opt-in automatic renewal credential. Cookie sources only. */\r\n refresh?: AuthRefreshCredentialDescriptor;\r\n redirect?: { to: string; returnUrlParam?: string };\r\n}\r\n\r\n/** Application policy that decides whether a resolved user may authenticate. */\r\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\r\n\r\n/** Password credentials plus the application-defined identity fields. */\r\nexport type AuthCredentials = Record<string, unknown> & {\r\n password: string;\r\n};\r\n\r\n/**\r\n * Access-token configuration.\r\n *\r\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\r\n * backward-compatible shim); one of the two is required at runtime.\r\n */\r\nexport type AccessTokenConfig = {\r\n /**\r\n * Override the persisted access-token model — extend {@link AccessToken} to\r\n * add columns (e.g. a tenant key) or rename them, then register the subclass\r\n * here. Defaults to the package's `AccessToken`.\r\n */\r\n model?: typeof AccessToken;\r\n /**\r\n * Secret used to sign access tokens.\r\n */\r\n secret?: string;\r\n /**\r\n * JWT algorithm.\r\n * @default \"HS256\"\r\n */\r\n algorithm?: Algorithm;\r\n /**\r\n * Access-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\r\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\r\n * first token issue: each of them would otherwise sign a token with no `exp`\r\n * claim at all.\r\n * @example \"1h\" or NO_EXPIRATION\r\n * @default \"1h\"\r\n */\r\n expiresIn?: string;\r\n};\r\n\r\n/**\r\n * Refresh-token configuration.\r\n */\r\nexport type RefreshTokenConfig = {\r\n /**\r\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\r\n * Defaults to the package's `RefreshToken`.\r\n */\r\n model?: typeof RefreshToken;\r\n /**\r\n * Enable refresh tokens.\r\n * @default true\r\n */\r\n enabled?: boolean;\r\n /**\r\n * Separate secret for refresh tokens (recommended for security). Falls back\r\n * to the access-token secret when omitted.\r\n */\r\n secret?: string;\r\n /**\r\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds; an unusable value throws naming this key on issue\r\n * (see {@link AccessTokenConfig.expiresIn}).\r\n * @example \"7d\" or \"1w\"\r\n * @default \"7d\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * Rotate the refresh token on each use (old token revoked, replay detected).\r\n * @default true\r\n */\r\n rotation?: boolean;\r\n /**\r\n * Maximum active refresh tokens per user; the oldest are revoked past it.\r\n * @default 5\r\n */\r\n maxPerUser?: number;\r\n /**\r\n * Behavior when logout is called without a refresh token.\r\n * @default \"revoke-all\"\r\n */\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n};\r\n\r\n/**\r\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\r\n * deprecation warning.\r\n *\r\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\r\n */\r\nexport type LegacyJwtConfig = {\r\n secret?: string;\r\n algorithm?: Algorithm;\r\n expiresIn?: string;\r\n refresh?: {\r\n secret?: string;\r\n enabled?: boolean;\r\n expiresIn?: string;\r\n rotation?: boolean;\r\n maxPerUser?: number;\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n };\r\n};\r\n\r\n/**\r\n * Page-route auth-failure behavior. When a guarded PAGE route (a React-SSR page,\r\n * `route.isPage`) rejects a logged-out browser, this decides whether it redirects\r\n * to a login page instead of returning the API-style JSON 401.\r\n *\r\n * Read at runtime through `authConfig.pageAuth` (`auth.pageAuth.*`). API routes\r\n * always keep the JSON 401 contract regardless of this setting.\r\n */\r\nexport type PageAuthConfig = {\r\n /**\r\n * Login path a guarded page route redirects a logged-out browser to (e.g.\r\n * `\"/login\"`). Unset ⇒ page-route auth failures keep the JSON 401, so the\r\n * behavior is opt-in and backward-compatible.\r\n */\r\n loginPath?: string;\r\n /**\r\n * Query parameter the redirect carries the originally-requested path in, so\r\n * the login flow can send the user back where they were headed.\r\n * @default \"returnUrl\"\r\n */\r\n returnUrlParam?: string;\r\n};\r\n\r\n/**\r\n * Data every email-verification / password-reset notification receives.\r\n */\r\nexport type OneTimeTokenNotificationData = {\r\n /** The raw token — the only place it exists; only its SHA-256 hash is stored. */\r\n token: string;\r\n /** When the token stops working. */\r\n expiresAt: Date;\r\n /** The link built by `auth.verification.url` / `auth.passwordReset.url`, when configured. */\r\n url?: string;\r\n};\r\n\r\n/**\r\n * Anything auth can deliver a token through — a `defineNotification(...)`\r\n * result satisfies it, as does any object with a compatible `send`.\r\n */\r\nexport type AuthNotification = {\r\n send: (to: Auth, data: OneTimeTokenNotificationData) => unknown;\r\n};\r\n\r\n/** Builds the link a token notification points at. */\r\nexport type OneTimeTokenUrlBuilder = (token: string, user: Auth) => string;\r\n\r\n/** Persists a new plaintext password for a user during a password reset. */\r\nexport type PasswordSetter = (user: Auth, plainPassword: string) => void | Promise<void>;\r\n\r\n/**\r\n * Email-verification configuration (`auth.verification`).\r\n */\r\nexport type EmailVerificationConfig = {\r\n /**\r\n * Token lifetime — a positive `ms` duration string.\r\n * @default \"24h\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * User attribute stamped with the verification `Date`. Declare it in the\r\n * user schema (e.g. `emailVerifiedAt: v.date().optional()`).\r\n * @default \"emailVerifiedAt\"\r\n */\r\n field?: string;\r\n /** Replace the default verification email. */\r\n notification?: AuthNotification;\r\n /** Build the verification link passed to the notification as `url`. */\r\n url?: OneTimeTokenUrlBuilder;\r\n};\r\n\r\n/**\r\n * Password-reset configuration (`auth.passwordReset`).\r\n */\r\nexport type PasswordResetConfig = {\r\n /**\r\n * Token lifetime — a positive `ms` duration string.\r\n * @default \"60m\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * User attribute `requestPasswordReset` looks the account up by.\r\n * @default \"email\"\r\n */\r\n identifierField?: string;\r\n /** Replace the default reset email. */\r\n notification?: AuthNotification;\r\n /** Build the reset link passed to the notification as `url`. */\r\n url?: OneTimeTokenUrlBuilder;\r\n /**\r\n * Persist the new password yourself. By default auth saves a hash made by\r\n * the same `hashPassword` login verifies against, and falls back to the\r\n * plaintext when the model hashes on save (`useHashedPassword()`), checking\r\n * the stored value with `verifyPassword` either way.\r\n */\r\n setPassword?: PasswordSetter;\r\n};\r\n\r\n/**\r\n * One-time token storage configuration (`auth.oneTimeToken`).\r\n */\r\nexport type OneTimeTokenConfig = {\r\n /** Override the persisted model — extend {@link OneTimeToken}. */\r\n model?: typeof OneTimeToken;\r\n};\r\n\r\nexport type AuthConfigurations = {\r\n /**\r\n * Define all user types — maps a user-type slug to its `Auth` model class so\r\n * tokens and the middleware can resolve the right model.\r\n */\r\n userType: {\r\n [userType: string]: ChildModel<Auth>;\r\n };\r\n /** Default user type used by `authMiddleware()` and options-only overloads. */\r\n defaultUserType?: string;\r\n /**\r\n * Decide whether a resolved user may authenticate or receive new tokens.\r\n * @default () => true\r\n */\r\n canAuthenticate?: CanAuthenticate;\r\n /**\r\n * Access-token configuration (secret, expiry, algorithm, model override).\r\n */\r\n accessToken?: AccessTokenConfig;\r\n /**\r\n * Refresh-token configuration (rotation, lifetime, cap, model override).\r\n */\r\n refreshToken?: RefreshTokenConfig;\r\n /**\r\n * Page-route auth-failure behavior — set `loginPath` to redirect a logged-out\r\n * browser on a guarded PAGE route to a login page instead of the JSON 401.\r\n * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.\r\n */\r\n pageAuth?: PageAuthConfig;\r\n /**\r\n * Cookie auth session config — read by `authService.setAuthCookie` /\r\n * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own\r\n * defaults (`name: \"access_token\"`, `path: \"/\"`).\r\n */\r\n cookie?: CookieAuthConfig;\r\n /**\r\n * CSRF Origin-check config for cookie-authenticated unsafe-method requests.\r\n */\r\n csrf?: CsrfConfig;\r\n /**\r\n * Email verification — token lifetime, verified field, notification override.\r\n */\r\n verification?: EmailVerificationConfig;\r\n /**\r\n * Password reset — token lifetime, lookup field, notification and password-writer overrides.\r\n */\r\n passwordReset?: PasswordResetConfig;\r\n /**\r\n * One-time token (verification / reset) storage — model override.\r\n */\r\n oneTimeToken?: OneTimeTokenConfig;\r\n /**\r\n * Redirect login providers (`google`, `custom`) and account-linking policy.\r\n */\r\n providers?: ProvidersConfig;\r\n /**\r\n * Passkey (WebAuthn) relying-party settings. Requires `@simplewebauthn/server`.\r\n */\r\n passkeys?: PasskeysConfig;\r\n /**\r\n * Phone one-time-code login — channel, lifetime, attempts, delivery.\r\n */\r\n otp?: OtpConfig;\r\n /**\r\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\r\n */\r\n jwt?: LegacyJwtConfig;\r\n};\r\n\r\nexport type AccessTokenOutput = {\r\n /**\r\n * JWT Token\r\n */\r\n token: string;\r\n /**\r\n * Exprie time in ISO format UTC time\r\n */\r\n expiresAt: string;\r\n};\r\n\r\n/**\r\n * Token pair returned after login or token refresh\r\n */\r\nexport type TokenPair = {\r\n /**\r\n * JWT access token (short-lived)\r\n */\r\n accessToken: AccessTokenOutput;\r\n /**\r\n * JWT refresh token (long-lived)\r\n */\r\n refreshToken?: AccessTokenOutput;\r\n};\r\n\r\n/**\r\n * Device information for session tracking\r\n */\r\nexport type DeviceInfo = {\r\n /**\r\n * User agent string from request\r\n */\r\n userAgent?: string;\r\n /**\r\n * Client IP address\r\n */\r\n ip?: string;\r\n /**\r\n * Optional device identifier\r\n */\r\n deviceId?: string;\r\n /**\r\n * Token family ID (for rotation tracking)\r\n * @internal\r\n */\r\n familyId?: string;\r\n /**\r\n * Access token payload\r\n */\r\n payload?: Record<string, unknown>;\r\n};\r\n\r\nexport type LoginResult<UserType extends Auth> = {\r\n user: UserType;\r\n tokens: TokenPair;\r\n};\r\n\r\n/**\r\n * Configuration for the cookie an app opts into via `authService.setAuthCookie`\r\n * / `clearAuthCookie` and reads back with `authMiddleware([], \"cookie:<name>\")`.\r\n * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT\r\n * configurable here — they come from core's `secureCookieDefaults()`, the\r\n * same floor every `response.cookie()` call gets, so a cookie auth session can\r\n * never be weakened to a laxer policy than the framework default.\r\n */\r\nexport type CookieAuthConfig = {\r\n /**\r\n * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a\r\n * `cookie:<name>` token source must match to read it back.\r\n * @default \"access_token\"\r\n */\r\n name?: string;\r\n /**\r\n * Cookie name the session helpers write and clear for the refresh token.\r\n * @default \"refresh_token\"\r\n */\r\n refreshName?: string;\r\n /**\r\n * Cookie `Path` attribute.\r\n * @default \"/\"\r\n */\r\n path?: string;\r\n};\r\n\r\n/**\r\n * CSRF Origin-check configuration. Read via\r\n * `authConfig.csrf.allowedOrigins()`.\r\n */\r\nexport type CsrfConfig = {\r\n /**\r\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\r\n * in addition to the request's own origin.\r\n * @default []\r\n */\r\n allowedOrigins?: string[];\r\n};\r\n\r\n/**\r\n * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted\r\n * falls back to `auth.cookie.*` config, then the package default. Cookie\r\n * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —\r\n * see {@link CookieAuthConfig}.\r\n */\r\nexport type SetAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). */\r\n path?: string;\r\n /**\r\n * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would\r\n * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a\r\n * bare token string with no `maxAge` produces a session cookie (cleared\r\n * when the browser closes).\r\n */\r\n maxAge?: number;\r\n};\r\n\r\n/** Per-call overrides for {@link AuthService.clearAuthCookie}. */\r\nexport type ClearAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). Must match the `Path` the cookie was set with. */\r\n path?: string;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,gBAAgB"}
|
package/esm/index.d.mts
CHANGED
|
@@ -5,7 +5,7 @@ import { Auth } from "./models/auth.model.mjs";
|
|
|
5
5
|
import { AccessToken, accessTokenSchema } from "./models/access-token/access-token.model.mjs";
|
|
6
6
|
import { OneTimeToken, OneTimeTokenPurpose, oneTimeTokenSchema } from "./models/one-time-token/one-time-token.model.mjs";
|
|
7
7
|
import { AppleProviderConfig, AuthProvider, DiscordProviderConfig, FacebookProviderConfig, GitHubProviderConfig, GoogleProviderConfig, LinkedInProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig, XProviderConfig } from "./contracts/providers.mjs";
|
|
8
|
-
import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig,
|
|
8
|
+
import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair } from "./contracts/types.mjs";
|
|
9
9
|
import { RefreshToken, RefreshTokenIssueOptions, refreshTokenSchema } from "./models/refresh-token/refresh-token.model.mjs";
|
|
10
10
|
import { Authenticable } from "./contracts/auth-contract.mjs";
|
|
11
11
|
import { RequestUser } from "./contracts/request-locals.mjs";
|
|
@@ -54,5 +54,5 @@ import { RequestUserFailure, RequestUserOutcome, ResolveRequestUserOptions, read
|
|
|
54
54
|
import { PageSessionOptions, PageSessionResolver, SessionMiddlewareOptions, SessionResolverOptions, SessionSource, pageSession, sessionMiddleware } from "./session/page-session.mjs";
|
|
55
55
|
import { safeEqual } from "./utils/safe-equal.mjs";
|
|
56
56
|
import { isNeverExpiring, isUsableExpiry, tokenHasExpClaim } from "./utils/token-expiry.mjs";
|
|
57
|
-
export { APPLE_AUTH_FEATURE, AccessToken, AccessTokenConfig, AccessTokenOutput, AppleJoseModule, AppleProvider, AppleProviderConfig, Auth, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthErrorCodes, AuthErrorTranslations, AuthEventCallback, AuthEventName, AuthEventPayloads, AuthMiddlewareOptions, AuthNotification, AuthProvider, AuthProviderSdkMissingError, AuthRefreshCredentialDescriptor, AuthTokenFamily, AuthTranslations, Authenticable, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig,
|
|
57
|
+
export { APPLE_AUTH_FEATURE, AccessToken, AccessTokenConfig, AccessTokenOutput, AppleJoseModule, AppleProvider, AppleProviderConfig, Auth, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthErrorCodes, AuthErrorTranslations, AuthEventCallback, AuthEventName, AuthEventPayloads, AuthMiddlewareOptions, AuthNotification, AuthProvider, AuthProviderSdkMissingError, AuthRefreshCredentialDescriptor, AuthTokenFamily, AuthTranslations, Authenticable, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, CsrfOriginMismatchError, CsrfOriginMismatchReason, DeviceInfo, DiscordProvider, DiscordProviderConfig, EmailNotVerifiedError, EmailVerificationConfig, FacebookProvider, FacebookProviderConfig, GOOGLE_AUTH_FEATURE, GitHubProvider, GitHubProviderConfig, GoogleProvider, GoogleProviderConfig, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, IssuedOneTimeToken, JoseModule, LINKEDIN_AUTH_FEATURE, LegacyJwtConfig, LinkedInProvider, LinkedInProviderConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenPurpose, OneTimeTokenUrlBuilder, OtpConfig, OtpMessage, OtpSender, PageAuthConfig, PageSessionOptions, PageSessionResolver, PasskeyCredential, type PasskeyOptionsJSON, type PasskeyResponseJSON, PasskeysConfig, PasswordResetConfig, PasswordSetter, ProviderAccount, ProviderAuthorizationState, ProviderCallbackParams, ProviderEmailNotVerifiedError, ProviderProfile, ProviderUserCreator, ProvidersConfig, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, RequestUser, RequestUserFailure, RequestUserOutcome, ResolveRequestUserOptions, SessionMiddlewareOptions, SessionResolverOptions, SessionSource, SetAuthCookieOptions, TokenFrom, TokenPair, TokenType, TokenTypeError, XProvider, XProviderConfig, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, authTokenFamilySchema, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, pageSession, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, readCredential, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, resolveRequestUser, resolveRequestUserOutcome, safeEqual, sendEmailVerification, sessionMiddleware, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
|
|
58
58
|
import "./contracts/request-locals.mjs";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Auth } from "../models/auth.model.mjs";
|
|
2
2
|
import { AccessToken } from "../models/access-token/access-token.model.mjs";
|
|
3
|
-
import { AccessTokenOutput, AuthCredentials, ClearAuthCookieOptions,
|
|
3
|
+
import { AccessTokenOutput, AuthCredentials, ClearAuthCookieOptions, DeviceInfo, LoginResult, SetAuthCookieOptions, TokenPair } from "../contracts/types.mjs";
|
|
4
4
|
import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
|
|
5
|
-
import { Request } from "@warlock.js/core";
|
|
5
|
+
import { Request, Response } from "@warlock.js/core";
|
|
6
6
|
import { ChildModel } from "@warlock.js/cascade";
|
|
7
7
|
|
|
8
8
|
//#region ../auth/src/services/auth.service.d.ts
|
|
@@ -116,7 +116,7 @@ declare class AuthService {
|
|
|
116
116
|
* cross-site Origin/Referer throws `CsrfOriginMismatchError` before any
|
|
117
117
|
* credential is looked at. Returns `null` on bad credentials.
|
|
118
118
|
*/
|
|
119
|
-
loginWithSessionCookies<T extends Auth>(request: Request, response:
|
|
119
|
+
loginWithSessionCookies<T extends Auth>(request: Request, response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, Model: ChildModel<T>, credentials: AuthCredentials, deviceInfo?: DeviceInfo): Promise<LoginResult<T> | null>;
|
|
120
120
|
/** The one token-issuing tail every login method shares. */
|
|
121
121
|
private finalizeLogin;
|
|
122
122
|
/**
|
|
@@ -216,7 +216,7 @@ declare class AuthService {
|
|
|
216
216
|
* const { user, tokens } = await authService.login(User, credentials);
|
|
217
217
|
* authService.setAuthCookie(response, tokens.accessToken);
|
|
218
218
|
*/
|
|
219
|
-
setAuthCookie(response:
|
|
219
|
+
setAuthCookie(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, token: string | AccessTokenOutput, options?: SetAuthCookieOptions): void;
|
|
220
220
|
/**
|
|
221
221
|
* Clear the auth cookie {@link setAuthCookie} wrote. An explicit
|
|
222
222
|
* app-controller call — pair it with {@link logout} after the token row is
|
|
@@ -227,7 +227,7 @@ declare class AuthService {
|
|
|
227
227
|
* await authService.logout(user, accessToken, refreshToken);
|
|
228
228
|
* authService.clearAuthCookie(response);
|
|
229
229
|
*/
|
|
230
|
-
clearAuthCookie(response:
|
|
230
|
+
clearAuthCookie(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, options?: ClearAuthCookieOptions): void;
|
|
231
231
|
/**
|
|
232
232
|
* Write the access and refresh cookies of a browser session. Both are
|
|
233
233
|
* `HttpOnly`, `SameSite=Lax`, `Path=/` (the refresh cookie must reach every
|
|
@@ -239,7 +239,7 @@ declare class AuthService {
|
|
|
239
239
|
* const { tokens } = await authService.login(User, credentials);
|
|
240
240
|
* authService.setSessionCookies(response, tokens);
|
|
241
241
|
*/
|
|
242
|
-
setSessionCookies(response:
|
|
242
|
+
setSessionCookies(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, tokens: {
|
|
243
243
|
accessToken: AccessTokenOutput;
|
|
244
244
|
refreshToken: AccessTokenOutput;
|
|
245
245
|
}): void;
|
|
@@ -247,7 +247,7 @@ declare class AuthService {
|
|
|
247
247
|
* Clear both session cookies {@link setSessionCookies} wrote, on `Path=/`.
|
|
248
248
|
* Pair it with {@link logout} after the token rows are revoked.
|
|
249
249
|
*/
|
|
250
|
-
clearSessionCookies(response:
|
|
250
|
+
clearSessionCookies(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }): void;
|
|
251
251
|
}
|
|
252
252
|
declare const authService: AuthService;
|
|
253
253
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.d.mts","names":[],"sources":["../../../../../../../auth/src/services/auth.service.ts"],"mappings":";;;;;;;;cAmCM,WAAA;;;;AAViD;;;cAiBzC,gBAAA;EAqBgC;;;EAAA,YAdhC,iBAAA;EA6DA;;;EAAA,YAtDA,iBAAA;EAwIG;;;EAjIR,uBAAA,CAAwB,IAAA,EAAM,IAAA,GAAO,MAAA;EA8LU;EArLzC,eAAA,CAAgB,IAAA,EAAM,IAAA,GAAO,OAAA;EAAA,QAI5B,qBAAA;EAAA,QAMA,gBAAA;EA+LH;;;;;EArKE,mBAAA,CACX,IAAA,EAAM,IAAA,EACN,OAAA,GAAU,MAAA,oBACT,OAAA,CAAQ,iBAAA;EAAA,QAMG,iBAAA;EA8csB;;;;;;EAAA,QAratB,qBAAA;EAycM;;;;EAzaP,kBAAA,CACX,IAAA,EAAM,IAAA,EACN,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,YAAA;EAAA,QAcG,cAAA;EA2ZX;;;EA7WU,eAAA,CAAgB,IAAA,EAAM,IAAA,EAAM,UAAA,GAAa,UAAA,GAAa,OAAA,CAAQ,SAAA;EAmYpD;;;;;EAlXV,aAAA,CACX,kBAAA,UACA,UAAA,GAAa,UAAA,GACZ,OAAA,CAAQ,SAAA;
|
|
1
|
+
{"version":3,"file":"auth.service.d.mts","names":[],"sources":["../../../../../../../auth/src/services/auth.service.ts"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.mjs","names":[],"sources":["../../../../../../../auth/src/services/auth.service.ts"],"sourcesContent":["import { Random } from \"@mongez/reinforcements\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ChildModel } from \"@warlock.js/cascade\";\nimport {\n config,\n ForbiddenError,\n hashPassword,\n type Request,\n verifyPassword,\n} from \"@warlock.js/core\";\nimport type {\n AccessTokenOutput,\n AuthCredentials,\n ClearAuthCookieOptions,\n CookieWriter,\n DeviceInfo,\n LoginResult,\n SetAuthCookieOptions,\n TokenPair,\n} from \"../contracts/types\";\nimport { assertCsrfOriginAllowed } from \"../middleware/csrf-origin-check\";\nimport { AccessToken } from \"../models/access-token\";\nimport type { Auth } from \"../models/auth.model\";\nimport { AuthTokenFamily } from \"../models/auth-token-family\";\nimport { OneTimeToken } from \"../models/one-time-token\";\nimport { RefreshToken } from \"../models/refresh-token\";\nimport { authConfig } from \"./auth-config\";\nimport { authEvents } from \"./auth-events\";\nimport { isInvalidCredentialError, jwt } from \"./jwt\";\nimport {\n afterTokenFamilyOperation,\n runTokenFamilyOperation,\n TokenFamilyUnavailableError,\n} from \"./token-family-operation\";\n\nclass AuthService {\n /**\n * Resolve the active access-token model — the package default, or a subclass\n * an app registered under `config.auth.accessToken.model` (e.g. to add a\n * tenant column). The service never references the concrete class directly so\n * an override is a pure config change.\n */\n private get accessTokenModel(): typeof AccessToken {\n return config.key(\"auth.accessToken.model\", AccessToken);\n }\n\n /**\n * Resolve the active refresh-token model (default or registered override).\n */\n private get refreshTokenModel(): typeof RefreshToken {\n return config.key(\"auth.refreshToken.model\", RefreshToken);\n }\n\n /**\n * Resolve the active one-time-token model (default or registered override).\n */\n private get oneTimeTokenModel(): typeof OneTimeToken {\n return config.key(\"auth.oneTimeToken.model\", OneTimeToken);\n }\n\n /**\n * Build the default access-token JWT payload from a user.\n */\n public buildAccessTokenPayload(user: Auth): Record<string, unknown> {\n return {\n id: user.id,\n userType: user.userType,\n created_at: Date.now(),\n };\n }\n\n /** One policy seam keeps account-state checks identical across all auth paths. */\n public async canAuthenticate(user: Auth): Promise<boolean> {\n return authConfig.canAuthenticate(user);\n }\n\n private async assertCanAuthenticate(user: Auth): Promise<void> {\n if (!(await this.canAuthenticate(user))) {\n throw new ForbiddenError(\"This user cannot authenticate.\");\n }\n }\n\n private async issueAccessToken(\n user: Auth,\n payload?: Record<string, unknown>,\n familyId?: string,\n ): Promise<AccessTokenOutput> {\n const data = { ...(payload || this.buildAccessTokenPayload(user)), jti: randomUUID() };\n // Validate before signing so an unusable lifetime cannot mint an immortal token.\n const expiresIn = authConfig.accessToken.expiresInMs();\n\n const token = await jwt.generate(data, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn);\n\n if (familyId) {\n await this.accessTokenModel.issue(user, token, expiresAt, familyId);\n } else {\n await this.accessTokenModel.issue(user, token, expiresAt);\n }\n\n return { token, expiresAt: expiresAt.toISOString() };\n }\n\n /**\n * Sign + persist an access token for the user and return the token with its\n * expiry. The expiry is computed locally from `expiresIn` rather than by\n * re-verifying the token we just signed.\n */\n public async generateAccessToken(\n user: Auth,\n payload?: Record<string, unknown>,\n ): Promise<AccessTokenOutput> {\n await this.assertCanAuthenticate(user);\n\n return this.issueAccessToken(user, payload);\n }\n\n private async issueRefreshToken(\n user: Auth,\n deviceInfo?: DeviceInfo,\n ): Promise<RefreshToken | undefined> {\n if (!authConfig.refreshToken.enabled()) return;\n\n // Validate the lifetime first — before the per-user cap is enforced, before\n // anything is signed — so a bad value can never revoke a user's oldest\n // session on its way to failing.\n const expiresIn = authConfig.refreshToken.expiresInMs();\n\n const familyId = deviceInfo?.familyId || Random.string(32);\n\n const family = await AuthTokenFamily.ensure(user, familyId);\n\n if (family.isRevoked) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n\n const payload = {\n userId: user.id,\n userType: user.userType,\n familyId,\n jti: randomUUID(),\n };\n\n const expiresAt = new Date(Date.now() + expiresIn).toISOString();\n\n await this.refreshTokenModel.enforceMax(user, authConfig.refreshToken.maxPerUser());\n\n const token = await jwt.generateRefreshToken(payload, { expiresIn });\n\n return this.refreshTokenModel.issue(user, token, { familyId, expiresAt, deviceInfo });\n }\n\n /**\n * Keep issuance into an explicitly reused family in the same durable\n * transition boundary as rotation and revocation. A new, caller-unspecified\n * family has no concurrent session to protect and retains the lightweight\n * issuance path.\n */\n private async issueInSuppliedFamily<T>(\n user: Auth,\n familyId: string,\n issue: () => Promise<T>,\n ): Promise<T> {\n // Preserve the established error for a family that was already revoked\n // before issuance started. The coordinator covers a revoke that races\n // after this observation.\n const family = await AuthTokenFamily.ensure(user, familyId);\n\n if (family.isRevoked) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n\n try {\n return await runTokenFamilyOperation(familyId, async () => issue());\n } catch (error) {\n // Do not expose a timing-dependent error shape to callers: a family\n // revoked between ensure() and the transaction is the same unusable\n // family condition as one found above.\n if (error instanceof TokenFamilyUnavailableError) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n\n throw error;\n }\n }\n\n /**\n * Sign + persist a refresh token for the user (enforcing the per-user cap\n * first). Resolves to `undefined` when refresh tokens are disabled in config.\n */\n public async createRefreshToken(\n user: Auth,\n deviceInfo?: DeviceInfo,\n ): Promise<RefreshToken | undefined> {\n if (!authConfig.refreshToken.enabled()) return;\n\n await this.assertCanAuthenticate(user);\n\n if (deviceInfo?.familyId) {\n return this.issueInSuppliedFamily(user, deviceInfo.familyId, () =>\n this.issueRefreshToken(user, deviceInfo),\n );\n }\n\n return this.issueRefreshToken(user, deviceInfo);\n }\n\n private async issueTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair> {\n const refreshEnabled = authConfig.refreshToken.enabled();\n const familyId = deviceInfo?.familyId || Random.string(32);\n\n if (refreshEnabled) {\n const family = await AuthTokenFamily.ensure(user, familyId);\n\n if (family.isRevoked) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n }\n\n const accessToken = await this.issueAccessToken(\n user,\n deviceInfo?.payload,\n refreshEnabled ? familyId : undefined,\n );\n const refreshToken = await this.issueRefreshToken(\n user,\n refreshEnabled ? { ...deviceInfo, familyId } : deviceInfo,\n );\n\n const tokenPair: TokenPair = {\n accessToken,\n refreshToken: refreshToken\n ? {\n token: refreshToken.get(\"token\"),\n expiresAt: refreshToken.get(\"expires_at\"),\n }\n : undefined,\n };\n\n afterTokenFamilyOperation(() => authEvents.emit(\"token.created\", user, tokenPair));\n\n if (refreshToken) {\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"session.created\", user, refreshToken, deviceInfo),\n );\n }\n\n return tokenPair;\n }\n\n /**\n * Issue both an access and a refresh token, emitting the creation events.\n */\n public async createTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair> {\n await this.assertCanAuthenticate(user);\n\n if (authConfig.refreshToken.enabled() && deviceInfo?.familyId) {\n return this.issueInSuppliedFamily(user, deviceInfo.familyId, () =>\n this.issueTokenPair(user, deviceInfo),\n );\n }\n\n return this.issueTokenPair(user, deviceInfo);\n }\n\n /**\n * Exchange a refresh token for a new token pair, with rotation + replay\n * detection. A concurrent reuse of the same token loses the atomic revoke and\n * is treated as a breach — the whole family is revoked and the request fails.\n */\n public async refreshTokens(\n refreshTokenString: string,\n deviceInfo?: DeviceInfo,\n ): Promise<TokenPair | null> {\n let decoded: {\n userId: number;\n userType: string;\n familyId: string;\n } | null;\n\n try {\n decoded = await jwt.verifyRefreshToken<{\n userId: number;\n userType: string;\n familyId: string;\n }>(refreshTokenString);\n } catch (error) {\n if (isInvalidCredentialError(error)) return null;\n\n throw error;\n }\n\n if (!decoded) return null;\n\n const presentedToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n\n if (!presentedToken || !this.refreshIdentityMatches(presentedToken, decoded)) return null;\n\n await AuthTokenFamily.ensure(\n { id: decoded.userId, userType: decoded.userType } as Auth,\n presentedToken.familyId,\n );\n return runTokenFamilyOperation(presentedToken.familyId, async () => {\n // Each serializable attempt must read the current row in its own snapshot.\n const refreshToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!refreshToken || !this.refreshIdentityMatches(refreshToken, decoded)) return null;\n if (!refreshToken.isValid) {\n // An already-invalid token may be a replay of a rotated credential.\n if (refreshToken) {\n await this.revokeTokenFamily(refreshToken.familyId);\n }\n\n return null;\n }\n\n const UserModel = config.key(`auth.userType.${decoded.userType}`);\n\n if (!UserModel) {\n throw new Error(`User type ${decoded.userType} is unknown type.`);\n }\n\n const user = (await UserModel.find(decoded.userId)) as Auth | null;\n\n if (!user) return null;\n\n if (!(await this.canAuthenticate(user))) return null;\n\n const rotationEnabled = authConfig.refreshToken.rotation();\n\n if (rotationEnabled) {\n const won = await refreshToken.revokeIfActive();\n\n if (!won) {\n // A concurrent request already rotated this token.\n await this.revokeTokenFamily(refreshToken.familyId);\n\n return null;\n }\n } else {\n await refreshToken.markAsUsed();\n }\n\n const newTokenPair = await this.issueTokenPair(user, {\n ...deviceInfo,\n familyId: refreshToken.familyId,\n });\n\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"token.refreshed\", user, newTokenPair, refreshToken),\n );\n\n return newTokenPair;\n }).catch((error) => {\n if (error instanceof TokenFamilyUnavailableError) return null;\n throw error;\n });\n }\n\n /**\n * Opt-in browser-session renewal. Unlike {@link refreshTokens}, this permits\n * a short duplicate presentation to receive the exact committed successor\n * pair. It is deliberately not used by the legacy/public refresh API.\n */\n public async renewAutomaticSession(\n refreshTokenString: string,\n expectedUserType: string,\n options: { overlapMs?: number; deviceInfo?: DeviceInfo; maxAgeMs?: number } = {},\n ): Promise<TokenPair | null> {\n let decoded: { userId: number; userType: string; familyId: string } | null;\n try {\n decoded = await jwt.verifyRefreshToken<{\n userId: number;\n userType: string;\n familyId: string;\n }>(refreshTokenString);\n } catch (error) {\n if (isInvalidCredentialError(error)) return null;\n throw error;\n }\n\n if (!decoded || decoded.userType !== expectedUserType) return null;\n const presentedToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!presentedToken || !this.refreshIdentityMatches(presentedToken, decoded)) return null;\n\n await AuthTokenFamily.ensure(\n { id: decoded.userId, userType: decoded.userType } as Auth,\n presentedToken.familyId,\n );\n return runTokenFamilyOperation(presentedToken.familyId, async () => {\n // A retry can follow another process's committed rotation. Never reuse\n // a model captured before the transaction or before a failed attempt.\n const oldToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!oldToken || !this.refreshIdentityMatches(oldToken, decoded)) return null;\n if (!oldToken.isValid) {\n const successor = await this.readAutomaticSuccessor(oldToken, decoded, options.overlapMs);\n if (successor) return successor;\n if (\n this.isAutomaticOverlapOpen(oldToken, options.overlapMs) &&\n this.hasSuccessor(oldToken)\n ) {\n return null;\n }\n await this.revokeTokenFamily(oldToken.familyId);\n return null;\n }\n\n // Rotation never extends a family past `maxAgeMs` from its first issue.\n if (\n options.maxAgeMs !== undefined &&\n (await this.familyExceedsMaxAge(oldToken.familyId, options.maxAgeMs))\n ) {\n await this.revokeTokenFamily(oldToken.familyId);\n return null;\n }\n\n const UserModel = config.key(`auth.userType.${decoded.userType}`);\n if (!UserModel) throw new Error(`User type ${decoded.userType} is unknown type.`);\n const user = (await UserModel.find(decoded.userId)) as Auth | null;\n if (!user || !(await this.canAuthenticate(user))) return null;\n\n if (!(await oldToken.revokeIfActive())) {\n // A newer successor is never substituted here. A stale automatic\n // response must fail quietly rather than revoke a legitimate family.\n const currentToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!currentToken) return null;\n const successor = await this.readAutomaticSuccessor(\n currentToken,\n decoded,\n options.overlapMs,\n );\n if (successor) return successor;\n if (\n this.isAutomaticOverlapOpen(currentToken, options.overlapMs) &&\n this.hasSuccessor(currentToken)\n )\n return null;\n await this.revokeTokenFamily(oldToken.familyId);\n return null;\n }\n\n const pair = await this.issueTokenPair(user, {\n ...options.deviceInfo,\n familyId: oldToken.familyId,\n });\n const successorAccess = await this.accessTokenModel.findByToken(pair.accessToken.token);\n const successorRefresh = pair.refreshToken\n ? await this.refreshTokenModel.findByToken(pair.refreshToken.token)\n : null;\n if (!successorAccess || !successorRefresh) {\n throw new Error(\"Automatic renewal did not persist a complete successor pair\");\n }\n\n // `revokeIfActive()` updates the database, not this hydrated instance.\n // Saving that stale instance would write its old `revoked_at: null` back\n // and reopen the predecessor. Update only the successor columns instead.\n const linked = await this.refreshTokenModel.atomic(\n { id: oldToken.id },\n {\n $set: {\n successor_access_token_id: successorAccess.id,\n successor_refresh_token_id: successorRefresh.id,\n },\n },\n );\n if (linked !== 1) throw new Error(\"Automatic renewal could not link its successor pair\");\n afterTokenFamilyOperation(() => authEvents.emit(\"token.refreshed\", user, pair, oldToken));\n return pair;\n }).catch((error) => {\n if (error instanceof TokenFamilyUnavailableError) return null;\n throw error;\n });\n }\n\n /**\n * Whether the family's first issue is older than `maxAgeMs`. The start is the\n * family row's `created_at`, falling back to its oldest refresh token when\n * the row carries none (an upserted legacy family).\n */\n private async familyExceedsMaxAge(familyId: string, maxAgeMs: number): Promise<boolean> {\n const family = await AuthTokenFamily.findByFamilyId(familyId);\n let started: unknown = family?.get(\"created_at\");\n\n if (!started) {\n const oldest = await this.refreshTokenModel\n .query()\n .where({ family_id: familyId })\n .orderBy(\"created_at\", \"asc\")\n .first();\n\n started = oldest?.get(\"created_at\");\n }\n\n const startedAt = started ? new Date(started as string | number | Date).getTime() : NaN;\n\n // Unknown start: nothing to measure against, so the cap cannot apply.\n if (!Number.isFinite(startedAt)) return false;\n\n return Date.now() - startedAt > maxAgeMs;\n }\n\n private automaticOverlapMs(overlapMs: number | undefined): number {\n return typeof overlapMs === \"number\" && Number.isFinite(overlapMs)\n ? Math.max(0, Math.min(10_000, overlapMs))\n : 5000;\n }\n\n private refreshIdentityMatches(\n token: RefreshToken,\n decoded: { userId: string | number; userType: string; familyId: string },\n ): boolean {\n return (\n token.familyId === decoded.familyId &&\n token.get(\"user_id\") === decoded.userId &&\n token.get(\"user_type\") === decoded.userType\n );\n }\n\n private hasSuccessor(oldToken: RefreshToken): boolean {\n return Boolean(\n oldToken.get(\"successor_access_token_id\") && oldToken.get(\"successor_refresh_token_id\"),\n );\n }\n\n private isAutomaticOverlapOpen(oldToken: RefreshToken, overlapMs: number | undefined): boolean {\n const revokedAt = oldToken.get<Date | undefined>(\"revoked_at\");\n const window = this.automaticOverlapMs(overlapMs);\n const elapsed = revokedAt ? Date.now() - new Date(revokedAt).getTime() : NaN;\n return window > 0 && elapsed >= 0 && elapsed <= window;\n }\n\n private async readAutomaticSuccessor(\n oldToken: RefreshToken,\n decoded: { userId: number; userType: string; familyId: string },\n overlapMs: number | undefined,\n ): Promise<TokenPair | null> {\n if (!this.isAutomaticOverlapOpen(oldToken, overlapMs)) return null;\n const accessId = oldToken.get<string | undefined>(\"successor_access_token_id\");\n const refreshId = oldToken.get<string | undefined>(\"successor_refresh_token_id\");\n if (!accessId || !refreshId) return null;\n const [access, refresh] = await Promise.all([\n this.accessTokenModel.find(accessId),\n this.refreshTokenModel.find(refreshId),\n ]);\n if (!access || access.isExpired || !refresh?.isValid) return null;\n if (\n access.get(\"family_id\") !== decoded.familyId ||\n refresh.get(\"family_id\") !== decoded.familyId ||\n access.get(\"user_id\") !== decoded.userId ||\n refresh.get(\"user_id\") !== decoded.userId ||\n access.get(\"user_type\") !== decoded.userType ||\n refresh.get(\"user_type\") !== decoded.userType\n )\n return null;\n const accessExpiry = new Date(access.get(\"expires_at\")).getTime();\n if (!Number.isFinite(accessExpiry) || accessExpiry <= Date.now()) return null;\n return {\n accessToken: { token: access.get(\"token\"), expiresAt: access.get(\"expires_at\") },\n refreshToken: { token: refresh.get(\"token\"), expiresAt: refresh.get(\"expires_at\") },\n };\n }\n\n /**\n * Verify a plaintext password against a stored hash.\n */\n public async verifyPassword(plainPassword: string, hashedPassword: string): Promise<boolean> {\n return verifyPassword(plainPassword, hashedPassword);\n }\n\n /**\n * Hash a plaintext password.\n */\n public async hashPassword(password: string): Promise<string> {\n return hashPassword(password);\n }\n\n /**\n * Resolve a user by credentials, verifying the password. Returns `null` on a\n * missing user or a wrong password, emitting `login.failed` either way.\n */\n public async attemptLogin<T extends Auth>(\n Model: ChildModel<T>,\n data: AuthCredentials,\n ): Promise<T | null> {\n const { password, ...otherData } = data;\n\n authEvents.emit(\"login.attempt\", otherData);\n\n const user = (await Model.first(otherData)) as T | null;\n\n if (!user) {\n authEvents.emit(\"login.failed\", otherData, \"User not found\");\n\n return null;\n }\n\n if (!(await this.verifyPassword(password, user.string(\"password\")!))) {\n authEvents.emit(\"login.failed\", otherData, \"Invalid password\");\n\n return null;\n }\n\n if (!(await this.canAuthenticate(user))) {\n authEvents.emit(\"login.failed\", otherData, \"Authentication not allowed\");\n\n return null;\n }\n\n return user;\n }\n\n /**\n * Full login flow: validate credentials, issue tokens, emit events. Returns\n * the user + token pair on success, `null` on failure.\n */\n public async login<T extends Auth>(\n Model: ChildModel<T>,\n credentials: AuthCredentials,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T> | null> {\n const user = await this.attemptLogin(Model, credentials);\n\n if (!user) {\n return null;\n }\n\n return this.finalizeLogin(user, deviceInfo);\n }\n\n /**\n * Log in a user some OTHER method already authenticated — a provider\n * callback, a passkey assertion, a one-time code. Applies the\n * `auth.canAuthenticate` policy (403 on refusal), then produces exactly\n * what {@link login} produces: the same tokens, rows and events. Pair with\n * {@link setAuthCookie} for a cookie session, as with password login.\n *\n * Never call this for a user you have not authenticated.\n */\n public async completeLogin<T extends Auth>(\n user: T,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T>> {\n await this.assertCanAuthenticate(user);\n\n return this.finalizeLogin(user, deviceInfo);\n }\n\n /**\n * Cookie login for a browser: same-origin check, credential login, then both\n * session cookies. Unlike the default CSRF guard, the Origin/Referer check\n * runs even when the request carries no cookies (login CSRF): a missing or\n * cross-site Origin/Referer throws `CsrfOriginMismatchError` before any\n * credential is looked at. Returns `null` on bad credentials.\n */\n public async loginWithSessionCookies<T extends Auth>(\n request: Request,\n response: CookieWriter,\n Model: ChildModel<T>,\n credentials: AuthCredentials,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T> | null> {\n assertCsrfOriginAllowed(request);\n\n const result = await this.login(Model, credentials, deviceInfo);\n\n if (!result) return null;\n\n if (result.tokens.refreshToken) {\n this.setSessionCookies(response, {\n accessToken: result.tokens.accessToken,\n refreshToken: result.tokens.refreshToken,\n });\n } else {\n this.setAuthCookie(response, result.tokens.accessToken);\n }\n\n return result;\n }\n\n /** The one token-issuing tail every login method shares. */\n private async finalizeLogin<T extends Auth>(\n user: T,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T>> {\n if (!authConfig.refreshToken.enabled()) {\n const accessToken = await this.issueAccessToken(user, deviceInfo?.payload);\n\n return { user, tokens: { accessToken } };\n }\n\n const tokens = await this.issueTokenPair(user, deviceInfo);\n\n authEvents.emit(\"login.success\", user, tokens, deviceInfo);\n\n return { user, tokens };\n }\n\n /**\n * Log a user out.\n *\n * @param accessToken - access token string to revoke (optional)\n * @param refreshToken - refresh token string to revoke (optional)\n *\n * When no refresh token is supplied, `config.auth.refreshToken.logoutWithoutToken`\n * decides the behavior: `\"revoke-all\"` (default, fail-safe) revokes every\n * refresh token; `\"error\"` requires the caller to pass one.\n */\n public async logout(user: Auth, accessToken?: string, refreshToken?: string): Promise<void> {\n if (accessToken) {\n await this.removeAccessToken(user, accessToken);\n }\n\n if (refreshToken) {\n const token = await this.refreshTokenModel.findForUser(user, refreshToken);\n\n if (token) {\n await this.revokeTokenFamily(token.familyId);\n authEvents.emit(\"session.destroyed\", user, token);\n }\n } else {\n const behavior = authConfig.refreshToken.logoutWithoutToken();\n\n if (behavior === \"error\") {\n throw new Error(\"Refresh token required for logout\");\n }\n\n await this.revokeAllTokens(user);\n authEvents.emit(\"logout.failsafe\", user);\n }\n\n authEvents.emit(\"logout\", user);\n }\n\n /**\n * Remove a specific access token belonging to the user.\n */\n public async removeAccessToken(user: Auth, token: string): Promise<void> {\n await this.accessTokenModel.deleteForUser(user, token);\n }\n\n /**\n * Remove every access token belonging to the user.\n */\n public async removeAllAccessTokens(user: Auth): Promise<void> {\n await this.accessTokenModel.deleteAllForUser(user);\n }\n\n /**\n * Remove a specific refresh token belonging to the user.\n */\n public async removeRefreshToken(user: Auth, token: string): Promise<void> {\n await this.refreshTokenModel.deleteForUser(user, token);\n }\n\n /**\n * Revoke every active refresh token for the user and delete their access\n * tokens — \"log out of all devices\". Each durable family is first marked through its revision coordinator; an\n * event fires per revoked token.\n */\n public async revokeAllTokens(user: Auth): Promise<void> {\n // Upgrade existing installations lazily before the family scan. Include\n // revoked rows: a rotation may have revoked its predecessor while its\n // durable family still needs logout's marker.\n const existingRows = await this.refreshTokenModel.familiesForUser(user);\n\n for (const token of existingRows) {\n await AuthTokenFamily.ensure(user, token.familyId);\n }\n\n const families = await AuthTokenFamily.activeFor(user);\n\n for (const family of families) {\n await this.revokeTokenFamily(family.get<string>(\"family_id\"));\n }\n\n // A deployment can still have rows created before the additive migration\n // ran; retain the old bulk cleanup as a final conservative backstop.\n const legacyTokens = await this.refreshTokenModel.revokeAllFor(user);\n\n for (const token of legacyTokens) {\n authEvents.emit(\"token.revoked\", user, token);\n }\n await this.removeAllAccessTokens(user);\n\n authEvents.emit(\"logout.all\", user);\n }\n\n /**\n * Revoke an entire token family — rotation breach containment.\n */\n public async revokeTokenFamily(familyId: string): Promise<void> {\n let family = await AuthTokenFamily.findByFamilyId(familyId);\n\n if (!family) {\n const legacyToken = await this.refreshTokenModel.findInFamily(familyId);\n\n if (!legacyToken) return;\n\n family = await AuthTokenFamily.ensure(\n {\n id: legacyToken.get(\"user_id\"),\n userType: legacyToken.get<string>(\"user_type\"),\n } as Auth,\n familyId,\n );\n }\n\n const user = {\n id: family.get(\"user_id\"),\n userType: family.get<string>(\"user_type\"),\n } as Auth;\n\n if (family.isRevoked) {\n // Logout and replay containment are idempotent. Re-run cleanup because a\n // request that lost the original race may have observed stale rows.\n const revokedTokens = await this.refreshTokenModel.revokeFamily(familyId);\n await this.accessTokenModel.deleteFamilyAndLegacyForUser(user, familyId);\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"token.familyRevoked\", familyId, revokedTokens),\n );\n\n return;\n }\n\n let revokedTokens: RefreshToken[];\n try {\n revokedTokens = await runTokenFamilyOperation(familyId, async (currentFamily) => {\n const familyUser = {\n id: currentFamily.get(\"user_id\"),\n userType: currentFamily.get<string>(\"user_type\"),\n } as Auth;\n const revision = currentFamily.get<number>(\"revision\");\n const revoked = await AuthTokenFamily.revoke(familyId, revision);\n\n if (revoked !== 1) {\n throw new Error(\"Token family revoke lost its revision transition\");\n }\n\n const familyTokens = await this.refreshTokenModel.revokeFamily(familyId);\n await this.accessTokenModel.deleteFamilyAndLegacyForUser(familyUser, familyId);\n\n return familyTokens;\n });\n } catch (error) {\n if (!(error instanceof TokenFamilyUnavailableError)) throw error;\n\n // Another request committed the revoke between the read above and its\n // CAS. A second logout is successful after idempotent cleanup.\n revokedTokens = await this.refreshTokenModel.revokeFamily(familyId);\n await this.accessTokenModel.deleteFamilyAndLegacyForUser(user, familyId);\n }\n\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"token.familyRevoked\", familyId, revokedTokens),\n );\n }\n\n /**\n * Delete expired tokens (refresh + access) and spent one-time tokens\n * (expired or consumed verification/reset/OTP/passkey rows). Emits `token.expired` per refresh\n * token and `cleanup.completed` with the refresh count. Drives the\n * `auth.cleanup` CLI command.\n */\n public async cleanupExpiredTokens(): Promise<number> {\n const expiredTokens = await this.refreshTokenModel.purgeExpired();\n\n for (const token of expiredTokens) {\n authEvents.emit(\"token.expired\", token);\n }\n\n await this.accessTokenModel.purgeExpired();\n await this.oneTimeTokenModel.purgeSpent();\n\n authEvents.emit(\"cleanup.completed\", expiredTokens.length);\n\n return expiredTokens.length;\n }\n\n /**\n * Find every persisted token — access and refresh — that can never retire\n * itself: an unusable `expires_at`, or a token carrying no `exp` claim.\n *\n * This is the remediation read for the pre-4.12.0 `expiresIn` defect (#25).\n * Nothing else surfaces these rows: `auth.cleanup` selects `expires_at < now`,\n * which an `Invalid Date` never satisfies, and a row whose date column is\n * perfectly fine can still hold a token with no deadline in it. Read-only —\n * pair with {@link purgeNeverExpiringTokens} to act on the answer.\n */\n public async findNeverExpiringTokens(): Promise<{\n accessTokens: AccessToken[];\n refreshTokens: RefreshToken[];\n }> {\n return {\n accessTokens: await this.accessTokenModel.findNeverExpiring(),\n refreshTokens: await this.refreshTokenModel.findNeverExpiring(),\n };\n }\n\n /**\n * Revoke every never-expiring token by deleting its row, emitting\n * `token.revoked` per refresh token. Drives `warlock auth.purge-never-expiring`.\n *\n * Access tokens first: deleting one is immediate revocation, while a refresh\n * token left in place for a moment can only mint an access token through a\n * verifier that now rejects it for the missing `exp`.\n */\n public async purgeNeverExpiringTokens(): Promise<{\n accessTokens: number;\n refreshTokens: number;\n }> {\n const accessTokens = await this.accessTokenModel.purgeNeverExpiring();\n const refreshTokens = await this.refreshTokenModel.purgeNeverExpiring();\n\n // `token.expired` (the event `auth.cleanup` already emits for a removed\n // refresh token) rather than `token.revoked`, which carries a loaded `Auth`\n // this batch path has no reason to fetch a user row for.\n for (const token of refreshTokens) {\n authEvents.emit(\"token.expired\", token);\n }\n\n return { accessTokens: accessTokens.length, refreshTokens: refreshTokens.length };\n }\n\n /**\n * Active, unexpired sessions for the user, newest first.\n */\n public async getActiveSessions(user: Auth): Promise<RefreshToken[]> {\n return this.refreshTokenModel.activeFor(user);\n }\n\n /**\n * `Max-Age` (seconds) `setAuthCookie` should apply, derived from an\n * `AccessTokenOutput`'s `expiresAt` — or `undefined` for a bare token\n * string (a session cookie) or an unparseable `expiresAt`.\n */\n private cookieMaxAgeFromToken(token: string | AccessTokenOutput): number | undefined {\n if (typeof token === \"string\") return undefined;\n\n const expiresAt = new Date(token.expiresAt).getTime();\n\n if (Number.isNaN(expiresAt)) return undefined;\n\n return Math.max(0, Math.round((expiresAt - Date.now()) / 1000));\n }\n\n /**\n * Write the auth cookie on `response` — the write side of the `cookie:<name>`\n * token source `authMiddleware([], \"cookie:<name>\")` already reads. An explicit\n * app-controller call, never a side effect of {@link login}, so an existing\n * bearer-only app never starts emitting `Set-Cookie` just by upgrading.\n *\n * Attribute flags (`HttpOnly`, `SameSite=Lax`, `Secure` outside dev) come\n * from `response.cookie()`'s own `secureCookieDefaults()` floor — this\n * method never overrides them, only `name`/`path`/`maxAge`. `raw: true`\n * writes the token unquoted, so the same string a `cookie:<name>` source\n * reads back is exactly what was issued, not a JSON-wrapped copy.\n *\n * @param token - the token string, or an `AccessTokenOutput` (e.g. from\n * {@link login}) whose `expiresAt` sets `Max-Age` automatically when\n * `options.maxAge` is not given. A bare string with no `options.maxAge`\n * produces a session cookie.\n *\n * @example\n * const { user, tokens } = await authService.login(User, credentials);\n * authService.setAuthCookie(response, tokens.accessToken);\n */\n public setAuthCookie(\n response: CookieWriter,\n token: string | AccessTokenOutput,\n options: SetAuthCookieOptions = {},\n ): void {\n const name = options.name ?? authConfig.cookie.name();\n const path = options.path ?? authConfig.cookie.path();\n const tokenValue = typeof token === \"string\" ? token : token.token;\n const maxAge = options.maxAge ?? this.cookieMaxAgeFromToken(token);\n\n response.cookie(name, tokenValue, {\n raw: true,\n path,\n ...(maxAge !== undefined ? { maxAge } : {}),\n });\n }\n\n /**\n * Clear the auth cookie {@link setAuthCookie} wrote. An explicit\n * app-controller call — pair it with {@link logout} after the token row is\n * revoked. `path` must match what the cookie was set with, or the browser\n * silently ignores the clear (see `response.clearCookie`'s own doc comment).\n *\n * @example\n * await authService.logout(user, accessToken, refreshToken);\n * authService.clearAuthCookie(response);\n */\n public clearAuthCookie(response: CookieWriter, options: ClearAuthCookieOptions = {}): void {\n const name = options.name ?? authConfig.cookie.name();\n const path = options.path ?? authConfig.cookie.path();\n\n response.clearCookie(name, { path });\n }\n\n /**\n * Write the access and refresh cookies of a browser session. Both are\n * `HttpOnly`, `SameSite=Lax`, `Path=/` (the refresh cookie must reach every\n * page) and `Secure` outside development, named by `auth.cookie.name` and\n * `auth.cookie.refreshName`. Each `Max-Age` is derived from its token's\n * `expiresAt`, so the cookie lives exactly as long as the token.\n *\n * @example\n * const { tokens } = await authService.login(User, credentials);\n * authService.setSessionCookies(response, tokens);\n */\n public setSessionCookies(\n response: CookieWriter,\n tokens: { accessToken: AccessTokenOutput; refreshToken: AccessTokenOutput },\n ): void {\n const cookies = [\n [authConfig.cookie.name(), tokens.accessToken],\n [authConfig.cookie.refreshName(), tokens.refreshToken],\n ] as const;\n\n for (const [name, token] of cookies) {\n const maxAge = this.cookieMaxAgeFromToken(token);\n\n response.cookie(name, token.token, {\n raw: true,\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n ...(maxAge !== undefined ? { maxAge } : {}),\n });\n }\n }\n\n /**\n * Clear both session cookies {@link setSessionCookies} wrote, on `Path=/`.\n * Pair it with {@link logout} after the token rows are revoked.\n */\n public clearSessionCookies(response: CookieWriter): void {\n response.clearCookie(authConfig.cookie.name(), { path: \"/\" });\n response.clearCookie(authConfig.cookie.refreshName(), { path: \"/\" });\n }\n}\n\nexport const authService = new AuthService();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth.service.mjs","names":[],"sources":["../../../../../../../auth/src/services/auth.service.ts"],"sourcesContent":["import { Random } from \"@mongez/reinforcements\";\nimport { randomUUID } from \"node:crypto\";\nimport type { ChildModel } from \"@warlock.js/cascade\";\nimport {\n config,\n ForbiddenError,\n hashPassword,\n type Request,\n type Response,\n verifyPassword,\n} from \"@warlock.js/core\";\nimport type {\n AccessTokenOutput,\n AuthCredentials,\n ClearAuthCookieOptions,\n DeviceInfo,\n LoginResult,\n SetAuthCookieOptions,\n TokenPair,\n} from \"../contracts/types\";\nimport { assertCsrfOriginAllowed } from \"../middleware/csrf-origin-check\";\nimport { AccessToken } from \"../models/access-token\";\nimport type { Auth } from \"../models/auth.model\";\nimport { AuthTokenFamily } from \"../models/auth-token-family\";\nimport { OneTimeToken } from \"../models/one-time-token\";\nimport { RefreshToken } from \"../models/refresh-token\";\nimport { authConfig } from \"./auth-config\";\nimport { authEvents } from \"./auth-events\";\nimport { isInvalidCredentialError, jwt } from \"./jwt\";\nimport {\n afterTokenFamilyOperation,\n runTokenFamilyOperation,\n TokenFamilyUnavailableError,\n} from \"./token-family-operation\";\n\nclass AuthService {\n /**\n * Resolve the active access-token model — the package default, or a subclass\n * an app registered under `config.auth.accessToken.model` (e.g. to add a\n * tenant column). The service never references the concrete class directly so\n * an override is a pure config change.\n */\n private get accessTokenModel(): typeof AccessToken {\n return config.key(\"auth.accessToken.model\", AccessToken);\n }\n\n /**\n * Resolve the active refresh-token model (default or registered override).\n */\n private get refreshTokenModel(): typeof RefreshToken {\n return config.key(\"auth.refreshToken.model\", RefreshToken);\n }\n\n /**\n * Resolve the active one-time-token model (default or registered override).\n */\n private get oneTimeTokenModel(): typeof OneTimeToken {\n return config.key(\"auth.oneTimeToken.model\", OneTimeToken);\n }\n\n /**\n * Build the default access-token JWT payload from a user.\n */\n public buildAccessTokenPayload(user: Auth): Record<string, unknown> {\n return {\n id: user.id,\n userType: user.userType,\n created_at: Date.now(),\n };\n }\n\n /** One policy seam keeps account-state checks identical across all auth paths. */\n public async canAuthenticate(user: Auth): Promise<boolean> {\n return authConfig.canAuthenticate(user);\n }\n\n private async assertCanAuthenticate(user: Auth): Promise<void> {\n if (!(await this.canAuthenticate(user))) {\n throw new ForbiddenError(\"This user cannot authenticate.\");\n }\n }\n\n private async issueAccessToken(\n user: Auth,\n payload?: Record<string, unknown>,\n familyId?: string,\n ): Promise<AccessTokenOutput> {\n const data = { ...(payload || this.buildAccessTokenPayload(user)), jti: randomUUID() };\n // Validate before signing so an unusable lifetime cannot mint an immortal token.\n const expiresIn = authConfig.accessToken.expiresInMs();\n\n const token = await jwt.generate(data, { expiresIn });\n const expiresAt = new Date(Date.now() + expiresIn);\n\n if (familyId) {\n await this.accessTokenModel.issue(user, token, expiresAt, familyId);\n } else {\n await this.accessTokenModel.issue(user, token, expiresAt);\n }\n\n return { token, expiresAt: expiresAt.toISOString() };\n }\n\n /**\n * Sign + persist an access token for the user and return the token with its\n * expiry. The expiry is computed locally from `expiresIn` rather than by\n * re-verifying the token we just signed.\n */\n public async generateAccessToken(\n user: Auth,\n payload?: Record<string, unknown>,\n ): Promise<AccessTokenOutput> {\n await this.assertCanAuthenticate(user);\n\n return this.issueAccessToken(user, payload);\n }\n\n private async issueRefreshToken(\n user: Auth,\n deviceInfo?: DeviceInfo,\n ): Promise<RefreshToken | undefined> {\n if (!authConfig.refreshToken.enabled()) return;\n\n // Validate the lifetime first — before the per-user cap is enforced, before\n // anything is signed — so a bad value can never revoke a user's oldest\n // session on its way to failing.\n const expiresIn = authConfig.refreshToken.expiresInMs();\n\n const familyId = deviceInfo?.familyId || Random.string(32);\n\n const family = await AuthTokenFamily.ensure(user, familyId);\n\n if (family.isRevoked) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n\n const payload = {\n userId: user.id,\n userType: user.userType,\n familyId,\n jti: randomUUID(),\n };\n\n const expiresAt = new Date(Date.now() + expiresIn).toISOString();\n\n await this.refreshTokenModel.enforceMax(user, authConfig.refreshToken.maxPerUser());\n\n const token = await jwt.generateRefreshToken(payload, { expiresIn });\n\n return this.refreshTokenModel.issue(user, token, { familyId, expiresAt, deviceInfo });\n }\n\n /**\n * Keep issuance into an explicitly reused family in the same durable\n * transition boundary as rotation and revocation. A new, caller-unspecified\n * family has no concurrent session to protect and retains the lightweight\n * issuance path.\n */\n private async issueInSuppliedFamily<T>(\n user: Auth,\n familyId: string,\n issue: () => Promise<T>,\n ): Promise<T> {\n // Preserve the established error for a family that was already revoked\n // before issuance started. The coordinator covers a revoke that races\n // after this observation.\n const family = await AuthTokenFamily.ensure(user, familyId);\n\n if (family.isRevoked) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n\n try {\n return await runTokenFamilyOperation(familyId, async () => issue());\n } catch (error) {\n // Do not expose a timing-dependent error shape to callers: a family\n // revoked between ensure() and the transaction is the same unusable\n // family condition as one found above.\n if (error instanceof TokenFamilyUnavailableError) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n\n throw error;\n }\n }\n\n /**\n * Sign + persist a refresh token for the user (enforcing the per-user cap\n * first). Resolves to `undefined` when refresh tokens are disabled in config.\n */\n public async createRefreshToken(\n user: Auth,\n deviceInfo?: DeviceInfo,\n ): Promise<RefreshToken | undefined> {\n if (!authConfig.refreshToken.enabled()) return;\n\n await this.assertCanAuthenticate(user);\n\n if (deviceInfo?.familyId) {\n return this.issueInSuppliedFamily(user, deviceInfo.familyId, () =>\n this.issueRefreshToken(user, deviceInfo),\n );\n }\n\n return this.issueRefreshToken(user, deviceInfo);\n }\n\n private async issueTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair> {\n const refreshEnabled = authConfig.refreshToken.enabled();\n const familyId = deviceInfo?.familyId || Random.string(32);\n\n if (refreshEnabled) {\n const family = await AuthTokenFamily.ensure(user, familyId);\n\n if (family.isRevoked) {\n throw new Error(\"Cannot issue tokens for a revoked token family\");\n }\n }\n\n const accessToken = await this.issueAccessToken(\n user,\n deviceInfo?.payload,\n refreshEnabled ? familyId : undefined,\n );\n const refreshToken = await this.issueRefreshToken(\n user,\n refreshEnabled ? { ...deviceInfo, familyId } : deviceInfo,\n );\n\n const tokenPair: TokenPair = {\n accessToken,\n refreshToken: refreshToken\n ? {\n token: refreshToken.get(\"token\"),\n expiresAt: refreshToken.get(\"expires_at\"),\n }\n : undefined,\n };\n\n afterTokenFamilyOperation(() => authEvents.emit(\"token.created\", user, tokenPair));\n\n if (refreshToken) {\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"session.created\", user, refreshToken, deviceInfo),\n );\n }\n\n return tokenPair;\n }\n\n /**\n * Issue both an access and a refresh token, emitting the creation events.\n */\n public async createTokenPair(user: Auth, deviceInfo?: DeviceInfo): Promise<TokenPair> {\n await this.assertCanAuthenticate(user);\n\n if (authConfig.refreshToken.enabled() && deviceInfo?.familyId) {\n return this.issueInSuppliedFamily(user, deviceInfo.familyId, () =>\n this.issueTokenPair(user, deviceInfo),\n );\n }\n\n return this.issueTokenPair(user, deviceInfo);\n }\n\n /**\n * Exchange a refresh token for a new token pair, with rotation + replay\n * detection. A concurrent reuse of the same token loses the atomic revoke and\n * is treated as a breach — the whole family is revoked and the request fails.\n */\n public async refreshTokens(\n refreshTokenString: string,\n deviceInfo?: DeviceInfo,\n ): Promise<TokenPair | null> {\n let decoded: {\n userId: number;\n userType: string;\n familyId: string;\n } | null;\n\n try {\n decoded = await jwt.verifyRefreshToken<{\n userId: number;\n userType: string;\n familyId: string;\n }>(refreshTokenString);\n } catch (error) {\n if (isInvalidCredentialError(error)) return null;\n\n throw error;\n }\n\n if (!decoded) return null;\n\n const presentedToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n\n if (!presentedToken || !this.refreshIdentityMatches(presentedToken, decoded)) return null;\n\n await AuthTokenFamily.ensure(\n { id: decoded.userId, userType: decoded.userType } as Auth,\n presentedToken.familyId,\n );\n return runTokenFamilyOperation(presentedToken.familyId, async () => {\n // Each serializable attempt must read the current row in its own snapshot.\n const refreshToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!refreshToken || !this.refreshIdentityMatches(refreshToken, decoded)) return null;\n if (!refreshToken.isValid) {\n // An already-invalid token may be a replay of a rotated credential.\n if (refreshToken) {\n await this.revokeTokenFamily(refreshToken.familyId);\n }\n\n return null;\n }\n\n const UserModel = config.key(`auth.userType.${decoded.userType}`);\n\n if (!UserModel) {\n throw new Error(`User type ${decoded.userType} is unknown type.`);\n }\n\n const user = (await UserModel.find(decoded.userId)) as Auth | null;\n\n if (!user) return null;\n\n if (!(await this.canAuthenticate(user))) return null;\n\n const rotationEnabled = authConfig.refreshToken.rotation();\n\n if (rotationEnabled) {\n const won = await refreshToken.revokeIfActive();\n\n if (!won) {\n // A concurrent request already rotated this token.\n await this.revokeTokenFamily(refreshToken.familyId);\n\n return null;\n }\n } else {\n await refreshToken.markAsUsed();\n }\n\n const newTokenPair = await this.issueTokenPair(user, {\n ...deviceInfo,\n familyId: refreshToken.familyId,\n });\n\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"token.refreshed\", user, newTokenPair, refreshToken),\n );\n\n return newTokenPair;\n }).catch((error) => {\n if (error instanceof TokenFamilyUnavailableError) return null;\n throw error;\n });\n }\n\n /**\n * Opt-in browser-session renewal. Unlike {@link refreshTokens}, this permits\n * a short duplicate presentation to receive the exact committed successor\n * pair. It is deliberately not used by the legacy/public refresh API.\n */\n public async renewAutomaticSession(\n refreshTokenString: string,\n expectedUserType: string,\n options: { overlapMs?: number; deviceInfo?: DeviceInfo; maxAgeMs?: number } = {},\n ): Promise<TokenPair | null> {\n let decoded: { userId: number; userType: string; familyId: string } | null;\n try {\n decoded = await jwt.verifyRefreshToken<{\n userId: number;\n userType: string;\n familyId: string;\n }>(refreshTokenString);\n } catch (error) {\n if (isInvalidCredentialError(error)) return null;\n throw error;\n }\n\n if (!decoded || decoded.userType !== expectedUserType) return null;\n const presentedToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!presentedToken || !this.refreshIdentityMatches(presentedToken, decoded)) return null;\n\n await AuthTokenFamily.ensure(\n { id: decoded.userId, userType: decoded.userType } as Auth,\n presentedToken.familyId,\n );\n return runTokenFamilyOperation(presentedToken.familyId, async () => {\n // A retry can follow another process's committed rotation. Never reuse\n // a model captured before the transaction or before a failed attempt.\n const oldToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!oldToken || !this.refreshIdentityMatches(oldToken, decoded)) return null;\n if (!oldToken.isValid) {\n const successor = await this.readAutomaticSuccessor(oldToken, decoded, options.overlapMs);\n if (successor) return successor;\n if (\n this.isAutomaticOverlapOpen(oldToken, options.overlapMs) &&\n this.hasSuccessor(oldToken)\n ) {\n return null;\n }\n await this.revokeTokenFamily(oldToken.familyId);\n return null;\n }\n\n // Rotation never extends a family past `maxAgeMs` from its first issue.\n if (\n options.maxAgeMs !== undefined &&\n (await this.familyExceedsMaxAge(oldToken.familyId, options.maxAgeMs))\n ) {\n await this.revokeTokenFamily(oldToken.familyId);\n return null;\n }\n\n const UserModel = config.key(`auth.userType.${decoded.userType}`);\n if (!UserModel) throw new Error(`User type ${decoded.userType} is unknown type.`);\n const user = (await UserModel.find(decoded.userId)) as Auth | null;\n if (!user || !(await this.canAuthenticate(user))) return null;\n\n if (!(await oldToken.revokeIfActive())) {\n // A newer successor is never substituted here. A stale automatic\n // response must fail quietly rather than revoke a legitimate family.\n const currentToken = await this.refreshTokenModel.findByToken(refreshTokenString);\n if (!currentToken) return null;\n const successor = await this.readAutomaticSuccessor(\n currentToken,\n decoded,\n options.overlapMs,\n );\n if (successor) return successor;\n if (\n this.isAutomaticOverlapOpen(currentToken, options.overlapMs) &&\n this.hasSuccessor(currentToken)\n )\n return null;\n await this.revokeTokenFamily(oldToken.familyId);\n return null;\n }\n\n const pair = await this.issueTokenPair(user, {\n ...options.deviceInfo,\n familyId: oldToken.familyId,\n });\n const successorAccess = await this.accessTokenModel.findByToken(pair.accessToken.token);\n const successorRefresh = pair.refreshToken\n ? await this.refreshTokenModel.findByToken(pair.refreshToken.token)\n : null;\n if (!successorAccess || !successorRefresh) {\n throw new Error(\"Automatic renewal did not persist a complete successor pair\");\n }\n\n // `revokeIfActive()` updates the database, not this hydrated instance.\n // Saving that stale instance would write its old `revoked_at: null` back\n // and reopen the predecessor. Update only the successor columns instead.\n const linked = await this.refreshTokenModel.atomic(\n { id: oldToken.id },\n {\n $set: {\n successor_access_token_id: successorAccess.id,\n successor_refresh_token_id: successorRefresh.id,\n },\n },\n );\n if (linked !== 1) throw new Error(\"Automatic renewal could not link its successor pair\");\n afterTokenFamilyOperation(() => authEvents.emit(\"token.refreshed\", user, pair, oldToken));\n return pair;\n }).catch((error) => {\n if (error instanceof TokenFamilyUnavailableError) return null;\n throw error;\n });\n }\n\n /**\n * Whether the family's first issue is older than `maxAgeMs`. The start is the\n * family row's `created_at`, falling back to its oldest refresh token when\n * the row carries none (an upserted legacy family).\n */\n private async familyExceedsMaxAge(familyId: string, maxAgeMs: number): Promise<boolean> {\n const family = await AuthTokenFamily.findByFamilyId(familyId);\n let started: unknown = family?.get(\"created_at\");\n\n if (!started) {\n const oldest = await this.refreshTokenModel\n .query()\n .where({ family_id: familyId })\n .orderBy(\"created_at\", \"asc\")\n .first();\n\n started = oldest?.get(\"created_at\");\n }\n\n const startedAt = started ? new Date(started as string | number | Date).getTime() : NaN;\n\n // Unknown start: nothing to measure against, so the cap cannot apply.\n if (!Number.isFinite(startedAt)) return false;\n\n return Date.now() - startedAt > maxAgeMs;\n }\n\n private automaticOverlapMs(overlapMs: number | undefined): number {\n return typeof overlapMs === \"number\" && Number.isFinite(overlapMs)\n ? Math.max(0, Math.min(10_000, overlapMs))\n : 5000;\n }\n\n private refreshIdentityMatches(\n token: RefreshToken,\n decoded: { userId: string | number; userType: string; familyId: string },\n ): boolean {\n return (\n token.familyId === decoded.familyId &&\n token.get(\"user_id\") === decoded.userId &&\n token.get(\"user_type\") === decoded.userType\n );\n }\n\n private hasSuccessor(oldToken: RefreshToken): boolean {\n return Boolean(\n oldToken.get(\"successor_access_token_id\") && oldToken.get(\"successor_refresh_token_id\"),\n );\n }\n\n private isAutomaticOverlapOpen(oldToken: RefreshToken, overlapMs: number | undefined): boolean {\n const revokedAt = oldToken.get<Date | undefined>(\"revoked_at\");\n const window = this.automaticOverlapMs(overlapMs);\n const elapsed = revokedAt ? Date.now() - new Date(revokedAt).getTime() : NaN;\n return window > 0 && elapsed >= 0 && elapsed <= window;\n }\n\n private async readAutomaticSuccessor(\n oldToken: RefreshToken,\n decoded: { userId: number; userType: string; familyId: string },\n overlapMs: number | undefined,\n ): Promise<TokenPair | null> {\n if (!this.isAutomaticOverlapOpen(oldToken, overlapMs)) return null;\n const accessId = oldToken.get<string | undefined>(\"successor_access_token_id\");\n const refreshId = oldToken.get<string | undefined>(\"successor_refresh_token_id\");\n if (!accessId || !refreshId) return null;\n const [access, refresh] = await Promise.all([\n this.accessTokenModel.find(accessId),\n this.refreshTokenModel.find(refreshId),\n ]);\n if (!access || access.isExpired || !refresh?.isValid) return null;\n if (\n access.get(\"family_id\") !== decoded.familyId ||\n refresh.get(\"family_id\") !== decoded.familyId ||\n access.get(\"user_id\") !== decoded.userId ||\n refresh.get(\"user_id\") !== decoded.userId ||\n access.get(\"user_type\") !== decoded.userType ||\n refresh.get(\"user_type\") !== decoded.userType\n )\n return null;\n const accessExpiry = new Date(access.get(\"expires_at\")).getTime();\n if (!Number.isFinite(accessExpiry) || accessExpiry <= Date.now()) return null;\n return {\n accessToken: { token: access.get(\"token\"), expiresAt: access.get(\"expires_at\") },\n refreshToken: { token: refresh.get(\"token\"), expiresAt: refresh.get(\"expires_at\") },\n };\n }\n\n /**\n * Verify a plaintext password against a stored hash.\n */\n public async verifyPassword(plainPassword: string, hashedPassword: string): Promise<boolean> {\n return verifyPassword(plainPassword, hashedPassword);\n }\n\n /**\n * Hash a plaintext password.\n */\n public async hashPassword(password: string): Promise<string> {\n return hashPassword(password);\n }\n\n /**\n * Resolve a user by credentials, verifying the password. Returns `null` on a\n * missing user or a wrong password, emitting `login.failed` either way.\n */\n public async attemptLogin<T extends Auth>(\n Model: ChildModel<T>,\n data: AuthCredentials,\n ): Promise<T | null> {\n const { password, ...otherData } = data;\n\n authEvents.emit(\"login.attempt\", otherData);\n\n const user = (await Model.first(otherData)) as T | null;\n\n if (!user) {\n authEvents.emit(\"login.failed\", otherData, \"User not found\");\n\n return null;\n }\n\n if (!(await this.verifyPassword(password, user.string(\"password\")!))) {\n authEvents.emit(\"login.failed\", otherData, \"Invalid password\");\n\n return null;\n }\n\n if (!(await this.canAuthenticate(user))) {\n authEvents.emit(\"login.failed\", otherData, \"Authentication not allowed\");\n\n return null;\n }\n\n return user;\n }\n\n /**\n * Full login flow: validate credentials, issue tokens, emit events. Returns\n * the user + token pair on success, `null` on failure.\n */\n public async login<T extends Auth>(\n Model: ChildModel<T>,\n credentials: AuthCredentials,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T> | null> {\n const user = await this.attemptLogin(Model, credentials);\n\n if (!user) {\n return null;\n }\n\n return this.finalizeLogin(user, deviceInfo);\n }\n\n /**\n * Log in a user some OTHER method already authenticated — a provider\n * callback, a passkey assertion, a one-time code. Applies the\n * `auth.canAuthenticate` policy (403 on refusal), then produces exactly\n * what {@link login} produces: the same tokens, rows and events. Pair with\n * {@link setAuthCookie} for a cookie session, as with password login.\n *\n * Never call this for a user you have not authenticated.\n */\n public async completeLogin<T extends Auth>(\n user: T,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T>> {\n await this.assertCanAuthenticate(user);\n\n return this.finalizeLogin(user, deviceInfo);\n }\n\n /**\n * Cookie login for a browser: same-origin check, credential login, then both\n * session cookies. Unlike the default CSRF guard, the Origin/Referer check\n * runs even when the request carries no cookies (login CSRF): a missing or\n * cross-site Origin/Referer throws `CsrfOriginMismatchError` before any\n * credential is looked at. Returns `null` on bad credentials.\n */\n public async loginWithSessionCookies<T extends Auth>(\n request: Request,\n response: { [K in \"cookie\" | \"clearCookie\"]: (...args: Parameters<Response[K]>) => unknown },\n Model: ChildModel<T>,\n credentials: AuthCredentials,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T> | null> {\n assertCsrfOriginAllowed(request);\n\n const result = await this.login(Model, credentials, deviceInfo);\n\n if (!result) return null;\n\n if (result.tokens.refreshToken) {\n this.setSessionCookies(response, {\n accessToken: result.tokens.accessToken,\n refreshToken: result.tokens.refreshToken,\n });\n } else {\n this.setAuthCookie(response, result.tokens.accessToken);\n }\n\n return result;\n }\n\n /** The one token-issuing tail every login method shares. */\n private async finalizeLogin<T extends Auth>(\n user: T,\n deviceInfo?: DeviceInfo,\n ): Promise<LoginResult<T>> {\n if (!authConfig.refreshToken.enabled()) {\n const accessToken = await this.issueAccessToken(user, deviceInfo?.payload);\n\n return { user, tokens: { accessToken } };\n }\n\n const tokens = await this.issueTokenPair(user, deviceInfo);\n\n authEvents.emit(\"login.success\", user, tokens, deviceInfo);\n\n return { user, tokens };\n }\n\n /**\n * Log a user out.\n *\n * @param accessToken - access token string to revoke (optional)\n * @param refreshToken - refresh token string to revoke (optional)\n *\n * When no refresh token is supplied, `config.auth.refreshToken.logoutWithoutToken`\n * decides the behavior: `\"revoke-all\"` (default, fail-safe) revokes every\n * refresh token; `\"error\"` requires the caller to pass one.\n */\n public async logout(user: Auth, accessToken?: string, refreshToken?: string): Promise<void> {\n if (accessToken) {\n await this.removeAccessToken(user, accessToken);\n }\n\n if (refreshToken) {\n const token = await this.refreshTokenModel.findForUser(user, refreshToken);\n\n if (token) {\n await this.revokeTokenFamily(token.familyId);\n authEvents.emit(\"session.destroyed\", user, token);\n }\n } else {\n const behavior = authConfig.refreshToken.logoutWithoutToken();\n\n if (behavior === \"error\") {\n throw new Error(\"Refresh token required for logout\");\n }\n\n await this.revokeAllTokens(user);\n authEvents.emit(\"logout.failsafe\", user);\n }\n\n authEvents.emit(\"logout\", user);\n }\n\n /**\n * Remove a specific access token belonging to the user.\n */\n public async removeAccessToken(user: Auth, token: string): Promise<void> {\n await this.accessTokenModel.deleteForUser(user, token);\n }\n\n /**\n * Remove every access token belonging to the user.\n */\n public async removeAllAccessTokens(user: Auth): Promise<void> {\n await this.accessTokenModel.deleteAllForUser(user);\n }\n\n /**\n * Remove a specific refresh token belonging to the user.\n */\n public async removeRefreshToken(user: Auth, token: string): Promise<void> {\n await this.refreshTokenModel.deleteForUser(user, token);\n }\n\n /**\n * Revoke every active refresh token for the user and delete their access\n * tokens — \"log out of all devices\". Each durable family is first marked through its revision coordinator; an\n * event fires per revoked token.\n */\n public async revokeAllTokens(user: Auth): Promise<void> {\n // Upgrade existing installations lazily before the family scan. Include\n // revoked rows: a rotation may have revoked its predecessor while its\n // durable family still needs logout's marker.\n const existingRows = await this.refreshTokenModel.familiesForUser(user);\n\n for (const token of existingRows) {\n await AuthTokenFamily.ensure(user, token.familyId);\n }\n\n const families = await AuthTokenFamily.activeFor(user);\n\n for (const family of families) {\n await this.revokeTokenFamily(family.get<string>(\"family_id\"));\n }\n\n // A deployment can still have rows created before the additive migration\n // ran; retain the old bulk cleanup as a final conservative backstop.\n const legacyTokens = await this.refreshTokenModel.revokeAllFor(user);\n\n for (const token of legacyTokens) {\n authEvents.emit(\"token.revoked\", user, token);\n }\n await this.removeAllAccessTokens(user);\n\n authEvents.emit(\"logout.all\", user);\n }\n\n /**\n * Revoke an entire token family — rotation breach containment.\n */\n public async revokeTokenFamily(familyId: string): Promise<void> {\n let family = await AuthTokenFamily.findByFamilyId(familyId);\n\n if (!family) {\n const legacyToken = await this.refreshTokenModel.findInFamily(familyId);\n\n if (!legacyToken) return;\n\n family = await AuthTokenFamily.ensure(\n {\n id: legacyToken.get(\"user_id\"),\n userType: legacyToken.get<string>(\"user_type\"),\n } as Auth,\n familyId,\n );\n }\n\n const user = {\n id: family.get(\"user_id\"),\n userType: family.get<string>(\"user_type\"),\n } as Auth;\n\n if (family.isRevoked) {\n // Logout and replay containment are idempotent. Re-run cleanup because a\n // request that lost the original race may have observed stale rows.\n const revokedTokens = await this.refreshTokenModel.revokeFamily(familyId);\n await this.accessTokenModel.deleteFamilyAndLegacyForUser(user, familyId);\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"token.familyRevoked\", familyId, revokedTokens),\n );\n\n return;\n }\n\n let revokedTokens: RefreshToken[];\n try {\n revokedTokens = await runTokenFamilyOperation(familyId, async (currentFamily) => {\n const familyUser = {\n id: currentFamily.get(\"user_id\"),\n userType: currentFamily.get<string>(\"user_type\"),\n } as Auth;\n const revision = currentFamily.get<number>(\"revision\");\n const revoked = await AuthTokenFamily.revoke(familyId, revision);\n\n if (revoked !== 1) {\n throw new Error(\"Token family revoke lost its revision transition\");\n }\n\n const familyTokens = await this.refreshTokenModel.revokeFamily(familyId);\n await this.accessTokenModel.deleteFamilyAndLegacyForUser(familyUser, familyId);\n\n return familyTokens;\n });\n } catch (error) {\n if (!(error instanceof TokenFamilyUnavailableError)) throw error;\n\n // Another request committed the revoke between the read above and its\n // CAS. A second logout is successful after idempotent cleanup.\n revokedTokens = await this.refreshTokenModel.revokeFamily(familyId);\n await this.accessTokenModel.deleteFamilyAndLegacyForUser(user, familyId);\n }\n\n afterTokenFamilyOperation(() =>\n authEvents.emit(\"token.familyRevoked\", familyId, revokedTokens),\n );\n }\n\n /**\n * Delete expired tokens (refresh + access) and spent one-time tokens\n * (expired or consumed verification/reset/OTP/passkey rows). Emits `token.expired` per refresh\n * token and `cleanup.completed` with the refresh count. Drives the\n * `auth.cleanup` CLI command.\n */\n public async cleanupExpiredTokens(): Promise<number> {\n const expiredTokens = await this.refreshTokenModel.purgeExpired();\n\n for (const token of expiredTokens) {\n authEvents.emit(\"token.expired\", token);\n }\n\n await this.accessTokenModel.purgeExpired();\n await this.oneTimeTokenModel.purgeSpent();\n\n authEvents.emit(\"cleanup.completed\", expiredTokens.length);\n\n return expiredTokens.length;\n }\n\n /**\n * Find every persisted token — access and refresh — that can never retire\n * itself: an unusable `expires_at`, or a token carrying no `exp` claim.\n *\n * This is the remediation read for the pre-4.12.0 `expiresIn` defect (#25).\n * Nothing else surfaces these rows: `auth.cleanup` selects `expires_at < now`,\n * which an `Invalid Date` never satisfies, and a row whose date column is\n * perfectly fine can still hold a token with no deadline in it. Read-only —\n * pair with {@link purgeNeverExpiringTokens} to act on the answer.\n */\n public async findNeverExpiringTokens(): Promise<{\n accessTokens: AccessToken[];\n refreshTokens: RefreshToken[];\n }> {\n return {\n accessTokens: await this.accessTokenModel.findNeverExpiring(),\n refreshTokens: await this.refreshTokenModel.findNeverExpiring(),\n };\n }\n\n /**\n * Revoke every never-expiring token by deleting its row, emitting\n * `token.revoked` per refresh token. Drives `warlock auth.purge-never-expiring`.\n *\n * Access tokens first: deleting one is immediate revocation, while a refresh\n * token left in place for a moment can only mint an access token through a\n * verifier that now rejects it for the missing `exp`.\n */\n public async purgeNeverExpiringTokens(): Promise<{\n accessTokens: number;\n refreshTokens: number;\n }> {\n const accessTokens = await this.accessTokenModel.purgeNeverExpiring();\n const refreshTokens = await this.refreshTokenModel.purgeNeverExpiring();\n\n // `token.expired` (the event `auth.cleanup` already emits for a removed\n // refresh token) rather than `token.revoked`, which carries a loaded `Auth`\n // this batch path has no reason to fetch a user row for.\n for (const token of refreshTokens) {\n authEvents.emit(\"token.expired\", token);\n }\n\n return { accessTokens: accessTokens.length, refreshTokens: refreshTokens.length };\n }\n\n /**\n * Active, unexpired sessions for the user, newest first.\n */\n public async getActiveSessions(user: Auth): Promise<RefreshToken[]> {\n return this.refreshTokenModel.activeFor(user);\n }\n\n /**\n * `Max-Age` (seconds) `setAuthCookie` should apply, derived from an\n * `AccessTokenOutput`'s `expiresAt` — or `undefined` for a bare token\n * string (a session cookie) or an unparseable `expiresAt`.\n */\n private cookieMaxAgeFromToken(token: string | AccessTokenOutput): number | undefined {\n if (typeof token === \"string\") return undefined;\n\n const expiresAt = new Date(token.expiresAt).getTime();\n\n if (Number.isNaN(expiresAt)) return undefined;\n\n return Math.max(0, Math.round((expiresAt - Date.now()) / 1000));\n }\n\n /**\n * Write the auth cookie on `response` — the write side of the `cookie:<name>`\n * token source `authMiddleware([], \"cookie:<name>\")` already reads. An explicit\n * app-controller call, never a side effect of {@link login}, so an existing\n * bearer-only app never starts emitting `Set-Cookie` just by upgrading.\n *\n * Attribute flags (`HttpOnly`, `SameSite=Lax`, `Secure` outside dev) come\n * from `response.cookie()`'s own `secureCookieDefaults()` floor — this\n * method never overrides them, only `name`/`path`/`maxAge`. `raw: true`\n * writes the token unquoted, so the same string a `cookie:<name>` source\n * reads back is exactly what was issued, not a JSON-wrapped copy.\n *\n * @param token - the token string, or an `AccessTokenOutput` (e.g. from\n * {@link login}) whose `expiresAt` sets `Max-Age` automatically when\n * `options.maxAge` is not given. A bare string with no `options.maxAge`\n * produces a session cookie.\n *\n * @example\n * const { user, tokens } = await authService.login(User, credentials);\n * authService.setAuthCookie(response, tokens.accessToken);\n */\n public setAuthCookie(\n response: { [K in \"cookie\" | \"clearCookie\"]: (...args: Parameters<Response[K]>) => unknown },\n token: string | AccessTokenOutput,\n options: SetAuthCookieOptions = {},\n ): void {\n const name = options.name ?? authConfig.cookie.name();\n const path = options.path ?? authConfig.cookie.path();\n const tokenValue = typeof token === \"string\" ? token : token.token;\n const maxAge = options.maxAge ?? this.cookieMaxAgeFromToken(token);\n\n response.cookie(name, tokenValue, {\n raw: true,\n path,\n ...(maxAge !== undefined ? { maxAge } : {}),\n });\n }\n\n /**\n * Clear the auth cookie {@link setAuthCookie} wrote. An explicit\n * app-controller call — pair it with {@link logout} after the token row is\n * revoked. `path` must match what the cookie was set with, or the browser\n * silently ignores the clear (see `response.clearCookie`'s own doc comment).\n *\n * @example\n * await authService.logout(user, accessToken, refreshToken);\n * authService.clearAuthCookie(response);\n */\n public clearAuthCookie(response: { [K in \"cookie\" | \"clearCookie\"]: (...args: Parameters<Response[K]>) => unknown }, options: ClearAuthCookieOptions = {}): void {\n const name = options.name ?? authConfig.cookie.name();\n const path = options.path ?? authConfig.cookie.path();\n\n response.clearCookie(name, { path });\n }\n\n /**\n * Write the access and refresh cookies of a browser session. Both are\n * `HttpOnly`, `SameSite=Lax`, `Path=/` (the refresh cookie must reach every\n * page) and `Secure` outside development, named by `auth.cookie.name` and\n * `auth.cookie.refreshName`. Each `Max-Age` is derived from its token's\n * `expiresAt`, so the cookie lives exactly as long as the token.\n *\n * @example\n * const { tokens } = await authService.login(User, credentials);\n * authService.setSessionCookies(response, tokens);\n */\n public setSessionCookies(\n response: { [K in \"cookie\" | \"clearCookie\"]: (...args: Parameters<Response[K]>) => unknown },\n tokens: { accessToken: AccessTokenOutput; refreshToken: AccessTokenOutput },\n ): void {\n const cookies = [\n [authConfig.cookie.name(), tokens.accessToken],\n [authConfig.cookie.refreshName(), tokens.refreshToken],\n ] as const;\n\n for (const [name, token] of cookies) {\n const maxAge = this.cookieMaxAgeFromToken(token);\n\n response.cookie(name, token.token, {\n raw: true,\n httpOnly: true,\n sameSite: \"lax\",\n path: \"/\",\n ...(maxAge !== undefined ? { maxAge } : {}),\n });\n }\n }\n\n /**\n * Clear both session cookies {@link setSessionCookies} wrote, on `Path=/`.\n * Pair it with {@link logout} after the token rows are revoked.\n */\n public clearSessionCookies(response: { [K in \"cookie\" | \"clearCookie\"]: (...args: Parameters<Response[K]>) => unknown }): void {\n response.clearCookie(authConfig.cookie.name(), { path: \"/\" });\n response.clearCookie(authConfig.cookie.refreshName(), { path: \"/\" });\n }\n}\n\nexport const authService = new AuthService();\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"ms": "^2.1.3"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
|
-
"@warlock.js/fs": "5.
|
|
16
|
-
"@warlock.js/cache": "5.
|
|
17
|
-
"@warlock.js/cascade": "5.
|
|
18
|
-
"@warlock.js/core": "5.
|
|
19
|
-
"@warlock.js/logger": "5.
|
|
20
|
-
"@warlock.js/seal": "5.
|
|
21
|
-
"@warlock.js/notifications": "5.
|
|
15
|
+
"@warlock.js/fs": "5.23.1",
|
|
16
|
+
"@warlock.js/cache": "5.23.1",
|
|
17
|
+
"@warlock.js/cascade": "5.23.1",
|
|
18
|
+
"@warlock.js/core": "5.23.1",
|
|
19
|
+
"@warlock.js/logger": "5.23.1",
|
|
20
|
+
"@warlock.js/seal": "5.23.1",
|
|
21
|
+
"@warlock.js/notifications": "5.23.1",
|
|
22
22
|
"@simplewebauthn/server": "^13.1.0",
|
|
23
23
|
"jose": "^6.1.0"
|
|
24
24
|
},
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
],
|
|
48
48
|
"author": "hassanzohdy",
|
|
49
49
|
"license": "MIT",
|
|
50
|
-
"version": "5.
|
|
50
|
+
"version": "5.23.1",
|
|
51
51
|
"type": "module",
|
|
52
52
|
"main": "./esm/index.mjs",
|
|
53
53
|
"module": "./esm/index.mjs",
|