@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.
- package/dist/common/src/collections/default-collections.d.ts +9 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/util/permissions.d.ts +1 -0
- package/dist/{index-BZoAtuqi.js → index-Cr1D21av.js} +11 -3
- package/dist/index-Cr1D21av.js.map +1 -0
- package/dist/index.es.js +2309 -370
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2298 -355
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/logs-routes.d.ts +37 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
- package/dist/server-core/src/api/types.d.ts +6 -1
- package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
- package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
- package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
- package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
- package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
- package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
- package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
- package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
- package/dist/server-core/src/auth/index.d.ts +5 -3
- package/dist/server-core/src/auth/interfaces.d.ts +93 -3
- package/dist/server-core/src/auth/jwt.d.ts +3 -1
- package/dist/server-core/src/auth/mfa.d.ts +49 -0
- package/dist/server-core/src/auth/middleware.d.ts +7 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
- package/dist/server-core/src/auth/routes.d.ts +3 -3
- package/dist/server-core/src/env.d.ts +6 -0
- package/dist/server-core/src/index.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +3 -3
- package/dist/server-core/src/services/webhook-service.d.ts +29 -0
- package/dist/server-core/src/storage/image-transform.d.ts +48 -0
- package/dist/server-core/src/storage/index.d.ts +3 -0
- package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
- package/dist/types/src/controllers/auth.d.ts +4 -26
- package/dist/types/src/controllers/client.d.ts +25 -43
- package/dist/types/src/controllers/collection_registry.d.ts +1 -1
- package/dist/types/src/controllers/data.d.ts +4 -0
- package/dist/types/src/controllers/data_driver.d.ts +23 -0
- package/dist/types/src/controllers/registry.d.ts +5 -4
- package/dist/types/src/rebase_context.d.ts +1 -1
- package/dist/types/src/types/auth_adapter.d.ts +5 -60
- package/dist/types/src/types/backend.d.ts +2 -2
- package/dist/types/src/types/backend_hooks.d.ts +2 -17
- package/dist/types/src/types/collections.d.ts +0 -4
- package/dist/types/src/types/component_ref.d.ts +1 -1
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +1 -0
- package/dist/types/src/types/export_import.d.ts +1 -1
- package/dist/types/src/types/formex.d.ts +2 -2
- package/dist/types/src/types/properties.d.ts +9 -7
- package/dist/types/src/types/translations.d.ts +28 -12
- package/dist/types/src/types/user_management_delegate.d.ts +22 -57
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -1
- package/package.json +8 -6
- package/src/api/ast-schema-editor.ts +4 -4
- package/src/api/errors.ts +16 -7
- package/src/api/logs-routes.ts +129 -0
- package/src/api/rest/api-generator.ts +42 -2
- package/src/api/rest/query-parser.ts +37 -1
- package/src/api/types.ts +6 -1
- package/src/auth/adapter-middleware.ts +20 -4
- package/src/auth/admin-routes.ts +36 -100
- package/src/auth/api-keys/api-key-middleware.ts +126 -0
- package/src/auth/api-keys/api-key-permission-guard.ts +64 -0
- package/src/auth/api-keys/api-key-routes.ts +183 -0
- package/src/auth/api-keys/api-key-store.ts +317 -0
- package/src/auth/api-keys/api-key-types.ts +94 -0
- package/src/auth/api-keys/index.ts +37 -0
- package/src/auth/{auth-overrides.ts → auth-hooks.ts} +81 -14
- package/src/auth/builtin-auth-adapter.ts +35 -73
- package/src/auth/custom-auth-adapter.ts +1 -1
- package/src/auth/index.ts +7 -3
- package/src/auth/interfaces.ts +111 -3
- package/src/auth/jwt.ts +19 -5
- package/src/auth/mfa.ts +160 -0
- package/src/auth/middleware.ts +20 -1
- package/src/auth/rate-limiter.ts +92 -0
- package/src/auth/routes.ts +455 -24
- package/src/cron/cron-loader.ts +5 -10
- package/src/cron/cron-scheduler.ts +11 -12
- package/src/cron/cron-store.ts +8 -7
- package/src/env.ts +2 -0
- package/src/functions/function-loader.ts +6 -9
- package/src/index.ts +1 -2
- package/src/init.ts +56 -8
- package/src/serve-spa.ts +5 -4
- package/src/services/webhook-service.ts +155 -0
- package/src/storage/image-transform.ts +202 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/routes.ts +56 -3
- package/src/storage/tus-handler.ts +315 -0
- package/src/utils/dev-port.ts +14 -0
- package/src/utils/logging.ts +9 -7
- package/test/admin-routes.test.ts +62 -164
- package/test/api-generator.test.ts +0 -1
- package/test/api-key-permission-guard.test.ts +132 -0
- package/test/ast-schema-editor.test.ts +26 -0
- package/test/auth-routes.test.ts +1 -2
- package/test/backend-hooks-admin.test.ts +3 -29
- package/test/custom-auth-adapter.test.ts +2 -10
- package/test/email-templates.test.ts +169 -0
- package/test/function-loader.test.ts +124 -0
- package/test/jwt.test.ts +4 -2
- package/test/mfa.test.ts +197 -0
- package/test/middleware.test.ts +10 -5
- package/test/webhook-service.test.ts +249 -0
- package/vite.config.ts +3 -2
- package/dist/index-BZoAtuqi.js.map +0 -1
- package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
- package/dist/types/src/users/roles.d.ts +0 -22
- package/src/bootstrappers/index.ts +0 -1
- 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
|
-
*
|
|
2
|
+
* AuthHooks
|
|
3
3
|
*
|
|
4
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
-
*
|
|
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
|
|
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 `
|
|
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
|
|
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
|
|
228
|
+
export function resolveAuthHooks(hooks?: AuthHooks): ResolvedAuthOperations {
|
|
162
229
|
return {
|
|
163
|
-
hashPassword:
|
|
230
|
+
hashPassword: hooks?.hashPassword
|
|
164
231
|
?? defaultHashPassword,
|
|
165
232
|
|
|
166
|
-
verifyPassword:
|
|
233
|
+
verifyPassword: hooks?.verifyPassword
|
|
167
234
|
?? defaultVerifyPassword,
|
|
168
235
|
|
|
169
|
-
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 {
|
|
36
|
-
import {
|
|
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
|
|
66
|
-
|
|
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
|
-
|
|
83
|
+
authHooks,
|
|
87
84
|
} = config;
|
|
88
85
|
|
|
89
|
-
const resolvedOps =
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
288
|
-
createData = await
|
|
282
|
+
if (authHooks?.beforeUserCreate) {
|
|
283
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
289
284
|
}
|
|
290
285
|
const user = await repo.createUser(createData);
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
|
|
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<
|
|
317
|
-
|
|
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
|
-
}
|
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 {
|
|
11
|
-
export {
|
|
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";
|
package/src/auth/interfaces.ts
CHANGED
|
@@ -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 { }
|