@warlock.js/auth 5.15.0 → 5.17.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 (94) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/esm/commands/auth-cleanup-command.mjs +1 -1
  3. package/esm/commands/auth-purge-never-expiring-command.mjs +1 -1
  4. package/esm/contracts/index.d.mts +1 -1
  5. package/esm/contracts/providers.d.mts +59 -1
  6. package/esm/contracts/providers.d.mts.map +1 -1
  7. package/esm/index.d.mts +11 -2
  8. package/esm/index.mjs +11 -1
  9. package/esm/localization/ar.translations.mjs +16 -0
  10. package/esm/localization/ar.translations.mjs.map +1 -0
  11. package/esm/localization/auth-default-translations.d.mts +10 -0
  12. package/esm/localization/auth-default-translations.d.mts.map +1 -0
  13. package/esm/localization/auth-default-translations.mjs +15 -0
  14. package/esm/localization/auth-default-translations.mjs.map +1 -0
  15. package/esm/localization/auth-translations.type.d.mts +27 -0
  16. package/esm/localization/auth-translations.type.d.mts.map +1 -0
  17. package/esm/localization/en.translations.mjs +16 -0
  18. package/esm/localization/en.translations.mjs.map +1 -0
  19. package/esm/localization/index.mjs +4 -0
  20. package/esm/localization/init.mjs +8 -0
  21. package/esm/localization/init.mjs.map +1 -0
  22. package/esm/localization/register-auth-translations.d.mts +17 -0
  23. package/esm/localization/register-auth-translations.d.mts.map +1 -0
  24. package/esm/localization/register-auth-translations.mjs +29 -0
  25. package/esm/localization/register-auth-translations.mjs.map +1 -0
  26. package/esm/middleware/auth.middleware.d.mts.map +1 -1
  27. package/esm/middleware/auth.middleware.mjs +4 -2
  28. package/esm/middleware/auth.middleware.mjs.map +1 -1
  29. package/esm/middleware/csrf-origin-check.d.mts +9 -0
  30. package/esm/middleware/csrf-origin-check.d.mts.map +1 -1
  31. package/esm/middleware/csrf-origin-check.mjs +12 -56
  32. package/esm/middleware/csrf-origin-check.mjs.map +1 -1
  33. package/esm/middleware/localized-login-path.mjs +48 -0
  34. package/esm/middleware/localized-login-path.mjs.map +1 -0
  35. package/esm/models/access-token/access-token.model.d.mts +7 -0
  36. package/esm/models/access-token/access-token.model.d.mts.map +1 -1
  37. package/esm/models/access-token/access-token.model.mjs +3 -0
  38. package/esm/models/access-token/access-token.model.mjs.map +1 -1
  39. package/esm/models/one-time-token/one-time-token.model.d.mts +6 -0
  40. package/esm/models/one-time-token/one-time-token.model.d.mts.map +1 -1
  41. package/esm/models/one-time-token/one-time-token.model.mjs +3 -0
  42. package/esm/models/one-time-token/one-time-token.model.mjs.map +1 -1
  43. package/esm/models/refresh-token/refresh-token.model.d.mts +7 -0
  44. package/esm/models/refresh-token/refresh-token.model.d.mts.map +1 -1
  45. package/esm/models/refresh-token/refresh-token.model.mjs +3 -0
  46. package/esm/models/refresh-token/refresh-token.model.mjs.map +1 -1
  47. package/esm/providers/apple-provider.d.mts +74 -0
  48. package/esm/providers/apple-provider.d.mts.map +1 -0
  49. package/esm/providers/apple-provider.mjs +157 -0
  50. package/esm/providers/apple-provider.mjs.map +1 -0
  51. package/esm/providers/discord-provider.d.mts +25 -0
  52. package/esm/providers/discord-provider.d.mts.map +1 -0
  53. package/esm/providers/discord-provider.mjs +84 -0
  54. package/esm/providers/discord-provider.mjs.map +1 -0
  55. package/esm/providers/facebook-provider.d.mts +36 -0
  56. package/esm/providers/facebook-provider.d.mts.map +1 -0
  57. package/esm/providers/facebook-provider.mjs +94 -0
  58. package/esm/providers/facebook-provider.mjs.map +1 -0
  59. package/esm/providers/github-provider.d.mts +31 -0
  60. package/esm/providers/github-provider.d.mts.map +1 -0
  61. package/esm/providers/github-provider.mjs +115 -0
  62. package/esm/providers/github-provider.mjs.map +1 -0
  63. package/esm/providers/index.d.mts +7 -1
  64. package/esm/providers/index.mjs +6 -0
  65. package/esm/providers/linkedin-provider.d.mts +35 -0
  66. package/esm/providers/linkedin-provider.d.mts.map +1 -0
  67. package/esm/providers/linkedin-provider.mjs +108 -0
  68. package/esm/providers/linkedin-provider.mjs.map +1 -0
  69. package/esm/providers/provider-login.d.mts +2 -1
  70. package/esm/providers/provider-login.d.mts.map +1 -1
  71. package/esm/providers/provider-login.mjs +47 -7
  72. package/esm/providers/provider-login.mjs.map +1 -1
  73. package/esm/providers/provider-state-cookie.mjs +35 -16
  74. package/esm/providers/provider-state-cookie.mjs.map +1 -1
  75. package/esm/providers/x-provider.d.mts +35 -0
  76. package/esm/providers/x-provider.d.mts.map +1 -0
  77. package/esm/providers/x-provider.mjs +101 -0
  78. package/esm/providers/x-provider.mjs.map +1 -0
  79. package/esm/services/auth-config.mjs +16 -1
  80. package/esm/services/auth-config.mjs.map +1 -1
  81. package/esm/services/auth.service.mjs +1 -1
  82. package/esm/services/generate-jwt-secret.mjs +1 -1
  83. package/llms-full.txt +1075 -765
  84. package/llms.txt +5 -5
  85. package/package.json +8 -8
  86. package/skills/auth-basics/SKILL.md +16 -1
  87. package/skills/customize-token-storage/SKILL.md +160 -122
  88. package/skills/customize-user-type/SKILL.md +168 -142
  89. package/skills/handle-login-and-logout/SKILL.md +179 -165
  90. package/skills/login-with-providers/SKILL.md +131 -2
  91. package/skills/protect-routes/SKILL.md +308 -249
  92. package/skills/register-user/SKILL.md +15 -1
  93. package/skills/run-auth-commands/SKILL.md +16 -1
  94. package/skills/throttle-login-attempts/SKILL.md +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,28 @@ 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.17.0 - 2026-09-21
8
+
9
+ ### Security
10
+
11
+ - The CSRF Origin/Referer check `authMiddleware("cookie:*")` runs (`assertCsrfOriginAllowed`) now delegates its same-origin/`auth.csrf.allowedOrigins` comparison to `@warlock.js/core`'s `resolveCsrfOriginVerdict`, shared with core's new default CSRF-Origin guard (card 8a752ab2, see `@warlock.js/core`'s 5.17.0 changelog) so the two checks can never drift apart. Behaviour is unchanged for routes already using `authMiddleware("cookie:*")`.
12
+
13
+ ### Fixed
14
+
15
+ - The `pageAuth` login redirect now carries the request's locale prefix when `@warlock.js/web`'s `web.localeRouting.strategy` is active. Before, an anonymous request to a locale-prefixed page (e.g. `/ar/admin`) always redirected to the bare `auth.pageAuth.loginPath` (e.g. `/login?returnUrl=%2Far%2Fadmin`), dropping the locale even though `returnUrl` kept it. It now redirects to the locale-prefixed login path (`/ar/login?returnUrl=%2Far%2Fadmin`) — reading `web.localeRouting.strategy` / `app.localeCodes` / `app.localeCode` from config directly (`auth` has no dependency on `web`, in either direction). Left unchanged when `loginPath` is absolute (`http…`) or already locale-prefixed.
16
+
17
+ ## 5.16.0 - 2026-09-18
18
+
19
+ ### Added
20
+
21
+ - Six new login providers: GitHub, Discord, LinkedIn, Apple, Facebook and X, configured under `auth.providers.<name>` and used through `startProviderLogin` / `completeProviderLogin`. Apple and LinkedIn verify their id_token with `jose`. The other four use plain OAuth 2.
22
+ - `AuthProvider.callbackMode` (`"query"` | `"form_post"`). Apple uses `form_post`, so its state cookie is written with `SameSite=None; Secure` (HTTPS required). Query-mode providers keep `SameSite=Lax`.
23
+
24
+ ### Fixed
25
+
26
+ - Provider names now resolve only from `auth.providers`' own keys, so a name inherited from the object prototype, such as `toString`, is never treated as a provider.
27
+ - The Set-Cookie that clears the provider state cookie now repeats the attributes the cookie was written with.
28
+
7
29
  ## 5.13.0 - 2026-09-17
8
30
 
9
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  import { authService } from "../services/auth.service.mjs";
2
- import { colors } from "@mongez/copper";
3
2
  import { command } from "@warlock.js/core";
3
+ import { colors } from "@mongez/copper";
4
4
 
5
5
  //#region ../auth/src/commands/auth-cleanup-command.ts
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { authService } from "../services/auth.service.mjs";
2
- import { colors } from "@mongez/copper";
3
2
  import { command } from "@warlock.js/core";
3
+ import { colors } from "@mongez/copper";
4
4
 
5
5
  //#region ../auth/src/commands/auth-purge-never-expiring-command.ts
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { AuthProvider, GoogleProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig } from "./providers.mjs";
1
+ import { AppleProviderConfig, AuthProvider, DiscordProviderConfig, FacebookProviderConfig, GitHubProviderConfig, GoogleProviderConfig, LinkedInProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig, XProviderConfig } from "./providers.mjs";
2
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
3
  import { Authenticable } from "./auth-contract.mjs";
4
4
  import { RequestUser } from "./request-locals.mjs";
@@ -37,6 +37,14 @@ type ProviderProfile = {
37
37
  */
38
38
  interface AuthProvider {
39
39
  readonly name: string;
40
+ /**
41
+ * How the provider's callback arrives. `"query"` (default) is a top-level
42
+ * GET redirect, safe for a `SameSite=Lax` state cookie. `"form_post"` is a
43
+ * cross-site POST (Apple, when `name`/`email` scopes are requested) — the
44
+ * state cookie needs `SameSite=None; Secure` to survive it.
45
+ * @default "query"
46
+ */
47
+ readonly callbackMode?: "query" | "form_post";
40
48
  /** The URL to send the browser to. */
41
49
  authorizationUrl(state: ProviderAuthorizationState): string | Promise<string>;
42
50
  /** Exchange the callback for a verified profile, or throw. */
@@ -49,11 +57,61 @@ type GoogleProviderConfig = {
49
57
  redirectUri: string; /** @default ["openid", "email", "profile"] */
50
58
  scopes?: string[];
51
59
  };
60
+ /** `auth.providers.github`. */
61
+ type GitHubProviderConfig = {
62
+ clientId: string;
63
+ clientSecret: string; /** Must exactly match the "Authorization callback URL" registered on the GitHub OAuth App. */
64
+ redirectUri: string; /** @default ["read:user", "user:email"] */
65
+ scopes?: string[];
66
+ };
67
+ /** `auth.providers.discord`. */
68
+ type DiscordProviderConfig = {
69
+ clientId: string;
70
+ clientSecret: string; /** Must exactly match a redirect registered on the Discord application. */
71
+ redirectUri: string; /** @default ["identify", "email"] */
72
+ scopes?: string[];
73
+ };
74
+ /** `auth.providers.linkedin`. */
75
+ type LinkedInProviderConfig = {
76
+ clientId: string;
77
+ clientSecret: string; /** Must exactly match a redirect URL registered on the LinkedIn app. */
78
+ redirectUri: string; /** @default ["openid", "profile", "email"] */
79
+ scopes?: string[];
80
+ };
81
+ /** `auth.providers.apple`. */
82
+ type AppleProviderConfig = {
83
+ /** The Services ID (or app id) registered with Sign in with Apple. */clientId: string; /** The Apple Developer team id — signed into the client-secret JWT as `iss`. */
84
+ teamId: string; /** The id of the private key created for Sign in with Apple — signed into the client-secret JWT header as `kid`. */
85
+ keyId: string; /** The PKCS8 PEM contents of that private key (`.p8` file), used to sign the client-secret JWT. */
86
+ privateKey: string; /** Must exactly match a return URL registered on the Services ID. */
87
+ redirectUri: string; /** @default ["name", "email"] */
88
+ scopes?: string[];
89
+ };
90
+ /** `auth.providers.facebook`. */
91
+ type FacebookProviderConfig = {
92
+ clientId: string;
93
+ clientSecret: string; /** Must exactly match a valid OAuth redirect URI registered on the Facebook app. */
94
+ redirectUri: string; /** @default ["email", "public_profile"] */
95
+ scopes?: string[];
96
+ };
97
+ /** `auth.providers.x`. */
98
+ type XProviderConfig = {
99
+ clientId: string;
100
+ clientSecret: string; /** Must exactly match a callback URI registered on the X app (confidential client). */
101
+ redirectUri: string; /** @default ["tweet.read", "users.read"] */
102
+ scopes?: string[];
103
+ };
52
104
  /** Creates the app user for a verified provider profile with no matching account. */
53
105
  type ProviderUserCreator = (profile: ProviderProfile, Model: ChildModel<Auth>) => Auth | Promise<Auth>;
54
106
  /** `auth.providers`. */
55
107
  type ProvidersConfig = {
56
108
  google?: GoogleProviderConfig;
109
+ github?: GitHubProviderConfig;
110
+ discord?: DiscordProviderConfig;
111
+ linkedin?: LinkedInProviderConfig;
112
+ apple?: AppleProviderConfig;
113
+ facebook?: FacebookProviderConfig;
114
+ x?: XProviderConfig;
57
115
  /**
58
116
  * User attribute a verified provider email is matched against.
59
117
  * @default "email"
@@ -97,5 +155,5 @@ type OtpConfig = {
97
155
  send?: OtpSender;
98
156
  };
99
157
  //#endregion
100
- export { AuthProvider, GoogleProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig };
158
+ export { AppleProviderConfig, AuthProvider, DiscordProviderConfig, FacebookProviderConfig, GitHubProviderConfig, GoogleProviderConfig, LinkedInProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig, XProviderConfig };
101
159
  //# sourceMappingURL=providers.d.mts.map
@@ -1 +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
+ {"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;EAUqD;;;;;;;EAAA,SAFrD,YAAA;EAET;EAAA,gBAAA,CAAiB,KAAA,EAAO,0BAAA,YAAsC,OAAA;EAA7C;EAEjB,cAAA,CAAe,MAAA,EAAQ,sBAAA,GAAyB,OAAA,CAAQ,eAAA;AAAA;;KAI9C,oBAAA;EACV,QAAA;EACA,YAAA,UANuE;EAQvE,WAAA,UAJU;EAMV,MAAA;AAAA;;KAIU,oBAAA;EACV,QAAA;EACA,YAAA,UANA;EAQA,WAAA,UARM;EAUN,MAAA;AAAA;;KAIU,qBAAA;EACV,QAAA;EACA,YAAA,UARA;EAUA,WAAA,UARM;EAUN,MAAA;AAAA;;KAIU,sBAAA;EACV,QAAA;EACA,YAAA,UAVA;EAYA,WAAA,UARA;EAUA,MAAA;AAAA;AANF;AAAA,KAUY,mBAAA;wEAEV,QAAA,UAXA;EAaA,MAAA,UAVA;EAYA,KAAA,UAVM;EAYN,UAAA,UARU;EAUV,WAAA;EAEA,MAAA;AAAA;;KAIU,sBAAA;EACV,QAAA;EACA,YAAA,UANA;EAQA,WAAA,UARM;EAUN,MAAA;AAAA;;KAIU,eAAA;EACV,QAAA;EACA,YAAA,UARA;EAUA,WAAA,UARM;EAUN,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;EACT,MAAA,GAAS,oBAAA;EACT,OAAA,GAAU,qBAAA;EACV,QAAA,GAAW,sBAAA;EACX,KAAA,GAAQ,mBAAA;EACR,QAAA,GAAW,sBAAA;EACX,CAAA,GAAI,eAAA;;;;;EAKJ,UAAA,WAfkB;EAiBlB,UAAA,GAAa,mBAAA,EAjBI;EAmBjB,MAAA,GAAS,MAAA,SAAe,YAAA;AAAA;;KAId,cAAA;EAxBQ,8EA0BlB,IAAA,UAzBG;EA2BH,MAAA,UA3BkB;EA6BlB,MAAA;EA7BsB;AAGxB;;;EA+BE,kBAAA,WA7BS;EA+BT,QAAA,IAAY,IAAA,EAAM,IAAI;AAAA;;KAIZ,UAAA;EA9BN,8CAgCJ,IAAA;EACA,IAAA;EACA,SAAA,EAAW,IAAI;AAAA;;KAIL,SAAA,IAAa,KAAA,UAAe,OAAA,EAAS,UAAU,EAAE,OAAA;;KAGjD,SAAA;EA9CD;;;;;EAoDT,OAAA,WAjDQ;EAmDR,UAAA,WAlDW;EAoDX,SAAA,WAnDI;EAqDJ,WAAA,WA9CA;EAgDA,OAAA,IAAW,IAAA,qBA9CX;EAgDA,IAAA,GAAO,SAAS;AAAA"}
package/esm/index.d.mts CHANGED
@@ -4,7 +4,7 @@ import { registerJWTSecretGeneratorCommand } from "./commands/jwt-secret-generat
4
4
  import { Auth } from "./models/auth.model.mjs";
5
5
  import { AccessToken, accessTokenSchema } from "./models/access-token/access-token.model.mjs";
6
6
  import { OneTimeToken, OneTimeTokenPurpose, oneTimeTokenSchema } from "./models/one-time-token/one-time-token.model.mjs";
7
- import { AuthProvider, GoogleProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig } from "./contracts/providers.mjs";
7
+ import { AppleProviderConfig, AuthProvider, DiscordProviderConfig, FacebookProviderConfig, GitHubProviderConfig, GoogleProviderConfig, LinkedInProviderConfig, OtpConfig, OtpMessage, OtpSender, PasskeysConfig, ProviderAuthorizationState, ProviderCallbackParams, ProviderProfile, ProviderUserCreator, ProvidersConfig, XProviderConfig } from "./contracts/providers.mjs";
8
8
  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 "./contracts/types.mjs";
9
9
  import { RefreshToken, RefreshTokenIssueOptions, refreshTokenSchema } from "./models/refresh-token/refresh-token.model.mjs";
10
10
  import { Authenticable } from "./contracts/auth-contract.mjs";
@@ -17,6 +17,9 @@ import { InvalidPasskeyError } from "./errors/invalid-passkey.error.mjs";
17
17
  import { InvalidProviderCallbackError } from "./errors/invalid-provider-callback.error.mjs";
18
18
  import { NotificationsUnavailableError } from "./errors/notifications-unavailable.error.mjs";
19
19
  import { ProviderEmailNotVerifiedError } from "./errors/provider-email-not-verified.error.mjs";
20
+ import { AuthErrorTranslations, AuthTranslations } from "./localization/auth-translations.type.mjs";
21
+ import { authDefaultTranslations } from "./localization/auth-default-translations.mjs";
22
+ import { registerAuthTranslations } from "./localization/register-auth-translations.mjs";
20
23
  import { authMiddleware } from "./middleware/auth.middleware.mjs";
21
24
  import { CsrfOriginMismatchError, CsrfOriginMismatchReason, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./middleware/csrf-origin-check.mjs";
22
25
  import { LoginThrottleIdentifier, LoginThrottleOptions, loginThrottleMiddleware } from "./middleware/login-throttle.middleware.mjs";
@@ -31,7 +34,13 @@ import { PasskeyOptionsJSON, PasskeyResponseJSON } from "./passkeys/simplewebaut
31
34
  import { generatePasskeyAuthenticationOptions, verifyPasskeyAuthentication } from "./passkeys/passkey-authentication.mjs";
32
35
  import { generatePasskeyRegistrationOptions, verifyPasskeyRegistration } from "./passkeys/passkey-registration.mjs";
33
36
  import { GOOGLE_AUTH_FEATURE, GoogleProvider, JoseModule, pkceChallenge } from "./providers/google-provider.mjs";
37
+ import { APPLE_AUTH_FEATURE, AppleJoseModule, AppleProvider } from "./providers/apple-provider.mjs";
38
+ import { DiscordProvider } from "./providers/discord-provider.mjs";
39
+ import { FacebookProvider } from "./providers/facebook-provider.mjs";
40
+ import { GitHubProvider } from "./providers/github-provider.mjs";
41
+ import { LINKEDIN_AUTH_FEATURE, LinkedInProvider } from "./providers/linkedin-provider.mjs";
34
42
  import { completeProviderLogin, resolveAuthProvider, startProviderLogin } from "./providers/provider-login.mjs";
43
+ import { XProvider } from "./providers/x-provider.mjs";
35
44
  import { AuthEventCallback, AuthEventName, AuthEventPayloads, authEvents } from "./services/auth-events.mjs";
36
45
  import { authService } from "./services/auth.service.mjs";
37
46
  import { currentUser } from "./services/current-user.mjs";
@@ -42,5 +51,5 @@ import { IssuedOneTimeToken, consumeOneTimeToken, hashOneTimeToken, issueOneTime
42
51
  import { requestPasswordReset, resetPassword } from "./services/password-reset.mjs";
43
52
  import { safeEqual } from "./utils/safe-equal.mjs";
44
53
  import { isNeverExpiring, isUsableExpiry, tokenHasExpClaim } from "./utils/token-expiry.mjs";
45
- export { AccessToken, AccessTokenConfig, AccessTokenOutput, Auth, AuthConfigurations, AuthCredentials, AuthErrorCodes, AuthEventCallback, AuthEventName, AuthEventPayloads, AuthNotification, AuthProvider, AuthProviderSdkMissingError, Authenticable, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, CsrfOriginMismatchError, CsrfOriginMismatchReason, DeviceInfo, EmailNotVerifiedError, EmailVerificationConfig, GOOGLE_AUTH_FEATURE, GoogleProvider, GoogleProviderConfig, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, IssuedOneTimeToken, JoseModule, LegacyJwtConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenPurpose, OneTimeTokenUrlBuilder, OtpConfig, OtpMessage, OtpSender, PageAuthConfig, PasskeyCredential, type PasskeyOptionsJSON, type PasskeyResponseJSON, PasskeysConfig, PasswordResetConfig, PasswordSetter, ProviderAccount, ProviderAuthorizationState, ProviderCallbackParams, ProviderEmailNotVerifiedError, ProviderProfile, ProviderUserCreator, ProvidersConfig, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, RequestUser, SetAuthCookieOptions, TokenFrom, TokenPair, TokenType, TokenTypeError, accessTokenSchema, assertCsrfOriginAllowed, authEvents, authMiddleware, authMigrations, authService, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, safeEqual, sendEmailVerification, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
54
+ export { APPLE_AUTH_FEATURE, AccessToken, AccessTokenConfig, AccessTokenOutput, AppleJoseModule, AppleProvider, AppleProviderConfig, Auth, AuthConfigurations, AuthCredentials, AuthErrorCodes, AuthErrorTranslations, AuthEventCallback, AuthEventName, AuthEventPayloads, AuthNotification, AuthProvider, AuthProviderSdkMissingError, AuthTranslations, Authenticable, CanAuthenticate, ClearAuthCookieOptions, CookieAuthConfig, CsrfConfig, CsrfOriginMismatchError, CsrfOriginMismatchReason, DeviceInfo, DiscordProvider, DiscordProviderConfig, EmailNotVerifiedError, EmailVerificationConfig, FacebookProvider, FacebookProviderConfig, GOOGLE_AUTH_FEATURE, GitHubProvider, GitHubProviderConfig, GoogleProvider, GoogleProviderConfig, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, IssuedOneTimeToken, JoseModule, LINKEDIN_AUTH_FEATURE, LegacyJwtConfig, LinkedInProvider, LinkedInProviderConfig, LoginResult, LoginThrottleIdentifier, LoginThrottleOptions, LogoutWithoutTokenBehavior, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, OneTimeTokenConfig, OneTimeTokenNotificationData, OneTimeTokenPurpose, OneTimeTokenUrlBuilder, OtpConfig, OtpMessage, OtpSender, PageAuthConfig, PasskeyCredential, type PasskeyOptionsJSON, type PasskeyResponseJSON, PasskeysConfig, PasswordResetConfig, PasswordSetter, ProviderAccount, ProviderAuthorizationState, ProviderCallbackParams, ProviderEmailNotVerifiedError, ProviderProfile, ProviderUserCreator, ProvidersConfig, RefreshToken, RefreshTokenConfig, RefreshTokenIssueOptions, RequestUser, SetAuthCookieOptions, TokenFrom, TokenPair, TokenType, TokenTypeError, XProvider, XProviderConfig, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, safeEqual, sendEmailVerification, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
46
55
  import "./contracts/request-locals.mjs";
package/esm/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ import { authDefaultTranslations } from "./localization/auth-default-translations.mjs";
2
+ import { registerAuthTranslations } from "./localization/register-auth-translations.mjs";
3
+ import "./localization/init.mjs";
1
4
  import { isNeverExpiring, isUsableExpiry, tokenHasExpClaim } from "./utils/token-expiry.mjs";
2
5
  import { AccessToken, accessTokenSchema } from "./models/access-token/access-token.model.mjs";
3
6
  import { OneTimeToken, oneTimeTokenSchema } from "./models/one-time-token/one-time-token.model.mjs";
@@ -19,6 +22,7 @@ import { InvalidProviderCallbackError } from "./errors/invalid-provider-callback
19
22
  import { NotificationsUnavailableError } from "./errors/notifications-unavailable.error.mjs";
20
23
  import { ProviderEmailNotVerifiedError } from "./errors/provider-email-not-verified.error.mjs";
21
24
  import "./errors/index.mjs";
25
+ import "./localization/index.mjs";
22
26
  import { CsrfOriginMismatchError, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./middleware/csrf-origin-check.mjs";
23
27
  import { authMiddleware } from "./middleware/auth.middleware.mjs";
24
28
  import { loginThrottleMiddleware } from "./middleware/login-throttle.middleware.mjs";
@@ -42,8 +46,14 @@ import { generatePasskeyAuthenticationOptions, verifyPasskeyAuthentication } fro
42
46
  import { generatePasskeyRegistrationOptions, verifyPasskeyRegistration } from "./passkeys/passkey-registration.mjs";
43
47
  import "./passkeys/index.mjs";
44
48
  import { GOOGLE_AUTH_FEATURE, GoogleProvider, pkceChallenge } from "./providers/google-provider.mjs";
49
+ import { APPLE_AUTH_FEATURE, AppleProvider } from "./providers/apple-provider.mjs";
50
+ import { DiscordProvider } from "./providers/discord-provider.mjs";
51
+ import { FacebookProvider } from "./providers/facebook-provider.mjs";
52
+ import { GitHubProvider } from "./providers/github-provider.mjs";
53
+ import { LINKEDIN_AUTH_FEATURE, LinkedInProvider } from "./providers/linkedin-provider.mjs";
54
+ import { XProvider } from "./providers/x-provider.mjs";
45
55
  import { completeProviderLogin, resolveAuthProvider, startProviderLogin } from "./providers/provider-login.mjs";
46
56
  import "./providers/index.mjs";
47
57
  import "./utils/index.mjs";
48
58
 
49
- export { AccessToken, Auth, AuthErrorCodes, AuthProviderSdkMissingError, CsrfOriginMismatchError, EmailNotVerifiedError, GOOGLE_AUTH_FEATURE, GoogleProvider, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, PasskeyCredential, ProviderAccount, ProviderEmailNotVerifiedError, RefreshToken, TokenTypeError, accessTokenSchema, assertCsrfOriginAllowed, authEvents, authMiddleware, authMigrations, authService, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, safeEqual, sendEmailVerification, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
59
+ export { APPLE_AUTH_FEATURE, AccessToken, AppleProvider, Auth, AuthErrorCodes, AuthProviderSdkMissingError, CsrfOriginMismatchError, DiscordProvider, EmailNotVerifiedError, FacebookProvider, GOOGLE_AUTH_FEATURE, GitHubProvider, GoogleProvider, InvalidOneTimeTokenError, InvalidPasskeyError, InvalidProviderCallbackError, LINKEDIN_AUTH_FEATURE, LinkedInProvider, NO_EXPIRATION, NotificationsUnavailableError, OneTimeToken, PasskeyCredential, ProviderAccount, ProviderEmailNotVerifiedError, RefreshToken, TokenTypeError, XProvider, accessTokenSchema, assertCsrfOriginAllowed, authDefaultTranslations, authEvents, authMiddleware, authMigrations, authService, completeProviderLogin, consumeOneTimeToken, currentUser, generateJWTSecret, generatePasskeyAuthenticationOptions, generatePasskeyRegistrationOptions, hashOneTimeToken, isEmailVerified, isInvalidCredentialError, isNeverExpiring, isUsableExpiry, issueOneTimeToken, jwt, loginThrottleMiddleware, oneTimeTokenModel, oneTimeTokenSchema, otpRequestThrottleMiddleware, otpVerifyThrottleMiddleware, passkeyCredentialSchema, pkceChallenge, providerAccountSchema, refreshTokenSchema, registerAuthCleanupCommand, registerAuthPurgeNeverExpiringCommand, registerAuthTranslations, registerJWTSecretGeneratorCommand, requestOtp, requestPasswordReset, requireVerifiedEmail, requiresCsrfOriginCheck, resetPassword, resolveAuthProvider, safeEqual, sendEmailVerification, startProviderLogin, tokenConsumeThrottleMiddleware, tokenHasExpClaim, tokenIssueThrottleMiddleware, verifyEmail, verifyOtp, verifyPasskeyAuthentication, verifyPasskeyRegistration };
@@ -0,0 +1,16 @@
1
+ //#region ../auth/src/localization/ar.translations.ts
2
+ /**
3
+ * Built-in Arabic text for auth's translation keys. Applications override
4
+ * any of these by registering the same key in their own locale files.
5
+ */
6
+ const authArabicTranslations = { auth: { errors: {
7
+ missingAccessToken: "رمز الوصول مفقود.",
8
+ invalidAccessToken: "رمز الوصول غير صالح أو منتهي الصلاحية.",
9
+ unauthorized: "غير مصرح لك بالوصول إلى هذا المورد.",
10
+ csrfOriginMismatch: "مصدر الطلب غير مسموح به.",
11
+ tooManyAttempts: "محاولات كثيرة جدًا. يرجى المحاولة مرة أخرى لاحقًا."
12
+ } } };
13
+
14
+ //#endregion
15
+ export { authArabicTranslations };
16
+ //# sourceMappingURL=ar.translations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ar.translations.mjs","names":[],"sources":["../../../../../../../auth/src/localization/ar.translations.ts"],"sourcesContent":["import type { AuthTranslations } from \"./auth-translations.type\";\n\n/**\n * Built-in Arabic text for auth's translation keys. Applications override\n * any of these by registering the same key in their own locale files.\n */\nexport const authArabicTranslations: AuthTranslations = {\n auth: {\n errors: {\n missingAccessToken: \"رمز الوصول مفقود.\",\n invalidAccessToken: \"رمز الوصول غير صالح أو منتهي الصلاحية.\",\n unauthorized: \"غير مصرح لك بالوصول إلى هذا المورد.\",\n csrfOriginMismatch: \"مصدر الطلب غير مسموح به.\",\n tooManyAttempts: \"محاولات كثيرة جدًا. يرجى المحاولة مرة أخرى لاحقًا.\",\n },\n },\n};\n"],"mappings":";;;;;AAMA,MAAa,yBAA2C,EACtD,MAAM,EACJ,QAAQ;CACN,oBAAoB;CACpB,oBAAoB;CACpB,cAAc;CACd,oBAAoB;CACpB,iBAAiB;AACnB,EACF,EACF"}
@@ -0,0 +1,10 @@
1
+ import { AuthTranslations } from "./auth-translations.type.mjs";
2
+
3
+ //#region ../auth/src/localization/auth-default-translations.d.ts
4
+ /**
5
+ * Every locale auth ships default text for, keyed by locale code.
6
+ */
7
+ declare const authDefaultTranslations: Record<string, AuthTranslations>;
8
+ //#endregion
9
+ export { authDefaultTranslations };
10
+ //# sourceMappingURL=auth-default-translations.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-default-translations.d.mts","names":[],"sources":["../../../../../../../auth/src/localization/auth-default-translations.ts"],"mappings":";;;;;AAOA;cAAa,uBAAA,EAAyB,MAAM,SAAS,gBAAA"}
@@ -0,0 +1,15 @@
1
+ import { authArabicTranslations } from "./ar.translations.mjs";
2
+ import { authEnglishTranslations } from "./en.translations.mjs";
3
+
4
+ //#region ../auth/src/localization/auth-default-translations.ts
5
+ /**
6
+ * Every locale auth ships default text for, keyed by locale code.
7
+ */
8
+ const authDefaultTranslations = {
9
+ en: authEnglishTranslations,
10
+ ar: authArabicTranslations
11
+ };
12
+
13
+ //#endregion
14
+ export { authDefaultTranslations };
15
+ //# sourceMappingURL=auth-default-translations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-default-translations.mjs","names":[],"sources":["../../../../../../../auth/src/localization/auth-default-translations.ts"],"sourcesContent":["import { authArabicTranslations } from \"./ar.translations\";\nimport type { AuthTranslations } from \"./auth-translations.type\";\nimport { authEnglishTranslations } from \"./en.translations\";\n\n/**\n * Every locale auth ships default text for, keyed by locale code.\n */\nexport const authDefaultTranslations: Record<string, AuthTranslations> = {\n en: authEnglishTranslations,\n ar: authArabicTranslations,\n};\n"],"mappings":";;;;;;;AAOA,MAAa,0BAA4D;CACvE,IAAI;CACJ,IAAI;AACN"}
@@ -0,0 +1,27 @@
1
+ //#region ../auth/src/localization/auth-translations.type.d.ts
2
+ /**
3
+ * Text for every error key auth passes to `t()`.
4
+ *
5
+ * Every key is required so a locale shipped by auth can never silently miss
6
+ * one — translating a new `auth.errors.*` key means adding it here, and the
7
+ * compiler then demands a string in each built-in locale.
8
+ */
9
+ type AuthErrorTranslations = {
10
+ /** 401 — no credential was sent. */missingAccessToken: string; /** 401 — the credential is malformed, expired, or revoked. */
11
+ invalidAccessToken: string; /** 401 — the credential is valid but its user type is not allowed here. */
12
+ unauthorized: string; /** 403 — a cookie-authenticated unsafe request came from a foreign origin. */
13
+ csrfOriginMismatch: string; /** 429 — login throttle lockout. */
14
+ tooManyAttempts: string;
15
+ };
16
+ /**
17
+ * One locale's worth of auth translations, shaped as the keyword tree
18
+ * `@mongez/localization`'s `extend(localeCode, keywords)` accepts.
19
+ */
20
+ type AuthTranslations = {
21
+ auth: {
22
+ errors: AuthErrorTranslations;
23
+ };
24
+ };
25
+ //#endregion
26
+ export { AuthErrorTranslations, AuthTranslations };
27
+ //# sourceMappingURL=auth-translations.type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-translations.type.d.mts","names":[],"sources":["../../../../../../../auth/src/localization/auth-translations.type.ts"],"mappings":";;AAOA;;;;;;KAAY,qBAAA;EAQV,oCANA,kBAAA,UAQe;EANf,kBAAA,UAaU;EAXV,YAAA;EAEA,kBAAA,UAUA;EARA,eAAA;AAAA;;AAS+B;;;KAFrB,gBAAA;EACV,IAAA;IACE,MAAA,EAAQ,qBAAqB;EAAA;AAAA"}
@@ -0,0 +1,16 @@
1
+ //#region ../auth/src/localization/en.translations.ts
2
+ /**
3
+ * Built-in English text for auth's translation keys. Applications override
4
+ * any of these by registering the same key in their own locale files.
5
+ */
6
+ const authEnglishTranslations = { auth: { errors: {
7
+ missingAccessToken: "Missing access token.",
8
+ invalidAccessToken: "Invalid or expired access token.",
9
+ unauthorized: "You are not authorized to access this resource.",
10
+ csrfOriginMismatch: "Request origin is not allowed.",
11
+ tooManyAttempts: "Too many attempts. Please try again later."
12
+ } } };
13
+
14
+ //#endregion
15
+ export { authEnglishTranslations };
16
+ //# sourceMappingURL=en.translations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"en.translations.mjs","names":[],"sources":["../../../../../../../auth/src/localization/en.translations.ts"],"sourcesContent":["import type { AuthTranslations } from \"./auth-translations.type\";\n\n/**\n * Built-in English text for auth's translation keys. Applications override\n * any of these by registering the same key in their own locale files.\n */\nexport const authEnglishTranslations: AuthTranslations = {\n auth: {\n errors: {\n missingAccessToken: \"Missing access token.\",\n invalidAccessToken: \"Invalid or expired access token.\",\n unauthorized: \"You are not authorized to access this resource.\",\n csrfOriginMismatch: \"Request origin is not allowed.\",\n tooManyAttempts: \"Too many attempts. Please try again later.\",\n },\n },\n};\n"],"mappings":";;;;;AAMA,MAAa,0BAA4C,EACvD,MAAM,EACJ,QAAQ;CACN,oBAAoB;CACpB,oBAAoB;CACpB,cAAc;CACd,oBAAoB;CACpB,iBAAiB;AACnB,EACF,EACF"}
@@ -0,0 +1,4 @@
1
+ import { authDefaultTranslations } from "./auth-default-translations.mjs";
2
+ import { registerAuthTranslations } from "./register-auth-translations.mjs";
3
+
4
+ export { };
@@ -0,0 +1,8 @@
1
+ import { registerAuthTranslations } from "./register-auth-translations.mjs";
2
+
3
+ //#region ../auth/src/localization/init.ts
4
+ registerAuthTranslations();
5
+
6
+ //#endregion
7
+ export { };
8
+ //# sourceMappingURL=init.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.mjs","names":[],"sources":["../../../../../../../auth/src/localization/init.ts"],"sourcesContent":["import { registerAuthTranslations } from \"./register-auth-translations\";\n\n// Side-effect module: importing `@warlock.js/auth` registers its default\n// translations, so `t(\"auth.errors.*\")` never returns the raw key.\nregisterAuthTranslations();\n"],"mappings":";;;AAIA,yBAAyB"}
@@ -0,0 +1,17 @@
1
+ //#region ../auth/src/localization/register-auth-translations.d.ts
2
+ /**
3
+ * Register auth's built-in translations with `@mongez/localization`, the
4
+ * same store `t()` from `@warlock.js/core` reads.
5
+ *
6
+ * Only keys the application has NOT already translated are added, so an app
7
+ * translation wins regardless of load order: registered before this runs, it
8
+ * is left untouched; registered after, `extend()` merges it over the default.
9
+ * An app locale file that predates a newer auth key therefore gets the
10
+ * default text for that key instead of the raw key string.
11
+ *
12
+ * Runs once at import of `@warlock.js/auth`; safe to call again.
13
+ */
14
+ declare function registerAuthTranslations(): void;
15
+ //#endregion
16
+ export { registerAuthTranslations };
17
+ //# sourceMappingURL=register-auth-translations.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-auth-translations.d.mts","names":[],"sources":["../../../../../../../auth/src/localization/register-auth-translations.ts"],"mappings":";;AAgBA;;;;AAAwC;;;;;;;iBAAxB,wBAAA"}
@@ -0,0 +1,29 @@
1
+ import { authDefaultTranslations } from "./auth-default-translations.mjs";
2
+ import { flatten, get, set } from "@mongez/reinforcements";
3
+ import { extend, getKeywordsListOf } from "@warlock.js/core";
4
+
5
+ //#region ../auth/src/localization/register-auth-translations.ts
6
+ /**
7
+ * Register auth's built-in translations with `@mongez/localization`, the
8
+ * same store `t()` from `@warlock.js/core` reads.
9
+ *
10
+ * Only keys the application has NOT already translated are added, so an app
11
+ * translation wins regardless of load order: registered before this runs, it
12
+ * is left untouched; registered after, `extend()` merges it over the default.
13
+ * An app locale file that predates a newer auth key therefore gets the
14
+ * default text for that key instead of the raw key string.
15
+ *
16
+ * Runs once at import of `@warlock.js/auth`; safe to call again.
17
+ */
18
+ function registerAuthTranslations() {
19
+ for (const [localeCode, translations] of Object.entries(authDefaultTranslations)) {
20
+ const existing = getKeywordsListOf(localeCode) ?? {};
21
+ const missing = {};
22
+ for (const [key, text] of Object.entries(flatten(translations))) if (get(existing, key) === void 0) set(missing, key, text);
23
+ if (Object.keys(missing).length > 0) extend(localeCode, missing);
24
+ }
25
+ }
26
+
27
+ //#endregion
28
+ export { registerAuthTranslations };
29
+ //# sourceMappingURL=register-auth-translations.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-auth-translations.mjs","names":[],"sources":["../../../../../../../auth/src/localization/register-auth-translations.ts"],"sourcesContent":["import { flatten, get, set } from \"@mongez/reinforcements\";\nimport { extend, getKeywordsListOf, type Keywords } from \"@warlock.js/core\";\nimport { authDefaultTranslations } from \"./auth-default-translations\";\n\n/**\n * Register auth's built-in translations with `@mongez/localization`, the\n * same store `t()` from `@warlock.js/core` reads.\n *\n * Only keys the application has NOT already translated are added, so an app\n * translation wins regardless of load order: registered before this runs, it\n * is left untouched; registered after, `extend()` merges it over the default.\n * An app locale file that predates a newer auth key therefore gets the\n * default text for that key instead of the raw key string.\n *\n * Runs once at import of `@warlock.js/auth`; safe to call again.\n */\nexport function registerAuthTranslations(): void {\n for (const [localeCode, translations] of Object.entries(authDefaultTranslations)) {\n const existing = getKeywordsListOf(localeCode) ?? {};\n const missing: Keywords = {};\n\n for (const [key, text] of Object.entries(flatten(translations))) {\n if (get(existing, key) === undefined) {\n set(missing, key, text);\n }\n }\n\n if (Object.keys(missing).length > 0) {\n extend(localeCode, missing);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,2BAAiC;CAC/C,KAAK,MAAM,CAAC,YAAY,iBAAiB,OAAO,QAAQ,uBAAuB,GAAG;EAChF,MAAM,WAAW,kBAAkB,UAAU,KAAK,CAAC;EACnD,MAAM,UAAoB,CAAC;EAE3B,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,QAAQ,YAAY,CAAC,GAC5D,IAAI,IAAI,UAAU,GAAG,MAAM,QACzB,IAAI,SAAS,KAAK,IAAI;EAI1B,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAChC,OAAO,YAAY,OAAO;CAE9B;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"mappings":";;;;;;AA6GA;;;;;;;;;AAGa;;;;;;;;;;iBAHG,cAAA,CACd,eAAA,qBACA,SAAA,GAAW,SAAA,GACV,UAAU"}
1
+ {"version":3,"file":"auth.middleware.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"mappings":";;;;;;AAoHA;;;;;;;;;AAGa;;;;;;;;;;iBAHG,cAAA,CACd,eAAA,qBACA,SAAA,GAAW,SAAA,GACV,UAAU"}
@@ -5,6 +5,7 @@ import { AuthErrorCodes } from "../utils/auth-error-codes.mjs";
5
5
  import { isInvalidCredentialError, jwt } from "../services/jwt.mjs";
6
6
  import { authService } from "../services/auth.service.mjs";
7
7
  import { CsrfOriginMismatchError, assertCsrfOriginAllowed, requiresCsrfOriginCheck } from "./csrf-origin-check.mjs";
8
+ import { localizedLoginPath } from "./localized-login-path.mjs";
8
9
  import { config, t } from "@warlock.js/core";
9
10
  import { log } from "@warlock.js/logger";
10
11
 
@@ -30,9 +31,10 @@ import { log } from "@warlock.js/logger";
30
31
  function rejectUnauthorized({ request, response }, payload) {
31
32
  const loginPath = authConfig.pageAuth.loginPath();
32
33
  if (loginPath && request.route?.isPage) {
33
- const separator = loginPath.includes("?") ? "&" : "?";
34
+ const destination = localizedLoginPath(loginPath, request.locale);
35
+ const separator = destination.includes("?") ? "&" : "?";
34
36
  const returnUrl = encodeURIComponent(request.url);
35
- return response.redirect(`${loginPath}${separator}${authConfig.pageAuth.returnUrlParam()}=${returnUrl}`);
37
+ return response.redirect(`${destination}${separator}${authConfig.pageAuth.returnUrlParam()}=${returnUrl}`);
36
38
  }
37
39
  return response.unauthorized(payload);
38
40
  }
@@ -1 +1 @@
1
- {"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, type HttpContext, t, type Middleware, type Request } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { TokenFrom } from \"../contracts/types\";\r\nimport { AccessToken } from \"../models/access-token\";\r\nimport { authConfig } from \"../services/auth-config\";\r\nimport { authService } from \"../services/auth.service\";\r\nimport { isInvalidCredentialError, jwt } from \"../services/jwt\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\nimport {\r\n assertCsrfOriginAllowed,\r\n CsrfOriginMismatchError,\r\n requiresCsrfOriginCheck,\r\n} from \"./csrf-origin-check\";\r\n\r\n/** The 401 body shape every rejection in this middleware carries. */\r\ntype UnauthorizedPayload = { error: string; errorCode: string };\r\n\r\n/**\r\n * Reject an unauthenticated request, choosing the representation by ROUTE KIND.\r\n *\r\n * An API route always gets the JSON 401 it has always gotten. A PAGE route\r\n * (`request.route.isPage` — the React-SSR pages) instead redirects a logged-out\r\n * browser to the configured login path with a `returnUrl`, so a human\r\n * navigating to a guarded page lands on the login screen rather than reading a\r\n * raw JSON error blob — the defect in finding b9ab9804.\r\n *\r\n * The redirect is OPT-IN and backward-compatible: with no `auth.pageAuth.loginPath`\r\n * configured, even a page route falls back to the JSON 401, so an app that has\r\n * not asked for page-login UX keeps exactly its current behavior. The API 401\r\n * contract is never changed for API routes.\r\n *\r\n * `returnUrl` carries `request.url` — the relative path + query the browser was\r\n * on — never the absolute URL: a relative target cannot be turned into an\r\n * open-redirect off this app's origin.\r\n */\r\nfunction rejectUnauthorized({ request, response }: HttpContext, payload: UnauthorizedPayload) {\r\n const loginPath = authConfig.pageAuth.loginPath();\r\n\r\n if (loginPath && request.route?.isPage) {\r\n const separator = loginPath.includes(\"?\") ? \"&\" : \"?\";\r\n const returnUrl = encodeURIComponent(request.url);\r\n\r\n return response.redirect(\r\n `${loginPath}${separator}${authConfig.pageAuth.returnUrlParam()}=${returnUrl}`,\r\n );\r\n }\r\n\r\n return response.unauthorized(payload);\r\n}\r\n\r\n/**\r\n * Decoded access-token claims the middleware reads. The full payload carries\r\n * more (`created_at`, `tokenType`, `iat`, `exp`) but only these drive routing.\r\n */\r\ntype DecodedAccessToken = {\r\n id: string | number;\r\n userType?: string;\r\n};\r\n\r\n/**\r\n * Whether the persisted row says the token is dead.\r\n *\r\n * The answer belongs to the model (`AccessToken.isExpired`), so a registered\r\n * override that renames or reshapes its expiry column stays authoritative and\r\n * the middleware never touches a column name. A row that cannot answer at all —\r\n * an override that dropped the getter — is treated as **expired**: the failure\r\n * mode of this whole defect class was a check that quietly answered \"fine\" when\r\n * it had nothing to check, and that is not repeated here.\r\n *\r\n * This is independent of the `exp` claim required by `jwt.verify`. That guard\r\n * catches a token whose *claims* carry no deadline; this one catches a token\r\n * whose *row* says the deadline has passed — a logged-out or expired session\r\n * whose JWT is still within its own lifetime. Neither subsumes the other.\r\n */\r\nfunction accessTokenRowIsExpired(accessToken: AccessToken): boolean {\r\n return typeof accessToken.isExpired === \"boolean\" ? accessToken.isExpired : true;\r\n}\r\n\r\nfunction readCredential(request: Request, tokenFrom: TokenFrom): string {\r\n if (tokenFrom === \"header\") {\r\n return request.authorizationValue;\r\n }\r\n\r\n const value = request.cookie(tokenFrom.slice(\"cookie:\".length));\r\n\r\n return value ? String(value) : \"\";\r\n}\r\n\r\n/**\r\n * Build a route gate that always requires an authenticated request.\r\n *\r\n * The argument is mandatory and selects which user types may pass:\r\n * - `[]` — any authenticated user (token required, type not checked).\r\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\r\n * `userType` must be one of the listed types.\r\n *\r\n * There is no anonymous/optional mode: a request without a valid access\r\n * token is always rejected with `401`. Routes that should be public\r\n * simply omit the middleware.\r\n *\r\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\r\n * A single source avoids making credential precedence depend on array order.\r\n *\r\n * @example\r\n * router.get(\"/account\", authMiddleware([]), accountController);\r\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\r\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\r\n * router.get(\"/browser-account\", authMiddleware([], \"cookie:token\"), accountController);\r\n */\r\nexport function authMiddleware(\r\n allowedUserType: string | string[],\r\n tokenFrom: TokenFrom = \"header\",\r\n): Middleware {\r\n const allowedTypes = Array.isArray(allowedUserType) ? allowedUserType : [allowedUserType];\r\n\r\n const auth: Middleware = async ({ request, response }) => {\r\n const authorizationValue = readCredential(request, tokenFrom);\r\n\r\n if (!authorizationValue) {\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.missingAccessToken\"),\r\n errorCode: AuthErrorCodes.MissingAccessToken,\r\n });\r\n }\r\n\r\n // CSRF Origin check: only in scope for a cookie-sourced\r\n // credential on an unsafe method. Header-token auth and safe methods\r\n // (GET/HEAD/OPTIONS) never reach `assertCsrfOriginAllowed`.\r\n if (requiresCsrfOriginCheck(tokenFrom, request.method)) {\r\n try {\r\n assertCsrfOriginAllowed(request);\r\n } catch (error) {\r\n if (!(error instanceof CsrfOriginMismatchError)) {\r\n throw error;\r\n }\r\n\r\n log.error(\"http\", \"auth\", error);\r\n\r\n return response.forbidden({\r\n error: t(\"auth.errors.csrfOriginMismatch\"),\r\n errorCode: AuthErrorCodes.CsrfOriginMismatch,\r\n });\r\n }\r\n }\r\n\r\n let decoded: DecodedAccessToken;\r\n\r\n // The ONLY try in this middleware, and it wraps a single call. Everything\r\n // after it is storage and configuration, where an exception means the\r\n // server is broken rather than the caller (D5).\r\n try {\r\n decoded = await jwt.verify<DecodedAccessToken>(authorizationValue);\r\n } catch (error) {\r\n if (!isInvalidCredentialError(error)) {\r\n // A DB/cache outage, a missing `JWT_SECRET`, or any other server-side\r\n // fault — not a verdict on this credential. Propagate to the request's\r\n // normal error path (a 500 monitoring can see) instead of answering\r\n // 401 and clearing the caller's session.\r\n throw error;\r\n }\r\n\r\n // A forged, malformed, expired token — or (D6) a `tokenType` mismatch —\r\n // is not an incident: it is a request carrying a credential the server\r\n // will never accept.\r\n log.error(\"http\", \"auth\", error);\r\n\r\n // Clearing rather than leaving a stale value matters: a request that\r\n // failed authentication must not carry the identity of whoever\r\n // `request.locals.user` last held (see the two-sided spec below that\r\n // pins this — removing the clear must make the previous identity\r\n // survive).\r\n request.locals.user = undefined;\r\n\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n request.decodedAccessToken = decoded;\r\n\r\n // A valid signature is not enough — the token must still exist in storage,\r\n // so deleting the row (logout) invalidates it before its JWT expiry.\r\n const AccessTokenModel = config.key(\"auth.accessToken.model\", AccessToken);\r\n const accessToken = await AccessTokenModel.findByToken(authorizationValue);\r\n\r\n if (!accessToken) {\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n // ... and the row must still be live. Existence alone was the whole check\r\n // before 4.12.0, so a row whose own `expires_at` had passed still opened\r\n // the gate. The stored expiry is now enforced, and the dead row is\r\n // removed on the way out rather than left for the cleanup command.\r\n if (accessTokenRowIsExpired(accessToken)) {\r\n await accessToken.destroy();\r\n\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n const userType = decoded.userType ?? accessToken.userType;\r\n\r\n if (allowedTypes.length && !allowedTypes.includes(userType)) {\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.unauthorized\"),\r\n errorCode: AuthErrorCodes.Unauthorized,\r\n });\r\n }\r\n\r\n const UserModel = config.key(`auth.userType.${userType}`);\r\n\r\n if (!UserModel) {\r\n // Configuration, not credentials. Throwing keeps a mis-registered app\r\n // loudly broken instead of quietly rejecting every request of this type.\r\n throw new Error(`User type ${userType} is unknown type.`);\r\n }\r\n\r\n const currentUser = await UserModel.find(decoded.id);\r\n\r\n if (!currentUser) {\r\n await accessToken.destroy();\r\n\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n });\r\n }\r\n\r\n if (!(await authService.canAuthenticate(currentUser))) {\r\n return rejectUnauthorized({ request, response }, {\r\n error: t(\"auth.errors.unauthorized\"),\r\n errorCode: AuthErrorCodes.Unauthorized,\r\n });\r\n }\r\n\r\n request.locals.user = currentUser;\r\n };\r\n\r\n return auth;\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAS,mBAAmB,EAAE,SAAS,YAAyB,SAA8B;CAC5F,MAAM,YAAY,WAAW,SAAS,UAAU;CAEhD,IAAI,aAAa,QAAQ,OAAO,QAAQ;EACtC,MAAM,YAAY,UAAU,SAAS,GAAG,IAAI,MAAM;EAClD,MAAM,YAAY,mBAAmB,QAAQ,GAAG;EAEhD,OAAO,SAAS,SACd,GAAG,YAAY,YAAY,WAAW,SAAS,eAAe,EAAE,GAAG,WACrE;CACF;CAEA,OAAO,SAAS,aAAa,OAAO;AACtC;;;;;;;;;;;;;;;;AA0BA,SAAS,wBAAwB,aAAmC;CAClE,OAAO,OAAO,YAAY,cAAc,YAAY,YAAY,YAAY;AAC9E;AAEA,SAAS,eAAe,SAAkB,WAA8B;CACtE,IAAI,cAAc,UAChB,OAAO,QAAQ;CAGjB,MAAM,QAAQ,QAAQ,OAAO,UAAU,MAAM,CAAgB,CAAC;CAE9D,OAAO,QAAQ,OAAO,KAAK,IAAI;AACjC;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,eACd,iBACA,YAAuB,UACX;CACZ,MAAM,eAAe,MAAM,QAAQ,eAAe,IAAI,kBAAkB,CAAC,eAAe;CAExF,MAAM,OAAmB,OAAO,EAAE,SAAS,eAAe;EACxD,MAAM,qBAAqB,eAAe,SAAS,SAAS;EAE5D,IAAI,CAAC,oBACH,OAAO,mBAAmB;GAAE;GAAS;EAAS,GAAG;GAC/C,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;EAMH,IAAI,wBAAwB,WAAW,QAAQ,MAAM,GACnD,IAAI;GACF,wBAAwB,OAAO;EACjC,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,0BACrB,MAAM;GAGR,IAAI,MAAM,QAAQ,QAAQ,KAAK;GAE/B,OAAO,SAAS,UAAU;IACxB,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAGF,IAAI;EAKJ,IAAI;GACF,UAAU,MAAM,IAAI,OAA2B,kBAAkB;EACnE,SAAS,OAAO;GACd,IAAI,CAAC,yBAAyB,KAAK,GAKjC,MAAM;GAMR,IAAI,MAAM,QAAQ,QAAQ,KAAK;GAO/B,QAAQ,OAAO,OAAO;GAEtB,OAAO,mBAAmB;IAAE;IAAS;GAAS,GAAG;IAC/C,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAEA,QAAQ,qBAAqB;EAK7B,MAAM,cAAc,MADK,OAAO,IAAI,0BAA0B,WACrB,CAAC,CAAC,YAAY,kBAAkB;EAEzE,IAAI,CAAC,aACH,OAAO,mBAAmB;GAAE;GAAS;EAAS,GAAG;GAC/C,OAAO,EAAE,gCAAgC;GACzC;EACF,CAAC;EAOH,IAAI,wBAAwB,WAAW,GAAG;GACxC,MAAM,YAAY,QAAQ;GAE1B,OAAO,mBAAmB;IAAE;IAAS;GAAS,GAAG;IAC/C,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAEA,MAAM,WAAW,QAAQ,YAAY,YAAY;EAEjD,IAAI,aAAa,UAAU,CAAC,aAAa,SAAS,QAAQ,GACxD,OAAO,mBAAmB;GAAE;GAAS;EAAS,GAAG;GAC/C,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,MAAM,YAAY,OAAO,IAAI,iBAAiB,UAAU;EAExD,IAAI,CAAC,WAGH,MAAM,IAAI,MAAM,aAAa,SAAS,kBAAkB;EAG1D,MAAM,cAAc,MAAM,UAAU,KAAK,QAAQ,EAAE;EAEnD,IAAI,CAAC,aAAa;GAChB,MAAM,YAAY,QAAQ;GAE1B,OAAO,mBAAmB;IAAE;IAAS;GAAS,GAAG;IAC/C,OAAO,EAAE,gCAAgC;IACzC;GACF,CAAC;EACH;EAEA,IAAI,CAAE,MAAM,YAAY,gBAAgB,WAAW,GACjD,OAAO,mBAAmB;GAAE;GAAS;EAAS,GAAG;GAC/C,OAAO,EAAE,0BAA0B;GACnC;EACF,CAAC;EAGH,QAAQ,OAAO,OAAO;CACxB;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"auth.middleware.mjs","names":[],"sources":["../../../../../../../auth/src/middleware/auth.middleware.ts"],"sourcesContent":["import { config, type HttpContext, t, type Middleware, type Request } from \"@warlock.js/core\";\r\nimport { log } from \"@warlock.js/logger\";\r\nimport type { TokenFrom } from \"../contracts/types\";\r\nimport { AccessToken } from \"../models/access-token\";\r\nimport { authConfig } from \"../services/auth-config\";\r\nimport { authService } from \"../services/auth.service\";\r\nimport { isInvalidCredentialError, jwt } from \"../services/jwt\";\r\nimport { AuthErrorCodes } from \"../utils/auth-error-codes\";\r\nimport {\r\n assertCsrfOriginAllowed,\r\n CsrfOriginMismatchError,\r\n requiresCsrfOriginCheck,\r\n} from \"./csrf-origin-check\";\r\nimport { localizedLoginPath } from \"./localized-login-path\";\r\n\r\n/** The 401 body shape every rejection in this middleware carries. */\r\ntype UnauthorizedPayload = { error: string; errorCode: string };\r\n\r\n/**\r\n * Reject an unauthenticated request, choosing the representation by ROUTE KIND.\r\n *\r\n * An API route always gets the JSON 401 it has always gotten. A PAGE route\r\n * (`request.route.isPage` — the React-SSR pages) instead redirects a logged-out\r\n * browser to the configured login path with a `returnUrl`, so a human\r\n * navigating to a guarded page lands on the login screen rather than reading a\r\n * raw JSON error blob — the defect in finding b9ab9804.\r\n *\r\n * The redirect is OPT-IN and backward-compatible: with no `auth.pageAuth.loginPath`\r\n * configured, even a page route falls back to the JSON 401, so an app that has\r\n * not asked for page-login UX keeps exactly its current behavior. The API 401\r\n * contract is never changed for API routes.\r\n *\r\n * `returnUrl` carries `request.url` — the relative path + query the browser was\r\n * on — never the absolute URL: a relative target cannot be turned into an\r\n * open-redirect off this app's origin.\r\n */\r\nfunction rejectUnauthorized({ request, response }: HttpContext, payload: UnauthorizedPayload) {\r\n const loginPath = authConfig.pageAuth.loginPath();\r\n\r\n if (loginPath && request.route?.isPage) {\r\n // Under an active `web.localeRouting` strategy, the login destination\r\n // itself must carry the request's locale prefix — otherwise an anonymous\r\n // `/ar/admin` bounces to the DEFAULT locale's `/login` rather than\r\n // `/ar/login`, even though `returnUrl` below correctly points back at\r\n // `/ar/admin`. See `./localized-login-path.ts`.\r\n const destination = localizedLoginPath(loginPath, request.locale);\r\n const separator = destination.includes(\"?\") ? \"&\" : \"?\";\r\n const returnUrl = encodeURIComponent(request.url);\r\n\r\n return response.redirect(\r\n `${destination}${separator}${authConfig.pageAuth.returnUrlParam()}=${returnUrl}`,\r\n );\r\n }\r\n\r\n return response.unauthorized(payload);\r\n}\r\n\r\n/**\r\n * Decoded access-token claims the middleware reads. The full payload carries\r\n * more (`created_at`, `tokenType`, `iat`, `exp`) but only these drive routing.\r\n */\r\ntype DecodedAccessToken = {\r\n id: string | number;\r\n userType?: string;\r\n};\r\n\r\n/**\r\n * Whether the persisted row says the token is dead.\r\n *\r\n * The answer belongs to the model (`AccessToken.isExpired`), so a registered\r\n * override that renames or reshapes its expiry column stays authoritative and\r\n * the middleware never touches a column name. A row that cannot answer at all —\r\n * an override that dropped the getter — is treated as **expired**: the failure\r\n * mode of this whole defect class was a check that quietly answered \"fine\" when\r\n * it had nothing to check, and that is not repeated here.\r\n *\r\n * This is independent of the `exp` claim required by `jwt.verify`. That guard\r\n * catches a token whose *claims* carry no deadline; this one catches a token\r\n * whose *row* says the deadline has passed — a logged-out or expired session\r\n * whose JWT is still within its own lifetime. Neither subsumes the other.\r\n */\r\nfunction accessTokenRowIsExpired(accessToken: AccessToken): boolean {\r\n return typeof accessToken.isExpired === \"boolean\" ? accessToken.isExpired : true;\r\n}\r\n\r\nfunction readCredential(request: Request, tokenFrom: TokenFrom): string {\r\n if (tokenFrom === \"header\") {\r\n return request.authorizationValue;\r\n }\r\n\r\n const value = request.cookie(tokenFrom.slice(\"cookie:\".length));\r\n\r\n return value ? String(value) : \"\";\r\n}\r\n\r\n/**\r\n * Build a route gate that always requires an authenticated request.\r\n *\r\n * The argument is mandatory and selects which user types may pass:\r\n * - `[]` — any authenticated user (token required, type not checked).\r\n * - `\"admin\"` / `[\"admin\", \"staff\"]` — token required AND the user's\r\n * `userType` must be one of the listed types.\r\n *\r\n * There is no anonymous/optional mode: a request without a valid access\r\n * token is always rejected with `401`. Routes that should be public\r\n * simply omit the middleware.\r\n *\r\n * `tokenFrom` selects one credential source and defaults to `\"header\"`.\r\n * A single source avoids making credential precedence depend on array order.\r\n *\r\n * @example\r\n * router.get(\"/account\", authMiddleware([]), accountController);\r\n * router.get(\"/admin\", authMiddleware(\"admin\"), adminController);\r\n * router.get(\"/back-office\", authMiddleware([\"admin\", \"staff\"]), backOfficeController);\r\n * router.get(\"/browser-account\", authMiddleware([], \"cookie:token\"), accountController);\r\n */\r\nexport function authMiddleware(\r\n allowedUserType: string | string[],\r\n tokenFrom: TokenFrom = \"header\",\r\n): Middleware {\r\n const allowedTypes = Array.isArray(allowedUserType) ? allowedUserType : [allowedUserType];\r\n\r\n const auth: Middleware = async ({ request, response }) => {\r\n const authorizationValue = readCredential(request, tokenFrom);\r\n\r\n if (!authorizationValue) {\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.missingAccessToken\"),\r\n errorCode: AuthErrorCodes.MissingAccessToken,\r\n },\r\n );\r\n }\r\n\r\n // CSRF Origin check: only in scope for a cookie-sourced\r\n // credential on an unsafe method. Header-token auth and safe methods\r\n // (GET/HEAD/OPTIONS) never reach `assertCsrfOriginAllowed`.\r\n if (requiresCsrfOriginCheck(tokenFrom, request.method)) {\r\n try {\r\n assertCsrfOriginAllowed(request);\r\n } catch (error) {\r\n if (!(error instanceof CsrfOriginMismatchError)) {\r\n throw error;\r\n }\r\n\r\n log.error(\"http\", \"auth\", error);\r\n\r\n return response.forbidden({\r\n error: t(\"auth.errors.csrfOriginMismatch\"),\r\n errorCode: AuthErrorCodes.CsrfOriginMismatch,\r\n });\r\n }\r\n }\r\n\r\n let decoded: DecodedAccessToken;\r\n\r\n // The ONLY try in this middleware, and it wraps a single call. Everything\r\n // after it is storage and configuration, where an exception means the\r\n // server is broken rather than the caller (D5).\r\n try {\r\n decoded = await jwt.verify<DecodedAccessToken>(authorizationValue);\r\n } catch (error) {\r\n if (!isInvalidCredentialError(error)) {\r\n // A DB/cache outage, a missing `JWT_SECRET`, or any other server-side\r\n // fault — not a verdict on this credential. Propagate to the request's\r\n // normal error path (a 500 monitoring can see) instead of answering\r\n // 401 and clearing the caller's session.\r\n throw error;\r\n }\r\n\r\n // A forged, malformed, expired token — or (D6) a `tokenType` mismatch —\r\n // is not an incident: it is a request carrying a credential the server\r\n // will never accept.\r\n log.error(\"http\", \"auth\", error);\r\n\r\n // Clearing rather than leaving a stale value matters: a request that\r\n // failed authentication must not carry the identity of whoever\r\n // `request.locals.user` last held (see the two-sided spec below that\r\n // pins this — removing the clear must make the previous identity\r\n // survive).\r\n request.locals.user = undefined;\r\n\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n },\r\n );\r\n }\r\n\r\n request.decodedAccessToken = decoded;\r\n\r\n // A valid signature is not enough — the token must still exist in storage,\r\n // so deleting the row (logout) invalidates it before its JWT expiry.\r\n const AccessTokenModel = config.key(\"auth.accessToken.model\", AccessToken);\r\n const accessToken = await AccessTokenModel.findByToken(authorizationValue);\r\n\r\n if (!accessToken) {\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n },\r\n );\r\n }\r\n\r\n // ... and the row must still be live. Existence alone was the whole check\r\n // before 4.12.0, so a row whose own `expires_at` had passed still opened\r\n // the gate. The stored expiry is now enforced, and the dead row is\r\n // removed on the way out rather than left for the cleanup command.\r\n if (accessTokenRowIsExpired(accessToken)) {\r\n await accessToken.destroy();\r\n\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n },\r\n );\r\n }\r\n\r\n const userType = decoded.userType ?? accessToken.userType;\r\n\r\n if (allowedTypes.length && !allowedTypes.includes(userType)) {\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.unauthorized\"),\r\n errorCode: AuthErrorCodes.Unauthorized,\r\n },\r\n );\r\n }\r\n\r\n const UserModel = config.key(`auth.userType.${userType}`);\r\n\r\n if (!UserModel) {\r\n // Configuration, not credentials. Throwing keeps a mis-registered app\r\n // loudly broken instead of quietly rejecting every request of this type.\r\n throw new Error(`User type ${userType} is unknown type.`);\r\n }\r\n\r\n const currentUser = await UserModel.find(decoded.id);\r\n\r\n if (!currentUser) {\r\n await accessToken.destroy();\r\n\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.invalidAccessToken\"),\r\n errorCode: AuthErrorCodes.InvalidAccessToken,\r\n },\r\n );\r\n }\r\n\r\n if (!(await authService.canAuthenticate(currentUser))) {\r\n return rejectUnauthorized(\r\n { request, response },\r\n {\r\n error: t(\"auth.errors.unauthorized\"),\r\n errorCode: AuthErrorCodes.Unauthorized,\r\n },\r\n );\r\n }\r\n\r\n request.locals.user = currentUser;\r\n };\r\n\r\n return auth;\r\n}\r\n"],"mappings":"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"}
@@ -31,6 +31,15 @@ declare function requiresCsrfOriginCheck(tokenFrom: TokenFrom, method: string):
31
31
  * Throws {@link CsrfOriginMismatchError} otherwise, including when BOTH
32
32
  * headers are absent.
33
33
  *
34
+ * Delegates the actual same-origin/allowedOrigins/trustProxy-aware
35
+ * comparison to `@warlock.js/core`'s `resolveCsrfOriginVerdict`
36
+ * (`core/src/http/csrf-origin-policy.ts`) — SECURITY card 8a752ab2 moved that
37
+ * logic down into `core` so this middleware's check and core's own default
38
+ * CSRF-Origin guard (`core/src/http/csrf-default-guard.ts`, which now runs
39
+ * even earlier, ahead of this middleware, for cookie-carrying unsafe-method
40
+ * requests generally) share one implementation instead of two copies that
41
+ * could drift apart.
42
+ *
34
43
  * Callers gate this behind {@link requiresCsrfOriginCheck} — this function
35
44
  * itself does not re-check the token source or the method.
36
45
  */
@@ -1 +1 @@
1
- {"version":3,"file":"csrf-origin-check.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/csrf-origin-check.ts"],"mappings":";;;;;;KASY,wBAAA;AAAZ;;;;AAAoC;AAWpC;AAXA,cAWa,uBAAA,SAAgC,KAAA;EAAA,SAGf,MAAA,EAAQ,wBAAA;EAAA,SAF3B,IAAA,GAAI,cAAA,CAAA,kBAAA;cAEe,MAAA,EAAQ,wBAAA;AAAA;;;;;;;;iBAatB,uBAAA,CAAwB,SAAA,EAAW,SAAS,EAAE,MAAA;;;;;;AAbA;AAa9D;;;;iBAmEgB,uBAAA,CAAwB,OAAgB,EAAP,OAAO"}
1
+ {"version":3,"file":"csrf-origin-check.d.mts","names":[],"sources":["../../../../../../../auth/src/middleware/csrf-origin-check.ts"],"mappings":";;;;;;KAQY,wBAAA;AAAZ;;;;AAAoC;AAWpC;AAXA,cAWa,uBAAA,SAAgC,KAAA;EAAA,SAGf,MAAA,EAAQ,wBAAA;EAAA,SAF3B,IAAA,GAAI,cAAA,CAAA,kBAAA;cAEe,MAAA,EAAQ,wBAAA;AAAA;;;;;;;;iBAatB,uBAAA,CAAwB,SAAA,EAAW,SAAS,EAAE,MAAA;;;;;;AAbA;AAa9D;;;;;;;;AAA4E;AAuB5E;;;;iBAAgB,uBAAA,CAAwB,OAAgB,EAAP,OAAO"}