@warlock.js/auth 5.25.0 → 5.26.0
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 +19 -9
- package/esm/contracts/index.d.mts +1 -1
- package/esm/contracts/request-locals.d.mts +1 -1
- package/esm/contracts/types.d.mts +8 -3
- package/esm/contracts/types.d.mts.map +1 -1
- package/esm/contracts/types.mjs.map +1 -1
- package/esm/index.d.mts +3 -3
- package/esm/index.mjs +2 -2
- package/esm/services/auth-config.mjs +3 -1
- package/esm/services/auth-config.mjs.map +1 -1
- package/esm/services/auth.service.d.mts +9 -4
- package/esm/services/auth.service.d.mts.map +1 -1
- package/esm/services/auth.service.mjs +35 -8
- package/esm/services/auth.service.mjs.map +1 -1
- package/esm/services/index.d.mts +1 -1
- package/esm/services/index.mjs +1 -1
- package/esm/services/resolve-request-user.d.mts +3 -2
- package/esm/services/resolve-request-user.d.mts.map +1 -1
- package/esm/services/resolve-request-user.mjs +8 -2
- package/esm/services/resolve-request-user.mjs.map +1 -1
- package/esm/session/page-session.d.mts +3 -1
- package/esm/session/page-session.d.mts.map +1 -1
- package/esm/session/page-session.mjs +2 -1
- package/esm/session/page-session.mjs.map +1 -1
- package/llms-full.txt +39 -20
- package/package.json +8 -8
- package/skills/handle-login-and-logout/SKILL.md +36 -21
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,16 @@ All notable changes to `@warlock.js/auth` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.26.0 - 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Cookie sessions now support `auth.cookie.cookieDomain(request)` and `pageSession`/`sessionMiddleware`'s `cookieDomain(request)` override. Returning a Domain scopes issued and renewed cookies to it; returning `undefined` preserves host-only cookies. Clearing a Domain-scoped session also clears the legacy host-only cookie names. When configured, pass `request` to `setSessionCookies(response, tokens, request)`, `clearSessionCookies(response, request)`, and `clearAuthCookie(response, options, request)`; omitting it throws `CookieDomainRequestRequiredError` rather than issuing or clearing a host-only cookie.
|
|
12
|
+
|
|
13
|
+
### Notes
|
|
14
|
+
|
|
15
|
+
- A page session configured with multiple `userTypes` cannot renew its cookie: the resolver only supplies a renewal user type when exactly one type is allowed, so it reads the session but skips automatic renewal.
|
|
16
|
+
|
|
7
17
|
## 5.25.0 - 2026-09-28
|
|
8
18
|
|
|
9
19
|
### Fixed
|
|
@@ -65,10 +75,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
65
75
|
- **BREAKING:** `authMiddleware` takes an ordered header/cookie `sources` list; a present Authorization header always wins and never falls back to the cookie, so an invalid header now returns `401`.
|
|
66
76
|
- JWTs carry a random `jti`, so tokens issued in the same second differ.
|
|
67
77
|
|
|
68
|
-
|
|
69
78
|
### Changed
|
|
70
79
|
|
|
71
80
|
- Lockstep release maintenance and dependency refresh.
|
|
81
|
+
|
|
72
82
|
## 5.20.0 - 2026-09-24
|
|
73
83
|
|
|
74
84
|
### Changed
|
|
@@ -258,17 +268,17 @@ changelogs.
|
|
|
258
268
|
|
|
259
269
|
- **A token with no `exp` claim is now rejected instead of being accepted forever.** `fast-jwt` has no deadline to check on such a token, so verification simply succeeds — measured against `fast-jwt@6.2.4`, a token with no `exp` verifies unchanged at `clockTimestamp` + 100 years. Both `jwt.verify` and `jwt.verifyRefreshToken` now require an `exp` claim.
|
|
260
270
|
|
|
261
|
-
This is deliberately on the
|
|
271
|
+
This is deliberately on the _verify_ side rather than the issue side: the tokens that lack an `exp` were minted by a version that no longer runs, so a guard on issue would not reach a single one of them. A caller may add to `requiredClaims`, never subtract — `jwt.verify(token, { requiredClaims: ["iat"] })` still requires `exp` too.
|
|
262
272
|
|
|
263
|
-
- **The persisted `expires_at` is now enforced on every request.** `authMiddleware` previously checked only that the access-token row
|
|
273
|
+
- **The persisted `expires_at` is now enforced on every request.** `authMiddleware` previously checked only that the access-token row _existed_; a row whose own expiry had passed still opened the gate, because nothing ever asked. The row is now checked against the clock and deleted on rejection.
|
|
264
274
|
|
|
265
|
-
These are two independent defences. The first catches a token whose
|
|
275
|
+
These are two independent defences. The first catches a token whose _claims_ carry no deadline; the second catches a token whose _row_ says the deadline has passed (a logged-out or expired session whose JWT is still within its own lifetime). Neither subsumes the other.
|
|
266
276
|
|
|
267
277
|
### Added
|
|
268
278
|
|
|
269
279
|
- **`warlock auth.purge-never-expiring`** — remediation for rows written by the `expiresIn` defect below. Finds every access- and refresh-token row that can never retire itself, on two independent signals: an `expires_at` that is missing or unparseable, and a persisted token carrying no `exp` claim. Reports `id`, `user_id`, `user_type` and `expires_at` per row (never the token string — it is a live credential until the command removes it), then deletes them. Pass `--dry-run` to report without deleting.
|
|
270
280
|
|
|
271
|
-
`auth.cleanup` cannot find these rows and never could: its predicate is `expires_at < now`, and an `Invalid Date` compares `false` against
|
|
281
|
+
`auth.cleanup` cannot find these rows and never could: its predicate is `expires_at < now`, and an `Invalid Date` compares `false` against _every_ date, so such a row satisfies neither `< now` nor `> now`. It is outside the reach of every date predicate rather than merely wrong. The "no `exp` claim" signal is not in a column at all.
|
|
272
282
|
|
|
273
283
|
Exposed programmatically as `authService.findNeverExpiringTokens()` (read-only) and `authService.purgeNeverExpiringTokens()`, and per model as `findNeverExpiring()` / `purgeNeverExpiring()`.
|
|
274
284
|
|
|
@@ -288,8 +298,8 @@ Affected users must log in again. To check **without deploying anything**, the r
|
|
|
288
298
|
- **MongoDB** — `bson` serialises an `Invalid Date` to **epoch 0**, so the row reads `1970-01-01T00:00:00Z` rather than an invalid value:
|
|
289
299
|
|
|
290
300
|
```js
|
|
291
|
-
db.access_tokens.find({ expires_at: { $lte: new Date(0) } })
|
|
292
|
-
db.refresh_tokens.find({ expires_at: { $lte: new Date(0) } })
|
|
301
|
+
db.access_tokens.find({ expires_at: { $lte: new Date(0) } });
|
|
302
|
+
db.refresh_tokens.find({ expires_at: { $lte: new Date(0) } });
|
|
293
303
|
```
|
|
294
304
|
|
|
295
305
|
- **PostgreSQL** — `pg` serialises an `Invalid Date` to the literal `0NaN-NaN-NaNTNaN:NaN:NaN.NaN+NaN:NaN`, which a `timestamp` column rejects, so the `INSERT` most likely failed and no row was written (the token was still signed and returned to the client — it is then rejected by the row check, since it has no row). Any rows that did land are visible as:
|
|
@@ -299,7 +309,7 @@ Affected users must log in again. To check **without deploying anything**, the r
|
|
|
299
309
|
SELECT id, user_id, user_type, expires_at FROM refresh_tokens WHERE expires_at IS NULL OR expires_at <= 'epoch';
|
|
300
310
|
```
|
|
301
311
|
|
|
302
|
-
These queries find the row-level shape only. The definitive test —
|
|
312
|
+
These queries find the row-level shape only. The definitive test — _does the stored token carry an `exp` claim at all_ — reads the JWT rather than a column, which is why the command exists and why it is the recommended path.
|
|
303
313
|
|
|
304
314
|
### Fixed
|
|
305
315
|
|
|
@@ -315,7 +325,7 @@ These queries find the row-level shape only. The definitive test — *does the s
|
|
|
315
325
|
|
|
316
326
|
- **`expiresIn: "0d"` (and any non-positive duration) is rejected too.** It is truthy and parses cleanly to `0`, so it survived any guard that only rejects `undefined` — and `fast-jwt` skips its own validation for `0`, emitting a token with **no `exp` claim** while the persisted row claims it expired immediately.
|
|
317
327
|
|
|
318
|
-
- A bare number (`expiresIn: 2592000`) is now rejected instead of silently corrupting the expiry. `ms`
|
|
328
|
+
- A bare number (`expiresIn: 2592000`) is now rejected instead of silently corrupting the expiry. `ms` _formats_ numbers rather than parsing them (`2592000` ⇒ `"43m"`), which then poisoned `Date.now() + expiresIn` into `Invalid Date`. Write `"30d"`.
|
|
319
329
|
|
|
320
330
|
### Changed
|
|
321
331
|
|
|
@@ -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, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair } from "./types.mjs";
|
|
2
|
+
import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CookieDomainResolver, 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";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `request.locals.user`.
|
|
5
5
|
*
|
|
6
6
|
* Moved here from `@warlock.js/core` in 5.12.0 (core's `Request` no longer
|
|
7
|
-
* has a `user` property at all
|
|
7
|
+
* has a `user` property at all). Empty by
|
|
8
8
|
* default, exactly as it was in core, so any shape is assignable at the
|
|
9
9
|
* declaration site; an app narrows it via module augmentation:
|
|
10
10
|
*
|
|
@@ -3,6 +3,7 @@ import { AccessToken } from "../models/access-token/access-token.model.mjs";
|
|
|
3
3
|
import { OneTimeToken } from "../models/one-time-token/one-time-token.model.mjs";
|
|
4
4
|
import { OtpConfig, PasskeysConfig, ProvidersConfig } from "./providers.mjs";
|
|
5
5
|
import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
|
|
6
|
+
import { Request } from "@warlock.js/core";
|
|
6
7
|
import { ChildModel } from "@warlock.js/cascade";
|
|
7
8
|
import { Algorithm } from "fast-jwt";
|
|
8
9
|
|
|
@@ -373,6 +374,8 @@ type LoginResult<UserType extends Auth> = {
|
|
|
373
374
|
user: UserType;
|
|
374
375
|
tokens: TokenPair;
|
|
375
376
|
};
|
|
377
|
+
/** Selects a cookie Domain for one request; return `undefined` for a host-only cookie. */
|
|
378
|
+
type CookieDomainResolver = (request: Request) => string | undefined;
|
|
376
379
|
/**
|
|
377
380
|
* Configuration for the cookie an app opts into via `authService.setAuthCookie`
|
|
378
381
|
* / `clearAuthCookie` and reads back with `authMiddleware([], "cookie:<name>")`.
|
|
@@ -397,7 +400,8 @@ type CookieAuthConfig = {
|
|
|
397
400
|
* Cookie `Path` attribute.
|
|
398
401
|
* @default "/"
|
|
399
402
|
*/
|
|
400
|
-
path?: string;
|
|
403
|
+
path?: string; /** Select a request-scoped Domain; `undefined` keeps the cookie host-only. */
|
|
404
|
+
cookieDomain?: CookieDomainResolver;
|
|
401
405
|
};
|
|
402
406
|
/**
|
|
403
407
|
* CSRF Origin-check configuration. Read via
|
|
@@ -419,7 +423,8 @@ type CsrfConfig = {
|
|
|
419
423
|
*/
|
|
420
424
|
type SetAuthCookieOptions = {
|
|
421
425
|
/** Cookie name; defaults to `auth.cookie.name` (package default `"access_token"`). */name?: string; /** Cookie `Path`; defaults to `auth.cookie.path` (package default `"/"`). */
|
|
422
|
-
path?: string;
|
|
426
|
+
path?: string; /** Cookie `Domain` attribute. Omit for a host-only cookie. */
|
|
427
|
+
domain?: string;
|
|
423
428
|
/**
|
|
424
429
|
* `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would
|
|
425
430
|
* otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a
|
|
@@ -434,5 +439,5 @@ type ClearAuthCookieOptions = {
|
|
|
434
439
|
path?: string;
|
|
435
440
|
};
|
|
436
441
|
//#endregion
|
|
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 };
|
|
442
|
+
export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CookieDomainResolver, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair };
|
|
438
443
|
//# sourceMappingURL=types.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":";;;;;;;;;;;;;;;AA4BA;;;;AAA0B;AAO1B;;;;AAAsC;AAGtC;;;;cAVa,aAAA;AAab;;;;AAEK;AAFL,KANY,0BAAA;;KAGA,SAAA;;UAGK,wBAAA;EACf,MAAA;EACA,GAAG;AAAA;;UAIY,+BAAA,SAAwC,wBAAwB;EAiBrE;EAfV,SAAS;AAAA;;UAIM,qBAAA,SAA8B,OAAA,CAAQ,wBAAA;EACrD,QAAA;EAAA;;;;;;;EAQA,OAAA,GAAU,wBAAA;EAG6B;EADvC,OAAA,GAAU,+BAAA;EACV,QAAA;IAAa,EAAA;IAAY,cAAA;EAAA;AAAA;;KAIf,eAAA,IAAmB,IAAA,EAAM,IAAA,eAAmB,OAAO;;KAGnD,eAAA,GAAkB,MAAM;EAClC,QAAQ;AAAA;;;AAAA;AASV;;;KAAY,iBAAA;EAMV;;;;;EAAA,KAAA,UAAe,WAAA;EAmBN;AAAA;AAMX;EArBE,MAAA;;;;;EAKA,SAAA,GAAY,SAAS;EA+BrB;;;;;;;AAuB+C;AASjD;EArDE,SAAA;AAAA;;;;KAMU,kBAAA;EAkDV;;;;EA7CA,KAAA,UAAe,YAAA;EAkDb;;;;EA7CF,OAAA;EA+CiD;AAYnD;;;EAtDE,MAAA;EAkEc;AAMhB;;;;;;EAhEE,SAAA;EAsEA;;AAAG;AAOL;EAxEE,QAAA;;;;;EAKA,UAAA;EAoEuB;;;AAA4B;EA/DnD,kBAAA,GAAqB,0BAA0B;AAAA;;;;;;;KASrC,eAAA;EACV,MAAA;EACA,SAAA,GAAY,SAAA;EACZ,SAAA;EACA,OAAA;IACE,MAAA;IACA,OAAA;IACA,SAAA;IACA,QAAA;IACA,UAAA;IACA,kBAAA,GAAqB,0BAA0B;EAAA;AAAA;;;;;;;;;KAYvC,cAAA;EA2DkB;AAM9B;;;;EA3DE,SAAA;EAgFc;;;;;EA1Ed,cAAc;AAAA;;;;KAMJ,4BAAA;EAoEkB,iFAlE5B,KAAA,UAwEU;EAtEV,SAAA,EAAW,IAAI;EAEf,GAAA;AAAA;AAyEF;;;;AAAA,KAlEY,gBAAA;EACV,IAAA,GAAO,EAAA,EAAI,IAAA,EAAM,IAAA,EAAM,4BAA4B;AAAA;;KAIzC,sBAAA,IAA0B,KAAA,UAAe,IAAA,EAAM,IAAI;;KAGnD,cAAA,IAAkB,IAAA,EAAM,IAAA,EAAM,aAAA,oBAAiC,OAAO;;;;KAKtE,uBAAA;EA+GC;;;;EA1GX,SAAA;EAqDA;;;;;EA/CA,KAAA,WAwDkB;EAtDlB,YAAA,GAAe,gBAAA,EA0DD;EAxDd,GAAA,GAAM,sBAAsB;AAAA;;;;KAMlB,mBAAA;EAsEV;;;;EAjEA,SAAA;EAyEgB;;;;EApEhB,eAAA,WAgFA;EA9EA,YAAA,GAAe,gBAAA,EAkFf;EAhFA,GAAA,GAAM,sBAAA;EAoFN;;;AAAqB;AAGvB;;EAhFE,WAAA,GAAc,cAAA;AAAA;AAwFL;AAMX;;AANW,KAlFC,kBAAA;EAgGsB,kEA9FhC,KAAA,UAAe,YAAY;AAAA;AAAA,KAGjB,kBAAA;EA2FK;;AAAiB;AAMlC;EA5FE,QAAA;IAAA,CACG,QAAA,WAAmB,UAAA,CAAW,IAAA;EAAA,GA+FjC;EA5FA,eAAA;EAoGA;;;;EA/FA,eAAA,GAAkB,eAAA;EAwGF;AAGlB;;EAvGE,WAAA,GAAc,iBAAA;EAuGyB;;;EAnGvC,YAAA,GAAe,kBAAA;EAqGE;;;;;EA/FjB,QAAA,GAAW,cAAA;EA+FH;;AAAS;AAInB;;EA7FE,MAAA,GAAS,gBAAA;EA6FkC;AAAO;AAUpD;EAnGE,IAAA,GAAO,UAAA;;;;EAIP,YAAA,GAAe,uBAAA;EA+Gf;;;EA3GA,aAAA,GAAgB,mBAAA;EA6GmB;AAOrC;;EAhHE,YAAA,GAAe,kBAAA;EAsHD;AAAA;AAShB;EA3HE,SAAA,GAAY,eAAA;;;;EAIZ,QAAA,GAAW,cAAA;EA6HX;;;EAzHA,GAAA,GAAM,SAAA;EAoII;;;EAhIV,GAAA,GAAM,eAAA;AAAA;AAAA,KAGI,iBAAA;;;;EAIV,KAAA;;;;EAIA,SAAS;AAAA;;;;KAMC,SAAA;;;;EAIV,WAAA,EAAa,iBAAA;;;;EAIb,YAAA,GAAe,iBAAiB;AAAA;;;;KAMtB,UAAA;;;;EAIV,SAAA;;;;EAIA,EAAA;;;;EAIA,QAAA;;;;;EAKA,QAAA;;;;EAIA,OAAA,GAAU,MAAM;AAAA;AAAA,KAGN,WAAA,kBAA6B,IAAA;EACvC,IAAA,EAAM,QAAA;EACN,MAAA,EAAQ,SAAA;AAAA;;KAIE,oBAAA,IAAwB,OAAgB,EAAP,OAAO;;;;;;;;;KAUxC,gBAAA;;;;;;EAMV,IAAA;;;;;EAKA,WAAA;;;;;EAKA,IAAA;EAEA,YAAA,GAAe,oBAAoB;AAAA;;;;;KAOzB,UAAA;;;;;;EAMV,cAAc;AAAA;;;;;;;KASJ,oBAAA;wFAEV,IAAA;EAEA,IAAA;EAEA,MAAA;;;;;;;EAOA,MAAA;AAAA;;KAIU,sBAAA;wFAEV,IAAA;EAEA,IAAI;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"sourcesContent":["import { type ChildModel } from \"@warlock.js/cascade\";\nimport { type Algorithm } from \"fast-jwt\";\nimport type { AccessToken } from \"../models/access-token\";\nimport type { Auth } from \"../models/auth.model\";\nimport type { OneTimeToken } from \"../models/one-time-token\";\nimport type { OtpConfig, PasskeysConfig, ProvidersConfig } from \"./providers\";\nimport type { RefreshToken } from \"../models/refresh-token\";\n\n/**\n * Symbol to indicate no expiration for tokens\n * Use this when you explicitly want tokens to never expire\n *\n * @example\n * ```typescript\n * // src/config/auth.ts\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\n *\n * const authConfigurations: AuthConfigurations = {\n * accessToken: {\n * secret: env(\"JWT_SECRET\"),\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\n * },\n * };\n *\n * export default authConfigurations;\n * ```\n */\nexport const NO_EXPIRATION = \"100y\";\n\n/**\n * Behavior when logout is called without a refresh token\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\n * - \"error\": Return an error requiring the refresh token\n */\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\n\n/** The single request credential source an auth middleware instance accepts. */\nexport type TokenFrom = \"header\" | `cookie:${string}`;\n\n/** One explicitly named credential location accepted by auth middleware. */\nexport interface AuthCredentialDescriptor {\n source: \"header\" | \"cookie\";\n key?: string;\n}\n\n/** Cookie credential and bounded duplicate window for automatic renewal. */\nexport interface AuthRefreshCredentialDescriptor extends AuthCredentialDescriptor {\n /** Milliseconds; the service clamps this to the 0–10 second range (default 5 seconds). */\n overlapMs?: number;\n}\n\n/** Per-route authentication behavior for the object middleware overload. */\nexport interface AuthMiddlewareOptions extends Partial<AuthCredentialDescriptor> {\n optional?: boolean;\n /**\n * Accept both a header and a cookie credential (at most one of each).\n * Precedence is fixed and independent of array order: a present\n * `Authorization` header wins, an invalid header rejects and never falls back\n * to the cookie, and renewal plus the CSRF Origin check apply only when the\n * cookie is the credential actually used. Cannot be combined with `source`.\n */\n sources?: AuthCredentialDescriptor[];\n /** Opt-in automatic renewal credential. Cookie sources only. */\n refresh?: AuthRefreshCredentialDescriptor;\n redirect?: { to: string; returnUrlParam?: string };\n}\n\n/** Application policy that decides whether a resolved user may authenticate. */\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\n\n/** Password credentials plus the application-defined identity fields. */\nexport type AuthCredentials = Record<string, unknown> & {\n password: string;\n};\n\n/**\n * Access-token configuration.\n *\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\n * backward-compatible shim); one of the two is required at runtime.\n */\nexport type AccessTokenConfig = {\n /**\n * Override the persisted access-token model — extend {@link AccessToken} to\n * add columns (e.g. a tenant key) or rename them, then register the subclass\n * here. Defaults to the package's `AccessToken`.\n */\n model?: typeof AccessToken;\n /**\n * Secret used to sign access tokens.\n */\n secret?: string;\n /**\n * JWT algorithm.\n * @default \"HS256\"\n */\n algorithm?: Algorithm;\n /**\n * Access-token lifetime — a duration string `ms` parses to a **positive**\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\n * first token issue: each of them would otherwise sign a token with no `exp`\n * claim at all.\n * @example \"1h\" or NO_EXPIRATION\n * @default \"1h\"\n */\n expiresIn?: string;\n};\n\n/**\n * Refresh-token configuration.\n */\nexport type RefreshTokenConfig = {\n /**\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\n * Defaults to the package's `RefreshToken`.\n */\n model?: typeof RefreshToken;\n /**\n * Enable refresh tokens.\n * @default true\n */\n enabled?: boolean;\n /**\n * Separate secret for refresh tokens (recommended for security). Falls back\n * to the access-token secret when omitted.\n */\n secret?: string;\n /**\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\n * number of milliseconds; an unusable value throws naming this key on issue\n * (see {@link AccessTokenConfig.expiresIn}).\n * @example \"7d\" or \"1w\"\n * @default \"7d\"\n */\n expiresIn?: string;\n /**\n * Rotate the refresh token on each use (old token revoked, replay detected).\n * @default true\n */\n rotation?: boolean;\n /**\n * Maximum active refresh tokens per user; the oldest are revoked past it.\n * @default 5\n */\n maxPerUser?: number;\n /**\n * Behavior when logout is called without a refresh token.\n * @default \"revoke-all\"\n */\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\n};\n\n/**\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\n * deprecation warning.\n *\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\n */\nexport type LegacyJwtConfig = {\n secret?: string;\n algorithm?: Algorithm;\n expiresIn?: string;\n refresh?: {\n secret?: string;\n enabled?: boolean;\n expiresIn?: string;\n rotation?: boolean;\n maxPerUser?: number;\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\n };\n};\n\n/**\n * Page-route auth-failure behavior. When a guarded PAGE route (a React-SSR page,\n * `route.isPage`) rejects a logged-out browser, this decides whether it redirects\n * to a login page instead of returning the API-style JSON 401.\n *\n * Read at runtime through `authConfig.pageAuth` (`auth.pageAuth.*`). API routes\n * always keep the JSON 401 contract regardless of this setting.\n */\nexport type PageAuthConfig = {\n /**\n * Login path a guarded page route redirects a logged-out browser to (e.g.\n * `\"/login\"`). Unset ⇒ page-route auth failures keep the JSON 401, so the\n * behavior is opt-in and backward-compatible.\n */\n loginPath?: string;\n /**\n * Query parameter the redirect carries the originally-requested path in, so\n * the login flow can send the user back where they were headed.\n * @default \"returnUrl\"\n */\n returnUrlParam?: string;\n};\n\n/**\n * Data every email-verification / password-reset notification receives.\n */\nexport type OneTimeTokenNotificationData = {\n /** The raw token — the only place it exists; only its SHA-256 hash is stored. */\n token: string;\n /** When the token stops working. */\n expiresAt: Date;\n /** The link built by `auth.verification.url` / `auth.passwordReset.url`, when configured. */\n url?: string;\n};\n\n/**\n * Anything auth can deliver a token through — a `defineNotification(...)`\n * result satisfies it, as does any object with a compatible `send`.\n */\nexport type AuthNotification = {\n send: (to: Auth, data: OneTimeTokenNotificationData) => unknown;\n};\n\n/** Builds the link a token notification points at. */\nexport type OneTimeTokenUrlBuilder = (token: string, user: Auth) => string;\n\n/** Persists a new plaintext password for a user during a password reset. */\nexport type PasswordSetter = (user: Auth, plainPassword: string) => void | Promise<void>;\n\n/**\n * Email-verification configuration (`auth.verification`).\n */\nexport type EmailVerificationConfig = {\n /**\n * Token lifetime — a positive `ms` duration string.\n * @default \"24h\"\n */\n expiresIn?: string;\n /**\n * User attribute stamped with the verification `Date`. Declare it in the\n * user schema (e.g. `emailVerifiedAt: v.date().optional()`).\n * @default \"emailVerifiedAt\"\n */\n field?: string;\n /** Replace the default verification email. */\n notification?: AuthNotification;\n /** Build the verification link passed to the notification as `url`. */\n url?: OneTimeTokenUrlBuilder;\n};\n\n/**\n * Password-reset configuration (`auth.passwordReset`).\n */\nexport type PasswordResetConfig = {\n /**\n * Token lifetime — a positive `ms` duration string.\n * @default \"60m\"\n */\n expiresIn?: string;\n /**\n * User attribute `requestPasswordReset` looks the account up by.\n * @default \"email\"\n */\n identifierField?: string;\n /** Replace the default reset email. */\n notification?: AuthNotification;\n /** Build the reset link passed to the notification as `url`. */\n url?: OneTimeTokenUrlBuilder;\n /**\n * Persist the new password yourself. By default auth saves a hash made by\n * the same `hashPassword` login verifies against, and falls back to the\n * plaintext when the model hashes on save (`useHashedPassword()`), checking\n * the stored value with `verifyPassword` either way.\n */\n setPassword?: PasswordSetter;\n};\n\n/**\n * One-time token storage configuration (`auth.oneTimeToken`).\n */\nexport type OneTimeTokenConfig = {\n /** Override the persisted model — extend {@link OneTimeToken}. */\n model?: typeof OneTimeToken;\n};\n\nexport type AuthConfigurations = {\n /**\n * Define all user types — maps a user-type slug to its `Auth` model class so\n * tokens and the middleware can resolve the right model.\n */\n userType: {\n [userType: string]: ChildModel<Auth>;\n };\n /** Default user type used by `authMiddleware()` and options-only overloads. */\n defaultUserType?: string;\n /**\n * Decide whether a resolved user may authenticate or receive new tokens.\n * @default () => true\n */\n canAuthenticate?: CanAuthenticate;\n /**\n * Access-token configuration (secret, expiry, algorithm, model override).\n */\n accessToken?: AccessTokenConfig;\n /**\n * Refresh-token configuration (rotation, lifetime, cap, model override).\n */\n refreshToken?: RefreshTokenConfig;\n /**\n * Page-route auth-failure behavior — set `loginPath` to redirect a logged-out\n * browser on a guarded PAGE route to a login page instead of the JSON 401.\n * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.\n */\n pageAuth?: PageAuthConfig;\n /**\n * Cookie auth session config — read by `authService.setAuthCookie` /\n * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own\n * defaults (`name: \"access_token\"`, `path: \"/\"`).\n */\n cookie?: CookieAuthConfig;\n /**\n * CSRF Origin-check config for cookie-authenticated unsafe-method requests.\n */\n csrf?: CsrfConfig;\n /**\n * Email verification — token lifetime, verified field, notification override.\n */\n verification?: EmailVerificationConfig;\n /**\n * Password reset — token lifetime, lookup field, notification and password-writer overrides.\n */\n passwordReset?: PasswordResetConfig;\n /**\n * One-time token (verification / reset) storage — model override.\n */\n oneTimeToken?: OneTimeTokenConfig;\n /**\n * Redirect login providers (`google`, `custom`) and account-linking policy.\n */\n providers?: ProvidersConfig;\n /**\n * Passkey (WebAuthn) relying-party settings. Requires `@simplewebauthn/server`.\n */\n passkeys?: PasskeysConfig;\n /**\n * Phone one-time-code login — channel, lifetime, attempts, delivery.\n */\n otp?: OtpConfig;\n /**\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\n */\n jwt?: LegacyJwtConfig;\n};\n\nexport type AccessTokenOutput = {\n /**\n * JWT Token\n */\n token: string;\n /**\n * Exprie time in ISO format UTC time\n */\n expiresAt: string;\n};\n\n/**\n * Token pair returned after login or token refresh\n */\nexport type TokenPair = {\n /**\n * JWT access token (short-lived)\n */\n accessToken: AccessTokenOutput;\n /**\n * JWT refresh token (long-lived)\n */\n refreshToken?: AccessTokenOutput;\n};\n\n/**\n * Device information for session tracking\n */\nexport type DeviceInfo = {\n /**\n * User agent string from request\n */\n userAgent?: string;\n /**\n * Client IP address\n */\n ip?: string;\n /**\n * Optional device identifier\n */\n deviceId?: string;\n /**\n * Token family ID (for rotation tracking)\n * @internal\n */\n familyId?: string;\n /**\n * Access token payload\n */\n payload?: Record<string, unknown>;\n};\n\nexport type LoginResult<UserType extends Auth> = {\n user: UserType;\n tokens: TokenPair;\n};\n\n/**\n * Configuration for the cookie an app opts into via `authService.setAuthCookie`\n * / `clearAuthCookie` and reads back with `authMiddleware([], \"cookie:<name>\")`.\n * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT\n * configurable here — they come from core's `secureCookieDefaults()`, the\n * same floor every `response.cookie()` call gets, so a cookie auth session can\n * never be weakened to a laxer policy than the framework default.\n */\nexport type CookieAuthConfig = {\n /**\n * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a\n * `cookie:<name>` token source must match to read it back.\n * @default \"access_token\"\n */\n name?: string;\n /**\n * Cookie name the session helpers write and clear for the refresh token.\n * @default \"refresh_token\"\n */\n refreshName?: string;\n /**\n * Cookie `Path` attribute.\n * @default \"/\"\n */\n path?: string;\n};\n\n/**\n * CSRF Origin-check configuration. Read via\n * `authConfig.csrf.allowedOrigins()`.\n */\nexport type CsrfConfig = {\n /**\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\n * in addition to the request's own origin.\n * @default []\n */\n allowedOrigins?: string[];\n};\n\n/**\n * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted\n * falls back to `auth.cookie.*` config, then the package default. Cookie\n * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —\n * see {@link CookieAuthConfig}.\n */\nexport type SetAuthCookieOptions = {\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\n name?: string;\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). */\n path?: string;\n /**\n * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would\n * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a\n * bare token string with no `maxAge` produces a session cookie (cleared\n * when the browser closes).\n */\n maxAge?: number;\n};\n\n/** Per-call overrides for {@link AuthService.clearAuthCookie}. */\nexport type ClearAuthCookieOptions = {\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\n name?: string;\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). Must match the `Path` the cookie was set with. */\n path?: string;\n};\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\";\nimport type { Request } from \"@warlock.js/core\";\nimport { type Algorithm } from \"fast-jwt\";\nimport type { AccessToken } from \"../models/access-token\";\nimport type { Auth } from \"../models/auth.model\";\nimport type { OneTimeToken } from \"../models/one-time-token\";\nimport type { OtpConfig, PasskeysConfig, ProvidersConfig } from \"./providers\";\nimport type { RefreshToken } from \"../models/refresh-token\";\n\n/**\n * Symbol to indicate no expiration for tokens\n * Use this when you explicitly want tokens to never expire\n *\n * @example\n * ```typescript\n * // src/config/auth.ts\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\n *\n * const authConfigurations: AuthConfigurations = {\n * accessToken: {\n * secret: env(\"JWT_SECRET\"),\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\n * },\n * };\n *\n * export default authConfigurations;\n * ```\n */\nexport const NO_EXPIRATION = \"100y\";\n\n/**\n * Behavior when logout is called without a refresh token\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\n * - \"error\": Return an error requiring the refresh token\n */\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\n\n/** The single request credential source an auth middleware instance accepts. */\nexport type TokenFrom = \"header\" | `cookie:${string}`;\n\n/** One explicitly named credential location accepted by auth middleware. */\nexport interface AuthCredentialDescriptor {\n source: \"header\" | \"cookie\";\n key?: string;\n}\n\n/** Cookie credential and bounded duplicate window for automatic renewal. */\nexport interface AuthRefreshCredentialDescriptor extends AuthCredentialDescriptor {\n /** Milliseconds; the service clamps this to the 0–10 second range (default 5 seconds). */\n overlapMs?: number;\n}\n\n/** Per-route authentication behavior for the object middleware overload. */\nexport interface AuthMiddlewareOptions extends Partial<AuthCredentialDescriptor> {\n optional?: boolean;\n /**\n * Accept both a header and a cookie credential (at most one of each).\n * Precedence is fixed and independent of array order: a present\n * `Authorization` header wins, an invalid header rejects and never falls back\n * to the cookie, and renewal plus the CSRF Origin check apply only when the\n * cookie is the credential actually used. Cannot be combined with `source`.\n */\n sources?: AuthCredentialDescriptor[];\n /** Opt-in automatic renewal credential. Cookie sources only. */\n refresh?: AuthRefreshCredentialDescriptor;\n redirect?: { to: string; returnUrlParam?: string };\n}\n\n/** Application policy that decides whether a resolved user may authenticate. */\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\n\n/** Password credentials plus the application-defined identity fields. */\nexport type AuthCredentials = Record<string, unknown> & {\n password: string;\n};\n\n/**\n * Access-token configuration.\n *\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\n * backward-compatible shim); one of the two is required at runtime.\n */\nexport type AccessTokenConfig = {\n /**\n * Override the persisted access-token model — extend {@link AccessToken} to\n * add columns (e.g. a tenant key) or rename them, then register the subclass\n * here. Defaults to the package's `AccessToken`.\n */\n model?: typeof AccessToken;\n /**\n * Secret used to sign access tokens.\n */\n secret?: string;\n /**\n * JWT algorithm.\n * @default \"HS256\"\n */\n algorithm?: Algorithm;\n /**\n * Access-token lifetime — a duration string `ms` parses to a **positive**\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\n * first token issue: each of them would otherwise sign a token with no `exp`\n * claim at all.\n * @example \"1h\" or NO_EXPIRATION\n * @default \"1h\"\n */\n expiresIn?: string;\n};\n\n/**\n * Refresh-token configuration.\n */\nexport type RefreshTokenConfig = {\n /**\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\n * Defaults to the package's `RefreshToken`.\n */\n model?: typeof RefreshToken;\n /**\n * Enable refresh tokens.\n * @default true\n */\n enabled?: boolean;\n /**\n * Separate secret for refresh tokens (recommended for security). Falls back\n * to the access-token secret when omitted.\n */\n secret?: string;\n /**\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\n * number of milliseconds; an unusable value throws naming this key on issue\n * (see {@link AccessTokenConfig.expiresIn}).\n * @example \"7d\" or \"1w\"\n * @default \"7d\"\n */\n expiresIn?: string;\n /**\n * Rotate the refresh token on each use (old token revoked, replay detected).\n * @default true\n */\n rotation?: boolean;\n /**\n * Maximum active refresh tokens per user; the oldest are revoked past it.\n * @default 5\n */\n maxPerUser?: number;\n /**\n * Behavior when logout is called without a refresh token.\n * @default \"revoke-all\"\n */\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\n};\n\n/**\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\n * deprecation warning.\n *\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\n */\nexport type LegacyJwtConfig = {\n secret?: string;\n algorithm?: Algorithm;\n expiresIn?: string;\n refresh?: {\n secret?: string;\n enabled?: boolean;\n expiresIn?: string;\n rotation?: boolean;\n maxPerUser?: number;\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\n };\n};\n\n/**\n * Page-route auth-failure behavior. When a guarded PAGE route (a React-SSR page,\n * `route.isPage`) rejects a logged-out browser, this decides whether it redirects\n * to a login page instead of returning the API-style JSON 401.\n *\n * Read at runtime through `authConfig.pageAuth` (`auth.pageAuth.*`). API routes\n * always keep the JSON 401 contract regardless of this setting.\n */\nexport type PageAuthConfig = {\n /**\n * Login path a guarded page route redirects a logged-out browser to (e.g.\n * `\"/login\"`). Unset ⇒ page-route auth failures keep the JSON 401, so the\n * behavior is opt-in and backward-compatible.\n */\n loginPath?: string;\n /**\n * Query parameter the redirect carries the originally-requested path in, so\n * the login flow can send the user back where they were headed.\n * @default \"returnUrl\"\n */\n returnUrlParam?: string;\n};\n\n/**\n * Data every email-verification / password-reset notification receives.\n */\nexport type OneTimeTokenNotificationData = {\n /** The raw token — the only place it exists; only its SHA-256 hash is stored. */\n token: string;\n /** When the token stops working. */\n expiresAt: Date;\n /** The link built by `auth.verification.url` / `auth.passwordReset.url`, when configured. */\n url?: string;\n};\n\n/**\n * Anything auth can deliver a token through — a `defineNotification(...)`\n * result satisfies it, as does any object with a compatible `send`.\n */\nexport type AuthNotification = {\n send: (to: Auth, data: OneTimeTokenNotificationData) => unknown;\n};\n\n/** Builds the link a token notification points at. */\nexport type OneTimeTokenUrlBuilder = (token: string, user: Auth) => string;\n\n/** Persists a new plaintext password for a user during a password reset. */\nexport type PasswordSetter = (user: Auth, plainPassword: string) => void | Promise<void>;\n\n/**\n * Email-verification configuration (`auth.verification`).\n */\nexport type EmailVerificationConfig = {\n /**\n * Token lifetime — a positive `ms` duration string.\n * @default \"24h\"\n */\n expiresIn?: string;\n /**\n * User attribute stamped with the verification `Date`. Declare it in the\n * user schema (e.g. `emailVerifiedAt: v.date().optional()`).\n * @default \"emailVerifiedAt\"\n */\n field?: string;\n /** Replace the default verification email. */\n notification?: AuthNotification;\n /** Build the verification link passed to the notification as `url`. */\n url?: OneTimeTokenUrlBuilder;\n};\n\n/**\n * Password-reset configuration (`auth.passwordReset`).\n */\nexport type PasswordResetConfig = {\n /**\n * Token lifetime — a positive `ms` duration string.\n * @default \"60m\"\n */\n expiresIn?: string;\n /**\n * User attribute `requestPasswordReset` looks the account up by.\n * @default \"email\"\n */\n identifierField?: string;\n /** Replace the default reset email. */\n notification?: AuthNotification;\n /** Build the reset link passed to the notification as `url`. */\n url?: OneTimeTokenUrlBuilder;\n /**\n * Persist the new password yourself. By default auth saves a hash made by\n * the same `hashPassword` login verifies against, and falls back to the\n * plaintext when the model hashes on save (`useHashedPassword()`), checking\n * the stored value with `verifyPassword` either way.\n */\n setPassword?: PasswordSetter;\n};\n\n/**\n * One-time token storage configuration (`auth.oneTimeToken`).\n */\nexport type OneTimeTokenConfig = {\n /** Override the persisted model — extend {@link OneTimeToken}. */\n model?: typeof OneTimeToken;\n};\n\nexport type AuthConfigurations = {\n /**\n * Define all user types — maps a user-type slug to its `Auth` model class so\n * tokens and the middleware can resolve the right model.\n */\n userType: {\n [userType: string]: ChildModel<Auth>;\n };\n /** Default user type used by `authMiddleware()` and options-only overloads. */\n defaultUserType?: string;\n /**\n * Decide whether a resolved user may authenticate or receive new tokens.\n * @default () => true\n */\n canAuthenticate?: CanAuthenticate;\n /**\n * Access-token configuration (secret, expiry, algorithm, model override).\n */\n accessToken?: AccessTokenConfig;\n /**\n * Refresh-token configuration (rotation, lifetime, cap, model override).\n */\n refreshToken?: RefreshTokenConfig;\n /**\n * Page-route auth-failure behavior — set `loginPath` to redirect a logged-out\n * browser on a guarded PAGE route to a login page instead of the JSON 401.\n * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.\n */\n pageAuth?: PageAuthConfig;\n /**\n * Cookie auth session config — read by `authService.setAuthCookie` /\n * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own\n * defaults (`name: \"access_token\"`, `path: \"/\"`).\n */\n cookie?: CookieAuthConfig;\n /**\n * CSRF Origin-check config for cookie-authenticated unsafe-method requests.\n */\n csrf?: CsrfConfig;\n /**\n * Email verification — token lifetime, verified field, notification override.\n */\n verification?: EmailVerificationConfig;\n /**\n * Password reset — token lifetime, lookup field, notification and password-writer overrides.\n */\n passwordReset?: PasswordResetConfig;\n /**\n * One-time token (verification / reset) storage — model override.\n */\n oneTimeToken?: OneTimeTokenConfig;\n /**\n * Redirect login providers (`google`, `custom`) and account-linking policy.\n */\n providers?: ProvidersConfig;\n /**\n * Passkey (WebAuthn) relying-party settings. Requires `@simplewebauthn/server`.\n */\n passkeys?: PasskeysConfig;\n /**\n * Phone one-time-code login — channel, lifetime, attempts, delivery.\n */\n otp?: OtpConfig;\n /**\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\n */\n jwt?: LegacyJwtConfig;\n};\n\nexport type AccessTokenOutput = {\n /**\n * JWT Token\n */\n token: string;\n /**\n * Exprie time in ISO format UTC time\n */\n expiresAt: string;\n};\n\n/**\n * Token pair returned after login or token refresh\n */\nexport type TokenPair = {\n /**\n * JWT access token (short-lived)\n */\n accessToken: AccessTokenOutput;\n /**\n * JWT refresh token (long-lived)\n */\n refreshToken?: AccessTokenOutput;\n};\n\n/**\n * Device information for session tracking\n */\nexport type DeviceInfo = {\n /**\n * User agent string from request\n */\n userAgent?: string;\n /**\n * Client IP address\n */\n ip?: string;\n /**\n * Optional device identifier\n */\n deviceId?: string;\n /**\n * Token family ID (for rotation tracking)\n * @internal\n */\n familyId?: string;\n /**\n * Access token payload\n */\n payload?: Record<string, unknown>;\n};\n\nexport type LoginResult<UserType extends Auth> = {\n user: UserType;\n tokens: TokenPair;\n};\n\n/** Selects a cookie Domain for one request; return `undefined` for a host-only cookie. */\nexport type CookieDomainResolver = (request: Request) => string | undefined;\n\n/**\n * Configuration for the cookie an app opts into via `authService.setAuthCookie`\n * / `clearAuthCookie` and reads back with `authMiddleware([], \"cookie:<name>\")`.\n * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT\n * configurable here — they come from core's `secureCookieDefaults()`, the\n * same floor every `response.cookie()` call gets, so a cookie auth session can\n * never be weakened to a laxer policy than the framework default.\n */\nexport type CookieAuthConfig = {\n /**\n * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a\n * `cookie:<name>` token source must match to read it back.\n * @default \"access_token\"\n */\n name?: string;\n /**\n * Cookie name the session helpers write and clear for the refresh token.\n * @default \"refresh_token\"\n */\n refreshName?: string;\n /**\n * Cookie `Path` attribute.\n * @default \"/\"\n */\n path?: string;\n /** Select a request-scoped Domain; `undefined` keeps the cookie host-only. */\n cookieDomain?: CookieDomainResolver;\n};\n\n/**\n * CSRF Origin-check configuration. Read via\n * `authConfig.csrf.allowedOrigins()`.\n */\nexport type CsrfConfig = {\n /**\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\n * in addition to the request's own origin.\n * @default []\n */\n allowedOrigins?: string[];\n};\n\n/**\n * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted\n * falls back to `auth.cookie.*` config, then the package default. Cookie\n * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —\n * see {@link CookieAuthConfig}.\n */\nexport type SetAuthCookieOptions = {\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\n name?: string;\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). */\n path?: string;\n /** Cookie `Domain` attribute. Omit for a host-only cookie. */\n domain?: string;\n /**\n * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would\n * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a\n * bare token string with no `maxAge` produces a session cookie (cleared\n * when the browser closes).\n */\n maxAge?: number;\n};\n\n/** Per-call overrides for {@link AuthService.clearAuthCookie}. */\nexport type ClearAuthCookieOptions = {\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\n name?: string;\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). Must match the `Path` the cookie was set with. */\n path?: string;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA4BA,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, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair } from "./contracts/types.mjs";
|
|
8
|
+
import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentialDescriptor, AuthCredentials, AuthMiddlewareOptions, AuthNotification, AuthRefreshCredentialDescriptor, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CookieDomainResolver, 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";
|
|
@@ -43,7 +43,7 @@ import { LINKEDIN_AUTH_FEATURE, LinkedInProvider } from "./providers/linkedin-pr
|
|
|
43
43
|
import { completeProviderLogin, resolveAuthProvider, startProviderLogin } from "./providers/provider-login.mjs";
|
|
44
44
|
import { XProvider } from "./providers/x-provider.mjs";
|
|
45
45
|
import { AuthEventCallback, AuthEventName, AuthEventPayloads, authEvents } from "./services/auth-events.mjs";
|
|
46
|
-
import { authService } from "./services/auth.service.mjs";
|
|
46
|
+
import { CookieDomainRequestRequiredError, authService } from "./services/auth.service.mjs";
|
|
47
47
|
import { currentUser } from "./services/current-user.mjs";
|
|
48
48
|
import { generateJWTSecret } from "./services/generate-jwt-secret.mjs";
|
|
49
49
|
import { TokenType, TokenTypeError, isInvalidCredentialError, jwt } from "./services/jwt.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, 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 };
|
|
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, CookieDomainRequestRequiredError, CookieDomainResolver, 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";
|
package/esm/index.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { OneTimeToken, oneTimeTokenSchema } from "./models/one-time-token/one-ti
|
|
|
10
10
|
import { RefreshToken, refreshTokenSchema } from "./models/refresh-token/refresh-token.model.mjs";
|
|
11
11
|
import { authEvents } from "./services/auth-events.mjs";
|
|
12
12
|
import { TokenTypeError, isInvalidCredentialError, jwt } from "./services/jwt.mjs";
|
|
13
|
-
import { authService } from "./services/auth.service.mjs";
|
|
13
|
+
import { CookieDomainRequestRequiredError, authService } from "./services/auth.service.mjs";
|
|
14
14
|
import { registerAuthCleanupCommand } from "./commands/auth-cleanup-command.mjs";
|
|
15
15
|
import { registerAuthPurgeNeverExpiringCommand } from "./commands/auth-purge-never-expiring-command.mjs";
|
|
16
16
|
import { generateJWTSecret } from "./services/generate-jwt-secret.mjs";
|
|
@@ -60,4 +60,4 @@ import { pageSession, sessionMiddleware } from "./session/page-session.mjs";
|
|
|
60
60
|
import "./session/index.mjs";
|
|
61
61
|
import "./utils/index.mjs";
|
|
62
62
|
|
|
63
|
-
export { APPLE_AUTH_FEATURE, AccessToken, AppleProvider, Auth, AuthErrorCodes, AuthProviderSdkMissingError, AuthTokenFamily, CsrfOriginMismatchError, DiscordProvider, EmailNotVerifiedError, FacebookProvider, GOOGLE_AUTH_FEATURE, GitHubProvider, GoogleProvider, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, LINKEDIN_AUTH_FEATURE, LinkedInProvider, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, PasskeyCredential, ProviderAccount, ProviderEmailNotVerifiedError, RefreshToken, TokenTypeError, XProvider, 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 };
|
|
63
|
+
export { APPLE_AUTH_FEATURE, AccessToken, AppleProvider, Auth, AuthErrorCodes, AuthProviderSdkMissingError, AuthTokenFamily, CookieDomainRequestRequiredError, CsrfOriginMismatchError, DiscordProvider, EmailNotVerifiedError, FacebookProvider, GOOGLE_AUTH_FEATURE, GitHubProvider, GoogleProvider, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, LINKEDIN_AUTH_FEATURE, LinkedInProvider, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, PasskeyCredential, ProviderAccount, ProviderEmailNotVerifiedError, RefreshToken, TokenTypeError, XProvider, 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 };
|
|
@@ -124,7 +124,9 @@ const authConfig = {
|
|
|
124
124
|
/** Refresh-token cookie name the session cookie helpers use. @default "refresh_token" */
|
|
125
125
|
refreshName: () => config.key("auth.cookie.refreshName", "refresh_token"),
|
|
126
126
|
/** Cookie `Path` attribute `setAuthCookie`/`clearAuthCookie` use. @default "/" */
|
|
127
|
-
path: () => config.key("auth.cookie.path", "/")
|
|
127
|
+
path: () => config.key("auth.cookie.path", "/"),
|
|
128
|
+
/** Select the request-scoped cookie Domain; undefined keeps it host-only. */
|
|
129
|
+
cookieDomain: () => config.key("auth.cookie.cookieDomain")
|
|
128
130
|
},
|
|
129
131
|
csrf: {
|
|
130
132
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-config.mjs","names":[],"sources":["../../../../../../../auth/src/services/auth-config.ts"],"sourcesContent":["import { config } from \"@warlock.js/core\";\nimport { log } from \"@warlock.js/logger\";\nimport { type Algorithm } from \"fast-jwt\";\nimport ms from \"ms\";\nimport type {\n AuthNotification,\n CanAuthenticate,\n LogoutWithoutTokenBehavior,\n OneTimeTokenUrlBuilder,\n PasswordSetter,\n} from \"../contracts/types\";\nimport type {\n AppleProviderConfig,\n AuthProvider,\n DiscordProviderConfig,\n FacebookProviderConfig,\n GitHubProviderConfig,\n GoogleProviderConfig,\n LinkedInProviderConfig,\n OtpSender,\n PasskeysConfig,\n ProviderUserCreator,\n XProviderConfig,\n} from \"../contracts/providers\";\nimport type { Auth } from \"../models/auth.model\";\n\nconst warnedLegacyKeys = new Set<string>();\n\nconst permitAuthentication: CanAuthenticate = (): boolean => true;\n\n/**\n * `ms` accepts anything at runtime and answers `undefined` for a string it\n * cannot parse (`\"30dayz\"`), a *formatted string* for a number (`2592000` ⇒\n * `\"43m\"`), and throws for `\"\"`. Its published types claim the far narrower\n * `(value: ms.StringValue) => number`, which is exactly what let an\n * `as ms.StringValue` cast smuggle arbitrary config text past the compiler and\n * hand `undefined` to the JWT signer. Declare the honest runtime signature once,\n * here, so every caller is forced to deal with the `undefined`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * Turn a configured lifetime into a positive number of milliseconds, or throw\n * naming the key.\n *\n * A token lifetime is a security boundary, so an unusable value is a hard error\n * rather than a silent fallback: substituting a default would trade one\n * unintended lifetime for another, just as quietly — and quiet is the whole\n * problem. Every non-positive, non-finite or non-numeric parse is rejected:\n *\n * - `\"30dayz\"` / `\"thirty days\"` ⇒ `undefined` ⇒ a JWT signed with **no `exp`\n * claim** and an `Invalid Date` written to the token row.\n * - `\"0d\"` ⇒ parses cleanly to `0`, which `fast-jwt` treats as falsy and also\n * emits with **no `exp` claim** — it survives any guard that only rejects\n * `undefined`. `\"-1h\"` signs an already-expired token.\n * - `2592000` (a bare number) ⇒ `ms` *formats* it as `\"43m\"`, a string, which\n * then turns `Date.now() + expiresIn` into an `Invalid Date`.\n */\nfunction parseDuration(key: string, raw: unknown): number {\n let parsed: number | string | undefined;\n\n try {\n parsed = parseMs(raw);\n } catch {\n // ms throws (rather than returning undefined) for \"\" and non-strings\n parsed = undefined;\n }\n\n if (typeof parsed !== \"number\" || !Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(\n `auth.${key}: ${JSON.stringify(raw)} is not a valid ms duration — ` +\n `use a positive duration string such as \"1h\", \"7d\", or NO_EXPIRATION.`,\n );\n }\n\n return parsed;\n}\n\n/** Email-verification token lifetime used when nothing is configured. */\nconst DEFAULT_VERIFICATION_EXPIRES_IN = \"24h\";\n\n/** Password-reset token lifetime used when nothing is configured. */\nconst DEFAULT_PASSWORD_RESET_EXPIRES_IN = \"60m\";\n\n/** Passkey challenge lifetime used when nothing is configured. */\nconst DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN = \"5m\";\n\n/** OTP lifetime used when nothing is configured. */\nconst DEFAULT_OTP_EXPIRES_IN = \"5m\";\n\n/** Access-token lifetime used when nothing is configured. */\nconst DEFAULT_ACCESS_TOKEN_EXPIRES_IN = \"1h\";\n\n/**\n * Resolve an auth setting, preferring the new `auth.accessToken.*` /\n * `auth.refreshToken.*` key and falling back to the deprecated `auth.jwt.*`\n * shape — warning once per legacy key. Returns `fallback` when neither is set.\n *\n * This is the backward-compatible shim that lets existing `auth.jwt.*` configs\n * keep working after the config split.\n */\nfunction resolve<T>(newKey: string, legacyKey: string, fallback?: T): T {\n const fromNew = config.key(`auth.${newKey}`);\n\n if (fromNew !== undefined && fromNew !== null) {\n return fromNew as T;\n }\n\n const fromLegacy = config.key(`auth.${legacyKey}`);\n\n if (fromLegacy !== undefined && fromLegacy !== null) {\n if (!warnedLegacyKeys.has(legacyKey)) {\n warnedLegacyKeys.add(legacyKey);\n log.warn(\n \"auth\",\n \"config-deprecation\",\n `auth.${legacyKey} is deprecated — use auth.${newKey}`,\n );\n }\n\n return fromLegacy as T;\n }\n\n return fallback as T;\n}\n\n/**\n * Typed, backward-compatible access to auth configuration. The service and the\n * jwt signer read configuration exclusively through here, so the new split\n * config and the legacy `auth.jwt.*` shape both resolve the same way.\n */\nexport const authConfig = {\n canAuthenticate: async (user: Auth): Promise<boolean> => {\n const canAuthenticate = config.key<CanAuthenticate>(\n \"auth.canAuthenticate\",\n permitAuthentication,\n );\n\n return canAuthenticate(user);\n },\n accessToken: {\n /** Signing secret (legacy: `auth.jwt.secret`). Throws if neither is set. */\n secret: (): string => {\n const secret = resolve<string | undefined>(\"accessToken.secret\", \"jwt.secret\");\n\n if (!secret) {\n throw new Error(\"auth: no JWT secret configured — set `auth.accessToken.secret`.\");\n }\n\n return secret;\n },\n /** Signing algorithm (legacy: `auth.jwt.algorithm`). */\n algorithm: (): Algorithm => resolve(\"accessToken.algorithm\", \"jwt.algorithm\", \"HS256\"),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.expiresIn`). */\n expiresIn: (): string | undefined => resolve(\"accessToken.expiresIn\", \"jwt.expiresIn\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Defaults\n * to 1 hour when unset; throws naming the key when the configured value is\n * not a positive `ms` duration. See {@link parseDuration}.\n */\n expiresInMs: (): number =>\n parseDuration(\n \"accessToken.expiresIn\",\n authConfig.accessToken.expiresIn() ?? DEFAULT_ACCESS_TOKEN_EXPIRES_IN,\n ),\n },\n pageAuth: {\n /**\n * Login path a guarded PAGE route redirects a logged-out browser to.\n *\n * Unset (the default) ⇒ page-route auth failures keep the JSON 401 API\n * contract, so existing apps see NO behavior change. Set it (e.g. \"/login\")\n * to opt a browser-facing app into login-redirect UX: a logged-out human\n * navigating to a guarded page lands on the login screen instead of reading\n * a raw JSON error blob. Finding b9ab9804.\n */\n loginPath: (): string | undefined => config.key(\"auth.pageAuth.loginPath\"),\n /**\n * Query parameter the redirect carries the originally-requested path in, so\n * the login flow can send the user back where they were headed. Defaults to\n * `\"returnUrl\"`.\n */\n returnUrlParam: (): string => config.key(\"auth.pageAuth.returnUrlParam\", \"returnUrl\"),\n },\n cookie: {\n /** Cookie name `setAuthCookie`/`clearAuthCookie` use. @default \"access_token\" */\n name: (): string => config.key(\"auth.cookie.name\", \"access_token\"),\n /** Refresh-token cookie name the session cookie helpers use. @default \"refresh_token\" */\n refreshName: (): string => config.key(\"auth.cookie.refreshName\", \"refresh_token\"),\n /** Cookie `Path` attribute `setAuthCookie`/`clearAuthCookie` use. @default \"/\" */\n path: (): string => config.key(\"auth.cookie.path\", \"/\"),\n },\n csrf: {\n /**\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\n * beyond the request's own origin. @default []\n */\n allowedOrigins: (): string[] => config.key(\"auth.csrf.allowedOrigins\", []),\n },\n verification: {\n /** Lifetime in ms of an email-verification token; throws naming the key when unusable. @default \"24h\" */\n expiresInMs: (): number =>\n parseDuration(\n \"verification.expiresIn\",\n config.key(\"auth.verification.expiresIn\", DEFAULT_VERIFICATION_EXPIRES_IN),\n ),\n /** User attribute stamped with the verification date. @default \"emailVerifiedAt\" */\n field: (): string => config.key(\"auth.verification.field\", \"emailVerifiedAt\"),\n /** App notification replacing the default verification email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.verification.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.verification.url\"),\n },\n passwordReset: {\n /** Lifetime in ms of a password-reset token; throws naming the key when unusable. @default \"60m\" */\n expiresInMs: (): number =>\n parseDuration(\n \"passwordReset.expiresIn\",\n config.key(\"auth.passwordReset.expiresIn\", DEFAULT_PASSWORD_RESET_EXPIRES_IN),\n ),\n /** User attribute `requestPasswordReset` looks the account up by. @default \"email\" */\n identifierField: (): string => config.key(\"auth.passwordReset.identifierField\", \"email\"),\n /** App notification replacing the default reset email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.passwordReset.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.passwordReset.url\"),\n /** App-owned password writer replacing the default. */\n setPassword: (): PasswordSetter | undefined => config.key(\"auth.passwordReset.setPassword\"),\n },\n providers: {\n /** `auth.providers.google`, when configured. */\n google: (): GoogleProviderConfig | undefined => config.key(\"auth.providers.google\"),\n /** `auth.providers.github`, when configured. */\n github: (): GitHubProviderConfig | undefined => config.key(\"auth.providers.github\"),\n /** `auth.providers.discord`, when configured. */\n discord: (): DiscordProviderConfig | undefined => config.key(\"auth.providers.discord\"),\n /** `auth.providers.linkedin`, when configured. */\n linkedin: (): LinkedInProviderConfig | undefined => config.key(\"auth.providers.linkedin\"),\n /** `auth.providers.apple`, when configured. */\n apple: (): AppleProviderConfig | undefined => config.key(\"auth.providers.apple\"),\n /** `auth.providers.facebook`, when configured. */\n facebook: (): FacebookProviderConfig | undefined => config.key(\"auth.providers.facebook\"),\n /** `auth.providers.x`, when configured. */\n x: (): XProviderConfig | undefined => config.key(\"auth.providers.x\"),\n /** An app-registered provider under `auth.providers.custom.<name>`. */\n custom: (name: string): AuthProvider | undefined => {\n const providers = config.key<Record<string, AuthProvider>>(\"auth.providers.custom\", {});\n\n // Own keys only: `name` arrives from a URL, and \"constructor\" must not resolve to Object.\n return Object.hasOwn(providers, name) ? providers[name] : undefined;\n },\n /** User attribute a verified provider email is matched against. @default \"email\" */\n emailField: (): string => config.key(\"auth.providers.emailField\", \"email\"),\n /** App-owned user creation for a first provider login. */\n createUser: (): ProviderUserCreator | undefined => config.key(\"auth.providers.createUser\"),\n },\n passkeys: {\n /** `auth.passkeys`; throws naming the keys when `rpID`, `rpName` or `origin` is missing. */\n settings: (): PasskeysConfig => {\n const settings = config.key<PasskeysConfig | undefined>(\"auth.passkeys\");\n\n if (!settings?.rpID || !settings.rpName || !settings.origin) {\n throw new Error(\n \"@warlock.js/auth: passkeys need `auth.passkeys.rpID`, `auth.passkeys.rpName` and `auth.passkeys.origin`.\",\n );\n }\n\n return settings;\n },\n /** Allowed ceremony origins, as a list. */\n origins: (): string[] => {\n const { origin } = authConfig.passkeys.settings();\n\n return Array.isArray(origin) ? origin : [origin];\n },\n /** Challenge lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n challengeExpiresInMs: (): number =>\n parseDuration(\n \"passkeys.challengeExpiresIn\",\n config.key(\"auth.passkeys.challengeExpiresIn\", DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN),\n ),\n },\n otp: {\n /** Notifications channel the code is sent through. @default \"sms\" */\n channel: (): string => config.key(\"auth.otp.channel\", \"sms\"),\n /** User attribute holding the phone number. @default \"phone\" */\n phoneField: (): string => config.key(\"auth.otp.phoneField\", \"phone\"),\n /** Code lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n expiresInMs: (): number =>\n parseDuration(\"otp.expiresIn\", config.key(\"auth.otp.expiresIn\", DEFAULT_OTP_EXPIRES_IN)),\n /** Verify attempts before the code is invalidated. @default 5 */\n maxAttempts: (): number => config.key(\"auth.otp.maxAttempts\", 5),\n /** The message text for a code. */\n message: (code: string): string => {\n const build = config.key<((code: string) => string) | undefined>(\"auth.otp.message\");\n\n return build ? build(code) : `Your verification code is ${code}`;\n },\n /** App-owned delivery replacing the notifications channel send. */\n send: (): OtpSender | undefined => config.key(\"auth.otp.send\"),\n },\n refreshToken: {\n /** Separate refresh secret (legacy: `auth.jwt.refresh.secret`); empty ⇒ fall back to the access secret. */\n secret: (): string | undefined => resolve(\"refreshToken.secret\", \"jwt.refresh.secret\"),\n /** Whether refresh tokens are enabled (legacy: `auth.jwt.refresh.enabled`). */\n enabled: (): boolean => resolve(\"refreshToken.enabled\", \"jwt.refresh.enabled\", true),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.refresh.expiresIn`). */\n expiresIn: (): string => resolve(\"refreshToken.expiresIn\", \"jwt.refresh.expiresIn\", \"7d\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Throws\n * naming the key when the configured value is not a positive `ms` duration.\n */\n expiresInMs: (): number =>\n parseDuration(\"refreshToken.expiresIn\", authConfig.refreshToken.expiresIn()),\n /** Rotate-on-use (legacy: `auth.jwt.refresh.rotation`). */\n rotation: (): boolean => resolve(\"refreshToken.rotation\", \"jwt.refresh.rotation\", true),\n /** Max active tokens per user (legacy: `auth.jwt.refresh.maxPerUser`). */\n maxPerUser: (): number => resolve(\"refreshToken.maxPerUser\", \"jwt.refresh.maxPerUser\", 5),\n /** Logout-without-token behavior (legacy: `auth.jwt.refresh.logoutWithoutToken`). */\n logoutWithoutToken: (): LogoutWithoutTokenBehavior =>\n resolve(\"refreshToken.logoutWithoutToken\", \"jwt.refresh.logoutWithoutToken\", \"revoke-all\"),\n },\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"auth-config.mjs","names":[],"sources":["../../../../../../../auth/src/services/auth-config.ts"],"sourcesContent":["import { config } from \"@warlock.js/core\";\nimport type { CookieDomainResolver } from \"../contracts/types\";\nimport { log } from \"@warlock.js/logger\";\nimport { type Algorithm } from \"fast-jwt\";\nimport ms from \"ms\";\nimport type {\n AuthNotification,\n CanAuthenticate,\n LogoutWithoutTokenBehavior,\n OneTimeTokenUrlBuilder,\n PasswordSetter,\n} from \"../contracts/types\";\nimport type {\n AppleProviderConfig,\n AuthProvider,\n DiscordProviderConfig,\n FacebookProviderConfig,\n GitHubProviderConfig,\n GoogleProviderConfig,\n LinkedInProviderConfig,\n OtpSender,\n PasskeysConfig,\n ProviderUserCreator,\n XProviderConfig,\n} from \"../contracts/providers\";\nimport type { Auth } from \"../models/auth.model\";\n\nconst warnedLegacyKeys = new Set<string>();\n\nconst permitAuthentication: CanAuthenticate = (): boolean => true;\n\n/**\n * `ms` accepts anything at runtime and answers `undefined` for a string it\n * cannot parse (`\"30dayz\"`), a *formatted string* for a number (`2592000` ⇒\n * `\"43m\"`), and throws for `\"\"`. Its published types claim the far narrower\n * `(value: ms.StringValue) => number`, which is exactly what let an\n * `as ms.StringValue` cast smuggle arbitrary config text past the compiler and\n * hand `undefined` to the JWT signer. Declare the honest runtime signature once,\n * here, so every caller is forced to deal with the `undefined`.\n */\nconst parseMs = ms as unknown as (value: unknown) => number | string | undefined;\n\n/**\n * Turn a configured lifetime into a positive number of milliseconds, or throw\n * naming the key.\n *\n * A token lifetime is a security boundary, so an unusable value is a hard error\n * rather than a silent fallback: substituting a default would trade one\n * unintended lifetime for another, just as quietly — and quiet is the whole\n * problem. Every non-positive, non-finite or non-numeric parse is rejected:\n *\n * - `\"30dayz\"` / `\"thirty days\"` ⇒ `undefined` ⇒ a JWT signed with **no `exp`\n * claim** and an `Invalid Date` written to the token row.\n * - `\"0d\"` ⇒ parses cleanly to `0`, which `fast-jwt` treats as falsy and also\n * emits with **no `exp` claim** — it survives any guard that only rejects\n * `undefined`. `\"-1h\"` signs an already-expired token.\n * - `2592000` (a bare number) ⇒ `ms` *formats* it as `\"43m\"`, a string, which\n * then turns `Date.now() + expiresIn` into an `Invalid Date`.\n */\nfunction parseDuration(key: string, raw: unknown): number {\n let parsed: number | string | undefined;\n\n try {\n parsed = parseMs(raw);\n } catch {\n // ms throws (rather than returning undefined) for \"\" and non-strings\n parsed = undefined;\n }\n\n if (typeof parsed !== \"number\" || !Number.isFinite(parsed) || parsed <= 0) {\n throw new Error(\n `auth.${key}: ${JSON.stringify(raw)} is not a valid ms duration — ` +\n `use a positive duration string such as \"1h\", \"7d\", or NO_EXPIRATION.`,\n );\n }\n\n return parsed;\n}\n\n/** Email-verification token lifetime used when nothing is configured. */\nconst DEFAULT_VERIFICATION_EXPIRES_IN = \"24h\";\n\n/** Password-reset token lifetime used when nothing is configured. */\nconst DEFAULT_PASSWORD_RESET_EXPIRES_IN = \"60m\";\n\n/** Passkey challenge lifetime used when nothing is configured. */\nconst DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN = \"5m\";\n\n/** OTP lifetime used when nothing is configured. */\nconst DEFAULT_OTP_EXPIRES_IN = \"5m\";\n\n/** Access-token lifetime used when nothing is configured. */\nconst DEFAULT_ACCESS_TOKEN_EXPIRES_IN = \"1h\";\n\n/**\n * Resolve an auth setting, preferring the new `auth.accessToken.*` /\n * `auth.refreshToken.*` key and falling back to the deprecated `auth.jwt.*`\n * shape — warning once per legacy key. Returns `fallback` when neither is set.\n *\n * This is the backward-compatible shim that lets existing `auth.jwt.*` configs\n * keep working after the config split.\n */\nfunction resolve<T>(newKey: string, legacyKey: string, fallback?: T): T {\n const fromNew = config.key(`auth.${newKey}`);\n\n if (fromNew !== undefined && fromNew !== null) {\n return fromNew as T;\n }\n\n const fromLegacy = config.key(`auth.${legacyKey}`);\n\n if (fromLegacy !== undefined && fromLegacy !== null) {\n if (!warnedLegacyKeys.has(legacyKey)) {\n warnedLegacyKeys.add(legacyKey);\n log.warn(\n \"auth\",\n \"config-deprecation\",\n `auth.${legacyKey} is deprecated — use auth.${newKey}`,\n );\n }\n\n return fromLegacy as T;\n }\n\n return fallback as T;\n}\n\n/**\n * Typed, backward-compatible access to auth configuration. The service and the\n * jwt signer read configuration exclusively through here, so the new split\n * config and the legacy `auth.jwt.*` shape both resolve the same way.\n */\nexport const authConfig = {\n canAuthenticate: async (user: Auth): Promise<boolean> => {\n const canAuthenticate = config.key<CanAuthenticate>(\n \"auth.canAuthenticate\",\n permitAuthentication,\n );\n\n return canAuthenticate(user);\n },\n accessToken: {\n /** Signing secret (legacy: `auth.jwt.secret`). Throws if neither is set. */\n secret: (): string => {\n const secret = resolve<string | undefined>(\"accessToken.secret\", \"jwt.secret\");\n\n if (!secret) {\n throw new Error(\"auth: no JWT secret configured — set `auth.accessToken.secret`.\");\n }\n\n return secret;\n },\n /** Signing algorithm (legacy: `auth.jwt.algorithm`). */\n algorithm: (): Algorithm => resolve(\"accessToken.algorithm\", \"jwt.algorithm\", \"HS256\"),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.expiresIn`). */\n expiresIn: (): string | undefined => resolve(\"accessToken.expiresIn\", \"jwt.expiresIn\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Defaults\n * to 1 hour when unset; throws naming the key when the configured value is\n * not a positive `ms` duration. See {@link parseDuration}.\n */\n expiresInMs: (): number =>\n parseDuration(\n \"accessToken.expiresIn\",\n authConfig.accessToken.expiresIn() ?? DEFAULT_ACCESS_TOKEN_EXPIRES_IN,\n ),\n },\n pageAuth: {\n /**\n * Login path a guarded PAGE route redirects a logged-out browser to.\n *\n * Unset (the default) ⇒ page-route auth failures keep the JSON 401 API\n * contract, so existing apps see NO behavior change. Set it (e.g. \"/login\")\n * to opt a browser-facing app into login-redirect UX: a logged-out human\n * navigating to a guarded page lands on the login screen instead of reading\n * a raw JSON error blob. Finding b9ab9804.\n */\n loginPath: (): string | undefined => config.key(\"auth.pageAuth.loginPath\"),\n /**\n * Query parameter the redirect carries the originally-requested path in, so\n * the login flow can send the user back where they were headed. Defaults to\n * `\"returnUrl\"`.\n */\n returnUrlParam: (): string => config.key(\"auth.pageAuth.returnUrlParam\", \"returnUrl\"),\n },\n cookie: {\n /** Cookie name `setAuthCookie`/`clearAuthCookie` use. @default \"access_token\" */\n name: (): string => config.key(\"auth.cookie.name\", \"access_token\"),\n /** Refresh-token cookie name the session cookie helpers use. @default \"refresh_token\" */\n refreshName: (): string => config.key(\"auth.cookie.refreshName\", \"refresh_token\"),\n /** Cookie `Path` attribute `setAuthCookie`/`clearAuthCookie` use. @default \"/\" */\n path: (): string => config.key(\"auth.cookie.path\", \"/\"),\n /** Select the request-scoped cookie Domain; undefined keeps it host-only. */\n cookieDomain: (): CookieDomainResolver | undefined => config.key(\"auth.cookie.cookieDomain\"),\n },\n csrf: {\n /**\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\n * beyond the request's own origin. @default []\n */\n allowedOrigins: (): string[] => config.key(\"auth.csrf.allowedOrigins\", []),\n },\n verification: {\n /** Lifetime in ms of an email-verification token; throws naming the key when unusable. @default \"24h\" */\n expiresInMs: (): number =>\n parseDuration(\n \"verification.expiresIn\",\n config.key(\"auth.verification.expiresIn\", DEFAULT_VERIFICATION_EXPIRES_IN),\n ),\n /** User attribute stamped with the verification date. @default \"emailVerifiedAt\" */\n field: (): string => config.key(\"auth.verification.field\", \"emailVerifiedAt\"),\n /** App notification replacing the default verification email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.verification.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.verification.url\"),\n },\n passwordReset: {\n /** Lifetime in ms of a password-reset token; throws naming the key when unusable. @default \"60m\" */\n expiresInMs: (): number =>\n parseDuration(\n \"passwordReset.expiresIn\",\n config.key(\"auth.passwordReset.expiresIn\", DEFAULT_PASSWORD_RESET_EXPIRES_IN),\n ),\n /** User attribute `requestPasswordReset` looks the account up by. @default \"email\" */\n identifierField: (): string => config.key(\"auth.passwordReset.identifierField\", \"email\"),\n /** App notification replacing the default reset email. */\n notification: (): AuthNotification | undefined => config.key(\"auth.passwordReset.notification\"),\n /** Link builder whose result is handed to the notification as `url`. */\n url: (): OneTimeTokenUrlBuilder | undefined => config.key(\"auth.passwordReset.url\"),\n /** App-owned password writer replacing the default. */\n setPassword: (): PasswordSetter | undefined => config.key(\"auth.passwordReset.setPassword\"),\n },\n providers: {\n /** `auth.providers.google`, when configured. */\n google: (): GoogleProviderConfig | undefined => config.key(\"auth.providers.google\"),\n /** `auth.providers.github`, when configured. */\n github: (): GitHubProviderConfig | undefined => config.key(\"auth.providers.github\"),\n /** `auth.providers.discord`, when configured. */\n discord: (): DiscordProviderConfig | undefined => config.key(\"auth.providers.discord\"),\n /** `auth.providers.linkedin`, when configured. */\n linkedin: (): LinkedInProviderConfig | undefined => config.key(\"auth.providers.linkedin\"),\n /** `auth.providers.apple`, when configured. */\n apple: (): AppleProviderConfig | undefined => config.key(\"auth.providers.apple\"),\n /** `auth.providers.facebook`, when configured. */\n facebook: (): FacebookProviderConfig | undefined => config.key(\"auth.providers.facebook\"),\n /** `auth.providers.x`, when configured. */\n x: (): XProviderConfig | undefined => config.key(\"auth.providers.x\"),\n /** An app-registered provider under `auth.providers.custom.<name>`. */\n custom: (name: string): AuthProvider | undefined => {\n const providers = config.key<Record<string, AuthProvider>>(\"auth.providers.custom\", {});\n\n // Own keys only: `name` arrives from a URL, and \"constructor\" must not resolve to Object.\n return Object.hasOwn(providers, name) ? providers[name] : undefined;\n },\n /** User attribute a verified provider email is matched against. @default \"email\" */\n emailField: (): string => config.key(\"auth.providers.emailField\", \"email\"),\n /** App-owned user creation for a first provider login. */\n createUser: (): ProviderUserCreator | undefined => config.key(\"auth.providers.createUser\"),\n },\n passkeys: {\n /** `auth.passkeys`; throws naming the keys when `rpID`, `rpName` or `origin` is missing. */\n settings: (): PasskeysConfig => {\n const settings = config.key<PasskeysConfig | undefined>(\"auth.passkeys\");\n\n if (!settings?.rpID || !settings.rpName || !settings.origin) {\n throw new Error(\n \"@warlock.js/auth: passkeys need `auth.passkeys.rpID`, `auth.passkeys.rpName` and `auth.passkeys.origin`.\",\n );\n }\n\n return settings;\n },\n /** Allowed ceremony origins, as a list. */\n origins: (): string[] => {\n const { origin } = authConfig.passkeys.settings();\n\n return Array.isArray(origin) ? origin : [origin];\n },\n /** Challenge lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n challengeExpiresInMs: (): number =>\n parseDuration(\n \"passkeys.challengeExpiresIn\",\n config.key(\"auth.passkeys.challengeExpiresIn\", DEFAULT_PASSKEY_CHALLENGE_EXPIRES_IN),\n ),\n },\n otp: {\n /** Notifications channel the code is sent through. @default \"sms\" */\n channel: (): string => config.key(\"auth.otp.channel\", \"sms\"),\n /** User attribute holding the phone number. @default \"phone\" */\n phoneField: (): string => config.key(\"auth.otp.phoneField\", \"phone\"),\n /** Code lifetime in ms; throws naming the key when unusable. @default \"5m\" */\n expiresInMs: (): number =>\n parseDuration(\"otp.expiresIn\", config.key(\"auth.otp.expiresIn\", DEFAULT_OTP_EXPIRES_IN)),\n /** Verify attempts before the code is invalidated. @default 5 */\n maxAttempts: (): number => config.key(\"auth.otp.maxAttempts\", 5),\n /** The message text for a code. */\n message: (code: string): string => {\n const build = config.key<((code: string) => string) | undefined>(\"auth.otp.message\");\n\n return build ? build(code) : `Your verification code is ${code}`;\n },\n /** App-owned delivery replacing the notifications channel send. */\n send: (): OtpSender | undefined => config.key(\"auth.otp.send\"),\n },\n refreshToken: {\n /** Separate refresh secret (legacy: `auth.jwt.refresh.secret`); empty ⇒ fall back to the access secret. */\n secret: (): string | undefined => resolve(\"refreshToken.secret\", \"jwt.refresh.secret\"),\n /** Whether refresh tokens are enabled (legacy: `auth.jwt.refresh.enabled`). */\n enabled: (): boolean => resolve(\"refreshToken.enabled\", \"jwt.refresh.enabled\", true),\n /** Lifetime as an `ms`-string (legacy: `auth.jwt.refresh.expiresIn`). */\n expiresIn: (): string => resolve(\"refreshToken.expiresIn\", \"jwt.refresh.expiresIn\", \"7d\"),\n /**\n * Lifetime in milliseconds — the only form a token issuer may use. Throws\n * naming the key when the configured value is not a positive `ms` duration.\n */\n expiresInMs: (): number =>\n parseDuration(\"refreshToken.expiresIn\", authConfig.refreshToken.expiresIn()),\n /** Rotate-on-use (legacy: `auth.jwt.refresh.rotation`). */\n rotation: (): boolean => resolve(\"refreshToken.rotation\", \"jwt.refresh.rotation\", true),\n /** Max active tokens per user (legacy: `auth.jwt.refresh.maxPerUser`). */\n maxPerUser: (): number => resolve(\"refreshToken.maxPerUser\", \"jwt.refresh.maxPerUser\", 5),\n /** Logout-without-token behavior (legacy: `auth.jwt.refresh.logoutWithoutToken`). */\n logoutWithoutToken: (): LogoutWithoutTokenBehavior =>\n resolve(\"refreshToken.logoutWithoutToken\", \"jwt.refresh.logoutWithoutToken\", \"revoke-all\"),\n },\n};\n"],"mappings":"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"}
|
|
@@ -6,6 +6,10 @@ 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
|
|
9
|
+
/** Thrown when a request-scoped cookie Domain cannot be resolved safely. */
|
|
10
|
+
declare class CookieDomainRequestRequiredError extends Error {
|
|
11
|
+
constructor(method: string);
|
|
12
|
+
}
|
|
9
13
|
declare class AuthService {
|
|
10
14
|
/**
|
|
11
15
|
* Resolve the active access-token model — the package default, or a subclass
|
|
@@ -195,6 +199,7 @@ declare class AuthService {
|
|
|
195
199
|
* string (a session cookie) or an unparseable `expiresAt`.
|
|
196
200
|
*/
|
|
197
201
|
private cookieMaxAgeFromToken;
|
|
202
|
+
private cookieDomainFor;
|
|
198
203
|
/**
|
|
199
204
|
* Write the auth cookie on `response` — the write side of the `cookie:<name>`
|
|
200
205
|
* token source `authMiddleware([], "cookie:<name>")` already reads. An explicit
|
|
@@ -227,7 +232,7 @@ declare class AuthService {
|
|
|
227
232
|
* await authService.logout(user, accessToken, refreshToken);
|
|
228
233
|
* authService.clearAuthCookie(response);
|
|
229
234
|
*/
|
|
230
|
-
clearAuthCookie(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, options?: ClearAuthCookieOptions): void;
|
|
235
|
+
clearAuthCookie(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, options?: ClearAuthCookieOptions, request?: Request): void;
|
|
231
236
|
/**
|
|
232
237
|
* Write the access and refresh cookies of a browser session. Both are
|
|
233
238
|
* `HttpOnly`, `SameSite=Lax`, `Path=/` (the refresh cookie must reach every
|
|
@@ -242,14 +247,14 @@ declare class AuthService {
|
|
|
242
247
|
setSessionCookies(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, tokens: {
|
|
243
248
|
accessToken: AccessTokenOutput;
|
|
244
249
|
refreshToken: AccessTokenOutput;
|
|
245
|
-
}): void;
|
|
250
|
+
}, request?: Request): void;
|
|
246
251
|
/**
|
|
247
252
|
* Clear both session cookies {@link setSessionCookies} wrote, on `Path=/`.
|
|
248
253
|
* Pair it with {@link logout} after the token rows are revoked.
|
|
249
254
|
*/
|
|
250
|
-
clearSessionCookies(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }): void;
|
|
255
|
+
clearSessionCookies(response: { [K in "cookie" | "clearCookie"]: (...args: Parameters<Response[K]>) => unknown }, request?: Request): void;
|
|
251
256
|
}
|
|
252
257
|
declare const authService: AuthService;
|
|
253
258
|
//#endregion
|
|
254
|
-
export { authService };
|
|
259
|
+
export { CookieDomainRequestRequiredError, authService };
|
|
255
260
|
//# sourceMappingURL=auth.service.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.d.mts","names":[],"sources":["../../../../../../../auth/src/services/auth.service.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.service.d.mts","names":[],"sources":["../../../../../../../auth/src/services/auth.service.ts"],"mappings":"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"}
|
|
@@ -16,6 +16,13 @@ import { ForbiddenError, config, hashPassword, verifyPassword } from "@warlock.j
|
|
|
16
16
|
import { randomUUID } from "node:crypto";
|
|
17
17
|
|
|
18
18
|
//#region ../auth/src/services/auth.service.ts
|
|
19
|
+
/** Thrown when a request-scoped cookie Domain cannot be resolved safely. */
|
|
20
|
+
var CookieDomainRequestRequiredError = class extends Error {
|
|
21
|
+
constructor(method) {
|
|
22
|
+
super(`${method} requires the request when auth.cookie.cookieDomain is configured because the cookie Domain must be resolved for that request.`);
|
|
23
|
+
this.name = "CookieDomainRequestRequiredError";
|
|
24
|
+
}
|
|
25
|
+
};
|
|
19
26
|
var AuthService = class {
|
|
20
27
|
/**
|
|
21
28
|
* Resolve the active access-token model — the package default, or a subclass
|
|
@@ -387,8 +394,8 @@ var AuthService = class {
|
|
|
387
394
|
if (result.tokens.refreshToken) this.setSessionCookies(response, {
|
|
388
395
|
accessToken: result.tokens.accessToken,
|
|
389
396
|
refreshToken: result.tokens.refreshToken
|
|
390
|
-
});
|
|
391
|
-
else this.setAuthCookie(response, result.tokens.accessToken);
|
|
397
|
+
}, request);
|
|
398
|
+
else this.setAuthCookie(response, result.tokens.accessToken, { domain: this.cookieDomainFor("loginWithSessionCookies", request) });
|
|
392
399
|
return result;
|
|
393
400
|
}
|
|
394
401
|
/** The one token-issuing tail every login method shares. */
|
|
@@ -569,6 +576,12 @@ var AuthService = class {
|
|
|
569
576
|
if (Number.isNaN(expiresAt)) return void 0;
|
|
570
577
|
return Math.max(0, Math.round((expiresAt - Date.now()) / 1e3));
|
|
571
578
|
}
|
|
579
|
+
cookieDomainFor(method, request) {
|
|
580
|
+
const resolver = authConfig.cookie.cookieDomain();
|
|
581
|
+
if (!resolver) return;
|
|
582
|
+
if (!request) throw new CookieDomainRequestRequiredError(method);
|
|
583
|
+
return resolver(request);
|
|
584
|
+
}
|
|
572
585
|
/**
|
|
573
586
|
* Write the auth cookie on `response` — the write side of the `cookie:<name>`
|
|
574
587
|
* token source `authMiddleware([], "cookie:<name>")` already reads. An explicit
|
|
@@ -598,6 +611,7 @@ var AuthService = class {
|
|
|
598
611
|
response.cookie(name, tokenValue, {
|
|
599
612
|
raw: true,
|
|
600
613
|
path,
|
|
614
|
+
...options.domain !== void 0 ? { domain: options.domain } : {},
|
|
601
615
|
...maxAge !== void 0 ? { maxAge } : {}
|
|
602
616
|
});
|
|
603
617
|
}
|
|
@@ -611,9 +625,14 @@ var AuthService = class {
|
|
|
611
625
|
* await authService.logout(user, accessToken, refreshToken);
|
|
612
626
|
* authService.clearAuthCookie(response);
|
|
613
627
|
*/
|
|
614
|
-
clearAuthCookie(response, options = {}) {
|
|
628
|
+
clearAuthCookie(response, options = {}, request) {
|
|
615
629
|
const name = options.name ?? authConfig.cookie.name();
|
|
616
630
|
const path = options.path ?? authConfig.cookie.path();
|
|
631
|
+
const domain = this.cookieDomainFor("clearAuthCookie", request);
|
|
632
|
+
if (domain !== void 0) response.clearCookie(name, {
|
|
633
|
+
path,
|
|
634
|
+
domain
|
|
635
|
+
});
|
|
617
636
|
response.clearCookie(name, { path });
|
|
618
637
|
}
|
|
619
638
|
/**
|
|
@@ -627,7 +646,8 @@ var AuthService = class {
|
|
|
627
646
|
* const { tokens } = await authService.login(User, credentials);
|
|
628
647
|
* authService.setSessionCookies(response, tokens);
|
|
629
648
|
*/
|
|
630
|
-
setSessionCookies(response, tokens) {
|
|
649
|
+
setSessionCookies(response, tokens, request) {
|
|
650
|
+
const domain = this.cookieDomainFor("setSessionCookies", request);
|
|
631
651
|
const cookies = [[authConfig.cookie.name(), tokens.accessToken], [authConfig.cookie.refreshName(), tokens.refreshToken]];
|
|
632
652
|
for (const [name, token] of cookies) {
|
|
633
653
|
const maxAge = this.cookieMaxAgeFromToken(token);
|
|
@@ -636,6 +656,7 @@ var AuthService = class {
|
|
|
636
656
|
httpOnly: true,
|
|
637
657
|
sameSite: "lax",
|
|
638
658
|
path: "/",
|
|
659
|
+
...domain !== void 0 ? { domain } : {},
|
|
639
660
|
...maxAge !== void 0 ? { maxAge } : {}
|
|
640
661
|
});
|
|
641
662
|
}
|
|
@@ -644,13 +665,19 @@ var AuthService = class {
|
|
|
644
665
|
* Clear both session cookies {@link setSessionCookies} wrote, on `Path=/`.
|
|
645
666
|
* Pair it with {@link logout} after the token rows are revoked.
|
|
646
667
|
*/
|
|
647
|
-
clearSessionCookies(response) {
|
|
648
|
-
|
|
649
|
-
|
|
668
|
+
clearSessionCookies(response, request) {
|
|
669
|
+
const domain = this.cookieDomainFor("clearSessionCookies", request);
|
|
670
|
+
for (const name of [authConfig.cookie.name(), authConfig.cookie.refreshName()]) {
|
|
671
|
+
if (domain !== void 0) response.clearCookie(name, {
|
|
672
|
+
path: "/",
|
|
673
|
+
domain
|
|
674
|
+
});
|
|
675
|
+
response.clearCookie(name, { path: "/" });
|
|
676
|
+
}
|
|
650
677
|
}
|
|
651
678
|
};
|
|
652
679
|
const authService = new AuthService();
|
|
653
680
|
|
|
654
681
|
//#endregion
|
|
655
|
-
export { authService };
|
|
682
|
+
export { CookieDomainRequestRequiredError, authService };
|
|
656
683
|
//# sourceMappingURL=auth.service.mjs.map
|