@rebasepro/server-core 0.2.3 → 0.2.5

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 (116) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +9 -0
  2. package/dist/common/src/collections/index.d.ts +1 -0
  3. package/dist/common/src/util/permissions.d.ts +1 -0
  4. package/dist/{index-BZoAtuqi.js → index-Cr1D21av.js} +11 -3
  5. package/dist/index-Cr1D21av.js.map +1 -0
  6. package/dist/index.es.js +2309 -370
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +2298 -355
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-core/src/api/logs-routes.d.ts +37 -0
  11. package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
  12. package/dist/server-core/src/api/types.d.ts +6 -1
  13. package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
  14. package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
  15. package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
  16. package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
  17. package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
  18. package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
  19. package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
  20. package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
  21. package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
  22. package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
  23. package/dist/server-core/src/auth/index.d.ts +5 -3
  24. package/dist/server-core/src/auth/interfaces.d.ts +93 -3
  25. package/dist/server-core/src/auth/jwt.d.ts +3 -1
  26. package/dist/server-core/src/auth/mfa.d.ts +49 -0
  27. package/dist/server-core/src/auth/middleware.d.ts +7 -0
  28. package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
  29. package/dist/server-core/src/auth/routes.d.ts +3 -3
  30. package/dist/server-core/src/env.d.ts +6 -0
  31. package/dist/server-core/src/index.d.ts +1 -0
  32. package/dist/server-core/src/init.d.ts +3 -3
  33. package/dist/server-core/src/services/webhook-service.d.ts +29 -0
  34. package/dist/server-core/src/storage/image-transform.d.ts +48 -0
  35. package/dist/server-core/src/storage/index.d.ts +3 -0
  36. package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
  37. package/dist/types/src/controllers/auth.d.ts +4 -26
  38. package/dist/types/src/controllers/client.d.ts +25 -43
  39. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  40. package/dist/types/src/controllers/data.d.ts +4 -0
  41. package/dist/types/src/controllers/data_driver.d.ts +23 -0
  42. package/dist/types/src/controllers/registry.d.ts +5 -4
  43. package/dist/types/src/rebase_context.d.ts +1 -1
  44. package/dist/types/src/types/auth_adapter.d.ts +5 -60
  45. package/dist/types/src/types/backend.d.ts +2 -2
  46. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  47. package/dist/types/src/types/collections.d.ts +0 -4
  48. package/dist/types/src/types/component_ref.d.ts +1 -1
  49. package/dist/types/src/types/cron.d.ts +1 -1
  50. package/dist/types/src/types/entity_views.d.ts +1 -0
  51. package/dist/types/src/types/export_import.d.ts +1 -1
  52. package/dist/types/src/types/formex.d.ts +2 -2
  53. package/dist/types/src/types/properties.d.ts +9 -7
  54. package/dist/types/src/types/translations.d.ts +28 -12
  55. package/dist/types/src/types/user_management_delegate.d.ts +22 -57
  56. package/dist/types/src/users/index.d.ts +0 -1
  57. package/dist/types/src/users/user.d.ts +0 -1
  58. package/package.json +8 -6
  59. package/src/api/ast-schema-editor.ts +4 -4
  60. package/src/api/errors.ts +16 -7
  61. package/src/api/logs-routes.ts +129 -0
  62. package/src/api/rest/api-generator.ts +42 -2
  63. package/src/api/rest/query-parser.ts +37 -1
  64. package/src/api/types.ts +6 -1
  65. package/src/auth/adapter-middleware.ts +20 -4
  66. package/src/auth/admin-routes.ts +36 -100
  67. package/src/auth/api-keys/api-key-middleware.ts +126 -0
  68. package/src/auth/api-keys/api-key-permission-guard.ts +64 -0
  69. package/src/auth/api-keys/api-key-routes.ts +183 -0
  70. package/src/auth/api-keys/api-key-store.ts +317 -0
  71. package/src/auth/api-keys/api-key-types.ts +94 -0
  72. package/src/auth/api-keys/index.ts +37 -0
  73. package/src/auth/{auth-overrides.ts → auth-hooks.ts} +81 -14
  74. package/src/auth/builtin-auth-adapter.ts +35 -73
  75. package/src/auth/custom-auth-adapter.ts +1 -1
  76. package/src/auth/index.ts +7 -3
  77. package/src/auth/interfaces.ts +111 -3
  78. package/src/auth/jwt.ts +19 -5
  79. package/src/auth/mfa.ts +160 -0
  80. package/src/auth/middleware.ts +20 -1
  81. package/src/auth/rate-limiter.ts +92 -0
  82. package/src/auth/routes.ts +455 -24
  83. package/src/cron/cron-loader.ts +5 -10
  84. package/src/cron/cron-scheduler.ts +11 -12
  85. package/src/cron/cron-store.ts +8 -7
  86. package/src/env.ts +2 -0
  87. package/src/functions/function-loader.ts +6 -9
  88. package/src/index.ts +1 -2
  89. package/src/init.ts +56 -8
  90. package/src/serve-spa.ts +5 -4
  91. package/src/services/webhook-service.ts +155 -0
  92. package/src/storage/image-transform.ts +202 -0
  93. package/src/storage/index.ts +3 -0
  94. package/src/storage/routes.ts +56 -3
  95. package/src/storage/tus-handler.ts +315 -0
  96. package/src/utils/dev-port.ts +14 -0
  97. package/src/utils/logging.ts +9 -7
  98. package/test/admin-routes.test.ts +62 -164
  99. package/test/api-generator.test.ts +0 -1
  100. package/test/api-key-permission-guard.test.ts +132 -0
  101. package/test/ast-schema-editor.test.ts +26 -0
  102. package/test/auth-routes.test.ts +1 -2
  103. package/test/backend-hooks-admin.test.ts +3 -29
  104. package/test/custom-auth-adapter.test.ts +2 -10
  105. package/test/email-templates.test.ts +169 -0
  106. package/test/function-loader.test.ts +124 -0
  107. package/test/jwt.test.ts +4 -2
  108. package/test/mfa.test.ts +197 -0
  109. package/test/middleware.test.ts +10 -5
  110. package/test/webhook-service.test.ts +249 -0
  111. package/vite.config.ts +3 -2
  112. package/dist/index-BZoAtuqi.js.map +0 -1
  113. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  114. package/dist/types/src/users/roles.d.ts +0 -22
  115. package/src/bootstrappers/index.ts +0 -1
  116. package/src/singleton.test.ts +0 -28
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Type definitions for Service API Keys.
3
+ *
4
+ * API keys provide machine-to-machine authentication for scripts, cron jobs,
5
+ * and third-party integrations. Each key is scoped to specific collections
6
+ * and operations via the `ApiKeyPermission` model.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ /**
12
+ * A single permission entry scoping an API key to a collection and set of operations.
13
+ *
14
+ * Use `"*"` as the collection value to grant access to all collections.
15
+ */
16
+ export interface ApiKeyPermission {
17
+ /** Collection slug, or `"*"` for all collections. */
18
+ collection: string;
19
+ /** Allowed operations on the collection. */
20
+ operations: ("read" | "write" | "delete")[];
21
+ }
22
+
23
+ /**
24
+ * Full database row for an API key.
25
+ * The `key_hash` is never exposed via the API — only stored for lookup.
26
+ */
27
+ export interface ApiKey {
28
+ id: string;
29
+ name: string;
30
+ /** First 12 characters of the plaintext key, for display only. */
31
+ key_prefix: string;
32
+ /** SHA-256 hash of the full plaintext key. */
33
+ key_hash: string;
34
+ permissions: ApiKeyPermission[];
35
+ /** Requests per 15-minute window. `null` means unlimited. */
36
+ rate_limit: number | null;
37
+ created_by: string;
38
+ created_at: string;
39
+ updated_at: string;
40
+ last_used_at: string | null;
41
+ expires_at: string | null;
42
+ revoked_at: string | null;
43
+ }
44
+
45
+ /**
46
+ * Masked version of an API key, safe for API responses.
47
+ * Omits `key_hash` and shows only the prefix.
48
+ */
49
+ export interface ApiKeyMasked {
50
+ id: string;
51
+ name: string;
52
+ key_prefix: string;
53
+ permissions: ApiKeyPermission[];
54
+ rate_limit: number | null;
55
+ created_by: string;
56
+ created_at: string;
57
+ updated_at: string;
58
+ last_used_at: string | null;
59
+ expires_at: string | null;
60
+ revoked_at: string | null;
61
+ }
62
+
63
+ /**
64
+ * Request body for creating a new API key.
65
+ */
66
+ export interface CreateApiKeyRequest {
67
+ name: string;
68
+ permissions: ApiKeyPermission[];
69
+ /** Requests per 15-minute window. Omit or `null` for unlimited. */
70
+ rate_limit?: number | null;
71
+ /** ISO-8601 expiration timestamp. Omit for no expiration. */
72
+ expires_at?: string | null;
73
+ }
74
+
75
+ /**
76
+ * Request body for updating an existing API key.
77
+ * All fields are optional — only provided fields are updated.
78
+ */
79
+ export interface UpdateApiKeyRequest {
80
+ name?: string;
81
+ permissions?: ApiKeyPermission[];
82
+ rate_limit?: number | null;
83
+ expires_at?: string | null;
84
+ }
85
+
86
+ /**
87
+ * Returned exactly once when a key is created.
88
+ * The `key` field contains the full plaintext key — it is never stored
89
+ * or returned again after creation.
90
+ */
91
+ export interface ApiKeyWithSecret extends ApiKeyMasked {
92
+ /** Full plaintext API key (e.g. `rk_live_abc123...`). */
93
+ key: string;
94
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Service API Keys module.
3
+ *
4
+ * Re-exports types, store, middleware, permission guard, and routes
5
+ * for the API key authentication system.
6
+ *
7
+ * @module
8
+ */
9
+
10
+ // Types
11
+ export type {
12
+ ApiKey,
13
+ ApiKeyMasked,
14
+ ApiKeyPermission,
15
+ ApiKeyWithSecret,
16
+ CreateApiKeyRequest,
17
+ UpdateApiKeyRequest,
18
+ } from "./api-key-types";
19
+
20
+ // Store
21
+ export { createApiKeyStore } from "./api-key-store";
22
+ export type { ApiKeyStore } from "./api-key-store";
23
+
24
+ // Middleware
25
+ export { isApiKeyToken, validateApiKey } from "./api-key-middleware";
26
+ export type { ApiKeyAuthOptions } from "./api-key-middleware";
27
+
28
+ // Permission guard
29
+ export {
30
+ httpMethodToOperation,
31
+ isOperationAllowed,
32
+ } from "./api-key-permission-guard";
33
+ export type { ApiKeyOperation } from "./api-key-permission-guard";
34
+
35
+ // Routes
36
+ export { createApiKeyRoutes } from "./api-key-routes";
37
+ export type { ApiKeyRouteOptions } from "./api-key-routes";
@@ -1,20 +1,20 @@
1
1
  /**
2
- * AuthOverrides
2
+ * AuthHooks
3
3
  *
4
- * Override specific behaviors of the built-in Rebase auth system.
4
+ * Customize specific behaviors of the built-in Rebase auth system.
5
5
  *
6
6
  * Each method replaces one piece of the default implementation.
7
7
  * Unset methods fall through to the built-in defaults (scrypt passwords,
8
8
  * standard validation rules, etc.).
9
9
  *
10
- * This interface is intentionally open for extension — new overrides
10
+ * This interface is intentionally open for extension — new hooks
11
11
  * can be added as optional methods without breaking existing configurations.
12
12
  *
13
13
  * @example bcrypt password support
14
14
  * ```ts
15
15
  * import bcrypt from "bcrypt";
16
16
  *
17
- * const overrides: AuthOverrides = {
17
+ * const hooks: AuthHooks = {
18
18
  * hashPassword: (pw) => bcrypt.hash(pw, 12),
19
19
  * verifyPassword: (pw, hash) => bcrypt.compare(pw, hash),
20
20
  * validatePasswordStrength: (pw) => ({
@@ -26,7 +26,7 @@
26
26
  *
27
27
  * @example Override the entire login credential check
28
28
  * ```ts
29
- * const overrides: AuthOverrides = {
29
+ * const hooks: AuthHooks = {
30
30
  * verifyCredentials: async (email, password, repo) => {
31
31
  * const user = await repo.getUserByEmail(email);
32
32
  * if (!user || !user.passwordHash) return null;
@@ -48,15 +48,15 @@ import type { AuthRepository, UserData, CreateUserData } from "./interfaces";
48
48
  /**
49
49
  * Authentication method identifier for lifecycle hooks.
50
50
  */
51
- export type AuthMethod = "login" | "register" | "oauth" | "refresh" | "password-reset";
51
+ export type AuthMethod = "login" | "register" | "oauth" | "refresh" | "password-reset" | "anonymous" | "magic-link" | "mfa";
52
52
 
53
53
  /**
54
- * Override specific parts of the built-in Rebase auth implementation.
54
+ * Hook specific parts of the built-in Rebase auth implementation.
55
55
  *
56
56
  * Every method is optional. The built-in defaults apply for any method
57
57
  * that is not provided.
58
58
  */
59
- export interface AuthOverrides {
59
+ export interface AuthHooks {
60
60
  // ─── Password Operations ──────────────────────────────────────────────
61
61
 
62
62
  /**
@@ -138,11 +138,78 @@ export interface AuthOverrides {
138
138
  * This is fire-and-forget — errors are logged but do not fail the request.
139
139
  */
140
140
  afterUserCreate?(user: UserData): Promise<void>;
141
+
142
+ // ─── Extended Lifecycle Hooks ─────────────────────────────────────────
143
+
144
+ /**
145
+ * Pre-login validation. Called before credential verification.
146
+ *
147
+ * Throw an error to reject the login attempt (e.g. for account lockout,
148
+ * IP-based restrictions, etc.).
149
+ */
150
+ beforeLogin?(email: string, method: AuthMethod): Promise<void>;
151
+
152
+ /**
153
+ * Post-logout cleanup.
154
+ *
155
+ * Called after a user's session has been invalidated.
156
+ * Use for audit logging, cleanup of temporary resources, etc.
157
+ *
158
+ * This is fire-and-forget — errors are logged but do not fail the request.
159
+ */
160
+ afterLogout?(userId: string): Promise<void>;
161
+
162
+ /**
163
+ * Called after successful MFA verification.
164
+ *
165
+ * Use for audit logging, notifying external systems, etc.
166
+ *
167
+ * This is fire-and-forget — errors are logged but do not fail the request.
168
+ */
169
+ onMfaVerified?(userId: string, factorId: string): Promise<void>;
170
+
171
+ /**
172
+ * Customize JWT access token claims before signing.
173
+ *
174
+ * Return the modified claims object. The returned claims are merged
175
+ * into the JWT payload alongside standard claims (userId, roles).
176
+ *
177
+ * @param claims - The default claims that would be included.
178
+ * @param user - The authenticated user data.
179
+ * @returns Modified claims to include in the JWT.
180
+ */
181
+ customizeAccessToken?(claims: Record<string, unknown>, user: UserData): Promise<Record<string, unknown>>;
182
+
183
+ /**
184
+ * Called after a successful password reset.
185
+ *
186
+ * Use for audit logging, sending confirmation notifications, etc.
187
+ *
188
+ * This is fire-and-forget — errors are logged but do not fail the request.
189
+ */
190
+ onPasswordReset?(userId: string): Promise<void>;
191
+
192
+ /**
193
+ * Called before a user is deleted.
194
+ *
195
+ * Throw an error to prevent deletion (e.g. for users with active
196
+ * subscriptions, pending transactions, etc.).
197
+ */
198
+ beforeUserDelete?(userId: string): Promise<void>;
199
+
200
+ /**
201
+ * Called after a user is deleted.
202
+ *
203
+ * Use for cleaning up external resources, audit logging, etc.
204
+ *
205
+ * This is fire-and-forget — errors are logged but do not fail the request.
206
+ */
207
+ afterUserDelete?(userId: string): Promise<void>;
141
208
  }
142
209
 
143
210
  /**
144
211
  * Resolved auth operations — every method is guaranteed to exist.
145
- * Created by `resolveAuthOverrides()` which merges user overrides
212
+ * Created by `resolveAuthHooks()` which merges user hooks
146
213
  * with built-in defaults.
147
214
  */
148
215
  export interface ResolvedAuthOperations {
@@ -152,21 +219,21 @@ export interface ResolvedAuthOperations {
152
219
  }
153
220
 
154
221
  /**
155
- * Merge user-provided overrides with the built-in defaults to produce
222
+ * Merge user-provided hooks with the built-in defaults to produce
156
223
  * a complete set of resolved operations.
157
224
  *
158
225
  * This is the single point where defaults are applied — all consumers
159
226
  * call this once and use the resolved operations throughout.
160
227
  */
161
- export function resolveAuthOverrides(overrides?: AuthOverrides): ResolvedAuthOperations {
228
+ export function resolveAuthHooks(hooks?: AuthHooks): ResolvedAuthOperations {
162
229
  return {
163
- hashPassword: overrides?.hashPassword
230
+ hashPassword: hooks?.hashPassword
164
231
  ?? defaultHashPassword,
165
232
 
166
- verifyPassword: overrides?.verifyPassword
233
+ verifyPassword: hooks?.verifyPassword
167
234
  ?? defaultVerifyPassword,
168
235
 
169
- validatePasswordStrength: overrides?.validatePasswordStrength
236
+ validatePasswordStrength: hooks?.validatePasswordStrength
170
237
  ?? defaultValidatePasswordStrength,
171
238
  };
172
239
  }
@@ -15,13 +15,10 @@ import type {
15
15
  AuthenticatedUser,
16
16
  AuthAdapterCapabilities,
17
17
  UserManagementAdapter,
18
- RoleManagementAdapter,
19
18
  AuthUserListOptions,
20
19
  AuthUserListResult,
21
20
  AuthUserData,
22
21
  AuthCreateUserData,
23
- AuthRoleData,
24
- AuthCreateRoleData,
25
22
  BootstrappedAuth,
26
23
  BackendHooks,
27
24
  } from "@rebasepro/types";
@@ -32,8 +29,8 @@ import type { AccessTokenPayload } from "./jwt";
32
29
  import { createAuthRoutes } from "./routes";
33
30
  import { createAdminRoutes } from "./admin-routes";
34
31
  import type { AuthRepository, OAuthProvider } from "./interfaces";
35
- import type { AuthOverrides, ResolvedAuthOperations } from "./auth-overrides";
36
- import { resolveAuthOverrides } from "./auth-overrides";
32
+ import type { AuthHooks, ResolvedAuthOperations } from "./auth-hooks";
33
+ import { resolveAuthHooks } from "./auth-hooks";
37
34
  import type { EmailService, EmailConfig } from "../email";
38
35
  import type { HonoEnv } from "../api/types";
39
36
  import { safeCompare } from "./crypto-utils";
@@ -62,8 +59,8 @@ export interface BuiltinAuthAdapterConfig {
62
59
  serviceKey?: string;
63
60
  /** Backend hooks for intercepting admin data. */
64
61
  hooks?: BackendHooks;
65
- /** Auth overrides for customizing password, credentials, lifecycle, etc. */
66
- overrides?: AuthOverrides;
62
+ /** Auth hooks for customizing password, credentials, lifecycle, etc. */
63
+ authHooks?: AuthHooks;
67
64
  }
68
65
 
69
66
  /**
@@ -83,10 +80,10 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
83
80
  oauthProviders = [],
84
81
  serviceKey,
85
82
  hooks,
86
- overrides,
83
+ authHooks,
87
84
  } = config;
88
85
 
89
- const resolvedOps = resolveAuthOverrides(overrides);
86
+ const resolvedOps = resolveAuthHooks(authHooks);
90
87
 
91
88
  const adapter: AuthAdapter = {
92
89
  id: "rebase-builtin",
@@ -131,8 +128,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
131
128
  // Resolve roles from the repository
132
129
  let roles: string[] = payload.roles || [];
133
130
  try {
134
- const userRoles = await authRepository.getUserRoles(payload.userId);
135
- roles = userRoles.map((r) => r.id);
131
+ roles = await authRepository.getUserRoleIds(payload.userId);
136
132
  } catch {
137
133
  // Fall back to token roles if repository lookup fails
138
134
  }
@@ -174,8 +170,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
174
170
 
175
171
  let roles: string[] = payload.roles || [];
176
172
  try {
177
- const userRoles = await authRepository.getUserRoles(payload.userId);
178
- roles = userRoles.map((r) => r.id);
173
+ roles = await authRepository.getUserRoleIds(payload.userId);
179
174
  } catch {
180
175
  // Fall back to token roles if repository lookup fails
181
176
  }
@@ -192,9 +187,9 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
192
187
  };
193
188
  },
194
189
 
195
- userManagement: createUserManagementFromRepo(authRepository, resolvedOps, overrides),
190
+ userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
191
+
196
192
 
197
- roleManagement: createRoleManagementFromRepo(authRepository),
198
193
 
199
194
  createAuthRoutes(): Hono<HonoEnv> | undefined {
200
195
  return createAuthRoutes({
@@ -204,7 +199,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
204
199
  allowRegistration,
205
200
  defaultRole,
206
201
  oauthProviders,
207
- overrides,
202
+ authHooks,
208
203
  });
209
204
  },
210
205
 
@@ -215,7 +210,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
215
210
  emailConfig,
216
211
  serviceKey,
217
212
  hooks,
218
- overrides,
213
+ authHooks,
219
214
  });
220
215
  },
221
216
 
@@ -251,7 +246,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
251
246
 
252
247
  // ─── Internal Helpers ────────────────────────────────────────────────────────
253
248
 
254
- function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: ResolvedAuthOperations, overrides?: AuthOverrides): UserManagementAdapter {
249
+ function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: ResolvedAuthOperations, authHooks?: AuthHooks): UserManagementAdapter {
255
250
  return {
256
251
  async listUsers(options?: AuthUserListOptions): Promise<AuthUserListResult> {
257
252
  const result = await repo.listUsersPaginated({
@@ -284,14 +279,16 @@ function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: Resolve
284
279
  photoUrl: data.photoUrl,
285
280
  metadata: data.metadata,
286
281
  };
287
- if (overrides?.beforeUserCreate) {
288
- createData = await overrides.beforeUserCreate(createData);
282
+ if (authHooks?.beforeUserCreate) {
283
+ createData = await authHooks.beforeUserCreate(createData);
289
284
  }
290
285
  const user = await repo.createUser(createData);
291
- if (overrides?.afterUserCreate) {
292
- overrides.afterUserCreate(user).catch(err => {
293
- console.error("[AuthOverrides] afterUserCreate error:", err instanceof Error ? err.message : err);
294
- });
286
+ if (authHooks?.afterUserCreate) {
287
+ try {
288
+ await authHooks.afterUserCreate(user);
289
+ } catch (err) {
290
+ console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
291
+ }
295
292
  }
296
293
  return toAuthUserData(user);
297
294
  },
@@ -310,12 +307,23 @@ function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: Resolve
310
307
  },
311
308
 
312
309
  async deleteUser(id: string): Promise<void> {
310
+ // Call beforeUserDelete hook (throw to prevent deletion)
311
+ if (authHooks?.beforeUserDelete) {
312
+ await authHooks.beforeUserDelete(id);
313
+ }
314
+
313
315
  await repo.deleteUser(id);
316
+
317
+ // Fire afterUserDelete hook (fire-and-forget)
318
+ if (authHooks?.afterUserDelete) {
319
+ authHooks.afterUserDelete(id).catch(err => {
320
+ console.error("[AuthHooks] afterUserDelete error:", err instanceof Error ? err.message : err);
321
+ });
322
+ }
314
323
  },
315
324
 
316
- async getUserRoles(userId: string): Promise<AuthRoleData[]> {
317
- const roles = await repo.getUserRoles(userId);
318
- return roles.map(toAuthRoleData);
325
+ async getUserRoles(userId: string): Promise<string[]> {
326
+ return repo.getUserRoleIds(userId);
319
327
  },
320
328
 
321
329
  async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
@@ -324,41 +332,6 @@ function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: Resolve
324
332
  };
325
333
  }
326
334
 
327
- function createRoleManagementFromRepo(repo: AuthRepository): RoleManagementAdapter {
328
- return {
329
- async listRoles(): Promise<AuthRoleData[]> {
330
- const roles = await repo.listRoles();
331
- return roles.map(toAuthRoleData);
332
- },
333
-
334
- async getRoleById(id: string): Promise<AuthRoleData | null> {
335
- const role = await repo.getRoleById(id);
336
- return role ? toAuthRoleData(role) : null;
337
- },
338
-
339
- async createRole(data: AuthCreateRoleData): Promise<AuthRoleData> {
340
- const role = await repo.createRole({
341
- id: data.id,
342
- name: data.name,
343
- isAdmin: data.isAdmin,
344
- defaultPermissions: data.defaultPermissions,
345
- collectionPermissions: data.collectionPermissions,
346
- config: data.config,
347
- });
348
- return toAuthRoleData(role);
349
- },
350
-
351
- async updateRole(id: string, data: Partial<AuthRoleData>): Promise<AuthRoleData | null> {
352
- const role = await repo.updateRole(id, data);
353
- return role ? toAuthRoleData(role) : null;
354
- },
355
-
356
- async deleteRole(id: string): Promise<void> {
357
- await repo.deleteRole(id);
358
- },
359
- };
360
- }
361
-
362
335
  function toAuthUserData(user: { id: string; email: string; displayName?: string | null; photoUrl?: string | null; emailVerified?: boolean; metadata?: Record<string, unknown>; createdAt?: Date; updatedAt?: Date }): AuthUserData {
363
336
  return {
364
337
  id: user.id,
@@ -371,14 +344,3 @@ function toAuthUserData(user: { id: string; email: string; displayName?: string
371
344
  updatedAt: user.updatedAt,
372
345
  };
373
346
  }
374
-
375
- function toAuthRoleData(role: { id: string; name: string; isAdmin: boolean; defaultPermissions?: unknown; collectionPermissions?: unknown; config?: unknown }): AuthRoleData {
376
- return {
377
- id: role.id,
378
- name: role.name,
379
- isAdmin: role.isAdmin,
380
- defaultPermissions: role.defaultPermissions as AuthRoleData["defaultPermissions"],
381
- collectionPermissions: role.collectionPermissions as AuthRoleData["collectionPermissions"],
382
- config: role.config as AuthRoleData["config"],
383
- };
384
- }
@@ -76,7 +76,7 @@ export function createCustomAuthAdapter(options: CustomAuthAdapterOptions): Auth
76
76
 
77
77
  userManagement: options.userManagement,
78
78
 
79
- roleManagement: options.roleManagement,
79
+
80
80
 
81
81
  getCapabilities() {
82
82
  return defaultCapabilities;
package/src/auth/index.ts CHANGED
@@ -7,8 +7,8 @@ export type { JwtConfig, AccessTokenPayload } from "./jwt";
7
7
  export { hashPassword, verifyPassword, validatePasswordStrength } from "./password";
8
8
  export type { PasswordValidationResult } from "./password";
9
9
 
10
- export type { AuthOverrides, AuthMethod, ResolvedAuthOperations } from "./auth-overrides";
11
- export { resolveAuthOverrides } from "./auth-overrides";
10
+ export type { AuthHooks, AuthMethod, ResolvedAuthOperations } from "./auth-hooks";
11
+ export { resolveAuthHooks } from "./auth-hooks";
12
12
 
13
13
  // OAuth Providers
14
14
  export { createGoogleProvider } from "./google-oauth";
@@ -35,7 +35,11 @@ export type { AuthModuleConfig } from "./routes";
35
35
  export { createAdminRoutes } from "./admin-routes";
36
36
 
37
37
 
38
- export { createRateLimiter, defaultAuthLimiter, strictAuthLimiter } from "./rate-limiter";
38
+ export { createRateLimiter, defaultAuthLimiter, strictAuthLimiter, createApiKeyRateLimiter, apiKeyKeyGenerator } from "./rate-limiter";
39
+
40
+ // API Keys
41
+ export { createApiKeyStore, createApiKeyRoutes, isApiKeyToken, validateApiKey, httpMethodToOperation, isOperationAllowed } from "./api-keys";
42
+ export type { ApiKey, ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest, ApiKeyStore, ApiKeyOperation } from "./api-keys";
39
43
 
40
44
  // Auth Adapters
41
45
  export { createBuiltinAuthAdapter } from "./builtin-auth-adapter";
@@ -20,6 +20,7 @@ export interface UserData {
20
20
  emailVerified: boolean;
21
21
  emailVerificationToken?: string | null;
22
22
  emailVerificationSentAt?: Date | null;
23
+ isAnonymous?: boolean;
23
24
  metadata?: Record<string, unknown>;
24
25
  createdAt: Date;
25
26
  updatedAt: Date;
@@ -34,6 +35,7 @@ export interface CreateUserData {
34
35
  displayName?: string;
35
36
  photoUrl?: string;
36
37
  emailVerified?: boolean;
38
+ isAnonymous?: boolean;
37
39
  metadata?: Record<string, unknown>;
38
40
  }
39
41
 
@@ -93,7 +95,6 @@ export interface RoleData {
93
95
  edit?: boolean;
94
96
  delete?: boolean;
95
97
  }> | null;
96
- config: Record<string, unknown> | null;
97
98
  }
98
99
 
99
100
  /**
@@ -105,7 +106,6 @@ export interface CreateRoleData {
105
106
  isAdmin?: boolean;
106
107
  defaultPermissions?: RoleData["defaultPermissions"];
107
108
  collectionPermissions?: RoleData["collectionPermissions"];
108
- config?: RoleData["config"];
109
109
  }
110
110
 
111
111
  /**
@@ -359,7 +359,115 @@ export interface TokenRepository {
359
359
  deleteExpiredTokens(): Promise<void>;
360
360
  }
361
361
 
362
+ // =============================================================================
363
+ // MFA INTERFACES
364
+ // =============================================================================
365
+
366
+ /**
367
+ * MFA factor data structure
368
+ */
369
+ export interface MfaFactor {
370
+ id: string;
371
+ userId: string;
372
+ factorType: "totp";
373
+ friendlyName?: string;
374
+ verified: boolean;
375
+ createdAt: Date;
376
+ updatedAt: Date;
377
+ }
378
+
379
+ /**
380
+ * MFA challenge information
381
+ */
382
+ export interface MfaChallengeInfo {
383
+ id: string;
384
+ factorId: string;
385
+ createdAt: Date;
386
+ verifiedAt?: Date;
387
+ ipAddress?: string;
388
+ }
389
+
390
+ /**
391
+ * Recovery code data structure
392
+ */
393
+ export interface RecoveryCode {
394
+ id: string;
395
+ userId: string;
396
+ usedAt?: Date;
397
+ }
398
+
399
+ /**
400
+ * Abstract MFA repository interface.
401
+ * Handles all MFA-related database operations.
402
+ */
403
+ export interface MfaRepository {
404
+ /**
405
+ * Create a new MFA factor for a user
406
+ */
407
+ createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
408
+
409
+ /**
410
+ * Get all MFA factors for a user
411
+ */
412
+ getMfaFactors(userId: string): Promise<MfaFactor[]>;
413
+
414
+ /**
415
+ * Get a specific MFA factor by ID
416
+ */
417
+ getMfaFactorById(factorId: string): Promise<(MfaFactor & { secretEncrypted: string }) | null>;
418
+
419
+ /**
420
+ * Mark an MFA factor as verified
421
+ */
422
+ verifyMfaFactor(factorId: string): Promise<void>;
423
+
424
+ /**
425
+ * Delete an MFA factor
426
+ */
427
+ deleteMfaFactor(factorId: string, userId: string): Promise<void>;
428
+
429
+ /**
430
+ * Create an MFA challenge
431
+ */
432
+ createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
433
+
434
+ /**
435
+ * Get an MFA challenge by ID
436
+ */
437
+ getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
438
+
439
+ /**
440
+ * Mark an MFA challenge as verified
441
+ */
442
+ verifyMfaChallenge(challengeId: string): Promise<void>;
443
+
444
+ /**
445
+ * Create recovery codes for a user
446
+ */
447
+ createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void>;
448
+
449
+ /**
450
+ * Use a recovery code (mark as used)
451
+ */
452
+ useRecoveryCode(userId: string, codeHash: string): Promise<boolean>;
453
+
454
+ /**
455
+ * Get unused recovery code count for a user
456
+ */
457
+ getUnusedRecoveryCodeCount(userId: string): Promise<number>;
458
+
459
+ /**
460
+ * Delete all recovery codes for a user
461
+ */
462
+ deleteAllRecoveryCodes(userId: string): Promise<void>;
463
+
464
+ /**
465
+ * Check if a user has any verified MFA factors
466
+ */
467
+ hasVerifiedMfaFactors(userId: string): Promise<boolean>;
468
+ }
469
+
362
470
  /**
363
471
  * Combined auth repository interface for convenience
364
472
  */
365
- export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository { }
473
+ export interface AuthRepository extends UserRepository, RoleRepository, TokenRepository, MfaRepository { }