@warlock.js/auth 5.11.0 → 5.13.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 (185) hide show
  1. package/CHANGELOG.md +65 -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 +4 -2
  6. package/esm/contracts/providers.d.mts +101 -0
  7. package/esm/contracts/providers.d.mts.map +1 -0
  8. package/esm/contracts/request-locals.d.mts +43 -0
  9. package/esm/contracts/request-locals.d.mts.map +1 -0
  10. package/esm/contracts/types.d.mts +159 -1
  11. package/esm/contracts/types.d.mts.map +1 -1
  12. package/esm/contracts/types.mjs.map +1 -1
  13. package/esm/errors/auth-provider-sdk-missing.error.d.mts +17 -0
  14. package/esm/errors/auth-provider-sdk-missing.error.d.mts.map +1 -0
  15. package/esm/errors/auth-provider-sdk-missing.error.mjs +22 -0
  16. package/esm/errors/auth-provider-sdk-missing.error.mjs.map +1 -0
  17. package/esm/errors/email-not-verified.error.d.mts +16 -0
  18. package/esm/errors/email-not-verified.error.d.mts.map +1 -0
  19. package/esm/errors/email-not-verified.error.mjs +20 -0
  20. package/esm/errors/email-not-verified.error.mjs.map +1 -0
  21. package/esm/errors/index.d.mts +7 -0
  22. package/esm/errors/index.mjs +9 -0
  23. package/esm/errors/invalid-one-time-token.error.d.mts +17 -0
  24. package/esm/errors/invalid-one-time-token.error.d.mts.map +1 -0
  25. package/esm/errors/invalid-one-time-token.error.mjs +21 -0
  26. package/esm/errors/invalid-one-time-token.error.mjs.map +1 -0
  27. package/esm/errors/invalid-passkey.error.d.mts +18 -0
  28. package/esm/errors/invalid-passkey.error.d.mts.map +1 -0
  29. package/esm/errors/invalid-passkey.error.mjs +23 -0
  30. package/esm/errors/invalid-passkey.error.mjs.map +1 -0
  31. package/esm/errors/invalid-provider-callback.error.d.mts +18 -0
  32. package/esm/errors/invalid-provider-callback.error.d.mts.map +1 -0
  33. package/esm/errors/invalid-provider-callback.error.mjs +23 -0
  34. package/esm/errors/invalid-provider-callback.error.mjs.map +1 -0
  35. package/esm/errors/notifications-unavailable.error.d.mts +15 -0
  36. package/esm/errors/notifications-unavailable.error.d.mts.map +1 -0
  37. package/esm/errors/notifications-unavailable.error.mjs +20 -0
  38. package/esm/errors/notifications-unavailable.error.mjs.map +1 -0
  39. package/esm/errors/provider-email-not-verified.error.d.mts +16 -0
  40. package/esm/errors/provider-email-not-verified.error.d.mts.map +1 -0
  41. package/esm/errors/provider-email-not-verified.error.mjs +20 -0
  42. package/esm/errors/provider-email-not-verified.error.mjs.map +1 -0
  43. package/esm/index.d.mts +31 -3
  44. package/esm/index.mjs +29 -1
  45. package/esm/middleware/auth.middleware.d.mts.map +1 -1
  46. package/esm/middleware/auth.middleware.mjs +13 -2
  47. package/esm/middleware/auth.middleware.mjs.map +1 -1
  48. package/esm/middleware/csrf-origin-check.d.mts +40 -0
  49. package/esm/middleware/csrf-origin-check.d.mts.map +1 -0
  50. package/esm/middleware/csrf-origin-check.mjs +105 -0
  51. package/esm/middleware/csrf-origin-check.mjs.map +1 -0
  52. package/esm/middleware/index.d.mts +5 -1
  53. package/esm/middleware/index.mjs +4 -0
  54. package/esm/middleware/one-time-token-throttle.middleware.d.mts +36 -0
  55. package/esm/middleware/one-time-token-throttle.middleware.d.mts.map +1 -0
  56. package/esm/middleware/one-time-token-throttle.middleware.mjs +53 -0
  57. package/esm/middleware/one-time-token-throttle.middleware.mjs.map +1 -0
  58. package/esm/middleware/otp-throttle.middleware.d.mts +30 -0
  59. package/esm/middleware/otp-throttle.middleware.d.mts.map +1 -0
  60. package/esm/middleware/otp-throttle.middleware.mjs +45 -0
  61. package/esm/middleware/otp-throttle.middleware.mjs.map +1 -0
  62. package/esm/middleware/require-verified-email.middleware.d.mts +18 -0
  63. package/esm/middleware/require-verified-email.middleware.d.mts.map +1 -0
  64. package/esm/middleware/require-verified-email.middleware.mjs +24 -0
  65. package/esm/middleware/require-verified-email.middleware.mjs.map +1 -0
  66. package/esm/models/access-token/access-token.model.d.mts +8 -0
  67. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  68. package/esm/models/access-token/access-token.model.mjs +3 -0
  69. package/esm/models/access-token/access-token.model.mjs.map +1 -1
  70. package/esm/models/index.d.mts +3 -1
  71. package/esm/models/index.d.mts.map +1 -1
  72. package/esm/models/index.mjs +16 -1
  73. package/esm/models/index.mjs.map +1 -1
  74. package/esm/models/one-time-token/index.d.mts +1 -0
  75. package/esm/models/one-time-token/index.mjs +3 -0
  76. package/esm/models/one-time-token/migration.mjs +31 -0
  77. package/esm/models/one-time-token/migration.mjs.map +1 -0
  78. package/esm/models/one-time-token/one-time-token.model.d.mts +173 -0
  79. package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -0
  80. package/esm/models/one-time-token/one-time-token.model.mjs +151 -0
  81. package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -0
  82. package/esm/models/passkey-credential/index.d.mts +1 -0
  83. package/esm/models/passkey-credential/index.mjs +3 -0
  84. package/esm/models/passkey-credential/migration.mjs +25 -0
  85. package/esm/models/passkey-credential/migration.mjs.map +1 -0
  86. package/esm/models/passkey-credential/passkey-credential.model.d.mts +108 -0
  87. package/esm/models/passkey-credential/passkey-credential.model.d.mts.map +1 -0
  88. package/esm/models/passkey-credential/passkey-credential.model.mjs +70 -0
  89. package/esm/models/passkey-credential/passkey-credential.model.mjs.map +1 -0
  90. package/esm/models/provider-account/index.d.mts +1 -0
  91. package/esm/models/provider-account/index.mjs +3 -0
  92. package/esm/models/provider-account/migration.mjs +25 -0
  93. package/esm/models/provider-account/migration.mjs.map +1 -0
  94. package/esm/models/provider-account/provider-account.model.d.mts +75 -0
  95. package/esm/models/provider-account/provider-account.model.d.mts.map +1 -0
  96. package/esm/models/provider-account/provider-account.model.mjs +55 -0
  97. package/esm/models/provider-account/provider-account.model.mjs.map +1 -0
  98. package/esm/models/refresh-token/refresh-token.model.d.mts +7 -0
  99. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  100. package/esm/models/refresh-token/refresh-token.model.mjs +3 -0
  101. package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
  102. package/esm/otp/index.d.mts +1 -0
  103. package/esm/otp/index.mjs +3 -0
  104. package/esm/otp/otp.d.mts +38 -0
  105. package/esm/otp/otp.d.mts.map +1 -0
  106. package/esm/otp/otp.mjs +105 -0
  107. package/esm/otp/otp.mjs.map +1 -0
  108. package/esm/passkeys/index.d.mts +3 -0
  109. package/esm/passkeys/index.mjs +4 -0
  110. package/esm/passkeys/passkey-authentication.d.mts +33 -0
  111. package/esm/passkeys/passkey-authentication.d.mts.map +1 -0
  112. package/esm/passkeys/passkey-authentication.mjs +93 -0
  113. package/esm/passkeys/passkey-authentication.mjs.map +1 -0
  114. package/esm/passkeys/passkey-challenges.mjs +53 -0
  115. package/esm/passkeys/passkey-challenges.mjs.map +1 -0
  116. package/esm/passkeys/passkey-registration.d.mts +29 -0
  117. package/esm/passkeys/passkey-registration.d.mts.map +1 -0
  118. package/esm/passkeys/passkey-registration.mjs +88 -0
  119. package/esm/passkeys/passkey-registration.mjs.map +1 -0
  120. package/esm/passkeys/simplewebauthn.d.mts +21 -0
  121. package/esm/passkeys/simplewebauthn.d.mts.map +1 -0
  122. package/esm/passkeys/simplewebauthn.mjs +13 -0
  123. package/esm/passkeys/simplewebauthn.mjs.map +1 -0
  124. package/esm/providers/google-provider.d.mts +40 -0
  125. package/esm/providers/google-provider.d.mts.map +1 -0
  126. package/esm/providers/google-provider.mjs +105 -0
  127. package/esm/providers/google-provider.mjs.map +1 -0
  128. package/esm/providers/index.d.mts +2 -0
  129. package/esm/providers/index.mjs +4 -0
  130. package/esm/providers/provider-login.d.mts +43 -0
  131. package/esm/providers/provider-login.d.mts.map +1 -0
  132. package/esm/providers/provider-login.mjs +86 -0
  133. package/esm/providers/provider-login.mjs.map +1 -0
  134. package/esm/providers/provider-state-cookie.mjs +63 -0
  135. package/esm/providers/provider-state-cookie.mjs.map +1 -0
  136. package/esm/providers/resolve-provider-user.mjs +45 -0
  137. package/esm/providers/resolve-provider-user.mjs.map +1 -0
  138. package/esm/services/auth-config.mjs +84 -0
  139. package/esm/services/auth-config.mjs.map +1 -1
  140. package/esm/services/auth.service.d.mts +59 -2
  141. package/esm/services/auth.service.d.mts.map +1 -1
  142. package/esm/services/auth.service.mjs +86 -1
  143. package/esm/services/auth.service.mjs.map +1 -1
  144. package/esm/services/current-user.d.mts +21 -0
  145. package/esm/services/current-user.d.mts.map +1 -0
  146. package/esm/services/current-user.mjs +24 -0
  147. package/esm/services/current-user.mjs.map +1 -0
  148. package/esm/services/email-verification.d.mts +35 -0
  149. package/esm/services/email-verification.d.mts.map +1 -0
  150. package/esm/services/email-verification.mjs +55 -0
  151. package/esm/services/email-verification.mjs.map +1 -0
  152. package/esm/services/index.d.mts +5 -1
  153. package/esm/services/index.mjs +4 -0
  154. package/esm/services/one-time-tokens.d.mts +32 -0
  155. package/esm/services/one-time-tokens.d.mts.map +1 -0
  156. package/esm/services/one-time-tokens.mjs +56 -0
  157. package/esm/services/one-time-tokens.mjs.map +1 -0
  158. package/esm/services/optional-peer.mjs +37 -0
  159. package/esm/services/optional-peer.mjs.map +1 -0
  160. package/esm/services/password-reset.d.mts +34 -0
  161. package/esm/services/password-reset.d.mts.map +1 -0
  162. package/esm/services/password-reset.mjs +81 -0
  163. package/esm/services/password-reset.mjs.map +1 -0
  164. package/esm/services/token-notifications.mjs +87 -0
  165. package/esm/services/token-notifications.mjs.map +1 -0
  166. package/esm/utils/auth-error-codes.d.mts +48 -1
  167. package/esm/utils/auth-error-codes.d.mts.map +1 -1
  168. package/esm/utils/auth-error-codes.mjs +41 -0
  169. package/esm/utils/auth-error-codes.mjs.map +1 -1
  170. package/esm/utils/index.mjs +1 -0
  171. package/esm/utils/safe-equal.d.mts +6 -0
  172. package/esm/utils/safe-equal.d.mts.map +1 -0
  173. package/esm/utils/safe-equal.mjs +13 -0
  174. package/esm/utils/safe-equal.mjs.map +1 -0
  175. package/llms-full.txt +402 -29
  176. package/llms.txt +2 -0
  177. package/package.json +21 -7
  178. package/skills/handle-login-and-logout/SKILL.md +1 -1
  179. package/skills/login-with-providers/SKILL.md +154 -0
  180. package/skills/manage-tokens/SKILL.md +7 -1
  181. package/skills/overview/SKILL.md +1 -1
  182. package/skills/protect-routes/SKILL.md +71 -13
  183. package/skills/register-user/SKILL.md +6 -8
  184. package/skills/run-auth-commands/SKILL.md +1 -1
  185. package/skills/verify-email-and-reset-password/SKILL.md +137 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,71 @@ 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
+
37
+ ## 5.12.0 - 2026-09-16
38
+
39
+ ### Added
40
+
41
+ - `authService.setAuthCookie(response, token, options?)` and `authService.clearAuthCookie(response, options?)` — the write side of the `cookie:<name>` token source `authMiddleware([], "cookie:<name>")` has accepted since 5.0.0 (card 50bf4f1a). `setAuthCookie` accepts a raw token string or an `AccessTokenOutput` (deriving `Max-Age` from `expiresAt` automatically); both are explicit app-controller calls — `login`/`logout` never set cookies implicitly, so upgrading never starts emitting `Set-Cookie` for an existing bearer-only app. Cookie name/path are configurable via the new `auth.cookie` config block (defaults: `"access_token"` / `"/"`); attribute flags (`HttpOnly`, `SameSite=Lax`, `Secure` outside dev) are not — they come from core's `secureCookieDefaults()`, the framework-wide floor.
42
+
43
+ ### Security
44
+
45
+ - **CSRF Origin check for cookie-authenticated writes.** `authMiddleware` now automatically rejects, with `403` (`AuthErrorCodes.CsrfOriginMismatch`, `"EC006"`), any request whose credential came from a `cookie:` source and whose method is unsafe (`POST`/`PUT`/`PATCH`/`DELETE`) unless `Origin` (or, absent that, `Referer`) names the request's own origin or an entry in the new `auth.csrf.allowedOrigins` config (default `[]`). A request with neither header is rejected, fail-closed. Header-token authentication and safe methods (`GET`/`HEAD`/`OPTIONS`) are completely unaffected. This closes the residual CSRF gap `SameSite=Lax` alone leaves open for cookie auth (a same-site GET redirect chain, or a client that ignores `SameSite`); a double-submit token mechanism is deferred to a later release.
46
+ - The CSRF Origin check's own-origin comparison includes the request's port (read from the `Host` header, since core's `request.hostname` never carries one), with default ports (`:80` on `http`, `:443` on `https`) normalised as equivalent to no port. A same-origin cookie-authenticated write on a non-default port — e.g. every `warlock dev` session — is now correctly allowed instead of being rejected with `403 EC006`.
47
+
48
+ ### Changed
49
+
50
+ - **BREAKING:** the authenticated user now lives at `request.locals.user`, declared by `@warlock.js/auth`. `request.user` is removed (in development it throws with the new location). `@warlock.js/auth`'s middleware writes `request.locals.user` after a successful token resolution and clears it (`= undefined`) on a forged, malformed, expired, or wrong-type token; `RequestUser` — the augmentable, empty-by-default interface apps narrow to their own model — moved from `@warlock.js/core` into `@warlock.js/auth` alongside it.
51
+
52
+ ```typescript
53
+ // Before (removed)
54
+ declare module "@warlock.js/core" {
55
+ interface RequestUser {
56
+ id: string | number;
57
+ }
58
+ }
59
+ const user = request.user;
60
+
61
+ // After
62
+ declare module "@warlock.js/auth" {
63
+ interface RequestUser {
64
+ id: string | number;
65
+ }
66
+ }
67
+ const user = request.locals.user;
68
+ ```
69
+
70
+ A typed `currentUser<UserType extends Auth>()` helper is also exported, wrapping core's `useCurrentUser()` for callers that want `Auth`-derived typing without repeating the assertion at every call site.
71
+
7
72
  ## 5.11.0 - 2026-09-14
8
73
 
9
74
  _Released in lockstep with the `@warlock.js/*` family; no package-specific changes in 5.11.0._
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,2 +1,4 @@
1
- import { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, PageAuthConfig, RefreshTokenConfig, TokenFrom, TokenPair } from "./types.mjs";
2
- import { Authenticable } from "./auth-contract.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";
3
+ import { Authenticable } from "./auth-contract.mjs";
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"}
@@ -0,0 +1,43 @@
1
+ //#region ../auth/src/contracts/request-locals.d.ts
2
+ /**
3
+ * The authenticated user attached to the current request —
4
+ * `request.locals.user`.
5
+ *
6
+ * Moved here from `@warlock.js/core` in 5.12.0 (core's `Request` no longer
7
+ * has a `user` property at all — see `RequestUserMovedError`). Empty by
8
+ * default, exactly as it was in core, so any shape is assignable at the
9
+ * declaration site; an app narrows it via module augmentation:
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * declare module "@warlock.js/auth" {
14
+ * interface RequestUser {
15
+ * id: string | number;
16
+ * }
17
+ * }
18
+ * ```
19
+ *
20
+ * Deliberately NOT `extends Auth<ModelSchema>` here: an `interface` can
21
+ * extend at most one class hierarchy, and a library-side
22
+ * `interface RequestUser extends Auth<ModelSchema>` would pre-claim that one
23
+ * extends slot, so an app's own `interface RequestUser extends User {}`
24
+ * would fail to compile (TS2320). See `access/src/middleware/gate.middleware.ts`
25
+ * for the same hazard documented at its call site.
26
+ */
27
+ interface RequestUser {}
28
+ /**
29
+ * Augments core's `RequestLocals` (`request.locals`) with the `user` key —
30
+ * done in the module that OWNS the key, per `RequestLocals`' own contract
31
+ * (`core/src/http/types.ts`). `authMiddleware` (`middleware/auth.middleware.ts`)
32
+ * is the sole writer: it sets `request.locals.user` after a successful token
33
+ * resolution and clears it (`= undefined`) on a forged, malformed, expired,
34
+ * or wrong-type token.
35
+ */
36
+ declare module "@warlock.js/core" {
37
+ interface RequestLocals {
38
+ user?: RequestUser;
39
+ }
40
+ } //# sourceMappingURL=request-locals.d.ts.map
41
+ //#endregion
42
+ export { RequestUser };
43
+ //# sourceMappingURL=request-locals.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-locals.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/request-locals.ts"],"mappings":";;AAyBA;;;;AAA4B;AAAG;;;;;;;;;AAYT;;;;;;;;;;UAZL,WAAA;;;;;;;;;;YAWL,aAAA;IACR,IAAA,GAAO,WAAW;EAAA;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
@@ -183,6 +252,40 @@ type AuthConfigurations = {
183
252
  * Opt-in; API routes are unaffected. Read via `authConfig.pageAuth`.
184
253
  */
185
254
  pageAuth?: PageAuthConfig;
255
+ /**
256
+ * Cookie auth session config — read by `authService.setAuthCookie` /
257
+ * `clearAuthCookie`. Opt-in: unset ⇒ the helpers fall back to their own
258
+ * defaults (`name: "access_token"`, `path: "/"`).
259
+ */
260
+ cookie?: CookieAuthConfig;
261
+ /**
262
+ * CSRF Origin-check config for cookie-authenticated unsafe-method requests.
263
+ */
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;
186
289
  /**
187
290
  * @deprecated Use `accessToken` / `refreshToken`. Read via a backward-compatible shim.
188
291
  */
@@ -241,6 +344,61 @@ type LoginResult<UserType extends Auth> = {
241
344
  user: UserType;
242
345
  tokens: TokenPair;
243
346
  };
347
+ /**
348
+ * Configuration for the cookie an app opts into via `authService.setAuthCookie`
349
+ * / `clearAuthCookie` and reads back with `authMiddleware([], "cookie:<name>")`.
350
+ * Attribute flags (`httpOnly` / `sameSite` / `secure`) are deliberately NOT
351
+ * configurable here — they come from core's `secureCookieDefaults()`, the
352
+ * same floor every `response.cookie()` call gets, so a cookie auth session can
353
+ * never be weakened to a laxer policy than the framework default.
354
+ */
355
+ type CookieAuthConfig = {
356
+ /**
357
+ * Cookie name `setAuthCookie`/`clearAuthCookie` write and clear, and that a
358
+ * `cookie:<name>` token source must match to read it back.
359
+ * @default "access_token"
360
+ */
361
+ name?: string;
362
+ /**
363
+ * Cookie `Path` attribute.
364
+ * @default "/"
365
+ */
366
+ path?: string;
367
+ };
368
+ /**
369
+ * CSRF Origin-check configuration. Read via
370
+ * `authConfig.csrf.allowedOrigins()`.
371
+ */
372
+ type CsrfConfig = {
373
+ /**
374
+ * Extra origins allowed on a cookie-authenticated unsafe-method request,
375
+ * in addition to the request's own origin.
376
+ * @default []
377
+ */
378
+ allowedOrigins?: string[];
379
+ };
380
+ /**
381
+ * Per-call overrides for {@link AuthService.setAuthCookie}. Anything omitted
382
+ * falls back to `auth.cookie.*` config, then the package default. Cookie
383
+ * *attribute* flags (`httpOnly`/`sameSite`/`secure`) are never settable here —
384
+ * see {@link CookieAuthConfig}.
385
+ */
386
+ type SetAuthCookieOptions = {
387
+ /** Cookie name; defaults to `auth.cookie.name` (package default `"access_token"`). */name?: string; /** Cookie `Path`; defaults to `auth.cookie.path` (package default `"/"`). */
388
+ path?: string;
389
+ /**
390
+ * `Max-Age`, in seconds. Overrides the expiry `setAuthCookie` would
391
+ * otherwise derive from an `AccessTokenOutput`'s `expiresAt`. Passing a
392
+ * bare token string with no `maxAge` produces a session cookie (cleared
393
+ * when the browser closes).
394
+ */
395
+ maxAge?: number;
396
+ };
397
+ /** Per-call overrides for {@link AuthService.clearAuthCookie}. */
398
+ type ClearAuthCookieOptions = {
399
+ /** Cookie name; defaults to `auth.cookie.name` (package default `"access_token"`). */name?: string; /** Cookie `Path`; defaults to `auth.cookie.path` (package default `"/"`). Must match the `Path` the cookie was set with. */
400
+ path?: string;
401
+ };
244
402
  //#endregion
245
- export { AccessTokenConfig, AccessTokenOutput, AuthConfigurations, AuthCredentials, CanAuthenticate, DeviceInfo, LegacyJwtConfig, LoginResult, LogoutWithoutTokenBehavior, NO_EXPIRATION, PageAuthConfig, RefreshTokenConfig, 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 };
246
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;EA2FqB;;;;EAtFrB,QAAA;EAoEA;;;;EA/DA,UAAA;EAuEe;;;;EAlEf,kBAAA,GAAqB,0BAA0B;AAAA;AA4E1B;AAGvB;;;;AAQW;AAXY,KAnEX,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;EAoGA;;;;;EA9FV,SAAA;EAgGiB;;;;;EA1FjB,cAAc;AAAA;AAAA,KAGJ,kBAAA;EAuFO;AAAA;;;EAlFjB,QAAA;IAAA,CACG,QAAA,WAAmB,UAAA,CAAW,IAAA;EAAA;;;;;EAMjC,eAAA,GAAkB,eAAA;;;;EAIlB,WAAA,GAAc,iBAAA;;;;EAId,YAAA,GAAe,kBAAA;;;;;;EAMf,QAAA,GAAW,cAAA;;;;EAIX,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"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../auth/src/contracts/types.ts"],"mappings":"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"}