@warlock.js/auth 5.12.0 → 5.14.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.
Files changed (169) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +88 -86
  3. package/esm/commands/auth-cleanup-command.mjs +1 -1
  4. package/esm/commands/auth-cleanup-command.mjs.map +1 -1
  5. package/esm/contracts/index.d.mts +2 -1
  6. package/esm/contracts/providers.d.mts +101 -0
  7. package/esm/contracts/providers.d.mts.map +1 -0
  8. package/esm/contracts/types.d.mts +94 -1
  9. package/esm/contracts/types.d.mts.map +1 -1
  10. package/esm/contracts/types.mjs.map +1 -1
  11. package/esm/errors/auth-provider-sdk-missing.error.d.mts +17 -0
  12. package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -0
  13. package/esm/errors/auth-provider-sdk-missing.error.mjs +22 -0
  14. package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -0
  15. package/esm/errors/email-not-verified.error.d.mts +16 -0
  16. package/esm/errors/email-not-verified.error.d.mts.map +1 -0
  17. package/esm/errors/email-not-verified.error.mjs +20 -0
  18. package/esm/errors/email-not-verified.error.mjs.map +1 -0
  19. package/esm/errors/index.d.mts +7 -0
  20. package/esm/errors/index.mjs +9 -0
  21. package/esm/errors/invalid-one-time-token.error.d.mts +17 -0
  22. package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -0
  23. package/esm/errors/invalid-one-time-token.error.mjs +21 -0
  24. package/esm/errors/invalid-one-time-token.error.mjs.map +1 -0
  25. package/esm/errors/invalid-passkey.error.d.mts +18 -0
  26. package/esm/errors/invalid-passkey.error.d.mts.map +1 -0
  27. package/esm/errors/invalid-passkey.error.mjs +23 -0
  28. package/esm/errors/invalid-passkey.error.mjs.map +1 -0
  29. package/esm/errors/invalid-provider-callback.error.d.mts +18 -0
  30. package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -0
  31. package/esm/errors/invalid-provider-callback.error.mjs +23 -0
  32. package/esm/errors/invalid-provider-callback.error.mjs.map +1 -0
  33. package/esm/errors/notifications-unavailable.error.d.mts +15 -0
  34. package/esm/errors/notifications-unavailable.error.d.mts.map +1 -0
  35. package/esm/errors/notifications-unavailable.error.mjs +20 -0
  36. package/esm/errors/notifications-unavailable.error.mjs.map +1 -0
  37. package/esm/errors/provider-email-not-verified.error.d.mts +16 -0
  38. package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -0
  39. package/esm/errors/provider-email-not-verified.error.mjs +20 -0
  40. package/esm/errors/provider-email-not-verified.error.mjs.map +1 -0
  41. package/esm/index.d.mts +27 -3
  42. package/esm/index.mjs +27 -1
  43. package/esm/middleware/index.d.mts +4 -1
  44. package/esm/middleware/index.mjs +3 -0
  45. package/esm/middleware/one-time-token-throttle.middleware.d.mts +36 -0
  46. package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -0
  47. package/esm/middleware/one-time-token-throttle.middleware.mjs +53 -0
  48. package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -0
  49. package/esm/middleware/otp-throttle.middleware.d.mts +30 -0
  50. package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -0
  51. package/esm/middleware/otp-throttle.middleware.mjs +45 -0
  52. package/esm/middleware/otp-throttle.middleware.mjs.map +1 -0
  53. package/esm/middleware/require-verified-email.middleware.d.mts +18 -0
  54. package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -0
  55. package/esm/middleware/require-verified-email.middleware.mjs +24 -0
  56. package/esm/middleware/require-verified-email.middleware.mjs.map +1 -0
  57. package/esm/models/access-token/access-token.model.d.mts +8 -0
  58. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  59. package/esm/models/access-token/access-token.model.mjs +3 -0
  60. package/esm/models/access-token/access-token.model.mjs.map +1 -1
  61. package/esm/models/index.d.mts +3 -1
  62. package/esm/models/index.d.mts.map +1 -1
  63. package/esm/models/index.mjs +16 -1
  64. package/esm/models/index.mjs.map +1 -1
  65. package/esm/models/one-time-token/index.d.mts +1 -0
  66. package/esm/models/one-time-token/index.mjs +3 -0
  67. package/esm/models/one-time-token/migration.mjs +31 -0
  68. package/esm/models/one-time-token/migration.mjs.map +1 -0
  69. package/esm/models/one-time-token/one-time-token.model.d.mts +173 -0
  70. package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -0
  71. package/esm/models/one-time-token/one-time-token.model.mjs +151 -0
  72. package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -0
  73. package/esm/models/passkey-credential/index.d.mts +1 -0
  74. package/esm/models/passkey-credential/index.mjs +3 -0
  75. package/esm/models/passkey-credential/migration.mjs +25 -0
  76. package/esm/models/passkey-credential/migration.mjs.map +1 -0
  77. package/esm/models/passkey-credential/passkey-credential.model.d.mts +108 -0
  78. package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -0
  79. package/esm/models/passkey-credential/passkey-credential.model.mjs +70 -0
  80. package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -0
  81. package/esm/models/provider-account/index.d.mts +1 -0
  82. package/esm/models/provider-account/index.mjs +3 -0
  83. package/esm/models/provider-account/migration.mjs +25 -0
  84. package/esm/models/provider-account/migration.mjs.map +1 -0
  85. package/esm/models/provider-account/provider-account.model.d.mts +75 -0
  86. package/esm/models/provider-account/provider-account.model.d.mts.map +1 -0
  87. package/esm/models/provider-account/provider-account.model.mjs +55 -0
  88. package/esm/models/provider-account/provider-account.model.mjs.map +1 -0
  89. package/esm/models/refresh-token/refresh-token.model.d.mts +7 -0
  90. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  91. package/esm/models/refresh-token/refresh-token.model.mjs +3 -0
  92. package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
  93. package/esm/otp/index.d.mts +1 -0
  94. package/esm/otp/index.mjs +3 -0
  95. package/esm/otp/otp.d.mts +38 -0
  96. package/esm/otp/otp.d.mts.map +1 -0
  97. package/esm/otp/otp.mjs +105 -0
  98. package/esm/otp/otp.mjs.map +1 -0
  99. package/esm/passkeys/index.d.mts +3 -0
  100. package/esm/passkeys/index.mjs +4 -0
  101. package/esm/passkeys/passkey-authentication.d.mts +33 -0
  102. package/esm/passkeys/passkey-authentication.d.mts.map +1 -0
  103. package/esm/passkeys/passkey-authentication.mjs +93 -0
  104. package/esm/passkeys/passkey-authentication.mjs.map +1 -0
  105. package/esm/passkeys/passkey-challenges.mjs +53 -0
  106. package/esm/passkeys/passkey-challenges.mjs.map +1 -0
  107. package/esm/passkeys/passkey-registration.d.mts +29 -0
  108. package/esm/passkeys/passkey-registration.d.mts.map +1 -0
  109. package/esm/passkeys/passkey-registration.mjs +88 -0
  110. package/esm/passkeys/passkey-registration.mjs.map +1 -0
  111. package/esm/passkeys/simplewebauthn.d.mts +21 -0
  112. package/esm/passkeys/simplewebauthn.d.mts.map +1 -0
  113. package/esm/passkeys/simplewebauthn.mjs +13 -0
  114. package/esm/passkeys/simplewebauthn.mjs.map +1 -0
  115. package/esm/providers/google-provider.d.mts +40 -0
  116. package/esm/providers/google-provider.d.mts.map +1 -0
  117. package/esm/providers/google-provider.mjs +105 -0
  118. package/esm/providers/google-provider.mjs.map +1 -0
  119. package/esm/providers/index.d.mts +2 -0
  120. package/esm/providers/index.mjs +4 -0
  121. package/esm/providers/provider-login.d.mts +43 -0
  122. package/esm/providers/provider-login.d.mts.map +1 -0
  123. package/esm/providers/provider-login.mjs +86 -0
  124. package/esm/providers/provider-login.mjs.map +1 -0
  125. package/esm/providers/provider-state-cookie.mjs +63 -0
  126. package/esm/providers/provider-state-cookie.mjs.map +1 -0
  127. package/esm/providers/resolve-provider-user.mjs +45 -0
  128. package/esm/providers/resolve-provider-user.mjs.map +1 -0
  129. package/esm/services/auth-config.mjs +72 -0
  130. package/esm/services/auth-config.mjs.map +1 -1
  131. package/esm/services/auth.service.d.mts +18 -1
  132. package/esm/services/auth.service.d.mts.map +1 -1
  133. package/esm/services/auth.service.mjs +28 -1
  134. package/esm/services/auth.service.mjs.map +1 -1
  135. package/esm/services/email-verification.d.mts +35 -0
  136. package/esm/services/email-verification.d.mts.map +1 -0
  137. package/esm/services/email-verification.mjs +55 -0
  138. package/esm/services/email-verification.mjs.map +1 -0
  139. package/esm/services/index.d.mts +4 -1
  140. package/esm/services/index.mjs +3 -0
  141. package/esm/services/one-time-tokens.d.mts +32 -0
  142. package/esm/services/one-time-tokens.d.mts.map +1 -0
  143. package/esm/services/one-time-tokens.mjs +56 -0
  144. package/esm/services/one-time-tokens.mjs.map +1 -0
  145. package/esm/services/optional-peer.mjs +37 -0
  146. package/esm/services/optional-peer.mjs.map +1 -0
  147. package/esm/services/password-reset.d.mts +34 -0
  148. package/esm/services/password-reset.d.mts.map +1 -0
  149. package/esm/services/password-reset.mjs +81 -0
  150. package/esm/services/password-reset.mjs.map +1 -0
  151. package/esm/services/token-notifications.mjs +87 -0
  152. package/esm/services/token-notifications.mjs.map +1 -0
  153. package/esm/utils/auth-error-codes.d.mts +39 -1
  154. package/esm/utils/auth-error-codes.d.mts.map +1 -1
  155. package/esm/utils/auth-error-codes.mjs +33 -0
  156. package/esm/utils/auth-error-codes.mjs.map +1 -1
  157. package/esm/utils/index.mjs +1 -0
  158. package/esm/utils/safe-equal.d.mts +6 -0
  159. package/esm/utils/safe-equal.d.mts.map +1 -0
  160. package/esm/utils/safe-equal.mjs +13 -0
  161. package/esm/utils/safe-equal.mjs.map +1 -0
  162. package/llms-full.txt +313 -10
  163. package/llms.txt +2 -0
  164. package/package.json +21 -7
  165. package/skills/login-with-providers/SKILL.md +154 -0
  166. package/skills/manage-tokens/SKILL.md +7 -1
  167. package/skills/register-user/SKILL.md +6 -8
  168. package/skills/run-auth-commands/SKILL.md +1 -1
  169. package/skills/verify-email-and-reset-password/SKILL.md +137 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,36 @@ 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.13.0 - 2026-09-17
8
+
9
+ ### Added
10
+
11
+ - **Email verification and password reset.** `sendEmailVerification(user)`, `verifyEmail(token)`, `requestPasswordReset(Model, email)` and `resetPassword(token, newPassword)`. Tokens are 32 random bytes, stored only as a SHA-256 hash, single-use (consumed with a conditional update, so of two concurrent uses exactly one succeeds), expiring (`auth.verification.expiresIn` default `"24h"`, `auth.passwordReset.expiresIn` default `"60m"`) and purpose-bound. A new reset request invalidates the user's earlier unused reset tokens. `requestPasswordReset` answers the same for unknown emails. `resetPassword` revokes every access token, refresh token and cookie session through `authService.revokeAllTokens`. Rejected tokens throw `InvalidOneTimeTokenError` (`400`, `EC008`).
12
+ - New `one_time_tokens` table (`OneTimeToken` model, overridable via `auth.oneTimeToken.model`), shipped in `authMigrations`. **Run your migrations.**
13
+ - Delivery goes through `@warlock.js/notifications`, now an **optional peer dependency**. Default mail notifications can be replaced via `auth.verification.notification` / `auth.passwordReset.notification`, and link builders set via `.url`. If the package is not installed or not configured, calls throw `NotificationsUnavailableError` before any token is issued.
14
+ - `requireVerifiedEmail()` middleware — rejects users without `auth.verification.field` (default `emailVerifiedAt`) with `EmailNotVerifiedError` (`403`, `EC007`). `isEmailVerified(user)` helper.
15
+ - `tokenIssueThrottleMiddleware()` (every request counts, per email + IP, 3/1h) and `tokenConsumeThrottleMiddleware()` (failures, per IP, 10/15m), both built on `loginThrottleMiddleware`.
16
+ - `AuthErrorCodes.EmailNotVerified` (`"EC007"`) and `AuthErrorCodes.InvalidOneTimeToken` (`"EC008"`).
17
+ - **Login with Google, passkeys and phone codes** (skill `login-with-providers`). Every method ends in the new `authService.completeLogin(user, deviceInfo?)`, which applies `auth.canAuthenticate` and then issues exactly what `authService.login` issues (same tokens, rows and `login.success` event). `login` now shares that ending.
18
+ - **Google:** `startProviderLogin(response, "google")` / `completeProviderLogin(Model, "google", request, response)`. Uses the OIDC authorization code flow with PKCE (S256), `state` and `nonce`, kept in a signed 10-minute HttpOnly cookie. The code is exchanged with `fetch`, and the id_token is verified with `jose` against Google's JWKS (signature, `iss`, `aud`, `exp`, `nonce`). Configure it under `auth.providers.google`. Other providers implement the `AuthProvider` contract and register under `auth.providers.custom`.
19
+ - **Account linking:** new `provider_accounts` table (`ProviderAccount`). A link always decides the user. Without one, only a provider-**verified** email links an existing user or creates a new one; an unverified email throws `ProviderEmailNotVerifiedError` (`403`, `EC010`). Callback rejections throw `InvalidProviderCallbackError` (`400`, `EC009`).
20
+ - **Passkeys:** `generatePasskeyRegistrationOptions` / `verifyPasskeyRegistration` / `generatePasskeyAuthenticationOptions` / `verifyPasskeyAuthentication`, built on `@simplewebauthn/server`. Challenges are stored hashed in `one_time_tokens`, expire, and are used once (consumed even when verification fails). The request `Origin` must be in `auth.passkeys.origin`, and a signature counter that does not advance is rejected. Credentials are stored in the new `passkey_credentials` table (`PasskeyCredential`). Rejections throw `InvalidPasskeyError` (`400`, `EC011`).
21
+ - **Phone OTP:** `requestOtp(Model, phone, { channel })` / `verifyOtp(Model, phone, code)`. Codes have 6 digits, are stored as a salted HMAC in `one_time_tokens` (purpose `otp`), expire after 5 minutes, and are invalidated after 5 attempts (counted atomically). Unknown phones get the same answer. Codes are delivered through the `@warlock.js/notifications` channel you configure (`auth.otp.channel`, default `"sms"`) or `auth.otp.send`; auth ships no SMS/WhatsApp driver. New `otpRequestThrottleMiddleware()` / `otpVerifyThrottleMiddleware()` presets are built on `loginThrottleMiddleware`.
22
+ - `jose` and `@simplewebauthn/server` are new **optional peer dependencies**, loaded lazily. When one is missing, the call throws `AuthProviderSdkMissingError`, whose message names `warlock add auth-google` / `warlock add auth-passkeys`.
23
+ - `one_time_tokens` gains an `attempts` column, and `user_id` becomes nullable (passkey login challenges have no user yet). `authMigrations` now includes `ProviderAccountMigration` and `PasskeyCredentialMigration`. **Run your migrations.**
24
+ - `auth.cleanup` / `authService.cleanupExpiredTokens()` also hard-deletes expired and consumed `one_time_tokens` rows via the new `OneTimeToken.purgeSpent()`. The delete is permanent on every driver, so MongoDB's default "trash" strategy does not copy token hashes into `one_time_tokensTrash`.
25
+ - Local real-database integration suites (`tests/integration/local`, skipped unless `LOCAL_MONGO_*` / `LOCAL_PG_*` are set) prove on MongoDB and Postgres: a one-time token consumed 20 times concurrently succeeds once, 20 concurrent wrong OTP guesses count at most 5 attempts and invalidate the code, a passkey login challenge with no user persists and is consumed, the counter compare-and-set and regression rejection hold, and `provider_accounts` allows one link per identity.
26
+ - Passkey specs now also run the real `@simplewebauthn/server` against a `node:crypto` software authenticator (`none` attestation, ES256 assertions).
27
+ - `AuthErrorCodes.InvalidProviderCallback` (`"EC009"`), `AuthErrorCodes.ProviderEmailNotVerified` (`"EC010"`) and `AuthErrorCodes.InvalidPasskey` (`"EC011"`).
28
+
29
+ ### Security
30
+
31
+ - **Access and refresh tokens (and one-time tokens, and passkey credentials) are no longer copied into MongoDB trash collections on delete.** `AccessToken`, `RefreshToken`, `OneTimeToken` and `PasskeyCredential` now declare `static deleteStrategy = "permanent"`, overriding the MongoDB driver's default `"trash"` strategy. Previously, `AccessToken.purgeExpired()`, `RefreshToken.purgeExpired()`/`purgeNeverExpiring()`, and `authMiddleware`'s expired-token cleanup all called `destroy()` without a strategy override, so on MongoDB every purge or invalidation copied the live JWT into `access_tokensTrash` / `refresh_tokensTrash` before deleting the original — leaving usable credential material behind after "revocation". `OneTimeToken.purgeSpent()` already forced `"permanent"` per-call (5.13.0 above); that override is now redundant and has been removed in favor of the model-level default. Apps that have been running on MongoDB may want to drop the `access_tokensTrash`, `refresh_tokensTrash` and `one_time_tokensTrash` collections, which may hold copies of now-revoked credentials.
32
+
33
+ ### Fixed
34
+
35
+ - `verifyPasskeyAuthentication` reported a cloned authenticator as `reason: "authentication-verification-failed"`, because `@simplewebauthn/server` rejects a non-advancing counter itself before auth's own check runs. It now reports `"counter-regression"`. The response (`400`, `EC011`) is unchanged.
36
+
7
37
  ## 5.12.0 - 2026-09-16
8
38
 
9
39
  ### Added
package/README.md CHANGED
@@ -1,86 +1,88 @@
1
- # Warlock Auth
2
-
3
- JWT authentication for [Warlock.js](https://github.com/warlockjs/core) applications — a base `Auth` model your user types extend, an `authMiddleware` route gate, an `authService` for login / logout / refresh (with refresh-token rotation + replay detection), persisted access + refresh tokens, multi-user-type support, lifecycle events, brute-force throttling, and three CLI commands.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- yarn add @warlock.js/auth
9
- ```
10
-
11
- `@warlock.js/auth` is coupled to `@warlock.js/core` — install it inside a Warlock project.
12
-
13
- ## Server-only
14
-
15
- `@warlock.js/auth`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. `@warlock.js/web`'s build treats this as a boundary: app client code cannot value-import this package (type-only imports are still allowed), while server loaders/controllers/modules can import it freely.
16
-
17
- ## Configure
18
-
19
- ```ts title="src/config/auth.ts"
20
- import { type AuthConfigurations } from "@warlock.js/auth";
21
- import { env } from "@warlock.js/core";
22
- import { User } from "app/users/models/user";
23
-
24
- const authConfigurations: AuthConfigurations = {
25
- userType: { user: User },
26
- accessToken: {
27
- secret: env("JWT_SECRET"),
28
- expiresIn: "1h",
29
- },
30
- refreshToken: {
31
- secret: env("JWT_REFRESH_SECRET"),
32
- expiresIn: "30d",
33
- rotation: true,
34
- },
35
- };
36
-
37
- export default authConfigurations;
38
- ```
39
-
40
- The legacy `jwt: { secret, expiresIn, refresh: {…} }` block is still honored (with a deprecation warning), but prefer the `accessToken` / `refreshToken` blocks.
41
-
42
- `expiresIn` must be a duration string [`ms`](https://github.com/vercel/ms) parses to a positive value (`"1h"`, `"30 days"`, `NO_EXPIRATION`); anything else throws naming the key on the first token issue, rather than signing a token with no expiry.
43
-
44
- A token is only accepted while **both** its own `exp` claim and its stored `expires_at` are in the future — a JWT with no `exp` at all is rejected outright, since a verifier with no deadline to check succeeds forever. If any deployment ran a pre-4.12.0 version with an invalid `expiresIn`, tokens issued then may never expire and are still live: run `warlock auth.purge-never-expiring --dry-run` to find them. See the 4.12.0 entry in [`CHANGELOG.md`](./CHANGELOG.md).
45
-
46
- ## Documentation
47
-
48
- Task-focused guides live under [`skills/`](./skills):
49
-
50
- - **overview** — what the package does and when to reach for it
51
- - **auth-basics** — the `Auth` model, middleware, and service
52
- - **handle-login-and-logout** — issue and revoke tokens
53
- - **register-user** — sign up + first token pair
54
- - **protect-routes** — gate routes with `authMiddleware`
55
- - **manage-tokens** — the full token lifecycle (rotation, revocation, cleanup)
56
- - **customize-user-type** — multiple user types in one system
57
- - **customize-token-storage** — override the token models (multi-tenant columns, custom storage)
58
- - **throttle-login-attempts** — brute-force protection via `loginThrottleMiddleware`
59
- - **run-auth-commands** — the bundled CLI commands
60
-
61
- ## Generate the JWT secret
62
-
63
- Register the command in `warlock.config.ts`:
64
-
65
- ```ts
66
- import { registerJWTSecretGeneratorCommand } from "@warlock.js/auth";
67
- import { defineConfig } from "@warlock.js/core";
68
-
69
- export default defineConfig({
70
- cli: {
71
- commands: [registerJWTSecretGeneratorCommand()],
72
- },
73
- });
74
- ```
75
-
76
- Then run:
77
-
78
- ```bash
79
- warlock jwt.generate
80
- ```
81
-
82
- It generates cryptographically-strong `JWT_SECRET` and `JWT_REFRESH_SECRET` values and writes them to your `.env` file. Existing values are left untouched, so it's safe to re-run.
83
-
84
- ## License
85
-
86
- MIT © Hasan Zohdy
1
+ # Warlock Auth
2
+
3
+ JWT authentication for [Warlock.js](https://github.com/warlockjs/core) applications — a base `Auth` model your user types extend, an `authMiddleware` route gate, an `authService` for login / logout / refresh (with refresh-token rotation + replay detection), persisted access + refresh tokens, multi-user-type support, lifecycle events, brute-force throttling, and three CLI commands.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ yarn add @warlock.js/auth
9
+ ```
10
+
11
+ `@warlock.js/auth` is coupled to `@warlock.js/core` — install it inside a Warlock project.
12
+
13
+ ## Server-only
14
+
15
+ `@warlock.js/auth`'s entire runtime surface is server-only — its `package.json` declares `"warlock": { "environment": "server" }`. `@warlock.js/web`'s build treats this as a boundary: app client code cannot value-import this package (type-only imports are still allowed), while server loaders/controllers/modules can import it freely.
16
+
17
+ ## Configure
18
+
19
+ ```ts title="src/config/auth.ts"
20
+ import { type AuthConfigurations } from "@warlock.js/auth";
21
+ import { env } from "@warlock.js/core";
22
+ import { User } from "app/users/models/user";
23
+
24
+ const authConfigurations: AuthConfigurations = {
25
+ userType: { user: User },
26
+ accessToken: {
27
+ secret: env("JWT_SECRET"),
28
+ expiresIn: "1h",
29
+ },
30
+ refreshToken: {
31
+ secret: env("JWT_REFRESH_SECRET"),
32
+ expiresIn: "30d",
33
+ rotation: true,
34
+ },
35
+ };
36
+
37
+ export default authConfigurations;
38
+ ```
39
+
40
+ The legacy `jwt: { secret, expiresIn, refresh: {…} }` block is still honored (with a deprecation warning), but prefer the `accessToken` / `refreshToken` blocks.
41
+
42
+ `expiresIn` must be a duration string [`ms`](https://github.com/vercel/ms) parses to a positive value (`"1h"`, `"30 days"`, `NO_EXPIRATION`); anything else throws naming the key on the first token issue, rather than signing a token with no expiry.
43
+
44
+ A token is only accepted while **both** its own `exp` claim and its stored `expires_at` are in the future — a JWT with no `exp` at all is rejected outright, since a verifier with no deadline to check succeeds forever. If any deployment ran a pre-4.12.0 version with an invalid `expiresIn`, tokens issued then may never expire and are still live: run `warlock auth.purge-never-expiring --dry-run` to find them. See the 4.12.0 entry in [`CHANGELOG.md`](./CHANGELOG.md).
45
+
46
+ ## Documentation
47
+
48
+ Task-focused guides live under [`skills/`](./skills):
49
+
50
+ - **overview** — what the package does and when to reach for it
51
+ - **auth-basics** — the `Auth` model, middleware, and service
52
+ - **handle-login-and-logout** — issue and revoke tokens
53
+ - **register-user** — sign up + first token pair
54
+ - **protect-routes** — gate routes with `authMiddleware`
55
+ - **manage-tokens** — the full token lifecycle (rotation, revocation, cleanup)
56
+ - **customize-user-type** — multiple user types in one system
57
+ - **customize-token-storage** — override the token models (multi-tenant columns, custom storage)
58
+ - **throttle-login-attempts** — brute-force protection via `loginThrottleMiddleware`
59
+ - **verify-email-and-reset-password** — email verification, password reset, `requireVerifiedEmail`
60
+ - **login-with-providers** — Google, passkeys and phone one-time codes, all ending in `completeLogin`
61
+ - **run-auth-commands** — the bundled CLI commands
62
+
63
+ ## Generate the JWT secret
64
+
65
+ Register the command in `warlock.config.ts`:
66
+
67
+ ```ts
68
+ import { registerJWTSecretGeneratorCommand } from "@warlock.js/auth";
69
+ import { defineConfig } from "@warlock.js/core";
70
+
71
+ export default defineConfig({
72
+ cli: {
73
+ commands: [registerJWTSecretGeneratorCommand()],
74
+ },
75
+ });
76
+ ```
77
+
78
+ Then run:
79
+
80
+ ```bash
81
+ warlock jwt.generate
82
+ ```
83
+
84
+ It generates cryptographically-strong `JWT_SECRET` and `JWT_REFRESH_SECRET` values and writes them to your `.env` file. Existing values are left untouched, so it's safe to re-run.
85
+
86
+ ## License
87
+
88
+ MIT © Hasan Zohdy
@@ -14,7 +14,7 @@ import { command } from "@warlock.js/core";
14
14
  function registerAuthCleanupCommand() {
15
15
  return command({
16
16
  name: "auth.cleanup",
17
- description: "Remove expired refresh tokens from the database",
17
+ description: "Remove expired access/refresh tokens and spent one-time tokens from the database",
18
18
  preload: {
19
19
  env: true,
20
20
  config: ["auth", "database"],
@@ -1 +1 @@
1
- {"version":3,"file":"auth-cleanup-command.mjs","names":[],"sources":["../../../../../../../auth/src/commands/auth-cleanup-command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { command } from \"@warlock.js/core\";\nimport { authService } from \"../services/auth.service\";\n\n/**\n * Register the auth:cleanup CLI command\n *\n * @example\n * ```bash\n * warlock auth:cleanup\n * ```\n */\nexport function registerAuthCleanupCommand() {\n return command({\n name: \"auth.cleanup\",\n description: \"Remove expired refresh tokens from the database\",\n preload: {\n env: true,\n config: [\"auth\", \"database\"],\n connectors: [\"database\"],\n },\n action: async () => {\n console.log(colors.cyan(\"🧹 Cleaning up expired tokens...\"));\n\n const count = await authService.cleanupExpiredTokens();\n\n if (count === 0) {\n console.log(colors.green(\"✅ No expired tokens found.\"));\n } else {\n console.log(colors.green(`✅ Removed ${count} expired token(s).`));\n }\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,6BAA6B;CAC3C,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,SAAS;GACP,KAAK;GACL,QAAQ,CAAC,QAAQ,UAAU;GAC3B,YAAY,CAAC,UAAU;EACzB;EACA,QAAQ,YAAY;GAClB,QAAQ,IAAI,OAAO,KAAK,kCAAkC,CAAC;GAE3D,MAAM,QAAQ,MAAM,YAAY,qBAAqB;GAErD,IAAI,UAAU,GACZ,QAAQ,IAAI,OAAO,MAAM,4BAA4B,CAAC;QAEtD,QAAQ,IAAI,OAAO,MAAM,aAAa,MAAM,mBAAmB,CAAC;EAEpE;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"auth-cleanup-command.mjs","names":[],"sources":["../../../../../../../auth/src/commands/auth-cleanup-command.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { command } from \"@warlock.js/core\";\nimport { authService } from \"../services/auth.service\";\n\n/**\n * Register the auth:cleanup CLI command\n *\n * @example\n * ```bash\n * warlock auth:cleanup\n * ```\n */\nexport function registerAuthCleanupCommand() {\n return command({\n name: \"auth.cleanup\",\n description: \"Remove expired access/refresh tokens and spent one-time tokens from the database\",\n preload: {\n env: true,\n config: [\"auth\", \"database\"],\n connectors: [\"database\"],\n },\n action: async () => {\n console.log(colors.cyan(\"🧹 Cleaning up expired tokens...\"));\n\n const count = await authService.cleanupExpiredTokens();\n\n if (count === 0) {\n console.log(colors.green(\"✅ No expired tokens found.\"));\n } else {\n console.log(colors.green(`✅ Removed ${count} expired token(s).`));\n }\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;AAYA,SAAgB,6BAA6B;CAC3C,OAAO,QAAQ;EACb,MAAM;EACN,aAAa;EACb,SAAS;GACP,KAAK;GACL,QAAQ,CAAC,QAAQ,UAAU;GAC3B,YAAY,CAAC,UAAU;EACzB;EACA,QAAQ,YAAY;GAClB,QAAQ,IAAI,OAAO,KAAK,kCAAkC,CAAC;GAE3D,MAAM,QAAQ,MAAM,YAAY,qBAAqB;GAErD,IAAI,UAAU,GACZ,QAAQ,IAAI,OAAO,MAAM,4BAA4B,CAAC;QAEtD,QAAQ,IAAI,OAAO,MAAM,aAAa,MAAM,mBAAmB,CAAC;EAEpE;CACF,CAAC;AACH"}
@@ -1,3 +1,4 @@
1
- import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, PageAuthConfig, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair } from "./types.mjs";
1
+ import { AuthProvider, GoogleProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig } from "./providers.mjs";
2
+ import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, AuthNotification, 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
3
  import { Authenticable } from "./auth-contract.mjs";
3
4
  import { RequestUser } from "./request-locals.mjs";
@@ -0,0 +1,101 @@
1
+ import { Auth } from "../models/auth.model.mjs";
2
+ import { ChildModel } from "@warlock.js/cascade";
3
+
4
+ //#region ../auth/src/contracts/providers.d.ts
5
+ /**
6
+ * The per-login secrets a redirect provider round-trips: `state` (CSRF),
7
+ * `nonce` (id_token replay binding) and the PKCE `codeVerifier`.
8
+ */
9
+ type ProviderAuthorizationState = {
10
+ state: string;
11
+ nonce: string;
12
+ codeVerifier: string;
13
+ };
14
+ /**
15
+ * What a provider's callback hands {@link AuthProvider.handleCallback}: the
16
+ * callback query and the state that was stored when the login started. The
17
+ * caller has already checked `query.state` against `expected.state`.
18
+ */
19
+ type ProviderCallbackParams = {
20
+ query: Record<string, unknown>;
21
+ expected: ProviderAuthorizationState;
22
+ };
23
+ /** The identity a provider asserts, normalized across providers. */
24
+ type ProviderProfile = {
25
+ /** Provider name, e.g. `"google"`. */provider: string; /** The provider's stable user id (`sub`). The link key — never the email. */
26
+ providerUserId: string;
27
+ email?: string; /** Whether the PROVIDER verified `email`. Accounts are never linked on `false`. */
28
+ emailVerified: boolean;
29
+ name?: string;
30
+ avatar?: string; /** The verified claims the profile was built from. */
31
+ raw: Record<string, unknown>;
32
+ };
33
+ /**
34
+ * A redirect-style login provider (OAuth 2 / OIDC authorization code).
35
+ * Register an instance under `auth.providers.custom.<name>` to add one auth does
36
+ * not ship.
37
+ */
38
+ interface AuthProvider {
39
+ readonly name: string;
40
+ /** The URL to send the browser to. */
41
+ authorizationUrl(state: ProviderAuthorizationState): string | Promise<string>;
42
+ /** Exchange the callback for a verified profile, or throw. */
43
+ handleCallback(params: ProviderCallbackParams): Promise<ProviderProfile>;
44
+ }
45
+ /** `auth.providers.google`. */
46
+ type GoogleProviderConfig = {
47
+ clientId: string;
48
+ clientSecret: string; /** Must exactly match a redirect URI registered in the Google console. */
49
+ redirectUri: string; /** @default ["openid", "email", "profile"] */
50
+ scopes?: string[];
51
+ };
52
+ /** Creates the app user for a verified provider profile with no matching account. */
53
+ type ProviderUserCreator = (profile: ProviderProfile, Model: ChildModel<Auth>) => Auth | Promise<Auth>;
54
+ /** `auth.providers`. */
55
+ type ProvidersConfig = {
56
+ google?: GoogleProviderConfig;
57
+ /**
58
+ * User attribute a verified provider email is matched against.
59
+ * @default "email"
60
+ */
61
+ emailField?: string; /** Replace the default user creation (`{ email, name, <verification.field>: now }`). */
62
+ createUser?: ProviderUserCreator; /** Providers auth does not ship, by name. */
63
+ custom?: Record<string, AuthProvider>;
64
+ };
65
+ /** `auth.passkeys`. */
66
+ type PasskeysConfig = {
67
+ /** Relying-party id — the site's registrable domain, e.g. `"example.com"`. */rpID: string; /** Name shown by the authenticator. */
68
+ rpName: string; /** Exact origin(s) the browser ceremony runs on, e.g. `"https://example.com"`. */
69
+ origin: string | string[];
70
+ /**
71
+ * Challenge lifetime — a positive `ms` duration string.
72
+ * @default "5m"
73
+ */
74
+ challengeExpiresIn?: string; /** Account name shown by the authenticator. @default user email, else id */
75
+ userName?: (user: Auth) => string;
76
+ };
77
+ /** Data an OTP delivery receives. */
78
+ type OtpMessage = {
79
+ /** Ready-to-send text containing the code. */body: string;
80
+ code: string;
81
+ expiresAt: Date;
82
+ };
83
+ /** Delivers an OTP. Replaces the default `notify.channel(channel).send(phone, message)`. */
84
+ type OtpSender = (phone: string, message: OtpMessage, channel: string) => unknown;
85
+ /** `auth.otp`. */
86
+ type OtpConfig = {
87
+ /**
88
+ * Notifications channel the code is sent through. The app registers it
89
+ * (auth ships no SMS/WhatsApp driver).
90
+ * @default "sms"
91
+ */
92
+ channel?: string; /** User attribute holding the phone number. @default "phone" */
93
+ phoneField?: string; /** Code lifetime. @default "5m" */
94
+ expiresIn?: string; /** Verify attempts before the code is invalidated. @default 5 */
95
+ maxAttempts?: number; /** Build the message text. @default "Your verification code is <code>" */
96
+ message?: (code: string) => string; /** Replace delivery entirely. */
97
+ send?: OtpSender;
98
+ };
99
+ //#endregion
100
+ export { AuthProvider, GoogleProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig };
101
+ //# sourceMappingURL=providers.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"providers.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/providers.ts"],"mappings":";;;;;;AAOA;;KAAY,0BAAA;EACV,KAAA;EACA,KAAA;EACA,YAAA;AAAA;;AAAY;AAQd;;;KAAY,sBAAA;EACV,KAAA,EAAO,MAAA;EACP,QAAA,EAAU,0BAA0B;AAAA;;KAI1B,eAAA;EAJ0B,sCAMpC,QAAA,UAFyB;EAIzB,cAAA;EACA,KAAA,WAHA;EAKA,aAAA;EACA,IAAA;EACA,MAAA,WADA;EAGA,GAAA,EAAK,MAAM;AAAA;;;AAAA;AAQb;;UAAiB,YAAA;EAAA,SACN,IAAA;EAEqD;EAA9D,gBAAA,CAAiB,KAAA,EAAO,0BAAA,YAAsC,OAAA;EAEN;EAAxD,cAAA,CAAe,MAAA,EAAQ,sBAAA,GAAyB,OAAA,CAAQ,eAAA;AAAA;;KAI9C,oBAAA;EACV,QAAA;EACA,YAAA,UARiB;EAUjB,WAAA,UARA;EAUA,MAAA;AAAA;;KAIU,mBAAA,IACV,OAAA,EAAS,eAAA,EACT,KAAA,EAAO,UAAA,CAAW,IAAA,MACf,IAAA,GAAO,OAAA,CAAQ,IAAA;;KAGR,eAAA;EACV,MAAA,GAAS,oBAAA;EAjBqB;;;;EAsB9B,UAAA,WAlBA;EAoBA,UAAA,GAAa,mBAAA,EAlBP;EAoBN,MAAA,GAAS,MAAA,SAAe,YAAA;AAAA;;KAId,cAAA;EAnBD,8EAqBT,IAAA,UApBO;EAsBP,MAAA,UArBkB;EAuBlB,MAAA;EAvBiB;;;;EA4BjB,kBAAA,WA7BkB;EA+BlB,QAAA,IAAY,IAAA,EAAM,IAAI;AAAA;;KAIZ,UAAA;EAlCY,8CAoCtB,IAAA;EACA,IAAA;EACA,SAAA,EAAW,IAAI;AAAA;;KAIL,SAAA,IAAa,KAAA,UAAe,OAAA,EAAS,UAAU,EAAE,OAAA;;KAGjD,SAAA;EAhCK;;;;;EAsCf,OAAA,WAxCa;EA0Cb,UAAA,WAxCS;EA0CT,SAAA,WA1CoC;EA4CpC,WAAA,WAxCU;EA0CV,OAAA,IAAW,IAAA;EAEX,IAAA,GAAO,SAAS;AAAA"}
@@ -1,5 +1,7 @@
1
1
  import { Auth } from "../models/auth.model.mjs";
2
2
  import { AccessToken } from "../models/access-token/access-token.model.mjs";
3
+ import { OneTimeToken } from "../models/one-time-token/one-time-token.model.mjs";
4
+ import { OtpConfig, PasskeysConfig, ProvidersConfig } from "./providers.mjs";
3
5
  import { RefreshToken } from "../models/refresh-token/refresh-token.model.mjs";
4
6
  import { ChildModel } from "@warlock.js/cascade";
5
7
  import { Algorithm } from "fast-jwt";
@@ -156,6 +158,73 @@ type PageAuthConfig = {
156
158
  */
157
159
  returnUrlParam?: string;
158
160
  };
161
+ /**
162
+ * Data every email-verification / password-reset notification receives.
163
+ */
164
+ type OneTimeTokenNotificationData = {
165
+ /** The raw token — the only place it exists; only its SHA-256 hash is stored. */token: string; /** When the token stops working. */
166
+ expiresAt: Date; /** The link built by `auth.verification.url` / `auth.passwordReset.url`, when configured. */
167
+ url?: string;
168
+ };
169
+ /**
170
+ * Anything auth can deliver a token through — a `defineNotification(...)`
171
+ * result satisfies it, as does any object with a compatible `send`.
172
+ */
173
+ type AuthNotification = {
174
+ send: (to: Auth, data: OneTimeTokenNotificationData) => unknown;
175
+ };
176
+ /** Builds the link a token notification points at. */
177
+ type OneTimeTokenUrlBuilder = (token: string, user: Auth) => string;
178
+ /** Persists a new plaintext password for a user during a password reset. */
179
+ type PasswordSetter = (user: Auth, plainPassword: string) => void | Promise<void>;
180
+ /**
181
+ * Email-verification configuration (`auth.verification`).
182
+ */
183
+ type EmailVerificationConfig = {
184
+ /**
185
+ * Token lifetime — a positive `ms` duration string.
186
+ * @default "24h"
187
+ */
188
+ expiresIn?: string;
189
+ /**
190
+ * User attribute stamped with the verification `Date`. Declare it in the
191
+ * user schema (e.g. `emailVerifiedAt: v.date().optional()`).
192
+ * @default "emailVerifiedAt"
193
+ */
194
+ field?: string; /** Replace the default verification email. */
195
+ notification?: AuthNotification; /** Build the verification link passed to the notification as `url`. */
196
+ url?: OneTimeTokenUrlBuilder;
197
+ };
198
+ /**
199
+ * Password-reset configuration (`auth.passwordReset`).
200
+ */
201
+ type PasswordResetConfig = {
202
+ /**
203
+ * Token lifetime — a positive `ms` duration string.
204
+ * @default "60m"
205
+ */
206
+ expiresIn?: string;
207
+ /**
208
+ * User attribute `requestPasswordReset` looks the account up by.
209
+ * @default "email"
210
+ */
211
+ identifierField?: string; /** Replace the default reset email. */
212
+ notification?: AuthNotification; /** Build the reset link passed to the notification as `url`. */
213
+ url?: OneTimeTokenUrlBuilder;
214
+ /**
215
+ * Persist the new password yourself. By default auth saves a hash made by
216
+ * the same `hashPassword` login verifies against, and falls back to the
217
+ * plaintext when the model hashes on save (`useHashedPassword()`), checking
218
+ * the stored value with `verifyPassword` either way.
219
+ */
220
+ setPassword?: PasswordSetter;
221
+ };
222
+ /**
223
+ * One-time token storage configuration (`auth.oneTimeToken`).
224
+ */
225
+ type OneTimeTokenConfig = {
226
+ /** Override the persisted model — extend {@link OneTimeToken}. */model?: typeof OneTimeToken;
227
+ };
159
228
  type AuthConfigurations = {
160
229
  /**
161
230
  * Define all user types — maps a user-type slug to its `Auth` model class so
@@ -193,6 +262,30 @@ type AuthConfigurations = {
193
262
  * CSRF Origin-check config for cookie-authenticated unsafe-method requests.
194
263
  */
195
264
  csrf?: CsrfConfig;
265
+ /**
266
+ * Email verification — token lifetime, verified field, notification override.
267
+ */
268
+ verification?: EmailVerificationConfig;
269
+ /**
270
+ * Password reset — token lifetime, lookup field, notification and password-writer overrides.
271
+ */
272
+ passwordReset?: PasswordResetConfig;
273
+ /**
274
+ * One-time token (verification / reset) storage — model override.
275
+ */
276
+ oneTimeToken?: OneTimeTokenConfig;
277
+ /**
278
+ * Redirect login providers (`google`, `custom`) and account-linking policy.
279
+ */
280
+ providers?: ProvidersConfig;
281
+ /**
282
+ * Passkey (WebAuthn) relying-party settings. Requires `@simplewebauthn/server`.
283
+ */
284
+ passkeys?: PasskeysConfig;
285
+ /**
286
+ * Phone one-time-code login — channel, lifetime, attempts, delivery.
287
+ */
288
+ otp?: OtpConfig;
196
289
  /**
197
290
  * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.
198
291
  */
@@ -307,5 +400,5 @@ type ClearAuthCookieOptions = {
307
400
  path?: string;
308
401
  };
309
402
  //#endregion
310
- export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, PageAuthConfig, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair };
403
+ export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, AuthNotification, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, DeviceInfo, EmailVerificationConfig, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenUrlBuilder, PageAuthConfig, PasswordResetConfig, PasswordSetter, RefreshTokenConfig, SetAuthCookieOptions, TokenFrom, TokenPair };
311
404
  //# sourceMappingURL=types.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":";;;;;;;;;;;AAyBA;;;;AAA0B;AAO1B;;;;AAAsC;AAGtC;;;;AAAqB;cAVR,aAAA;;;;;;KAOD,0BAAA;;KAGA,SAAA;AAMZ;AAAA,KAHY,eAAA,IAAmB,IAAA,EAAM,IAAA,eAAmB,OAAO;;KAGnD,eAAA,GAAkB,MAAM;EAClC,QAAQ;AAAA;;;;;;;KASE,iBAAA;EAeE;;;AAUH;AAMX;EAzBE,KAAA,UAAe,WAAA;;;;EAIf,MAAA;EA+BA;;;;EA1BA,SAAA,GAAY,SAAS;EAsDrB;;;AAA+C;AASjD;;;;;EArDE,SAAA;AAAA;;;;KAMU,kBAAA;EAsDR;;;;EAjDF,KAAA,UAAe,YAAA;EAoDkC;AAAA;AAYnD;;EA3DE,OAAA;EAiEA;AAMc;AAGhB;;EArEE,MAAA;EA2EiC;;;;;;;EAnEjC,SAAA;EAqGM;;;;EAhGN,QAAA;EA8DsB;;;;EAzDtB,UAAA;EAmEc;;;;EA9Dd,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;;;AA4FjB;AAMlC;;;;;KAtFY,cAAA;EAkGV;;;;;EA5FA,SAAA;EAwGU;;;;;EAlGV,cAAc;AAAA;AAAA,KAGJ,kBAAA;EA+FY;;;;EA1FtB,QAAA;IAAA,CACG,QAAA,WAAmB,UAAA,CAAW,IAAA;EAAA;EA2FhB;AAWnB;;;EAhGE,eAAA,GAAkB,eAAA;EA2Gd;AAON;;EA9GE,WAAA,GAAc,iBAAA;EAoHA;AAAA;AAShB;EAzHE,YAAA,GAAe,kBAAA;;;;;;EAMf,QAAA,GAAW,cAAA;EA8HL;AAIR;;;;EA5HE,MAAA,GAAS,gBAAA;;;;EAIT,IAAA,GAAO,UAAA;;;;EAIP,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;;;;;;;;;KAWE,gBAAA;;;;;;EAMV,IAAA;;;;;EAKA,IAAI;AAAA;;;;;KAOM,UAAA;;;;;;EAMV,cAAc;AAAA;;;;;;;KASJ,oBAAA;wFAEV,IAAA;EAEA,IAAA;;;;;;;EAOA,MAAA;AAAA;;KAIU,sBAAA;wFAEV,IAAA;EAEA,IAAI;AAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":";;;;;;;;;;;;;;AA2BA;;;;AAA0B;AAO1B;;;;AAAsC;AAGtC;;;;cAVa,aAAA;AAab;;;;;AAAA,KANY,0BAAA;;KAGA,SAAA;AAGmD;AAAA,KAAnD,eAAA,IAAmB,IAAA,EAAM,IAAA,eAAmB,OAAO;;KAGnD,eAAA,GAAkB,MAAM;EAClC,QAAQ;AAAA;AASV;;;;;;AAAA,KAAY,iBAAA;EAeV;;;;AAUS;EAnBT,KAAA,UAAe,WAAA;EAyBa;;;EArB5B,MAAA;EA0Be;;;;EArBf,SAAA,GAAY,SAAS;EAiDrB;;;;AAK+C;AASjD;;;;EArDE,SAAA;AAAA;;;;KAMU,kBAAA;EAqDR;;;;EAhDF,KAAA,UAAe,YAAA;EAoDQ;;AAA0B;AAYnD;EA3DE,OAAA;;;AAuEc;AAMhB;EAxEE,MAAA;;;;;;;;EAQA,SAAA;EA6EU;;;;EAxEV,QAAA;EAyEW;;;;EApEX,UAAA;EAoEmD;AAIrD;;;EAnEE,kBAAA,GAAqB,0BAA0B;AAAA;;;;AAmEc;AAG/D;;KA7DY,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;;;;;;;AAuErB;AAM9B;KAjEY,cAAA;;;;;;EAMV,SAAA;EAgEA;;;;;EA1DA,cAAc;AAAA;;;AA0Ec;KApElB,4BAAA;EA0EkB,iFAxE5B,KAAA,UA0EA;EAxEA,SAAA,EAAW,IAAI,EA2EL;EAzEV,GAAA;AAAA;;;;;KAOU,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;EAqHW;;;;EAhHrB,SAAA;EAsDiC;;;;;EAhDjC,KAAA,WA8De;EA5Df,YAAA,GAAe,gBAAA,EAkEJ;EAhEX,GAAA,GAAM,sBAAsB;AAAA;;;;KAMlB,mBAAA;EA4EV;;;;EAvEA,SAAA;EA+EY;;;;EA1EZ,eAAA,WAsFA;EApFA,YAAA,GAAe,gBAAA,EAoFM;EAlFrB,GAAA,GAAM,sBAAA;EAqFI;;;;AAQD;AAMX;EA5FE,WAAA,GAAc,cAAA;AAAA;;;;KAMJ,kBAAA;EA8FK,kEA5Ff,KAAA,UAAe,YAAY;AAAA;AAAA,KAGjB,kBAAA;EA+FU;;;;EA1FpB,QAAA;IAAA,CACG,QAAA,WAAmB,UAAA,CAAW,IAAA;EAAA;EA8GjC;;;AAAgB;EAxGhB,eAAA,GAAkB,eAAA;EA2GG;;;EAvGrB,WAAA,GAAc,iBAAA;EAyGN;;;EArGR,YAAA,GAAe,kBAAA;EAmGwB;;;;;EA7FvC,QAAA,GAAW,cAAA;EA+FM;AAWnB;;;;EApGE,MAAA,GAAS,gBAAA;EAsHC;;;EAlHV,IAAA,GAAO,UAAA;EAwHO;AAShB;;EA7HE,YAAA,GAAe,uBAAA;EA6He;;;EAzH9B,aAAA,GAAgB,mBAAA;EAoIV;AAAA;AAIR;EApIE,YAAA,GAAe,kBAAA;;;AAwIX;EApIJ,SAAA,GAAY,eAAA;;;;EAIZ,QAAA,GAAW,cAAA;;;;EAIX,GAAA,GAAM,SAAA;;;;EAIN,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;;;;;;;;;KAWE,gBAAA;;;;;;EAMV,IAAA;;;;;EAKA,IAAI;AAAA;;;;;KAOM,UAAA;;;;;;EAMV,cAAc;AAAA;;;;;;;KASJ,oBAAA;wFAEV,IAAA;EAEA,IAAA;;;;;;;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\";\r\nimport { type Algorithm } from \"fast-jwt\";\r\nimport type { AccessToken } from \"../models/access-token\";\r\nimport type { Auth } from \"../models/auth.model\";\r\nimport type { RefreshToken } from \"../models/refresh-token\";\r\n\r\n/**\r\n * Symbol to indicate no expiration for tokens\r\n * Use this when you explicitly want tokens to never expire\r\n *\r\n * @example\r\n * ```typescript\r\n * // src/config/auth.ts\r\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\r\n *\r\n * const authConfigurations: AuthConfigurations = {\r\n * accessToken: {\r\n * secret: env(\"JWT_SECRET\"),\r\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\r\n * },\r\n * };\r\n *\r\n * export default authConfigurations;\r\n * ```\r\n */\r\nexport const NO_EXPIRATION = \"100y\";\r\n\r\n/**\r\n * Behavior when logout is called without a refresh token\r\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\r\n * - \"error\": Return an error requiring the refresh token\r\n */\r\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\r\n\r\n/** The single request credential source an auth middleware instance accepts. */\r\nexport type TokenFrom = \"header\" | `cookie:${string}`;\r\n\r\n/** Application policy that decides whether a resolved user may authenticate. */\r\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\r\n\r\n/** Password credentials plus the application-defined identity fields. */\r\nexport type AuthCredentials = Record<string, unknown> & {\r\n password: string;\r\n};\r\n\r\n/**\r\n * Access-token configuration.\r\n *\r\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\r\n * backward-compatible shim); one of the two is required at runtime.\r\n */\r\nexport type AccessTokenConfig = {\r\n /**\r\n * Override the persisted access-token model — extend {@link AccessToken} to\r\n * add columns (e.g. a tenant key) or rename them, then register the subclass\r\n * here. Defaults to the package's `AccessToken`.\r\n */\r\n model?: typeof AccessToken;\r\n /**\r\n * Secret used to sign access tokens.\r\n */\r\n secret?: string;\r\n /**\r\n * JWT algorithm.\r\n * @default \"HS256\"\r\n */\r\n algorithm?: Algorithm;\r\n /**\r\n * Access-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\r\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\r\n * first token issue: each of them would otherwise sign a token with no `exp`\r\n * claim at all.\r\n * @example \"1h\" or NO_EXPIRATION\r\n * @default \"1h\"\r\n */\r\n expiresIn?: string;\r\n};\r\n\r\n/**\r\n * Refresh-token configuration.\r\n */\r\nexport type RefreshTokenConfig = {\r\n /**\r\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\r\n * Defaults to the package's `RefreshToken`.\r\n */\r\n model?: typeof RefreshToken;\r\n /**\r\n * Enable refresh tokens.\r\n * @default true\r\n */\r\n enabled?: boolean;\r\n /**\r\n * Separate secret for refresh tokens (recommended for security). Falls back\r\n * to the access-token secret when omitted.\r\n */\r\n secret?: string;\r\n /**\r\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds; an unusable value throws naming this key on issue\r\n * (see {@link AccessTokenConfig.expiresIn}).\r\n * @example \"7d\" or \"1w\"\r\n * @default \"7d\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * Rotate the refresh token on each use (old token revoked, replay detected).\r\n * @default true\r\n */\r\n rotation?: boolean;\r\n /**\r\n * Maximum active refresh tokens per user; the oldest are revoked past it.\r\n * @default 5\r\n */\r\n maxPerUser?: number;\r\n /**\r\n * Behavior when logout is called without a refresh token.\r\n * @default \"revoke-all\"\r\n */\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n};\r\n\r\n/**\r\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\r\n * deprecation warning.\r\n *\r\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\r\n */\r\nexport type LegacyJwtConfig = {\r\n secret?: string;\r\n algorithm?: Algorithm;\r\n expiresIn?: string;\r\n refresh?: {\r\n secret?: string;\r\n enabled?: boolean;\r\n expiresIn?: string;\r\n rotation?: boolean;\r\n maxPerUser?: number;\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n };\r\n};\r\n\r\n/**\r\n * Page-route auth-failure behavior. When a guarded PAGE route (a React-SSR page,\r\n * `route.isPage`) rejects a logged-out browser, this decides whether it redirects\r\n * to a login page instead of returning the API-style JSON 401.\r\n *\r\n * Read at runtime through `authConfig.pageAuth` (`auth.pageAuth.*`). API routes\r\n * always keep the JSON 401 contract regardless of this setting.\r\n */\r\nexport type PageAuthConfig = {\r\n /**\r\n * Login path a guarded page route redirects a logged-out browser to (e.g.\r\n * `\"/login\"`). Unset ⇒ page-route auth failures keep the JSON 401, so the\r\n * behavior is opt-in and backward-compatible.\r\n */\r\n loginPath?: string;\r\n /**\r\n * Query parameter the redirect carries the originally-requested path in, so\r\n * the login flow can send the user back where they were headed.\r\n * @default \"returnUrl\"\r\n */\r\n returnUrlParam?: string;\r\n};\r\n\r\nexport type AuthConfigurations = {\r\n /**\r\n * Define all user types — maps a user-type slug to its `Auth` model class so\r\n * tokens and the middleware can resolve the right model.\r\n */\r\n userType: {\r\n [userType: string]: ChildModel<Auth>;\r\n };\r\n /**\r\n * Decide whether a resolved user may authenticate or receive new tokens.\r\n * @default () => true\r\n */\r\n canAuthenticate?: CanAuthenticate;\r\n /**\r\n * Access-token configuration (secret, expiry, algorithm, model override).\r\n */\r\n accessToken?: AccessTokenConfig;\r\n /**\r\n * Refresh-token configuration (rotation, lifetime, cap, model override).\r\n */\r\n refreshToken?: RefreshTokenConfig;\r\n /**\r\n * Page-route auth-failure behavior — set `loginPath` to redirect a logged-out\r\n * browser on a guarded PAGE route to a login page instead of the JSON 401.\r\n * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.\r\n */\r\n pageAuth?: PageAuthConfig;\r\n /**\r\n * Cookie auth session config — read by `authService.setAuthCookie` /\r\n * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own\r\n * defaults (`name: \"access_token\"`, `path: \"/\"`).\r\n */\r\n cookie?: CookieAuthConfig;\r\n /**\r\n * CSRF Origin-check config for cookie-authenticated unsafe-method requests.\r\n */\r\n csrf?: CsrfConfig;\r\n /**\r\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\r\n */\r\n jwt?: LegacyJwtConfig;\r\n};\r\n\r\nexport type AccessTokenOutput = {\r\n /**\r\n * JWT Token\r\n */\r\n token: string;\r\n /**\r\n * Exprie time in ISO format UTC time\r\n */\r\n expiresAt: string;\r\n};\r\n\r\n/**\r\n * Token pair returned after login or token refresh\r\n */\r\nexport type TokenPair = {\r\n /**\r\n * JWT access token (short-lived)\r\n */\r\n accessToken: AccessTokenOutput;\r\n /**\r\n * JWT refresh token (long-lived)\r\n */\r\n refreshToken?: AccessTokenOutput;\r\n};\r\n\r\n/**\r\n * Device information for session tracking\r\n */\r\nexport type DeviceInfo = {\r\n /**\r\n * User agent string from request\r\n */\r\n userAgent?: string;\r\n /**\r\n * Client IP address\r\n */\r\n ip?: string;\r\n /**\r\n * Optional device identifier\r\n */\r\n deviceId?: string;\r\n /**\r\n * Token family ID (for rotation tracking)\r\n * @internal\r\n */\r\n familyId?: string;\r\n /**\r\n * Access token payload\r\n */\r\n payload?: Record<string, unknown>;\r\n};\r\n\r\nexport type LoginResult<UserType extends Auth> = {\r\n user: UserType;\r\n tokens: TokenPair;\r\n};\r\n\r\n/**\r\n * Configuration for the cookie an app opts into via `authService.setAuthCookie`\r\n * / `clearAuthCookie` and reads back with `authMiddleware([], \"cookie:<name>\")`.\r\n * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT\r\n * configurable here — they come from core's `secureCookieDefaults()`, the\r\n * same floor every `response.cookie()` call gets, so a cookie auth session can\r\n * never be weakened to a laxer policy than the framework default.\r\n */\r\nexport type CookieAuthConfig = {\r\n /**\r\n * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a\r\n * `cookie:<name>` token source must match to read it back.\r\n * @default \"access_token\"\r\n */\r\n name?: string;\r\n /**\r\n * Cookie `Path` attribute.\r\n * @default \"/\"\r\n */\r\n path?: string;\r\n};\r\n\r\n/**\r\n * CSRF Origin-check configuration. Read via\r\n * `authConfig.csrf.allowedOrigins()`.\r\n */\r\nexport type CsrfConfig = {\r\n /**\r\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\r\n * in addition to the request's own origin.\r\n * @default []\r\n */\r\n allowedOrigins?: string[];\r\n};\r\n\r\n/**\r\n * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted\r\n * falls back to `auth.cookie.*` config, then the package default. Cookie\r\n * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —\r\n * see {@link CookieAuthConfig}.\r\n */\r\nexport type SetAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). */\r\n path?: string;\r\n /**\r\n * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would\r\n * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a\r\n * bare token string with no `maxAge` produces a session cookie (cleared\r\n * when the browser closes).\r\n */\r\n maxAge?: number;\r\n};\r\n\r\n/** Per-call overrides for {@link AuthService.clearAuthCookie}. */\r\nexport type ClearAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). Must match the `Path` the cookie was set with. */\r\n path?: string;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,gBAAgB"}
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"sourcesContent":["import { type ChildModel } from \"@warlock.js/cascade\";\r\nimport { type Algorithm } from \"fast-jwt\";\r\nimport type { AccessToken } from \"../models/access-token\";\r\nimport type { Auth } from \"../models/auth.model\";\r\nimport type { OneTimeToken } from \"../models/one-time-token\";\r\nimport type { OtpConfig, PasskeysConfig, ProvidersConfig } from \"./providers\";\r\nimport type { RefreshToken } from \"../models/refresh-token\";\r\n\r\n/**\r\n * Symbol to indicate no expiration for tokens\r\n * Use this when you explicitly want tokens to never expire\r\n *\r\n * @example\r\n * ```typescript\r\n * // src/config/auth.ts\r\n * import { NO_EXPIRATION, type AuthConfigurations } from \"@warlock.js/auth\";\r\n *\r\n * const authConfigurations: AuthConfigurations = {\r\n * accessToken: {\r\n * secret: env(\"JWT_SECRET\"),\r\n * expiresIn: NO_EXPIRATION, // Token expires within 100 years\r\n * },\r\n * };\r\n *\r\n * export default authConfigurations;\r\n * ```\r\n */\r\nexport const NO_EXPIRATION = \"100y\";\r\n\r\n/**\r\n * Behavior when logout is called without a refresh token\r\n * - \"revoke-all\": Revoke all refresh tokens for the user (secure default)\r\n * - \"error\": Return an error requiring the refresh token\r\n */\r\nexport type LogoutWithoutTokenBehavior = \"revoke-all\" | \"error\";\r\n\r\n/** The single request credential source an auth middleware instance accepts. */\r\nexport type TokenFrom = \"header\" | `cookie:${string}`;\r\n\r\n/** Application policy that decides whether a resolved user may authenticate. */\r\nexport type CanAuthenticate = (user: Auth) => boolean | Promise<boolean>;\r\n\r\n/** Password credentials plus the application-defined identity fields. */\r\nexport type AuthCredentials = Record<string, unknown> & {\r\n password: string;\r\n};\r\n\r\n/**\r\n * Access-token configuration.\r\n *\r\n * `secret` may also be supplied via the deprecated `jwt.secret` (resolved by a\r\n * backward-compatible shim); one of the two is required at runtime.\r\n */\r\nexport type AccessTokenConfig = {\r\n /**\r\n * Override the persisted access-token model — extend {@link AccessToken} to\r\n * add columns (e.g. a tenant key) or rename them, then register the subclass\r\n * here. Defaults to the package's `AccessToken`.\r\n */\r\n model?: typeof AccessToken;\r\n /**\r\n * Secret used to sign access tokens.\r\n */\r\n secret?: string;\r\n /**\r\n * JWT algorithm.\r\n * @default \"HS256\"\r\n */\r\n algorithm?: Algorithm;\r\n /**\r\n * Access-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds. A value `ms` cannot parse (`\"30dayz\"`), a\r\n * non-positive one (`\"0d\"`), or a bare number throws naming this key on the\r\n * first token issue: each of them would otherwise sign a token with no `exp`\r\n * claim at all.\r\n * @example \"1h\" or NO_EXPIRATION\r\n * @default \"1h\"\r\n */\r\n expiresIn?: string;\r\n};\r\n\r\n/**\r\n * Refresh-token configuration.\r\n */\r\nexport type RefreshTokenConfig = {\r\n /**\r\n * Override the persisted refresh-token model — extend {@link RefreshToken}.\r\n * Defaults to the package's `RefreshToken`.\r\n */\r\n model?: typeof RefreshToken;\r\n /**\r\n * Enable refresh tokens.\r\n * @default true\r\n */\r\n enabled?: boolean;\r\n /**\r\n * Separate secret for refresh tokens (recommended for security). Falls back\r\n * to the access-token secret when omitted.\r\n */\r\n secret?: string;\r\n /**\r\n * Refresh-token lifetime — a duration string `ms` parses to a **positive**\r\n * number of milliseconds; an unusable value throws naming this key on issue\r\n * (see {@link AccessTokenConfig.expiresIn}).\r\n * @example \"7d\" or \"1w\"\r\n * @default \"7d\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * Rotate the refresh token on each use (old token revoked, replay detected).\r\n * @default true\r\n */\r\n rotation?: boolean;\r\n /**\r\n * Maximum active refresh tokens per user; the oldest are revoked past it.\r\n * @default 5\r\n */\r\n maxPerUser?: number;\r\n /**\r\n * Behavior when logout is called without a refresh token.\r\n * @default \"revoke-all\"\r\n */\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n};\r\n\r\n/**\r\n * Legacy JWT configuration shape. Still honored by the resolver shim with a\r\n * deprecation warning.\r\n *\r\n * @deprecated Use the top-level `accessToken` / `refreshToken` blocks.\r\n */\r\nexport type LegacyJwtConfig = {\r\n secret?: string;\r\n algorithm?: Algorithm;\r\n expiresIn?: string;\r\n refresh?: {\r\n secret?: string;\r\n enabled?: boolean;\r\n expiresIn?: string;\r\n rotation?: boolean;\r\n maxPerUser?: number;\r\n logoutWithoutToken?: LogoutWithoutTokenBehavior;\r\n };\r\n};\r\n\r\n/**\r\n * Page-route auth-failure behavior. When a guarded PAGE route (a React-SSR page,\r\n * `route.isPage`) rejects a logged-out browser, this decides whether it redirects\r\n * to a login page instead of returning the API-style JSON 401.\r\n *\r\n * Read at runtime through `authConfig.pageAuth` (`auth.pageAuth.*`). API routes\r\n * always keep the JSON 401 contract regardless of this setting.\r\n */\r\nexport type PageAuthConfig = {\r\n /**\r\n * Login path a guarded page route redirects a logged-out browser to (e.g.\r\n * `\"/login\"`). Unset ⇒ page-route auth failures keep the JSON 401, so the\r\n * behavior is opt-in and backward-compatible.\r\n */\r\n loginPath?: string;\r\n /**\r\n * Query parameter the redirect carries the originally-requested path in, so\r\n * the login flow can send the user back where they were headed.\r\n * @default \"returnUrl\"\r\n */\r\n returnUrlParam?: string;\r\n};\r\n\r\n/**\r\n * Data every email-verification / password-reset notification receives.\r\n */\r\nexport type OneTimeTokenNotificationData = {\r\n /** The raw token — the only place it exists; only its SHA-256 hash is stored. */\r\n token: string;\r\n /** When the token stops working. */\r\n expiresAt: Date;\r\n /** The link built by `auth.verification.url` / `auth.passwordReset.url`, when configured. */\r\n url?: string;\r\n};\r\n\r\n/**\r\n * Anything auth can deliver a token through — a `defineNotification(...)`\r\n * result satisfies it, as does any object with a compatible `send`.\r\n */\r\nexport type AuthNotification = {\r\n send: (to: Auth, data: OneTimeTokenNotificationData) => unknown;\r\n};\r\n\r\n/** Builds the link a token notification points at. */\r\nexport type OneTimeTokenUrlBuilder = (token: string, user: Auth) => string;\r\n\r\n/** Persists a new plaintext password for a user during a password reset. */\r\nexport type PasswordSetter = (user: Auth, plainPassword: string) => void | Promise<void>;\r\n\r\n/**\r\n * Email-verification configuration (`auth.verification`).\r\n */\r\nexport type EmailVerificationConfig = {\r\n /**\r\n * Token lifetime — a positive `ms` duration string.\r\n * @default \"24h\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * User attribute stamped with the verification `Date`. Declare it in the\r\n * user schema (e.g. `emailVerifiedAt: v.date().optional()`).\r\n * @default \"emailVerifiedAt\"\r\n */\r\n field?: string;\r\n /** Replace the default verification email. */\r\n notification?: AuthNotification;\r\n /** Build the verification link passed to the notification as `url`. */\r\n url?: OneTimeTokenUrlBuilder;\r\n};\r\n\r\n/**\r\n * Password-reset configuration (`auth.passwordReset`).\r\n */\r\nexport type PasswordResetConfig = {\r\n /**\r\n * Token lifetime — a positive `ms` duration string.\r\n * @default \"60m\"\r\n */\r\n expiresIn?: string;\r\n /**\r\n * User attribute `requestPasswordReset` looks the account up by.\r\n * @default \"email\"\r\n */\r\n identifierField?: string;\r\n /** Replace the default reset email. */\r\n notification?: AuthNotification;\r\n /** Build the reset link passed to the notification as `url`. */\r\n url?: OneTimeTokenUrlBuilder;\r\n /**\r\n * Persist the new password yourself. By default auth saves a hash made by\r\n * the same `hashPassword` login verifies against, and falls back to the\r\n * plaintext when the model hashes on save (`useHashedPassword()`), checking\r\n * the stored value with `verifyPassword` either way.\r\n */\r\n setPassword?: PasswordSetter;\r\n};\r\n\r\n/**\r\n * One-time token storage configuration (`auth.oneTimeToken`).\r\n */\r\nexport type OneTimeTokenConfig = {\r\n /** Override the persisted model — extend {@link OneTimeToken}. */\r\n model?: typeof OneTimeToken;\r\n};\r\n\r\nexport type AuthConfigurations = {\r\n /**\r\n * Define all user types — maps a user-type slug to its `Auth` model class so\r\n * tokens and the middleware can resolve the right model.\r\n */\r\n userType: {\r\n [userType: string]: ChildModel<Auth>;\r\n };\r\n /**\r\n * Decide whether a resolved user may authenticate or receive new tokens.\r\n * @default () => true\r\n */\r\n canAuthenticate?: CanAuthenticate;\r\n /**\r\n * Access-token configuration (secret, expiry, algorithm, model override).\r\n */\r\n accessToken?: AccessTokenConfig;\r\n /**\r\n * Refresh-token configuration (rotation, lifetime, cap, model override).\r\n */\r\n refreshToken?: RefreshTokenConfig;\r\n /**\r\n * Page-route auth-failure behavior — set `loginPath` to redirect a logged-out\r\n * browser on a guarded PAGE route to a login page instead of the JSON 401.\r\n * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.\r\n */\r\n pageAuth?: PageAuthConfig;\r\n /**\r\n * Cookie auth session config — read by `authService.setAuthCookie` /\r\n * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own\r\n * defaults (`name: \"access_token\"`, `path: \"/\"`).\r\n */\r\n cookie?: CookieAuthConfig;\r\n /**\r\n * CSRF Origin-check config for cookie-authenticated unsafe-method requests.\r\n */\r\n csrf?: CsrfConfig;\r\n /**\r\n * Email verification — token lifetime, verified field, notification override.\r\n */\r\n verification?: EmailVerificationConfig;\r\n /**\r\n * Password reset — token lifetime, lookup field, notification and password-writer overrides.\r\n */\r\n passwordReset?: PasswordResetConfig;\r\n /**\r\n * One-time token (verification / reset) storage — model override.\r\n */\r\n oneTimeToken?: OneTimeTokenConfig;\r\n /**\r\n * Redirect login providers (`google`, `custom`) and account-linking policy.\r\n */\r\n providers?: ProvidersConfig;\r\n /**\r\n * Passkey (WebAuthn) relying-party settings. Requires `@simplewebauthn/server`.\r\n */\r\n passkeys?: PasskeysConfig;\r\n /**\r\n * Phone one-time-code login — channel, lifetime, attempts, delivery.\r\n */\r\n otp?: OtpConfig;\r\n /**\r\n * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.\r\n */\r\n jwt?: LegacyJwtConfig;\r\n};\r\n\r\nexport type AccessTokenOutput = {\r\n /**\r\n * JWT Token\r\n */\r\n token: string;\r\n /**\r\n * Exprie time in ISO format UTC time\r\n */\r\n expiresAt: string;\r\n};\r\n\r\n/**\r\n * Token pair returned after login or token refresh\r\n */\r\nexport type TokenPair = {\r\n /**\r\n * JWT access token (short-lived)\r\n */\r\n accessToken: AccessTokenOutput;\r\n /**\r\n * JWT refresh token (long-lived)\r\n */\r\n refreshToken?: AccessTokenOutput;\r\n};\r\n\r\n/**\r\n * Device information for session tracking\r\n */\r\nexport type DeviceInfo = {\r\n /**\r\n * User agent string from request\r\n */\r\n userAgent?: string;\r\n /**\r\n * Client IP address\r\n */\r\n ip?: string;\r\n /**\r\n * Optional device identifier\r\n */\r\n deviceId?: string;\r\n /**\r\n * Token family ID (for rotation tracking)\r\n * @internal\r\n */\r\n familyId?: string;\r\n /**\r\n * Access token payload\r\n */\r\n payload?: Record<string, unknown>;\r\n};\r\n\r\nexport type LoginResult<UserType extends Auth> = {\r\n user: UserType;\r\n tokens: TokenPair;\r\n};\r\n\r\n/**\r\n * Configuration for the cookie an app opts into via `authService.setAuthCookie`\r\n * / `clearAuthCookie` and reads back with `authMiddleware([], \"cookie:<name>\")`.\r\n * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT\r\n * configurable here — they come from core's `secureCookieDefaults()`, the\r\n * same floor every `response.cookie()` call gets, so a cookie auth session can\r\n * never be weakened to a laxer policy than the framework default.\r\n */\r\nexport type CookieAuthConfig = {\r\n /**\r\n * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a\r\n * `cookie:<name>` token source must match to read it back.\r\n * @default \"access_token\"\r\n */\r\n name?: string;\r\n /**\r\n * Cookie `Path` attribute.\r\n * @default \"/\"\r\n */\r\n path?: string;\r\n};\r\n\r\n/**\r\n * CSRF Origin-check configuration. Read via\r\n * `authConfig.csrf.allowedOrigins()`.\r\n */\r\nexport type CsrfConfig = {\r\n /**\r\n * Extra origins allowed on a cookie-authenticated unsafe-method request,\r\n * in addition to the request's own origin.\r\n * @default []\r\n */\r\n allowedOrigins?: string[];\r\n};\r\n\r\n/**\r\n * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted\r\n * falls back to `auth.cookie.*` config, then the package default. Cookie\r\n * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —\r\n * see {@link CookieAuthConfig}.\r\n */\r\nexport type SetAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). */\r\n path?: string;\r\n /**\r\n * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would\r\n * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a\r\n * bare token string with no `maxAge` produces a session cookie (cleared\r\n * when the browser closes).\r\n */\r\n maxAge?: number;\r\n};\r\n\r\n/** Per-call overrides for {@link AuthService.clearAuthCookie}. */\r\nexport type ClearAuthCookieOptions = {\r\n /** Cookie name; defaults to `auth.cookie.name` (package default `\"access_token\"`). */\r\n name?: string;\r\n /** Cookie `Path`; defaults to `auth.cookie.path` (package default `\"/\"`). Must match the `Path` the cookie was set with. */\r\n path?: string;\r\n};\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2BA,MAAa,gBAAgB"}
@@ -0,0 +1,17 @@
1
+ import { ServerError } from "@warlock.js/core";
2
+
3
+ //#region ../auth/src/errors/auth-provider-sdk-missing.error.d.ts
4
+ /**
5
+ * A login method needs a vendor SDK that is an optional peer of
6
+ * `@warlock.js/auth` and is not installed. The message names the package and
7
+ * the `warlock add` command that installs it; the resolver failure is kept on
8
+ * `cause`.
9
+ */
10
+ declare class AuthProviderSdkMissingError extends ServerError {
11
+ readonly packageName: string;
12
+ readonly feature: string;
13
+ constructor(packageName: string, feature: string, cause?: unknown);
14
+ }
15
+ //#endregion
16
+ export { AuthProviderSdkMissingError };
17
+ //# sourceMappingURL=auth-provider-sdk-missing.error.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-provider-sdk-missing.error.d.mts","names":[],"sources":["../../../../../../../auth/src/errors/auth-provider-sdk-missing.error.ts"],"mappings":";;;;;AAQA;;;;cAAa,2BAAA,SAAoC,WAAW;EAAA,SAExC,WAAA;EAAA,SACA,OAAA;cADA,WAAA,UACA,OAAA,UAChB,KAAA;AAAA"}
@@ -0,0 +1,22 @@
1
+ import { ServerError } from "@warlock.js/core";
2
+
3
+ //#region ../auth/src/errors/auth-provider-sdk-missing.error.ts
4
+ /**
5
+ * A login method needs a vendor SDK that is an optional peer of
6
+ * `@warlock.js/auth` and is not installed. The message names the package and
7
+ * the `warlock add` command that installs it; the resolver failure is kept on
8
+ * `cause`.
9
+ */
10
+ var AuthProviderSdkMissingError = class extends ServerError {
11
+ constructor(packageName, feature, cause) {
12
+ super(`@warlock.js/auth: this login method needs "${packageName}", which is not installed — run \`warlock add ${feature}\`.`);
13
+ this.packageName = packageName;
14
+ this.feature = feature;
15
+ this.name = "AuthProviderSdkMissingError";
16
+ this.cause = cause;
17
+ }
18
+ };
19
+
20
+ //#endregion
21
+ export { AuthProviderSdkMissingError };
22
+ //# sourceMappingURL=auth-provider-sdk-missing.error.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-provider-sdk-missing.error.mjs","names":[],"sources":["../../../../../../../auth/src/errors/auth-provider-sdk-missing.error.ts"],"sourcesContent":["import { ServerError } from \"@warlock.js/core\";\n\n/**\n * A login method needs a vendor SDK that is an optional peer of\n * `@warlock.js/auth` and is not installed. The message names the package and\n * the `warlock add` command that installs it; the resolver failure is kept on\n * `cause`.\n */\nexport class AuthProviderSdkMissingError extends ServerError {\n public constructor(\n public readonly packageName: string,\n public readonly feature: string,\n cause?: unknown,\n ) {\n super(\n `@warlock.js/auth: this login method needs \"${packageName}\", which is not installed — ` +\n `run \\`warlock add ${feature}\\`.`,\n );\n this.name = \"AuthProviderSdkMissingError\";\n this.cause = cause;\n }\n}\n"],"mappings":";;;;;;;;;AAQA,IAAa,8BAAb,cAAiD,YAAY;CAC3D,AAAO,YACL,AAAgB,aAChB,AAAgB,SAChB,OACA;EACA,MACE,8CAA8C,YAAY,gDACnC,QAAQ,IACjC;EAPgB;EACA;EAOhB,KAAK,OAAO;EACZ,KAAK,QAAQ;CACf;AACF"}
@@ -0,0 +1,16 @@
1
+ import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
2
+ import { ForbiddenError } from "@warlock.js/core";
3
+
4
+ //#region ../auth/src/errors/email-not-verified.error.d.ts
5
+ /**
6
+ * The authenticated user has not verified their email address. Thrown by
7
+ * `requireVerifiedEmail()`; core's error handler answers it with `403` and a
8
+ * `payload.errorCode` of `EC007`.
9
+ */
10
+ declare class EmailNotVerifiedError extends ForbiddenError {
11
+ readonly code = AuthErrorCodes.EmailNotVerified;
12
+ constructor(message?: string);
13
+ }
14
+ //#endregion
15
+ export { EmailNotVerifiedError };
16
+ //# sourceMappingURL=email-not-verified.error.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-not-verified.error.d.mts","names":[],"sources":["../../../../../../../auth/src/errors/email-not-verified.error.ts"],"mappings":";;;;;;AAQA;;;cAAa,qBAAA,SAA8B,cAAc;EAAA,SACvC,IAAA,GAAI,cAAA,CAAA,gBAAA;cAED,OAAA;AAAA"}