@rebasepro/server-core 0.1.0 → 0.2.1
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/LICENSE +22 -6
- package/dist/common/src/util/entities.d.ts +2 -2
- package/dist/common/src/util/relations.d.ts +1 -1
- package/dist/{index-DXVBFp5V.js → index-BZoAtuqi.js} +6 -2
- package/dist/index-BZoAtuqi.js.map +1 -0
- package/dist/index.es.js +15909 -16083
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +15847 -16017
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/auth/adapter-middleware.d.ts +33 -0
- package/dist/server-core/src/auth/admin-routes.d.ts +6 -0
- package/dist/server-core/src/auth/auth-overrides.d.ts +139 -0
- package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +49 -0
- package/dist/server-core/src/auth/crypto-utils.d.ts +16 -0
- package/dist/server-core/src/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/server-core/src/auth/index.d.ts +7 -0
- package/dist/server-core/src/auth/interfaces.d.ts +2 -0
- package/dist/server-core/src/auth/middleware.d.ts +18 -0
- package/dist/server-core/src/auth/rls-scope.d.ts +31 -0
- package/dist/server-core/src/auth/routes.d.ts +7 -1
- package/dist/server-core/src/env.d.ts +131 -0
- package/dist/server-core/src/index.d.ts +2 -0
- package/dist/server-core/src/init.d.ts +62 -3
- package/dist/types/src/controllers/auth.d.ts +9 -8
- package/dist/types/src/controllers/client.d.ts +3 -0
- package/dist/types/src/types/auth_adapter.d.ts +356 -0
- package/dist/types/src/types/collections.d.ts +67 -2
- package/dist/types/src/types/database_adapter.d.ts +94 -0
- package/dist/types/src/types/entity_actions.d.ts +7 -1
- package/dist/types/src/types/entity_callbacks.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +36 -1
- package/dist/types/src/types/index.d.ts +2 -0
- package/dist/types/src/types/plugins.d.ts +1 -1
- package/dist/types/src/types/properties.d.ts +24 -5
- package/dist/types/src/types/property_config.d.ts +6 -2
- package/dist/types/src/types/relations.d.ts +1 -1
- package/dist/types/src/types/translations.d.ts +8 -0
- package/dist/types/src/users/user.d.ts +5 -0
- package/package.json +26 -26
- package/src/api/errors.ts +1 -1
- package/src/api/graphql/graphql-schema-generator.ts +7 -0
- package/src/api/openapi-generator.ts +13 -1
- package/src/api/rest/api-generator-count.test.ts +14 -12
- package/src/api/rest/query-parser.ts +2 -20
- package/src/auth/adapter-middleware.ts +83 -0
- package/src/auth/admin-routes.ts +36 -43
- package/src/auth/auth-overrides.ts +172 -0
- package/src/auth/builtin-auth-adapter.ts +384 -0
- package/src/auth/crypto-utils.ts +31 -0
- package/src/auth/custom-auth-adapter.ts +85 -0
- package/src/auth/index.ts +10 -0
- package/src/auth/interfaces.ts +2 -0
- package/src/auth/jwt.ts +3 -1
- package/src/auth/middleware.ts +2 -46
- package/src/auth/rls-scope.ts +58 -0
- package/src/auth/routes.ts +74 -32
- package/src/cron/cron-scheduler.test.ts +9 -9
- package/src/cron/cron-scheduler.ts +1 -1
- package/src/env.ts +224 -0
- package/src/index.ts +4 -0
- package/src/init.ts +355 -135
- package/src/storage/routes.ts +1 -19
- package/src/utils/logging.ts +3 -3
- package/test/admin-routes.test.ts +10 -4
- package/test/auth-routes.test.ts +2 -2
- package/test/backend-hooks-admin.test.ts +32 -12
- package/test/custom-auth-adapter.test.ts +177 -0
- package/test/env.test.ts +138 -0
- package/test/query-parser.test.ts +0 -29
- package/tsconfig.json +3 -0
- package/app/frontend/node_modules/esbuild/LICENSE.md +0 -21
- package/app/frontend/node_modules/esbuild/README.md +0 -3
- package/app/frontend/node_modules/esbuild/bin/esbuild +0 -220
- package/app/frontend/node_modules/esbuild/install.js +0 -285
- package/app/frontend/node_modules/esbuild/lib/main.d.ts +0 -705
- package/app/frontend/node_modules/esbuild/lib/main.js +0 -2239
- package/app/frontend/node_modules/esbuild/package.json +0 -46
- package/dist/index-DXVBFp5V.js.map +0 -1
- package/examples/firebase/node_modules/esbuild/LICENSE.md +0 -21
- package/examples/firebase/node_modules/esbuild/README.md +0 -3
- package/examples/firebase/node_modules/esbuild/bin/esbuild +0 -220
- package/examples/firebase/node_modules/esbuild/install.js +0 -285
- package/examples/firebase/node_modules/esbuild/lib/main.d.ts +0 -705
- package/examples/firebase/node_modules/esbuild/lib/main.js +0 -2239
- package/examples/firebase/node_modules/esbuild/package.json +0 -46
- package/examples/medmot-staging/frontend/node_modules/esbuild/LICENSE.md +0 -21
- package/examples/medmot-staging/frontend/node_modules/esbuild/README.md +0 -3
- package/examples/medmot-staging/frontend/node_modules/esbuild/bin/esbuild +0 -220
- package/examples/medmot-staging/frontend/node_modules/esbuild/install.js +0 -285
- package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.d.ts +0 -705
- package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.js +0 -2239
- package/examples/medmot-staging/frontend/node_modules/esbuild/package.json +0 -46
- package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +0 -21
- package/examples/sdk-demo/node_modules/esbuild/README.md +0 -3
- package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +0 -223
- package/examples/sdk-demo/node_modules/esbuild/install.js +0 -289
- package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +0 -716
- package/examples/sdk-demo/node_modules/esbuild/lib/main.js +0 -2242
- package/examples/sdk-demo/node_modules/esbuild/package.json +0 -49
- package/packages/client/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/client/node_modules/esbuild/README.md +0 -3
- package/packages/client/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/client/node_modules/esbuild/install.js +0 -285
- package/packages/client/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/client/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/client/node_modules/esbuild/package.json +0 -46
- package/packages/client-postgresql/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/client-postgresql/node_modules/esbuild/README.md +0 -3
- package/packages/client-postgresql/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/client-postgresql/node_modules/esbuild/install.js +0 -285
- package/packages/client-postgresql/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/client-postgresql/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/client-postgresql/node_modules/esbuild/package.json +0 -46
- package/packages/common/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/common/node_modules/esbuild/README.md +0 -3
- package/packages/common/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/common/node_modules/esbuild/install.js +0 -285
- package/packages/common/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/common/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/common/node_modules/esbuild/package.json +0 -46
- package/packages/server-mongodb/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/server-mongodb/node_modules/esbuild/README.md +0 -3
- package/packages/server-mongodb/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/server-mongodb/node_modules/esbuild/install.js +0 -285
- package/packages/server-mongodb/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/server-mongodb/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/server-mongodb/node_modules/esbuild/package.json +0 -46
- package/packages/server-postgresql/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/server-postgresql/node_modules/esbuild/README.md +0 -3
- package/packages/server-postgresql/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/server-postgresql/node_modules/esbuild/install.js +0 -285
- package/packages/server-postgresql/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/server-postgresql/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/server-postgresql/node_modules/esbuild/package.json +0 -46
- package/packages/types/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/types/node_modules/esbuild/README.md +0 -3
- package/packages/types/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/types/node_modules/esbuild/install.js +0 -285
- package/packages/types/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/types/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/types/node_modules/esbuild/package.json +0 -46
- package/packages/utils/node_modules/esbuild/LICENSE.md +0 -21
- package/packages/utils/node_modules/esbuild/README.md +0 -3
- package/packages/utils/node_modules/esbuild/bin/esbuild +0 -220
- package/packages/utils/node_modules/esbuild/install.js +0 -285
- package/packages/utils/node_modules/esbuild/lib/main.d.ts +0 -705
- package/packages/utils/node_modules/esbuild/lib/main.js +0 -2239
- package/packages/utils/node_modules/esbuild/package.json +0 -46
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthOverrides
|
|
3
|
+
*
|
|
4
|
+
* Override specific behaviors of the built-in Rebase auth system.
|
|
5
|
+
*
|
|
6
|
+
* Each method replaces one piece of the default implementation.
|
|
7
|
+
* Unset methods fall through to the built-in defaults (scrypt passwords,
|
|
8
|
+
* standard validation rules, etc.).
|
|
9
|
+
*
|
|
10
|
+
* This interface is intentionally open for extension — new overrides
|
|
11
|
+
* can be added as optional methods without breaking existing configurations.
|
|
12
|
+
*
|
|
13
|
+
* @example bcrypt password support
|
|
14
|
+
* ```ts
|
|
15
|
+
* import bcrypt from "bcrypt";
|
|
16
|
+
*
|
|
17
|
+
* const overrides: AuthOverrides = {
|
|
18
|
+
* hashPassword: (pw) => bcrypt.hash(pw, 12),
|
|
19
|
+
* verifyPassword: (pw, hash) => bcrypt.compare(pw, hash),
|
|
20
|
+
* validatePasswordStrength: (pw) => ({
|
|
21
|
+
* valid: pw.length >= 6,
|
|
22
|
+
* errors: pw.length < 6 ? ["Password must be at least 6 characters"] : []
|
|
23
|
+
* })
|
|
24
|
+
* };
|
|
25
|
+
* ```
|
|
26
|
+
*
|
|
27
|
+
* @example Override the entire login credential check
|
|
28
|
+
* ```ts
|
|
29
|
+
* const overrides: AuthOverrides = {
|
|
30
|
+
* verifyCredentials: async (email, password, repo) => {
|
|
31
|
+
* const user = await repo.getUserByEmail(email);
|
|
32
|
+
* if (!user || !user.passwordHash) return null;
|
|
33
|
+
* const valid = await myCustomVerify(password, user.passwordHash);
|
|
34
|
+
* return valid ? user : null;
|
|
35
|
+
* }
|
|
36
|
+
* };
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
hashPassword as defaultHashPassword,
|
|
42
|
+
verifyPassword as defaultVerifyPassword,
|
|
43
|
+
validatePasswordStrength as defaultValidatePasswordStrength
|
|
44
|
+
} from "./password";
|
|
45
|
+
import type { PasswordValidationResult } from "./password";
|
|
46
|
+
import type { AuthRepository, UserData, CreateUserData } from "./interfaces";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Authentication method identifier for lifecycle hooks.
|
|
50
|
+
*/
|
|
51
|
+
export type AuthMethod = "login" | "register" | "oauth" | "refresh" | "password-reset";
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Override specific parts of the built-in Rebase auth implementation.
|
|
55
|
+
*
|
|
56
|
+
* Every method is optional. The built-in defaults apply for any method
|
|
57
|
+
* that is not provided.
|
|
58
|
+
*/
|
|
59
|
+
export interface AuthOverrides {
|
|
60
|
+
// ─── Password Operations ──────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Hash a cleartext password for storage.
|
|
64
|
+
*
|
|
65
|
+
* Default: scrypt (Node.js crypto, 64-byte key, random 32-byte salt).
|
|
66
|
+
*
|
|
67
|
+
* @param password - The cleartext password.
|
|
68
|
+
* @returns The hashed password string (format is implementation-defined).
|
|
69
|
+
*/
|
|
70
|
+
hashPassword?(password: string): Promise<string>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Verify a cleartext password against a stored hash.
|
|
74
|
+
*
|
|
75
|
+
* Default: scrypt verification with timing-safe comparison.
|
|
76
|
+
*
|
|
77
|
+
* @param password - The cleartext password to check.
|
|
78
|
+
* @param storedHash - The hash string retrieved from the database.
|
|
79
|
+
* @returns `true` if the password matches the hash.
|
|
80
|
+
*/
|
|
81
|
+
verifyPassword?(password: string, storedHash: string): Promise<boolean>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Validate password strength before hashing.
|
|
85
|
+
*
|
|
86
|
+
* Default: minimum 8 characters, at least one uppercase, one lowercase, one digit.
|
|
87
|
+
*
|
|
88
|
+
* @param password - The cleartext password to validate.
|
|
89
|
+
* @returns Validation result with `valid` flag and error messages.
|
|
90
|
+
*/
|
|
91
|
+
validatePasswordStrength?(password: string): PasswordValidationResult;
|
|
92
|
+
|
|
93
|
+
// ─── Credential Resolution ────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Override the complete credential verification during email/password login.
|
|
97
|
+
*
|
|
98
|
+
* When set, this replaces the default flow:
|
|
99
|
+
* 1. Look up user by email
|
|
100
|
+
* 2. Verify password hash
|
|
101
|
+
*
|
|
102
|
+
* The auth repository is provided for database access. Return the user
|
|
103
|
+
* data if credentials are valid, or `null` to reject the login.
|
|
104
|
+
*
|
|
105
|
+
* Default: `getUserByEmail(email)` + `verifyPassword(password, user.passwordHash)`.
|
|
106
|
+
*/
|
|
107
|
+
verifyCredentials?(email: string, password: string, repo: AuthRepository): Promise<UserData | null>;
|
|
108
|
+
|
|
109
|
+
// ─── Lifecycle Hooks ──────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Called after any successful authentication event (login, register,
|
|
113
|
+
* OAuth, token refresh, password reset).
|
|
114
|
+
*
|
|
115
|
+
* Use for audit logging, syncing external state, updating
|
|
116
|
+
* last-login timestamps, etc.
|
|
117
|
+
*
|
|
118
|
+
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
119
|
+
*/
|
|
120
|
+
onAuthenticated?(user: UserData, method: AuthMethod): Promise<void>;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Called before a new user is created (registration or admin creation).
|
|
124
|
+
*
|
|
125
|
+
* Return modified data to alter what gets stored, or throw an error
|
|
126
|
+
* to reject the creation entirely.
|
|
127
|
+
*
|
|
128
|
+
* Default: passthrough (returns data unchanged).
|
|
129
|
+
*/
|
|
130
|
+
beforeUserCreate?(data: CreateUserData): Promise<CreateUserData>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Called after a new user is created.
|
|
134
|
+
*
|
|
135
|
+
* Use for provisioning external resources, sending notifications
|
|
136
|
+
* to third-party systems, etc.
|
|
137
|
+
*
|
|
138
|
+
* This is fire-and-forget — errors are logged but do not fail the request.
|
|
139
|
+
*/
|
|
140
|
+
afterUserCreate?(user: UserData): Promise<void>;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Resolved auth operations — every method is guaranteed to exist.
|
|
145
|
+
* Created by `resolveAuthOverrides()` which merges user overrides
|
|
146
|
+
* with built-in defaults.
|
|
147
|
+
*/
|
|
148
|
+
export interface ResolvedAuthOperations {
|
|
149
|
+
hashPassword(password: string): Promise<string>;
|
|
150
|
+
verifyPassword(password: string, storedHash: string): Promise<boolean>;
|
|
151
|
+
validatePasswordStrength(password: string): PasswordValidationResult;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Merge user-provided overrides with the built-in defaults to produce
|
|
156
|
+
* a complete set of resolved operations.
|
|
157
|
+
*
|
|
158
|
+
* This is the single point where defaults are applied — all consumers
|
|
159
|
+
* call this once and use the resolved operations throughout.
|
|
160
|
+
*/
|
|
161
|
+
export function resolveAuthOverrides(overrides?: AuthOverrides): ResolvedAuthOperations {
|
|
162
|
+
return {
|
|
163
|
+
hashPassword: overrides?.hashPassword
|
|
164
|
+
?? defaultHashPassword,
|
|
165
|
+
|
|
166
|
+
verifyPassword: overrides?.verifyPassword
|
|
167
|
+
?? defaultVerifyPassword,
|
|
168
|
+
|
|
169
|
+
validatePasswordStrength: overrides?.validatePasswordStrength
|
|
170
|
+
?? defaultValidatePasswordStrength,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RebaseBuiltinAuthAdapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps Rebase's existing built-in JWT auth system (routes, middleware, user/role
|
|
5
|
+
* management) into the `AuthAdapter` interface. This is the default adapter used
|
|
6
|
+
* when the user passes a plain `RebaseAuthConfig` object.
|
|
7
|
+
*
|
|
8
|
+
* This is NOT a rewrite — it delegates to the existing `createAuthRoutes()`,
|
|
9
|
+
* `createAdminRoutes()`, and `verifyAccessToken()` functions. The goal is to
|
|
10
|
+
* present the same functionality through the pluggable `AuthAdapter` contract.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
AuthAdapter,
|
|
15
|
+
AuthenticatedUser,
|
|
16
|
+
AuthAdapterCapabilities,
|
|
17
|
+
UserManagementAdapter,
|
|
18
|
+
RoleManagementAdapter,
|
|
19
|
+
AuthUserListOptions,
|
|
20
|
+
AuthUserListResult,
|
|
21
|
+
AuthUserData,
|
|
22
|
+
AuthCreateUserData,
|
|
23
|
+
AuthRoleData,
|
|
24
|
+
AuthCreateRoleData,
|
|
25
|
+
BootstrappedAuth,
|
|
26
|
+
BackendHooks,
|
|
27
|
+
} from "@rebasepro/types";
|
|
28
|
+
|
|
29
|
+
import type { Hono } from "hono";
|
|
30
|
+
import { verifyAccessToken } from "./jwt";
|
|
31
|
+
import type { AccessTokenPayload } from "./jwt";
|
|
32
|
+
import { createAuthRoutes } from "./routes";
|
|
33
|
+
import { createAdminRoutes } from "./admin-routes";
|
|
34
|
+
import type { AuthRepository, OAuthProvider } from "./interfaces";
|
|
35
|
+
import type { AuthOverrides, ResolvedAuthOperations } from "./auth-overrides";
|
|
36
|
+
import { resolveAuthOverrides } from "./auth-overrides";
|
|
37
|
+
import type { EmailService, EmailConfig } from "../email";
|
|
38
|
+
import type { HonoEnv } from "../api/types";
|
|
39
|
+
import { safeCompare } from "./crypto-utils";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configuration for the built-in Rebase auth adapter.
|
|
43
|
+
*
|
|
44
|
+
* This mirrors the existing `RebaseAuthConfig` — users pass this and
|
|
45
|
+
* server-core auto-wraps it in a `RebaseBuiltinAuthAdapter`.
|
|
46
|
+
*/
|
|
47
|
+
export interface BuiltinAuthAdapterConfig {
|
|
48
|
+
/** The bootstrapper-provided auth repository (users, roles, tokens). */
|
|
49
|
+
authRepository: AuthRepository;
|
|
50
|
+
/** Email service for password resets, verification, etc. */
|
|
51
|
+
emailService?: EmailService;
|
|
52
|
+
/** Email configuration. */
|
|
53
|
+
emailConfig?: EmailConfig;
|
|
54
|
+
/** Whether to allow new user registration. */
|
|
55
|
+
allowRegistration?: boolean;
|
|
56
|
+
/** Default role to assign to new users. */
|
|
57
|
+
defaultRole?: string;
|
|
58
|
+
/** OAuth providers to register. */
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
oauthProviders?: OAuthProvider<any>[];
|
|
61
|
+
/** Static service key for server-to-server auth. */
|
|
62
|
+
serviceKey?: string;
|
|
63
|
+
/** Backend hooks for intercepting admin data. */
|
|
64
|
+
hooks?: BackendHooks;
|
|
65
|
+
/** Auth overrides for customizing password, credentials, lifecycle, etc. */
|
|
66
|
+
overrides?: AuthOverrides;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create the built-in Rebase auth adapter.
|
|
71
|
+
*
|
|
72
|
+
* This wraps the existing auth infrastructure (JWT, OAuth, user/role management)
|
|
73
|
+
* into the `AuthAdapter` interface. It's used internally by `initializeRebaseBackend()`
|
|
74
|
+
* when the user passes a plain `RebaseAuthConfig` object.
|
|
75
|
+
*/
|
|
76
|
+
export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): AuthAdapter {
|
|
77
|
+
const {
|
|
78
|
+
authRepository,
|
|
79
|
+
emailService,
|
|
80
|
+
emailConfig,
|
|
81
|
+
allowRegistration = false,
|
|
82
|
+
defaultRole,
|
|
83
|
+
oauthProviders = [],
|
|
84
|
+
serviceKey,
|
|
85
|
+
hooks,
|
|
86
|
+
overrides,
|
|
87
|
+
} = config;
|
|
88
|
+
|
|
89
|
+
const resolvedOps = resolveAuthOverrides(overrides);
|
|
90
|
+
|
|
91
|
+
const adapter: AuthAdapter = {
|
|
92
|
+
id: "rebase-builtin",
|
|
93
|
+
|
|
94
|
+
serviceKey,
|
|
95
|
+
|
|
96
|
+
async verifyRequest(request: Request): Promise<AuthenticatedUser | null> {
|
|
97
|
+
const authHeader = request.headers.get("authorization");
|
|
98
|
+
const url = new URL(request.url, "http://localhost");
|
|
99
|
+
const queryToken = url.searchParams.get("token");
|
|
100
|
+
const hasBearer = authHeader?.startsWith("Bearer ");
|
|
101
|
+
|
|
102
|
+
if (!hasBearer && !queryToken) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const token = hasBearer ? authHeader!.substring(7) : queryToken!;
|
|
107
|
+
|
|
108
|
+
// Check service key first (constant-time)
|
|
109
|
+
if (serviceKey && safeCompare(token, serviceKey)) {
|
|
110
|
+
return {
|
|
111
|
+
uid: "service",
|
|
112
|
+
email: "service@rebase.internal",
|
|
113
|
+
roles: ["admin"],
|
|
114
|
+
isAdmin: true,
|
|
115
|
+
rawToken: token,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// JWT verification
|
|
120
|
+
const payload = verifyAccessToken(token);
|
|
121
|
+
if (!payload) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The decoded JWT may contain additional claims beyond the typed payload
|
|
126
|
+
const extendedPayload = payload as AccessTokenPayload & {
|
|
127
|
+
email?: string;
|
|
128
|
+
displayName?: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
// Resolve roles from the repository
|
|
132
|
+
let roles: string[] = payload.roles || [];
|
|
133
|
+
try {
|
|
134
|
+
const userRoles = await authRepository.getUserRoles(payload.userId);
|
|
135
|
+
roles = userRoles.map((r) => r.id);
|
|
136
|
+
} catch {
|
|
137
|
+
// Fall back to token roles if repository lookup fails
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
uid: payload.userId,
|
|
144
|
+
email: extendedPayload.email ?? "",
|
|
145
|
+
displayName: extendedPayload.displayName ?? null,
|
|
146
|
+
roles,
|
|
147
|
+
isAdmin,
|
|
148
|
+
rawToken: token,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async verifyToken(token: string): Promise<AuthenticatedUser | null> {
|
|
153
|
+
// Service key check (constant-time)
|
|
154
|
+
if (serviceKey && safeCompare(token, serviceKey)) {
|
|
155
|
+
return {
|
|
156
|
+
uid: "service",
|
|
157
|
+
email: "service@rebase.internal",
|
|
158
|
+
roles: ["admin"],
|
|
159
|
+
isAdmin: true,
|
|
160
|
+
rawToken: token,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// JWT verification
|
|
165
|
+
const payload = verifyAccessToken(token);
|
|
166
|
+
if (!payload) {
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const extendedPayload = payload as AccessTokenPayload & {
|
|
171
|
+
email?: string;
|
|
172
|
+
displayName?: string;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
let roles: string[] = payload.roles || [];
|
|
176
|
+
try {
|
|
177
|
+
const userRoles = await authRepository.getUserRoles(payload.userId);
|
|
178
|
+
roles = userRoles.map((r) => r.id);
|
|
179
|
+
} catch {
|
|
180
|
+
// Fall back to token roles if repository lookup fails
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
uid: payload.userId,
|
|
187
|
+
email: extendedPayload.email ?? "",
|
|
188
|
+
displayName: extendedPayload.displayName ?? null,
|
|
189
|
+
roles,
|
|
190
|
+
isAdmin,
|
|
191
|
+
rawToken: token,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, overrides),
|
|
196
|
+
|
|
197
|
+
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
198
|
+
|
|
199
|
+
createAuthRoutes(): Hono<HonoEnv> | undefined {
|
|
200
|
+
return createAuthRoutes({
|
|
201
|
+
authRepo: authRepository,
|
|
202
|
+
emailService,
|
|
203
|
+
emailConfig,
|
|
204
|
+
allowRegistration,
|
|
205
|
+
defaultRole,
|
|
206
|
+
oauthProviders,
|
|
207
|
+
overrides,
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
createAdminRoutes(): Hono<HonoEnv> | undefined {
|
|
212
|
+
return createAdminRoutes({
|
|
213
|
+
authRepo: authRepository,
|
|
214
|
+
emailService,
|
|
215
|
+
emailConfig,
|
|
216
|
+
serviceKey,
|
|
217
|
+
hooks,
|
|
218
|
+
overrides,
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
async getCapabilities(): Promise<AuthAdapterCapabilities> {
|
|
223
|
+
// Detect bootstrap mode: are there any users?
|
|
224
|
+
let needsSetup = false;
|
|
225
|
+
try {
|
|
226
|
+
const result = await authRepository.listUsersPaginated({ limit: 1 });
|
|
227
|
+
needsSetup = result.total === 0;
|
|
228
|
+
} catch {
|
|
229
|
+
// If the check fails, assume not in setup mode
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const enabledProviders = oauthProviders.map((p) => p.id);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
hasBuiltInAuthRoutes: true,
|
|
236
|
+
emailPasswordLogin: true,
|
|
237
|
+
registration: allowRegistration || needsSetup,
|
|
238
|
+
registrationEnabled: allowRegistration || needsSetup,
|
|
239
|
+
passwordReset: !!emailService?.isConfigured(),
|
|
240
|
+
sessionManagement: true,
|
|
241
|
+
profileUpdate: true,
|
|
242
|
+
emailVerification: !!emailService?.isConfigured(),
|
|
243
|
+
enabledProviders,
|
|
244
|
+
needsSetup,
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return adapter;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ─── Internal Helpers ────────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: ResolvedAuthOperations, overrides?: AuthOverrides): UserManagementAdapter {
|
|
255
|
+
return {
|
|
256
|
+
async listUsers(options?: AuthUserListOptions): Promise<AuthUserListResult> {
|
|
257
|
+
const result = await repo.listUsersPaginated({
|
|
258
|
+
limit: options?.limit,
|
|
259
|
+
offset: options?.offset,
|
|
260
|
+
search: options?.search,
|
|
261
|
+
orderBy: options?.orderBy,
|
|
262
|
+
orderDir: options?.orderDir,
|
|
263
|
+
roleId: options?.roleId,
|
|
264
|
+
});
|
|
265
|
+
return {
|
|
266
|
+
users: result.users.map(toAuthUserData),
|
|
267
|
+
total: result.total,
|
|
268
|
+
limit: result.limit,
|
|
269
|
+
offset: result.offset,
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async getUserById(id: string): Promise<AuthUserData | null> {
|
|
274
|
+
const user = await repo.getUserById(id);
|
|
275
|
+
return user ? toAuthUserData(user) : null;
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
async createUser(data: AuthCreateUserData): Promise<AuthUserData> {
|
|
279
|
+
const passwordHash = data.password ? await resolvedOps.hashPassword(data.password) : undefined;
|
|
280
|
+
let createData: import("./interfaces").CreateUserData = {
|
|
281
|
+
email: data.email,
|
|
282
|
+
passwordHash,
|
|
283
|
+
displayName: data.displayName,
|
|
284
|
+
photoUrl: data.photoUrl,
|
|
285
|
+
metadata: data.metadata,
|
|
286
|
+
};
|
|
287
|
+
if (overrides?.beforeUserCreate) {
|
|
288
|
+
createData = await overrides.beforeUserCreate(createData);
|
|
289
|
+
}
|
|
290
|
+
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
|
+
});
|
|
295
|
+
}
|
|
296
|
+
return toAuthUserData(user);
|
|
297
|
+
},
|
|
298
|
+
|
|
299
|
+
async updateUser(id: string, data: Partial<AuthCreateUserData>): Promise<AuthUserData | null> {
|
|
300
|
+
const updateData: Record<string, unknown> = {};
|
|
301
|
+
if (data.email !== undefined) updateData.email = data.email;
|
|
302
|
+
if (data.displayName !== undefined) updateData.displayName = data.displayName;
|
|
303
|
+
if (data.photoUrl !== undefined) updateData.photoUrl = data.photoUrl;
|
|
304
|
+
if (data.metadata !== undefined) updateData.metadata = data.metadata;
|
|
305
|
+
if (data.password) {
|
|
306
|
+
updateData.passwordHash = await resolvedOps.hashPassword(data.password);
|
|
307
|
+
}
|
|
308
|
+
const user = await repo.updateUser(id, updateData);
|
|
309
|
+
return user ? toAuthUserData(user) : null;
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
async deleteUser(id: string): Promise<void> {
|
|
313
|
+
await repo.deleteUser(id);
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
async getUserRoles(userId: string): Promise<AuthRoleData[]> {
|
|
317
|
+
const roles = await repo.getUserRoles(userId);
|
|
318
|
+
return roles.map(toAuthRoleData);
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
|
|
322
|
+
await repo.setUserRoles(userId, roleIds);
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
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
|
+
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
|
+
return {
|
|
364
|
+
id: user.id,
|
|
365
|
+
email: user.email,
|
|
366
|
+
displayName: user.displayName,
|
|
367
|
+
photoUrl: user.photoUrl,
|
|
368
|
+
emailVerified: user.emailVerified,
|
|
369
|
+
metadata: user.metadata,
|
|
370
|
+
createdAt: user.createdAt,
|
|
371
|
+
updatedAt: user.updatedAt,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic utility functions for auth.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { timingSafeEqual } from "crypto";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Constant-time string comparison to prevent timing attacks.
|
|
11
|
+
*
|
|
12
|
+
* Used for comparing service keys, tokens, and other secrets where
|
|
13
|
+
* timing side-channels could leak information about the expected value.
|
|
14
|
+
*
|
|
15
|
+
* @param a - First string to compare.
|
|
16
|
+
* @param b - Second string to compare.
|
|
17
|
+
* @returns `true` if the strings are identical, `false` otherwise.
|
|
18
|
+
*/
|
|
19
|
+
export function safeCompare(a: string, b: string): boolean {
|
|
20
|
+
const maxLen = Math.max(a.length, b.length);
|
|
21
|
+
const bufA = Buffer.alloc(maxLen);
|
|
22
|
+
const bufB = Buffer.alloc(maxLen);
|
|
23
|
+
bufA.write(a);
|
|
24
|
+
bufB.write(b);
|
|
25
|
+
try {
|
|
26
|
+
const isEqual = timingSafeEqual(bufA, bufB);
|
|
27
|
+
return isEqual && a.length === b.length;
|
|
28
|
+
} catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Auth Adapter Factory
|
|
3
|
+
*
|
|
4
|
+
* Provides a minimal-config way for users with existing auth systems
|
|
5
|
+
* to plug into Rebase. Only `verifyRequest` is required — everything
|
|
6
|
+
* else is optional.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { createCustomAuthAdapter } from "@rebasepro/server-core";
|
|
11
|
+
*
|
|
12
|
+
* const auth = createCustomAuthAdapter({
|
|
13
|
+
* verifyRequest: async (request) => {
|
|
14
|
+
* const token = request.headers.get("Authorization")?.replace("Bearer ", "");
|
|
15
|
+
* if (!token) return null;
|
|
16
|
+
* const decoded = jwt.verify(token, MY_SECRET);
|
|
17
|
+
* return {
|
|
18
|
+
* uid: decoded.sub,
|
|
19
|
+
* email: decoded.email,
|
|
20
|
+
* displayName: decoded.name,
|
|
21
|
+
* roles: decoded.roles || [],
|
|
22
|
+
* isAdmin: decoded.roles?.includes("admin") ?? false,
|
|
23
|
+
* };
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import type {
|
|
30
|
+
AuthAdapter,
|
|
31
|
+
AuthAdapterCapabilities,
|
|
32
|
+
CustomAuthAdapterOptions,
|
|
33
|
+
} from "@rebasepro/types";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a custom auth adapter from minimal options.
|
|
37
|
+
*
|
|
38
|
+
* This is the recommended entry point for users who already have their own
|
|
39
|
+
* auth system and just need to tell Rebase how to extract the user from
|
|
40
|
+
* incoming requests.
|
|
41
|
+
*
|
|
42
|
+
* @param options - Configuration options. Only `verifyRequest` is required.
|
|
43
|
+
* @returns A fully-formed `AuthAdapter` ready for `initializeRebaseBackend()`.
|
|
44
|
+
*/
|
|
45
|
+
export function createCustomAuthAdapter(options: CustomAuthAdapterOptions): AuthAdapter {
|
|
46
|
+
const defaultCapabilities: AuthAdapterCapabilities = {
|
|
47
|
+
hasBuiltInAuthRoutes: false,
|
|
48
|
+
emailPasswordLogin: false,
|
|
49
|
+
registration: false,
|
|
50
|
+
passwordReset: false,
|
|
51
|
+
sessionManagement: false,
|
|
52
|
+
profileUpdate: false,
|
|
53
|
+
emailVerification: false,
|
|
54
|
+
enabledProviders: [],
|
|
55
|
+
...options.capabilities,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const resolvedVerifyToken = options.verifyToken
|
|
59
|
+
?? (async (token: string) => {
|
|
60
|
+
// Synthesize a minimal Request so adapters that only implement
|
|
61
|
+
// verifyRequest still work for WebSocket token verification.
|
|
62
|
+
const syntheticRequest = new Request("http://localhost/_ws_auth", {
|
|
63
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
64
|
+
});
|
|
65
|
+
return options.verifyRequest(syntheticRequest);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
id: "custom",
|
|
70
|
+
|
|
71
|
+
serviceKey: options.serviceKey,
|
|
72
|
+
|
|
73
|
+
verifyRequest: options.verifyRequest,
|
|
74
|
+
|
|
75
|
+
verifyToken: resolvedVerifyToken,
|
|
76
|
+
|
|
77
|
+
userManagement: options.userManagement,
|
|
78
|
+
|
|
79
|
+
roleManagement: options.roleManagement,
|
|
80
|
+
|
|
81
|
+
getCapabilities() {
|
|
82
|
+
return defaultCapabilities;
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
package/src/auth/index.ts
CHANGED
|
@@ -7,6 +7,9 @@ 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";
|
|
12
|
+
|
|
10
13
|
// OAuth Providers
|
|
11
14
|
export { createGoogleProvider } from "./google-oauth";
|
|
12
15
|
export type { GoogleProviderConfig } from "./google-oauth";
|
|
@@ -33,3 +36,10 @@ export { createAdminRoutes } from "./admin-routes";
|
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
export { createRateLimiter, defaultAuthLimiter, strictAuthLimiter } from "./rate-limiter";
|
|
39
|
+
|
|
40
|
+
// Auth Adapters
|
|
41
|
+
export { createBuiltinAuthAdapter } from "./builtin-auth-adapter";
|
|
42
|
+
export type { BuiltinAuthAdapterConfig } from "./builtin-auth-adapter";
|
|
43
|
+
export { createCustomAuthAdapter } from "./custom-auth-adapter";
|
|
44
|
+
export { createAdapterAuthMiddleware } from "./adapter-middleware";
|
|
45
|
+
export type { AdapterAuthMiddlewareOptions } from "./adapter-middleware";
|