@stratal/framework 0.0.18 → 0.0.20

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 (54) hide show
  1. package/dist/access-control/index.d.mts +180 -0
  2. package/dist/access-control/index.d.mts.map +1 -0
  3. package/dist/access-control/index.mjs +71 -0
  4. package/dist/access-control/index.mjs.map +1 -0
  5. package/dist/access.service-BjYVtUJw.mjs +145 -0
  6. package/dist/access.service-BjYVtUJw.mjs.map +1 -0
  7. package/dist/auth/index.d.mts +131 -6
  8. package/dist/auth/index.d.mts.map +1 -1
  9. package/dist/auth/index.mjs +341 -67
  10. package/dist/auth/index.mjs.map +1 -1
  11. package/dist/auth-context-6Li1JkIq.mjs +85 -0
  12. package/dist/auth-context-6Li1JkIq.mjs.map +1 -0
  13. package/dist/auth-context-B44CDZCt.d.mts +86 -0
  14. package/dist/auth-context-B44CDZCt.d.mts.map +1 -0
  15. package/dist/context/index.d.mts +2 -2
  16. package/dist/context/index.mjs +2 -2
  17. package/dist/database/index.d.mts +3 -3
  18. package/dist/database/index.mjs +54 -46
  19. package/dist/database/index.mjs.map +1 -1
  20. package/dist/{decorate-C12QolJF.mjs → decorate-CdfCRvAc.mjs} +1 -1
  21. package/dist/{decorateMetadata-rWbWGUuO.mjs → decorateMetadata-CqtSx3_1.mjs} +1 -1
  22. package/dist/decorateParam-Dc5DGEpb.mjs +18 -0
  23. package/dist/decorateParam-Dc5DGEpb.mjs.map +1 -0
  24. package/dist/{errors-C_KIIU1v.mjs → errors-B1vVXc1T.mjs} +1 -1
  25. package/dist/{errors-C_KIIU1v.mjs.map → errors-B1vVXc1T.mjs.map} +1 -1
  26. package/dist/factory/index.d.mts +1 -1
  27. package/dist/guards/index.d.mts +7 -6
  28. package/dist/guards/index.d.mts.map +1 -1
  29. package/dist/guards/index.mjs +38 -29
  30. package/dist/guards/index.mjs.map +1 -1
  31. package/dist/{index-B1iGBJcO.d.mts → index-CCDPF-1Y.d.mts} +30 -42
  32. package/dist/index-CCDPF-1Y.d.mts.map +1 -0
  33. package/dist/index.d.mts +2 -2
  34. package/dist/insufficient-permissions.error-CRnOHYvq.mjs +23 -0
  35. package/dist/insufficient-permissions.error-CRnOHYvq.mjs.map +1 -0
  36. package/dist/types-BLyu9dAd.d.mts +11 -0
  37. package/dist/types-BLyu9dAd.d.mts.map +1 -0
  38. package/dist/types-BZlcRR2M.d.mts +92 -0
  39. package/dist/types-BZlcRR2M.d.mts.map +1 -0
  40. package/package.json +23 -22
  41. package/dist/auth-context-BD2ApWg1.d.mts +0 -38
  42. package/dist/auth-context-BD2ApWg1.d.mts.map +0 -1
  43. package/dist/auth-context-BfekHvM9.mjs +0 -55
  44. package/dist/auth-context-BfekHvM9.mjs.map +0 -1
  45. package/dist/decorateParam-WGqsyT5s.mjs +0 -8
  46. package/dist/index-B1iGBJcO.d.mts.map +0 -1
  47. package/dist/rbac/index.d.mts +0 -206
  48. package/dist/rbac/index.d.mts.map +0 -1
  49. package/dist/rbac/index.mjs +0 -346
  50. package/dist/rbac/index.mjs.map +0 -1
  51. package/dist/tokens-Di1ofovy.mjs +0 -32
  52. package/dist/tokens-Di1ofovy.mjs.map +0 -1
  53. package/dist/types-Gjk0d2qB.d.mts +0 -47
  54. package/dist/types-Gjk0d2qB.d.mts.map +0 -1
@@ -1,12 +1,15 @@
1
- import { t as __decorate } from "../decorate-C12QolJF.mjs";
2
- import { t as AuthContext } from "../auth-context-BfekHvM9.mjs";
3
- import { t as __decorateMetadata } from "../decorateMetadata-rWbWGUuO.mjs";
4
- import { t as __decorateParam } from "../decorateParam-WGqsyT5s.mjs";
1
+ import { n as createStratalAcPlugin, t as AccessService } from "../access.service-BjYVtUJw.mjs";
2
+ import { n as AC_TOKENS, t as __decorateParam } from "../decorateParam-Dc5DGEpb.mjs";
3
+ import { t as __decorateMetadata } from "../decorateMetadata-CqtSx3_1.mjs";
4
+ import { t as __decorate } from "../decorate-CdfCRvAc.mjs";
5
+ import { t as AuthContext } from "../auth-context-6Li1JkIq.mjs";
6
+ import { CONTAINER_TOKEN, DI_TOKENS, Transient } from "stratal/di";
7
+ import { I18nModule } from "stratal/i18n";
5
8
  import { Module } from "stratal/module";
6
- import { DI_TOKENS, Transient } from "stratal/di";
7
- import { ApplicationError, ERROR_CODES, InternalError } from "stratal/errors";
9
+ import { RATE_LIMITER_TOKENS, RateLimiterRegistry } from "stratal/rate-limiter";
10
+ import { ApplicationError, ERROR_CODES } from "stratal/errors";
8
11
  import { LOGGER_TOKENS } from "stratal/logger";
9
- import { inject } from "tsyringe";
12
+ import { inject as inject$1 } from "tsyringe";
10
13
  import { betterAuth } from "better-auth";
11
14
  import { APIError } from "better-auth/api";
12
15
  //#region src/auth/auth.tokens.ts
@@ -15,13 +18,63 @@ const AUTH_SERVICE = Symbol.for("stratal:auth:service");
15
18
  /** Token for Better Auth options configuration */
16
19
  const AUTH_OPTIONS = Symbol.for("stratal:auth:options");
17
20
  //#endregion
21
+ //#region src/auth/i18n/en.ts
22
+ const authMessages = { en: { auth: {
23
+ errors: {
24
+ tokenRequired: "Verification token is required",
25
+ invalidToken: "Invalid or expired verification token",
26
+ verificationFailed: "Verification failed. Please try again.",
27
+ userNotFound: "User not found. Please check your credentials.",
28
+ invalidCredentials: "Invalid email or password",
29
+ invalidPassword: "Invalid password",
30
+ invalidEmail: "Invalid email address",
31
+ sessionExpired: "Your session has expired. Please sign in again.",
32
+ emailNotVerified: "Please verify your email address before signing in",
33
+ passwordTooShort: "Password must be at least {minLength} characters",
34
+ passwordTooLong: "Password must be at most {maxLength} characters",
35
+ accountAlreadyExists: "An account with this email already exists",
36
+ failedToCreateUser: "Failed to create user account. Please try again.",
37
+ failedToCreateSession: "Failed to create session. Please try again.",
38
+ failedToGetSession: "Failed to retrieve session. Please try again.",
39
+ failedToUpdateUser: "Failed to update user information. Please try again.",
40
+ failedToGetUserInfo: "Failed to retrieve user information. Please try again.",
41
+ socialAccountLinked: "This social account is already linked to another user",
42
+ providerNotFound: "Authentication provider not found",
43
+ userEmailNotFound: "User email address not found",
44
+ accountNotFound: "Account not found",
45
+ credentialAccountNotFound: "Credential account not found",
46
+ cannotUnlinkLastAccount: "Cannot unlink your last account",
47
+ userAlreadyHasPassword: "User already has a password set",
48
+ emailCannotBeUpdated: "Email address cannot be updated at this time",
49
+ tokenExpired: "The verification token has expired. Please request a new verification email.",
50
+ invalidCallbackUrl: "Invalid callback URL",
51
+ invalidOrigin: "Request origin is not allowed",
52
+ validationFailed: "Authentication validation failed",
53
+ emailAlreadyVerified: "Email address is already verified",
54
+ emailMismatch: "Email address does not match",
55
+ unknownError: "An authentication error occurred"
56
+ },
57
+ org: {
58
+ organizationNotFound: "Organization not found",
59
+ memberNotFound: "Member not found",
60
+ invitationNotFound: "Invitation not found",
61
+ permissionDenied: "You do not have permission to perform this action",
62
+ invitationRecipientMismatch: "You are not the recipient of this invitation",
63
+ conflict: "A resource with this identifier already exists",
64
+ limitReached: "The maximum limit has been reached",
65
+ membershipError: "This action cannot be performed due to membership constraints",
66
+ teamNotFound: "Team not found",
67
+ roleNotFound: "Role not found"
68
+ }
69
+ } } };
70
+ //#endregion
18
71
  //#region src/auth/middleware/auth-context.middleware.ts
19
72
  let AuthContextMiddleware = class AuthContextMiddleware {
20
73
  async handle(ctx, next) {
21
74
  const requestContainer = ctx.getContainer();
22
75
  const authContext = new AuthContext();
23
76
  requestContainer.registerValue(DI_TOKENS.AuthContext, authContext);
24
- await next();
77
+ return next();
25
78
  }
26
79
  };
27
80
  AuthContextMiddleware = __decorate([Transient()], AuthContextMiddleware);
@@ -34,162 +87,326 @@ let SessionVerificationMiddleware = class SessionVerificationMiddleware {
34
87
  }
35
88
  async handle(ctx, next) {
36
89
  try {
37
- const session = await this.authService.auth.api.getSession({ headers: ctx.c.req.raw.headers });
38
- if (session) ctx.getContainer().resolve(DI_TOKENS.AuthContext).setAuthContext({ userId: session.user.id });
39
- await next();
90
+ const result = await this.authService.auth.api.getSession({ headers: ctx.c.req.raw.headers });
91
+ if (result) ctx.getContainer().resolve(DI_TOKENS.AuthContext).setAuthContext({ user: result.user });
40
92
  } catch (error) {
41
93
  this.logger.debug("Session validation failed (e.g., invalidated in DB)", { error });
42
- await next();
43
94
  }
95
+ return next();
44
96
  }
45
97
  };
46
98
  SessionVerificationMiddleware = __decorate([
47
99
  Transient(),
48
- __decorateParam(0, inject(AUTH_SERVICE)),
49
- __decorateParam(1, inject(LOGGER_TOKENS.LoggerService)),
100
+ __decorateParam(0, inject$1(AUTH_SERVICE)),
101
+ __decorateParam(1, inject$1(LOGGER_TOKENS.LoggerService)),
50
102
  __decorateMetadata("design:paramtypes", [Object, Object])
51
103
  ], SessionVerificationMiddleware);
52
104
  //#endregion
105
+ //#region src/auth/rate-limit-bridge.ts
106
+ /**
107
+ * Rate-limit bridge between Stratal's `RateLimiterModule` and better-auth.
108
+ *
109
+ * Importing this file (transitively, via `auth.module.ts`) does two things:
110
+ *
111
+ * 1. Augments `RateLimiterRegistry` with `forPath()` + `pathEntries()` via
112
+ * Stratal's `Macroable`. Path-keyed rules registered on the same registry
113
+ * used for Stratal's own throttling are projected into better-auth's
114
+ * `customRules` by {@link projectCustomRules}.
115
+ * 2. Exports {@link createBetterAuthRateLimitStorage} — adapts Stratal's
116
+ * {@link IRateLimiterStore} into better-auth's `customStorage`, so both
117
+ * systems share one backing store.
118
+ *
119
+ * `AuthModule.forRootAsync` wires both automatically when `RateLimiterModule`
120
+ * is imported. Users with explicit `rateLimit.customStorage` /
121
+ * `rateLimit.customRules` keys in their auth factory keep precedence.
122
+ *
123
+ * Frictions, documented for path-keyed entries:
124
+ *
125
+ * - `Limit.by(...)` is meaningless. Better-auth scopes per-IP+path.
126
+ * - Multiple `Limit`s reduce to the most restrictive (smallest max-per-second).
127
+ * - `Limit.none()` projects to `false` (better-auth's "disable" sentinel).
128
+ * - `Limit.response(...)` is a no-op. Better-auth renders its own 429.
129
+ * - Snapshot caveat: `customRules` is built once at AuthService construction,
130
+ * so register all `forPath()` entries inside `OnInitialize` hooks.
131
+ */
132
+ const pathResolvers = /* @__PURE__ */ new WeakMap();
133
+ function getOrCreatePathMap(registry) {
134
+ let map = pathResolvers.get(registry);
135
+ if (!map) {
136
+ map = /* @__PURE__ */ new Map();
137
+ pathResolvers.set(registry, map);
138
+ }
139
+ return map;
140
+ }
141
+ RateLimiterRegistry.macro("forPath", function(path, resolver) {
142
+ getOrCreatePathMap(this).set(path, resolver);
143
+ });
144
+ RateLimiterRegistry.macro("pathEntries", function() {
145
+ return (pathResolvers.get(this) ?? /* @__PURE__ */ new Map()).entries();
146
+ });
147
+ const BETTER_AUTH_TTL_SECONDS = 86400;
148
+ const BETTER_AUTH_KEY_PREFIX = "ba-rl:";
149
+ /**
150
+ * Adapt Stratal's `IRateLimiterStore` into better-auth's `customStorage` shape.
151
+ * Better-auth supplies its own `RateLimit` records (`{ key, count, lastRequest }`);
152
+ * the adapter just persists them under a separate key namespace.
153
+ */
154
+ function createBetterAuthRateLimitStorage(store) {
155
+ return {
156
+ async get(key) {
157
+ return await store.get(`${BETTER_AUTH_KEY_PREFIX}${key}`);
158
+ },
159
+ async set(key, value, _update) {
160
+ await store.set(`${BETTER_AUTH_KEY_PREFIX}${key}`, value, BETTER_AUTH_TTL_SECONDS);
161
+ }
162
+ };
163
+ }
164
+ /**
165
+ * Project every `forPath` entry on the registry into better-auth's
166
+ * `customRules` shape. Each entry becomes an async function that resolves
167
+ * the user's `Limit`(s) and reduces them to a single `{ window, max }` pair
168
+ * (or `false` for `Limit.none()`).
169
+ *
170
+ * Multi-`Limit` reduction picks the most restrictive — smallest
171
+ * `max / windowSeconds` ratio; ties favour the first.
172
+ */
173
+ function projectCustomRules(registry) {
174
+ const rules = {};
175
+ for (const [path, resolver] of registry.pathEntries()) rules[path] = async (req) => {
176
+ const resolved = await resolver(req);
177
+ const candidates = (Array.isArray(resolved) ? resolved : [resolved]).filter((l) => !l.disabled);
178
+ if (candidates.length === 0) return false;
179
+ const chosen = candidates.reduce((a, b) => a.max / a.windowSeconds <= b.max / b.windowSeconds ? a : b);
180
+ return {
181
+ window: chosen.windowSeconds,
182
+ max: chosen.max
183
+ };
184
+ };
185
+ return rules;
186
+ }
187
+ //#endregion
53
188
  //#region src/auth/errors/auth-errors.ts
54
189
  var UserNotFoundError = class extends ApplicationError {
55
190
  constructor(email) {
56
- super("errors.auth.userNotFound", ERROR_CODES.RESOURCE.NOT_FOUND, email ? { email } : void 0);
191
+ super("auth.errors.userNotFound", ERROR_CODES.RESOURCE.NOT_FOUND, email ? { email } : void 0);
57
192
  }
58
193
  };
59
194
  var InvalidCredentialsError = class extends ApplicationError {
60
195
  constructor() {
61
- super("errors.auth.invalidCredentials", ERROR_CODES.AUTH.INVALID_CREDENTIALS);
196
+ super("auth.errors.invalidCredentials", ERROR_CODES.AUTH.INVALID_CREDENTIALS);
62
197
  }
63
198
  };
64
199
  var InvalidPasswordError = class extends ApplicationError {
65
200
  constructor() {
66
- super("errors.auth.invalidPassword", ERROR_CODES.AUTH.INVALID_CREDENTIALS);
201
+ super("auth.errors.invalidPassword", ERROR_CODES.AUTH.INVALID_CREDENTIALS);
67
202
  }
68
203
  };
69
204
  var InvalidEmailError = class extends ApplicationError {
70
205
  constructor(email) {
71
- super("errors.auth.invalidEmail", ERROR_CODES.VALIDATION.INVALID_FORMAT, email ? { email } : void 0);
206
+ super("auth.errors.invalidEmail", ERROR_CODES.VALIDATION.INVALID_FORMAT, email ? { email } : void 0);
72
207
  }
73
208
  };
74
209
  var SessionExpiredError = class extends ApplicationError {
75
210
  constructor() {
76
- super("errors.auth.sessionExpired", ERROR_CODES.AUTH.SESSION_EXPIRED);
211
+ super("auth.errors.sessionExpired", ERROR_CODES.AUTH.SESSION_EXPIRED);
77
212
  }
78
213
  };
79
214
  var EmailNotVerifiedError = class extends ApplicationError {
80
215
  constructor(email) {
81
- super("errors.auth.emailNotVerified", ERROR_CODES.AUTH.EMAIL_NOT_VERIFIED, email ? { email } : void 0);
216
+ super("auth.errors.emailNotVerified", ERROR_CODES.AUTH.EMAIL_NOT_VERIFIED, email ? { email } : void 0);
82
217
  }
83
218
  };
84
219
  var PasswordTooShortError = class extends ApplicationError {
85
220
  constructor(minLength) {
86
- super("errors.auth.passwordTooShort", ERROR_CODES.AUTH.PASSWORD_TOO_SHORT, { minLength });
221
+ super("auth.errors.passwordTooShort", ERROR_CODES.AUTH.PASSWORD_TOO_SHORT, { minLength });
87
222
  }
88
223
  };
89
224
  var PasswordTooLongError = class extends ApplicationError {
90
225
  constructor(maxLength) {
91
- super("errors.auth.passwordTooLong", ERROR_CODES.AUTH.PASSWORD_TOO_LONG, { maxLength });
226
+ super("auth.errors.passwordTooLong", ERROR_CODES.AUTH.PASSWORD_TOO_LONG, { maxLength });
92
227
  }
93
228
  };
94
229
  var AccountAlreadyExistsError = class extends ApplicationError {
95
230
  constructor(email) {
96
- super("errors.auth.accountAlreadyExists", ERROR_CODES.AUTH.ACCOUNT_ALREADY_EXISTS, email ? { email } : void 0);
231
+ super("auth.errors.accountAlreadyExists", ERROR_CODES.AUTH.ACCOUNT_ALREADY_EXISTS, email ? { email } : void 0);
97
232
  }
98
233
  };
99
234
  var FailedToCreateUserError = class extends ApplicationError {
100
235
  constructor(reason) {
101
- super("errors.auth.failedToCreateUser", ERROR_CODES.AUTH.FAILED_TO_CREATE_USER, reason ? { reason } : void 0);
236
+ super("auth.errors.failedToCreateUser", ERROR_CODES.AUTH.FAILED_TO_CREATE_USER, reason ? { reason } : void 0);
102
237
  }
103
238
  };
104
239
  var FailedToCreateSessionError = class extends ApplicationError {
105
240
  constructor(reason) {
106
- super("errors.auth.failedToCreateSession", ERROR_CODES.AUTH.FAILED_TO_CREATE_SESSION, reason ? { reason } : void 0);
241
+ super("auth.errors.failedToCreateSession", ERROR_CODES.AUTH.FAILED_TO_CREATE_SESSION, reason ? { reason } : void 0);
107
242
  }
108
243
  };
109
244
  var FailedToUpdateUserError = class extends ApplicationError {
110
245
  constructor(reason) {
111
- super("errors.auth.failedToUpdateUser", ERROR_CODES.AUTH.FAILED_TO_UPDATE_USER, reason ? { reason } : void 0);
246
+ super("auth.errors.failedToUpdateUser", ERROR_CODES.AUTH.FAILED_TO_UPDATE_USER, reason ? { reason } : void 0);
112
247
  }
113
248
  };
114
249
  var SocialAccountLinkedError = class extends ApplicationError {
115
250
  constructor(provider) {
116
- super("errors.auth.socialAccountLinked", ERROR_CODES.AUTH.SOCIAL_ACCOUNT_LINKED, provider ? { provider } : void 0);
251
+ super("auth.errors.socialAccountLinked", ERROR_CODES.AUTH.SOCIAL_ACCOUNT_LINKED, provider ? { provider } : void 0);
117
252
  }
118
253
  };
119
254
  var CannotUnlinkLastAccountError = class extends ApplicationError {
120
255
  constructor() {
121
- super("errors.auth.cannotUnlinkLastAccount", ERROR_CODES.AUTH.CANNOT_UNLINK_LAST_ACCOUNT);
256
+ super("auth.errors.cannotUnlinkLastAccount", ERROR_CODES.AUTH.CANNOT_UNLINK_LAST_ACCOUNT);
122
257
  }
123
258
  };
124
259
  var ProviderNotFoundError = class extends ApplicationError {
125
260
  constructor(provider) {
126
- super("errors.auth.providerNotFound", ERROR_CODES.RESOURCE.NOT_FOUND, provider ? { provider } : void 0);
261
+ super("auth.errors.providerNotFound", ERROR_CODES.RESOURCE.NOT_FOUND, provider ? { provider } : void 0);
127
262
  }
128
263
  };
129
264
  var UserEmailNotFoundError = class extends ApplicationError {
130
265
  constructor() {
131
- super("errors.auth.userEmailNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
266
+ super("auth.errors.userEmailNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
132
267
  }
133
268
  };
134
269
  var AccountNotFoundError = class extends ApplicationError {
135
270
  constructor() {
136
- super("errors.auth.accountNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
271
+ super("auth.errors.accountNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
137
272
  }
138
273
  };
139
274
  var CredentialAccountNotFoundError = class extends ApplicationError {
140
275
  constructor() {
141
- super("errors.auth.credentialAccountNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
276
+ super("auth.errors.credentialAccountNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
142
277
  }
143
278
  };
144
279
  var UserAlreadyHasPasswordError = class extends ApplicationError {
145
280
  constructor() {
146
- super("errors.auth.userAlreadyHasPassword", ERROR_CODES.RESOURCE.CONFLICT);
281
+ super("auth.errors.userAlreadyHasPassword", ERROR_CODES.RESOURCE.CONFLICT);
147
282
  }
148
283
  };
149
284
  var EmailCannotBeUpdatedError = class extends ApplicationError {
150
285
  constructor(reason) {
151
- super("errors.auth.emailCannotBeUpdated", ERROR_CODES.VALIDATION.GENERIC, reason ? { reason } : void 0);
286
+ super("auth.errors.emailCannotBeUpdated", ERROR_CODES.VALIDATION.GENERIC, reason ? { reason } : void 0);
152
287
  }
153
288
  };
154
289
  var FailedToGetSessionError = class extends ApplicationError {
155
290
  constructor(reason) {
156
- super("errors.auth.failedToGetSession", ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : void 0);
291
+ super("auth.errors.failedToGetSession", ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : void 0);
157
292
  }
158
293
  };
159
294
  var FailedToGetUserInfoError = class extends ApplicationError {
160
295
  constructor(reason) {
161
- super("errors.auth.failedToGetUserInfo", ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : void 0);
296
+ super("auth.errors.failedToGetUserInfo", ERROR_CODES.SYSTEM.INTERNAL_ERROR, reason ? { reason } : void 0);
162
297
  }
163
298
  };
164
299
  var IdTokenNotSupportedError = class extends ApplicationError {
165
300
  constructor() {
166
- super("errors.auth.invalidToken", ERROR_CODES.VALIDATION.GENERIC);
301
+ super("auth.errors.invalidToken", ERROR_CODES.VALIDATION.GENERIC);
167
302
  }
168
303
  };
169
304
  var TokenExpiredError = class extends ApplicationError {
170
305
  constructor() {
171
- super("errors.auth.tokenExpired", ERROR_CODES.VALIDATION.GENERIC);
306
+ super("auth.errors.tokenExpired", ERROR_CODES.VALIDATION.GENERIC);
307
+ }
308
+ };
309
+ var InvalidCallbackUrlError = class extends ApplicationError {
310
+ constructor() {
311
+ super("auth.errors.invalidCallbackUrl", ERROR_CODES.VALIDATION.INVALID_FORMAT);
312
+ }
313
+ };
314
+ var InvalidOriginError = class extends ApplicationError {
315
+ constructor() {
316
+ super("auth.errors.invalidOrigin", ERROR_CODES.AUTHZ.FORBIDDEN);
317
+ }
318
+ };
319
+ var AuthValidationFailedError = class extends ApplicationError {
320
+ constructor() {
321
+ super("auth.errors.validationFailed", ERROR_CODES.VALIDATION.GENERIC);
322
+ }
323
+ };
324
+ var EmailAlreadyVerifiedError = class extends ApplicationError {
325
+ constructor() {
326
+ super("auth.errors.emailAlreadyVerified", ERROR_CODES.RESOURCE.CONFLICT);
327
+ }
328
+ };
329
+ var EmailMismatchError = class extends ApplicationError {
330
+ constructor() {
331
+ super("auth.errors.emailMismatch", ERROR_CODES.VALIDATION.INVALID_FORMAT);
332
+ }
333
+ };
334
+ var BetterAuthUnknownError = class extends ApplicationError {
335
+ constructor(errorCode) {
336
+ super("auth.errors.unknownError", ERROR_CODES.SYSTEM.INTERNAL_ERROR, errorCode ? { errorCode } : void 0);
172
337
  }
173
338
  };
174
339
  //#endregion
175
340
  //#region src/auth/errors/invalid-token.error.ts
176
341
  var InvalidTokenError = class extends ApplicationError {
177
342
  constructor() {
178
- super("errors.auth.invalidToken", ERROR_CODES.AUTH.INVALID_TOKEN);
343
+ super("auth.errors.invalidToken", ERROR_CODES.AUTH.INVALID_TOKEN);
344
+ }
345
+ };
346
+ //#endregion
347
+ //#region src/auth/errors/organization-errors.ts
348
+ var OrganizationNotFoundError = class extends ApplicationError {
349
+ constructor() {
350
+ super("auth.org.organizationNotFound", ERROR_CODES.AUTH.ORGANIZATION_NOT_FOUND);
351
+ }
352
+ };
353
+ var OrganizationMemberNotFoundError = class extends ApplicationError {
354
+ constructor() {
355
+ super("auth.org.memberNotFound", ERROR_CODES.AUTH.MEMBER_NOT_FOUND);
356
+ }
357
+ };
358
+ var OrganizationInvitationNotFoundError = class extends ApplicationError {
359
+ constructor() {
360
+ super("auth.org.invitationNotFound", ERROR_CODES.AUTH.INVITATION_NOT_FOUND);
361
+ }
362
+ };
363
+ var OrganizationPermissionDeniedError = class extends ApplicationError {
364
+ constructor() {
365
+ super("auth.org.permissionDenied", ERROR_CODES.AUTHZ.FORBIDDEN);
366
+ }
367
+ };
368
+ var OrganizationInvitationRecipientMismatchError = class extends ApplicationError {
369
+ constructor() {
370
+ super("auth.org.invitationRecipientMismatch", ERROR_CODES.AUTH.INVITATION_RECIPIENT_MISMATCH);
371
+ }
372
+ };
373
+ var OrganizationConflictError = class extends ApplicationError {
374
+ constructor() {
375
+ super("auth.org.conflict", ERROR_CODES.RESOURCE.CONFLICT);
376
+ }
377
+ };
378
+ var OrganizationLimitReachedError = class extends ApplicationError {
379
+ constructor() {
380
+ super("auth.org.limitReached", ERROR_CODES.AUTH.ORGANIZATION_LIMIT_REACHED);
381
+ }
382
+ };
383
+ var OrganizationMembershipError = class extends ApplicationError {
384
+ constructor() {
385
+ super("auth.org.membershipError", ERROR_CODES.AUTH.ORGANIZATION_MEMBERSHIP_REQUIRED);
386
+ }
387
+ };
388
+ var OrganizationTeamNotFoundError = class extends ApplicationError {
389
+ constructor() {
390
+ super("auth.org.teamNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
391
+ }
392
+ };
393
+ var OrganizationRoleNotFoundError = class extends ApplicationError {
394
+ constructor() {
395
+ super("auth.org.roleNotFound", ERROR_CODES.RESOURCE.NOT_FOUND);
179
396
  }
180
397
  };
181
398
  //#endregion
182
399
  //#region src/auth/errors/token-required.error.ts
183
400
  var TokenRequiredError = class extends ApplicationError {
184
401
  constructor() {
185
- super("errors.auth.tokenRequired", ERROR_CODES.VALIDATION.REQUIRED_FIELD, { field: "token" });
402
+ super("auth.errors.tokenRequired", ERROR_CODES.VALIDATION.REQUIRED_FIELD, { field: "token" });
186
403
  }
187
404
  };
188
405
  //#endregion
189
406
  //#region src/auth/errors/verification-failed.error.ts
190
407
  var VerificationFailedError = class extends ApplicationError {
191
408
  constructor() {
192
- super("errors.auth.verificationFailed", ERROR_CODES.AUTH.INVALID_CREDENTIALS);
409
+ super("auth.errors.verificationFailed", ERROR_CODES.AUTH.INVALID_CREDENTIALS);
193
410
  }
194
411
  };
195
412
  //#endregion
@@ -208,20 +425,19 @@ function mapBetterAuthError(error) {
208
425
  if (location.includes("failed_to_create_user")) return new FailedToCreateUserError();
209
426
  if (location.includes("failed_to_create_session")) return new FailedToCreateSessionError();
210
427
  }
211
- if (!errorCode) return new InternalError({
212
- originalError: `Better Auth error: ${error.message}`,
213
- stack: error.stack
214
- });
215
- if (errorCode === "USER_NOT_FOUND") return new UserNotFoundError();
428
+ if (!errorCode) return new BetterAuthUnknownError();
429
+ if (errorCode === "USER_NOT_FOUND" || errorCode === "INVALID_USER") return new UserNotFoundError();
216
430
  if (errorCode === "USER_EMAIL_NOT_FOUND") return new UserEmailNotFoundError();
217
431
  if (errorCode === "INVALID_EMAIL_OR_PASSWORD") return new InvalidCredentialsError();
218
432
  if (errorCode === "INVALID_PASSWORD") return new InvalidPasswordError();
219
433
  if (errorCode === "INVALID_EMAIL") return new InvalidEmailError();
220
- if (errorCode === "SESSION_EXPIRED") return new SessionExpiredError();
434
+ if (errorCode === "SESSION_EXPIRED" || errorCode === "SESSION_NOT_FRESH") return new SessionExpiredError();
221
435
  if (errorCode === "FAILED_TO_CREATE_SESSION") return new FailedToCreateSessionError();
222
436
  if (errorCode === "FAILED_TO_GET_SESSION") return new FailedToGetSessionError();
223
437
  if (errorCode === "EMAIL_NOT_VERIFIED") return new EmailNotVerifiedError();
224
438
  if (errorCode === "EMAIL_CAN_NOT_BE_UPDATED") return new EmailCannotBeUpdatedError();
439
+ if (errorCode === "EMAIL_ALREADY_VERIFIED") return new EmailAlreadyVerifiedError();
440
+ if (errorCode === "EMAIL_MISMATCH") return new EmailMismatchError();
225
441
  if (errorCode === "PASSWORD_TOO_SHORT") return new PasswordTooShortError(8);
226
442
  if (errorCode === "PASSWORD_TOO_LONG") return new PasswordTooLongError(128);
227
443
  if (errorCode === "USER_ALREADY_EXISTS" || errorCode === "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL") return new AccountAlreadyExistsError();
@@ -231,16 +447,27 @@ function mapBetterAuthError(error) {
231
447
  if (errorCode === "FAILED_TO_CREATE_USER") return new FailedToCreateUserError();
232
448
  if (errorCode === "FAILED_TO_UPDATE_USER") return new FailedToUpdateUserError();
233
449
  if (errorCode === "FAILED_TO_GET_USER_INFO") return new FailedToGetUserInfoError();
234
- if (errorCode === "SOCIAL_ACCOUNT_ALREADY_LINKED") return new SocialAccountLinkedError();
450
+ if (errorCode === "SOCIAL_ACCOUNT_ALREADY_LINKED" || errorCode === "LINKED_ACCOUNT_ALREADY_EXISTS") return new SocialAccountLinkedError();
235
451
  if (errorCode === "PROVIDER_NOT_FOUND") return new ProviderNotFoundError();
236
452
  if (errorCode === "ID_TOKEN_NOT_SUPPORTED") return new IdTokenNotSupportedError();
237
- if (errorCode === "INVALID_TOKEN") return new IdTokenNotSupportedError();
453
+ if (errorCode === "INVALID_TOKEN") return new InvalidTokenError();
238
454
  if (errorCode === "TOKEN_EXPIRED") return new TokenExpiredError();
239
- if (errorCode === "USER_ALREADY_HAS_PASSWORD") return new UserAlreadyHasPasswordError();
240
- return new InternalError({
241
- originalError: `Better Auth error [${errorCode}]: ${error.message}`,
242
- stack: error.stack
243
- });
455
+ if (errorCode === "USER_ALREADY_HAS_PASSWORD" || errorCode === "PASSWORD_ALREADY_SET") return new UserAlreadyHasPasswordError();
456
+ if (errorCode === "INVALID_CALLBACK_URL" || errorCode === "INVALID_REDIRECT_URL" || errorCode === "INVALID_NEW_USER_CALLBACK_URL" || errorCode === "INVALID_ERROR_CALLBACK_URL" || errorCode === "CALLBACK_URL_REQUIRED") return new InvalidCallbackUrlError();
457
+ if (errorCode === "INVALID_ORIGIN" || errorCode === "MISSING_OR_NULL_ORIGIN" || errorCode === "CROSS_SITE_NAVIGATION_LOGIN_BLOCKED") return new InvalidOriginError();
458
+ if (errorCode === "VALIDATION_ERROR" || errorCode === "MISSING_FIELD" || errorCode === "FIELD_NOT_ALLOWED" || errorCode === "BODY_MUST_BE_AN_OBJECT" || errorCode === "ASYNC_VALIDATION_NOT_SUPPORTED" || errorCode === "METHOD_NOT_ALLOWED_DEFER_SESSION_REQUIRED") return new AuthValidationFailedError();
459
+ if (errorCode === "FAILED_TO_CREATE_VERIFICATION" || errorCode === "VERIFICATION_EMAIL_NOT_ENABLED") return new FailedToCreateSessionError();
460
+ if (errorCode === "ORGANIZATION_NOT_FOUND" || errorCode === "NO_ACTIVE_ORGANIZATION") return new OrganizationNotFoundError();
461
+ if (errorCode === "MEMBER_NOT_FOUND" || errorCode === "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION" || errorCode === "USER_IS_NOT_A_MEMBER_OF_THE_TEAM") return new OrganizationMemberNotFoundError();
462
+ if (errorCode === "INVITATION_NOT_FOUND" || errorCode === "FAILED_TO_RETRIEVE_INVITATION") return new OrganizationInvitationNotFoundError();
463
+ if (errorCode === "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION" || errorCode === "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION") return new OrganizationInvitationRecipientMismatchError();
464
+ if (errorCode === "TEAM_NOT_FOUND" || errorCode === "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM") return new OrganizationTeamNotFoundError();
465
+ if (errorCode === "ROLE_NOT_FOUND" || errorCode === "INVALID_RESOURCE") return new OrganizationRoleNotFoundError();
466
+ if (errorCode === "ORGANIZATION_ALREADY_EXISTS" || errorCode === "ORGANIZATION_SLUG_ALREADY_TAKEN" || errorCode === "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION" || errorCode === "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION" || errorCode === "TEAM_ALREADY_EXISTS" || errorCode === "ROLE_NAME_IS_ALREADY_TAKEN") return new OrganizationConflictError();
467
+ if (errorCode === "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS" || errorCode === "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS" || errorCode === "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED" || errorCode === "INVITATION_LIMIT_REACHED" || errorCode === "TEAM_MEMBER_LIMIT_REACHED" || errorCode === "TOO_MANY_ROLES") return new OrganizationLimitReachedError();
468
+ if (errorCode === "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER" || errorCode === "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER" || errorCode === "UNABLE_TO_REMOVE_LAST_TEAM" || errorCode === "CANNOT_DELETE_A_PRE_DEFINED_ROLE" || errorCode === "ROLE_IS_ASSIGNED_TO_MEMBERS" || errorCode === "YOU_CANNOT_IMPERSONATE_ADMINS" || errorCode === "YOU_CANNOT_BAN_YOURSELF" || errorCode === "YOU_CANNOT_REMOVE_YOURSELF" || errorCode === "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION") return new OrganizationMembershipError();
469
+ if (errorCode.startsWith("YOU_ARE_NOT_ALLOWED_TO_") || errorCode === "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION" || errorCode === "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM" || errorCode === "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE" || errorCode === "MISSING_AC_INSTANCE") return new OrganizationPermissionDeniedError();
470
+ return new BetterAuthUnknownError(errorCode);
244
471
  }
245
472
  /**
246
473
  * Type guard to check if an error is a Better Auth APIError.
@@ -297,7 +524,7 @@ let AuthService = class AuthService {
297
524
  };
298
525
  AuthService = __decorate([
299
526
  Transient(AUTH_SERVICE),
300
- __decorateParam(0, inject(AUTH_OPTIONS)),
527
+ __decorateParam(0, inject$1(AUTH_OPTIONS)),
301
528
  __decorateMetadata("design:paramtypes", [Object])
302
529
  ], AuthService);
303
530
  //#endregion
@@ -309,30 +536,77 @@ let AuthModule = _AuthModule = class AuthModule {
309
536
  *
310
537
  * Registers middlewares in order:
311
538
  * 1. AuthContextMiddleware - Creates and registers AuthContext in request container
312
- * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId
539
+ * 2. SessionVerificationMiddleware - Verifies session and populates AuthContext with userId + role
313
540
  */
314
541
  configureRoutes(router) {
315
542
  router.use(AuthContextMiddleware, SessionVerificationMiddleware);
316
543
  }
317
544
  /**
318
- * Configure AuthModule with async options factory
545
+ * Configure AuthModule with async options factory.
546
+ * Optionally provide `accessControl` to enable permission-based authorization.
547
+ *
548
+ * When `RateLimiterModule` is also imported, better-auth's `rateLimit`
549
+ * block is auto-wired: `customStorage` shares Stratal's backing store, and
550
+ * any `RateLimiterRegistry.forPath(...)` entries are projected into
551
+ * `customRules`. User-supplied `rateLimit.{customStorage, customRules}` keys
552
+ * take precedence on a per-key basis.
319
553
  */
320
554
  static forRootAsync(options) {
555
+ const { accessControl } = options;
556
+ const userInject = options.inject ?? [];
557
+ const userFactory = options.useFactory;
558
+ const authOptionsProvider = {
559
+ provide: AUTH_OPTIONS,
560
+ useFactory: (container, ...userDeps) => {
561
+ let raw = userFactory(...userDeps);
562
+ if (accessControl) raw = {
563
+ ...raw,
564
+ plugins: [createStratalAcPlugin(accessControl), ...raw.plugins ?? []]
565
+ };
566
+ if (container.getTsyringeContainer().isRegistered(RATE_LIMITER_TOKENS.ModuleMarker, true)) {
567
+ const store = container.resolve(RATE_LIMITER_TOKENS.Store);
568
+ const registry = container.resolve(RATE_LIMITER_TOKENS.Registry);
569
+ raw = {
570
+ ...raw,
571
+ rateLimit: {
572
+ enabled: true,
573
+ ...raw.rateLimit,
574
+ customStorage: raw.rateLimit?.customStorage ?? createBetterAuthRateLimitStorage(store),
575
+ customRules: {
576
+ ...projectCustomRules(registry),
577
+ ...raw.rateLimit?.customRules ?? {}
578
+ }
579
+ }
580
+ };
581
+ }
582
+ return raw;
583
+ },
584
+ inject: [CONTAINER_TOKEN, ...userInject]
585
+ };
321
586
  return {
322
587
  module: _AuthModule,
323
- providers: [{
324
- provide: AUTH_OPTIONS,
325
- useFactory: options.useFactory,
326
- inject: options.inject
327
- }, {
328
- provide: AUTH_SERVICE,
329
- useClass: AuthService
330
- }]
588
+ providers: [
589
+ authOptionsProvider,
590
+ {
591
+ provide: AUTH_SERVICE,
592
+ useClass: AuthService
593
+ },
594
+ ...accessControl ? [{
595
+ provide: AC_TOKENS.Options,
596
+ useValue: accessControl
597
+ }, {
598
+ provide: AC_TOKENS.AccessService,
599
+ useClass: AccessService
600
+ }] : []
601
+ ]
331
602
  };
332
603
  }
333
604
  };
334
- AuthModule = _AuthModule = __decorate([Module({ providers: [] })], AuthModule);
605
+ AuthModule = _AuthModule = __decorate([Module({
606
+ imports: [I18nModule.registerMessages(authMessages)],
607
+ providers: []
608
+ })], AuthModule);
335
609
  //#endregion
336
- export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthContextMiddleware, AuthModule, AuthService, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailCannotBeUpdatedError, EmailNotVerifiedError, FailedToCreateSessionError, FailedToCreateUserError, FailedToGetSessionError, FailedToGetUserInfoError, FailedToUpdateUserError, IdTokenNotSupportedError, InvalidCredentialsError, InvalidEmailError, InvalidPasswordError, InvalidTokenError, PasswordTooLongError, PasswordTooShortError, ProviderNotFoundError, SessionExpiredError, SessionVerificationMiddleware, SocialAccountLinkedError, TokenExpiredError, TokenRequiredError, UserAlreadyHasPasswordError, UserEmailNotFoundError, UserNotFoundError, VerificationFailedError, getErrorHandlerConfig, isAPIError, mapBetterAuthError, wrapBetterAuth };
610
+ export { AUTH_OPTIONS, AUTH_SERVICE, AccountAlreadyExistsError, AccountNotFoundError, AuthContextMiddleware, AuthModule, AuthService, AuthValidationFailedError, BetterAuthUnknownError, CannotUnlinkLastAccountError, CredentialAccountNotFoundError, EmailAlreadyVerifiedError, EmailCannotBeUpdatedError, EmailMismatchError, EmailNotVerifiedError, FailedToCreateSessionError, FailedToCreateUserError, FailedToGetSessionError, FailedToGetUserInfoError, FailedToUpdateUserError, IdTokenNotSupportedError, InvalidCallbackUrlError, InvalidCredentialsError, InvalidEmailError, InvalidOriginError, InvalidPasswordError, InvalidTokenError, OrganizationConflictError, OrganizationInvitationNotFoundError, OrganizationInvitationRecipientMismatchError, OrganizationLimitReachedError, OrganizationMemberNotFoundError, OrganizationMembershipError, OrganizationNotFoundError, OrganizationPermissionDeniedError, OrganizationRoleNotFoundError, OrganizationTeamNotFoundError, PasswordTooLongError, PasswordTooShortError, ProviderNotFoundError, SessionExpiredError, SessionVerificationMiddleware, SocialAccountLinkedError, TokenExpiredError, TokenRequiredError, UserAlreadyHasPasswordError, UserEmailNotFoundError, UserNotFoundError, VerificationFailedError, authMessages, getErrorHandlerConfig, isAPIError, mapBetterAuthError, wrapBetterAuth };
337
611
 
338
612
  //# sourceMappingURL=index.mjs.map