@rebasepro/server-core 0.0.1-canary.f81da60 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/frontend/node_modules/esbuild/LICENSE.md +21 -0
- package/app/frontend/node_modules/esbuild/README.md +3 -0
- package/app/frontend/node_modules/esbuild/bin/esbuild +220 -0
- package/app/frontend/node_modules/esbuild/install.js +285 -0
- package/app/frontend/node_modules/esbuild/lib/main.d.ts +705 -0
- package/app/frontend/node_modules/esbuild/lib/main.js +2239 -0
- package/app/frontend/node_modules/esbuild/package.json +46 -0
- package/dist/index.es.js +140 -28
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +140 -28
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/auth/google-oauth.d.ts +33 -3
- package/dist/server-core/src/auth/index.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +1 -0
- package/dist/types/src/controllers/auth.d.ts +8 -2
- package/dist/types/src/controllers/client.d.ts +13 -0
- package/dist/types/src/controllers/navigation.d.ts +18 -6
- package/dist/types/src/controllers/registry.d.ts +9 -1
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
- package/dist/types/src/rebase_context.d.ts +17 -0
- package/dist/types/src/types/collections.d.ts +20 -1
- package/dist/types/src/types/component_ref.d.ts +47 -0
- package/dist/types/src/types/entity_views.d.ts +2 -1
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/properties.d.ts +15 -3
- package/dist/types/src/types/translations.d.ts +2 -0
- package/examples/firebase/node_modules/esbuild/LICENSE.md +21 -0
- package/examples/firebase/node_modules/esbuild/README.md +3 -0
- package/examples/firebase/node_modules/esbuild/bin/esbuild +220 -0
- package/examples/firebase/node_modules/esbuild/install.js +285 -0
- package/examples/firebase/node_modules/esbuild/lib/main.d.ts +705 -0
- package/examples/firebase/node_modules/esbuild/lib/main.js +2239 -0
- package/examples/firebase/node_modules/esbuild/package.json +46 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/LICENSE.md +21 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/README.md +3 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/bin/esbuild +220 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/install.js +285 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.d.ts +705 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.js +2239 -0
- package/examples/medmot-staging/frontend/node_modules/esbuild/package.json +46 -0
- package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +21 -0
- package/examples/sdk-demo/node_modules/esbuild/README.md +3 -0
- package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +223 -0
- package/examples/sdk-demo/node_modules/esbuild/install.js +289 -0
- package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +716 -0
- package/examples/sdk-demo/node_modules/esbuild/lib/main.js +2242 -0
- package/examples/sdk-demo/node_modules/esbuild/package.json +49 -0
- package/package.json +7 -7
- package/packages/client/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/client/node_modules/esbuild/README.md +3 -0
- package/packages/client/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/client/node_modules/esbuild/install.js +285 -0
- package/packages/client/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/client/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/client/node_modules/esbuild/package.json +46 -0
- package/packages/client-postgresql/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/client-postgresql/node_modules/esbuild/README.md +3 -0
- package/packages/client-postgresql/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/client-postgresql/node_modules/esbuild/install.js +285 -0
- package/packages/client-postgresql/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/client-postgresql/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/client-postgresql/node_modules/esbuild/package.json +46 -0
- package/packages/common/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/common/node_modules/esbuild/README.md +3 -0
- package/packages/common/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/common/node_modules/esbuild/install.js +285 -0
- package/packages/common/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/common/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/common/node_modules/esbuild/package.json +46 -0
- package/packages/server-mongodb/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/server-mongodb/node_modules/esbuild/README.md +3 -0
- package/packages/server-mongodb/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/server-mongodb/node_modules/esbuild/install.js +285 -0
- package/packages/server-mongodb/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/server-mongodb/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/server-mongodb/node_modules/esbuild/package.json +46 -0
- package/packages/server-postgresql/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/server-postgresql/node_modules/esbuild/README.md +3 -0
- package/packages/server-postgresql/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/server-postgresql/node_modules/esbuild/install.js +285 -0
- package/packages/server-postgresql/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/server-postgresql/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/server-postgresql/node_modules/esbuild/package.json +46 -0
- package/packages/types/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/types/node_modules/esbuild/README.md +3 -0
- package/packages/types/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/types/node_modules/esbuild/install.js +285 -0
- package/packages/types/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/types/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/types/node_modules/esbuild/package.json +46 -0
- package/packages/utils/node_modules/esbuild/LICENSE.md +21 -0
- package/packages/utils/node_modules/esbuild/README.md +3 -0
- package/packages/utils/node_modules/esbuild/bin/esbuild +220 -0
- package/packages/utils/node_modules/esbuild/install.js +285 -0
- package/packages/utils/node_modules/esbuild/lib/main.d.ts +705 -0
- package/packages/utils/node_modules/esbuild/lib/main.js +2239 -0
- package/packages/utils/node_modules/esbuild/package.json +46 -0
- package/src/api/errors.ts +3 -2
- package/src/api/server.ts +5 -2
- package/src/auth/google-oauth.ts +148 -17
- package/src/auth/index.ts +1 -0
- package/src/auth/routes.ts +25 -5
- package/src/collections/loader.ts +3 -3
- package/src/init.ts +14 -2
- package/history_diff.log +0 -385
- package/scratch.ts +0 -9
- package/test-ast.ts +0 -28
- package/test_output.txt +0 -1133
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "esbuild",
|
|
3
|
+
"version": "0.21.5",
|
|
4
|
+
"description": "An extremely fast JavaScript and CSS bundler and minifier.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/evanw/esbuild.git"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node install.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "lib/main.js",
|
|
13
|
+
"types": "lib/main.d.ts",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=12"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"esbuild": "bin/esbuild"
|
|
19
|
+
},
|
|
20
|
+
"optionalDependencies": {
|
|
21
|
+
"@esbuild/aix-ppc64": "0.21.5",
|
|
22
|
+
"@esbuild/android-arm": "0.21.5",
|
|
23
|
+
"@esbuild/android-arm64": "0.21.5",
|
|
24
|
+
"@esbuild/android-x64": "0.21.5",
|
|
25
|
+
"@esbuild/darwin-arm64": "0.21.5",
|
|
26
|
+
"@esbuild/darwin-x64": "0.21.5",
|
|
27
|
+
"@esbuild/freebsd-arm64": "0.21.5",
|
|
28
|
+
"@esbuild/freebsd-x64": "0.21.5",
|
|
29
|
+
"@esbuild/linux-arm": "0.21.5",
|
|
30
|
+
"@esbuild/linux-arm64": "0.21.5",
|
|
31
|
+
"@esbuild/linux-ia32": "0.21.5",
|
|
32
|
+
"@esbuild/linux-loong64": "0.21.5",
|
|
33
|
+
"@esbuild/linux-mips64el": "0.21.5",
|
|
34
|
+
"@esbuild/linux-ppc64": "0.21.5",
|
|
35
|
+
"@esbuild/linux-riscv64": "0.21.5",
|
|
36
|
+
"@esbuild/linux-s390x": "0.21.5",
|
|
37
|
+
"@esbuild/linux-x64": "0.21.5",
|
|
38
|
+
"@esbuild/netbsd-x64": "0.21.5",
|
|
39
|
+
"@esbuild/openbsd-x64": "0.21.5",
|
|
40
|
+
"@esbuild/sunos-x64": "0.21.5",
|
|
41
|
+
"@esbuild/win32-arm64": "0.21.5",
|
|
42
|
+
"@esbuild/win32-ia32": "0.21.5",
|
|
43
|
+
"@esbuild/win32-x64": "0.21.5"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT"
|
|
46
|
+
}
|
package/src/api/errors.ts
CHANGED
|
@@ -120,10 +120,11 @@ export const errorHandler: ErrorHandler = (err, c) => {
|
|
|
120
120
|
`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}`
|
|
121
121
|
);
|
|
122
122
|
|
|
123
|
-
// Suppress the huge stack trace for known
|
|
123
|
+
// Suppress the huge stack trace for known DB errors (it's noisy and leaks SQL)
|
|
124
124
|
const causePg = (error.cause && typeof error.cause === "object") ? (error.cause as PgLikeError) : undefined;
|
|
125
125
|
const pgErrorCode = causePg?.code || error.code;
|
|
126
|
-
|
|
126
|
+
const suppressStack = pgErrorCode === "42703" || pgErrorCode === "42P01" || (statusCode < 500 && code === "BAD_REQUEST");
|
|
127
|
+
if (!suppressStack) {
|
|
127
128
|
console.error(error.stack || error);
|
|
128
129
|
}
|
|
129
130
|
|
package/src/api/server.ts
CHANGED
|
@@ -69,8 +69,11 @@ export class RebaseApiServer {
|
|
|
69
69
|
* Setup Hono middleware
|
|
70
70
|
*/
|
|
71
71
|
private setupMiddleware(): void {
|
|
72
|
-
// Security headers
|
|
73
|
-
|
|
72
|
+
// Security headers — use same-origin-allow-popups for COOP so that
|
|
73
|
+
// OAuth popup flows (Google, etc.) can postMessage back to the opener.
|
|
74
|
+
this.router.use("/*", secureHeaders({
|
|
75
|
+
crossOriginOpenerPolicy: "same-origin-allow-popups"
|
|
76
|
+
}));
|
|
74
77
|
|
|
75
78
|
// CORS — only applied if explicitly configured via `cors` option.
|
|
76
79
|
// If omitted, the user is expected to configure CORS on their own
|
package/src/auth/google-oauth.ts
CHANGED
|
@@ -10,26 +10,69 @@ export interface GoogleUserInfo {
|
|
|
10
10
|
emailVerified: boolean;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
export interface GoogleProviderConfig {
|
|
14
|
+
clientId: string;
|
|
15
|
+
/**
|
|
16
|
+
* The OAuth 2.0 client secret from Google Cloud Console.
|
|
17
|
+
*
|
|
18
|
+
* Required for the **authorization code flow** (Path 3), where the
|
|
19
|
+
* frontend sends an authorization `code` and the backend exchanges it
|
|
20
|
+
* server-side for tokens. This is the most secure flow because tokens
|
|
21
|
+
* never touch the browser.
|
|
22
|
+
*
|
|
23
|
+
* When omitted, only ID-token and access-token verification are available
|
|
24
|
+
* (Paths 1 & 2), which rely on the frontend obtaining tokens directly.
|
|
25
|
+
*/
|
|
26
|
+
clientSecret?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
/**
|
|
14
30
|
* Creates a Google OAuth Provider integration.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
31
|
+
*
|
|
32
|
+
* Supports three verification paths:
|
|
33
|
+
*
|
|
34
|
+
* **Path 1 – ID Token** (One Tap / Sign In With Google button):
|
|
35
|
+
* Frontend sends `idToken`. Backend verifies cryptographically using
|
|
36
|
+
* Google's public keys. No secret required.
|
|
37
|
+
*
|
|
38
|
+
* **Path 2 – Access Token** (popup via `initTokenClient`):
|
|
39
|
+
* Frontend sends `accessToken`. Backend validates by calling Google's
|
|
40
|
+
* userinfo endpoint. No secret required.
|
|
41
|
+
*
|
|
42
|
+
* **Path 3 – Authorization Code** (most secure, requires `clientSecret`):
|
|
43
|
+
* Frontend sends `code` + `redirectUri`. Backend exchanges the code
|
|
44
|
+
* server-side for an ID token using `clientId` + `clientSecret`, then
|
|
45
|
+
* verifies the ID token. Tokens never touch the browser.
|
|
17
46
|
*/
|
|
18
|
-
export function createGoogleProvider(
|
|
19
|
-
|
|
47
|
+
export function createGoogleProvider(config: GoogleProviderConfig | string): OAuthProvider<{
|
|
48
|
+
idToken?: string;
|
|
49
|
+
accessToken?: string;
|
|
50
|
+
code?: string;
|
|
51
|
+
redirectUri?: string;
|
|
52
|
+
}> {
|
|
53
|
+
const clientId = typeof config === "string" ? config : config.clientId;
|
|
54
|
+
const clientSecret = typeof config === "string" ? undefined : config.clientSecret;
|
|
55
|
+
const googleClient = new OAuth2Client(clientId, clientSecret);
|
|
20
56
|
|
|
21
57
|
return {
|
|
22
58
|
id: "google",
|
|
23
59
|
schema: z.object({
|
|
24
60
|
idToken: z.string().min(1).optional(),
|
|
25
|
-
accessToken: z.string().min(1).optional()
|
|
61
|
+
accessToken: z.string().min(1).optional(),
|
|
62
|
+
code: z.string().min(1).optional(),
|
|
63
|
+
redirectUri: z.string().min(1).optional()
|
|
26
64
|
}).refine(
|
|
27
|
-
(data) => data.idToken || data.accessToken,
|
|
28
|
-
{ message: "
|
|
65
|
+
(data) => data.idToken || data.accessToken || (data.code && data.redirectUri),
|
|
66
|
+
{ message: "One of idToken, accessToken, or code+redirectUri is required" }
|
|
29
67
|
),
|
|
30
|
-
verify: async (payload: {
|
|
68
|
+
verify: async (payload: {
|
|
69
|
+
idToken?: string;
|
|
70
|
+
accessToken?: string;
|
|
71
|
+
code?: string;
|
|
72
|
+
redirectUri?: string;
|
|
73
|
+
}): Promise<OAuthProviderProfile | null> => {
|
|
31
74
|
try {
|
|
32
|
-
// Path 1: verify an ID token (
|
|
75
|
+
// Path 1: verify an ID token (One Tap / renderButton)
|
|
33
76
|
if (payload.idToken) {
|
|
34
77
|
const ticket = await googleClient.verifyIdToken({
|
|
35
78
|
idToken: payload.idToken,
|
|
@@ -38,7 +81,7 @@ export function createGoogleProvider(clientId: string): OAuthProvider<{ idToken?
|
|
|
38
81
|
|
|
39
82
|
const content = ticket.getPayload();
|
|
40
83
|
if (!content) {
|
|
41
|
-
|
|
84
|
+
throw new Error("Google ID token payload was empty");
|
|
42
85
|
}
|
|
43
86
|
|
|
44
87
|
return {
|
|
@@ -56,8 +99,7 @@ export function createGoogleProvider(clientId: string): OAuthProvider<{ idToken?
|
|
|
56
99
|
{ headers: { Authorization: `Bearer ${payload.accessToken}` } }
|
|
57
100
|
);
|
|
58
101
|
if (!res.ok) {
|
|
59
|
-
|
|
60
|
-
return null;
|
|
102
|
+
throw new Error(`Google userinfo request failed with status ${res.status}`);
|
|
61
103
|
}
|
|
62
104
|
const info = await res.json() as {
|
|
63
105
|
sub: string;
|
|
@@ -66,7 +108,7 @@ export function createGoogleProvider(clientId: string): OAuthProvider<{ idToken?
|
|
|
66
108
|
picture?: string;
|
|
67
109
|
};
|
|
68
110
|
if (!info.sub || !info.email) {
|
|
69
|
-
|
|
111
|
+
throw new Error("Google userinfo response missing sub or email");
|
|
70
112
|
}
|
|
71
113
|
return {
|
|
72
114
|
providerId: info.sub,
|
|
@@ -76,12 +118,101 @@ export function createGoogleProvider(clientId: string): OAuthProvider<{ idToken?
|
|
|
76
118
|
};
|
|
77
119
|
}
|
|
78
120
|
|
|
79
|
-
|
|
121
|
+
// Path 3: authorization code exchange (most secure)
|
|
122
|
+
// The frontend obtained a one-time authorization code via the
|
|
123
|
+
// Google OAuth consent screen. We exchange it server-side for
|
|
124
|
+
// tokens, so the access/id tokens never touch the browser.
|
|
125
|
+
if (payload.code && payload.redirectUri) {
|
|
126
|
+
if (!clientSecret) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"Google authorization code flow requires clientSecret. " +
|
|
129
|
+
"Configure GOOGLE_CLIENT_SECRET in your environment."
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Exchange the authorization code for tokens
|
|
134
|
+
const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
137
|
+
body: new URLSearchParams({
|
|
138
|
+
code: payload.code,
|
|
139
|
+
client_id: clientId,
|
|
140
|
+
client_secret: clientSecret,
|
|
141
|
+
redirect_uri: payload.redirectUri,
|
|
142
|
+
grant_type: "authorization_code"
|
|
143
|
+
})
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
if (!tokenResponse.ok) {
|
|
147
|
+
const errorBody = await tokenResponse.text();
|
|
148
|
+
throw new Error(`Google token exchange failed (${tokenResponse.status}): ${errorBody}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const tokenData = await tokenResponse.json() as {
|
|
152
|
+
id_token?: string;
|
|
153
|
+
access_token?: string;
|
|
154
|
+
error?: string;
|
|
155
|
+
error_description?: string;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (tokenData.error) {
|
|
159
|
+
throw new Error(`Google token exchange error: ${tokenData.error} – ${tokenData.error_description || "no details"}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Prefer verifying the ID token (cryptographic verification)
|
|
163
|
+
if (tokenData.id_token) {
|
|
164
|
+
const ticket = await googleClient.verifyIdToken({
|
|
165
|
+
idToken: tokenData.id_token,
|
|
166
|
+
audience: clientId
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const content = ticket.getPayload();
|
|
170
|
+
if (!content) {
|
|
171
|
+
throw new Error("Google ID token payload was empty after code exchange");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
providerId: content.sub,
|
|
176
|
+
email: content.email || "",
|
|
177
|
+
displayName: content.name || null,
|
|
178
|
+
photoUrl: content.picture || null
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Fallback: use the access token to fetch userinfo
|
|
183
|
+
if (tokenData.access_token) {
|
|
184
|
+
const userInfoRes = await fetch(
|
|
185
|
+
"https://www.googleapis.com/oauth2/v3/userinfo",
|
|
186
|
+
{ headers: { Authorization: `Bearer ${tokenData.access_token}` } }
|
|
187
|
+
);
|
|
188
|
+
if (!userInfoRes.ok) {
|
|
189
|
+
throw new Error(`Google userinfo request failed after code exchange (${userInfoRes.status})`);
|
|
190
|
+
}
|
|
191
|
+
const info = await userInfoRes.json() as {
|
|
192
|
+
sub: string;
|
|
193
|
+
email?: string;
|
|
194
|
+
name?: string;
|
|
195
|
+
picture?: string;
|
|
196
|
+
};
|
|
197
|
+
if (!info.sub || !info.email) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
providerId: info.sub,
|
|
202
|
+
email: info.email,
|
|
203
|
+
displayName: info.name || null,
|
|
204
|
+
photoUrl: info.picture || null
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
throw new Error("Google token exchange returned neither id_token nor access_token");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
throw new Error("No valid Google credential provided (expected idToken, accessToken, or code+redirectUri)");
|
|
80
212
|
} catch (error) {
|
|
81
|
-
console.error("
|
|
82
|
-
|
|
213
|
+
console.error("Google OAuth verification failed:", error);
|
|
214
|
+
throw error;
|
|
83
215
|
}
|
|
84
216
|
}
|
|
85
217
|
};
|
|
86
218
|
}
|
|
87
|
-
|
package/src/auth/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export type { PasswordValidationResult } from "./password";
|
|
|
9
9
|
|
|
10
10
|
// OAuth Providers
|
|
11
11
|
export { createGoogleProvider } from "./google-oauth";
|
|
12
|
+
export type { GoogleProviderConfig } from "./google-oauth";
|
|
12
13
|
export { createLinkedinProvider } from "./linkedin-oauth";
|
|
13
14
|
export { createGitHubProvider } from "./github-oauth";
|
|
14
15
|
export { createMicrosoftProvider } from "./microsoft-oauth";
|
package/src/auth/routes.ts
CHANGED
|
@@ -233,8 +233,16 @@ export function createAuthRoutes(config: AuthModuleConfig): Hono<HonoEnv> {
|
|
|
233
233
|
displayName: displayName || undefined
|
|
234
234
|
});
|
|
235
235
|
|
|
236
|
-
//
|
|
237
|
-
|
|
236
|
+
// Auto-bootstrap: if this is the very first user in the system, promote to admin.
|
|
237
|
+
// This avoids the chicken-and-egg problem where the first user has no permissions
|
|
238
|
+
// and no way to access the bootstrap endpoint from the UI.
|
|
239
|
+
const existingUsers = await authRepo.listUsers();
|
|
240
|
+
const isFirstUser = existingUsers.length === 1 && existingUsers[0].id === user.id;
|
|
241
|
+
|
|
242
|
+
if (isFirstUser) {
|
|
243
|
+
await authRepo.setUserRoles(user.id, ["admin"]);
|
|
244
|
+
} else if (config.defaultRole) {
|
|
245
|
+
// Assign configured default role (never auto-assign admin via registration)
|
|
238
246
|
await authRepo.assignDefaultRole(user.id, config.defaultRole);
|
|
239
247
|
}
|
|
240
248
|
|
|
@@ -289,7 +297,13 @@ displayName: user.displayName });
|
|
|
289
297
|
router.post(`/${provider.id}`, defaultAuthLimiter, async (c) => {
|
|
290
298
|
const payload = parseBody(provider.schema, await c.req.json());
|
|
291
299
|
|
|
292
|
-
|
|
300
|
+
let externalUser;
|
|
301
|
+
try {
|
|
302
|
+
externalUser = await provider.verify(payload);
|
|
303
|
+
} catch (err: unknown) {
|
|
304
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
305
|
+
throw ApiError.unauthorized(`${provider.id} login failed: ${msg}`, "OAUTH_ERROR");
|
|
306
|
+
}
|
|
293
307
|
if (!externalUser) {
|
|
294
308
|
throw ApiError.unauthorized(`Invalid ${provider.id} credentials`, "INVALID_TOKEN");
|
|
295
309
|
}
|
|
@@ -320,8 +334,14 @@ displayName: user.displayName });
|
|
|
320
334
|
|
|
321
335
|
await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, { email: externalUser.email });
|
|
322
336
|
|
|
323
|
-
//
|
|
324
|
-
|
|
337
|
+
// Auto-bootstrap: first user in the system gets admin
|
|
338
|
+
const allUsers = await authRepo.listUsers();
|
|
339
|
+
const isFirstUser = allUsers.length === 1 && allUsers[0].id === user.id;
|
|
340
|
+
|
|
341
|
+
if (isFirstUser) {
|
|
342
|
+
await authRepo.setUserRoles(user.id, ["admin"]);
|
|
343
|
+
} else if (config.defaultRole) {
|
|
344
|
+
// Assign configured default role (never auto-assign admin via registration)
|
|
325
345
|
await authRepo.assignDefaultRole(user.id, config.defaultRole);
|
|
326
346
|
}
|
|
327
347
|
|
|
@@ -26,9 +26,9 @@ export async function loadCollectionsFromDirectory(directory: string): Promise<E
|
|
|
26
26
|
try {
|
|
27
27
|
const fileUrl = pathToFileURL(filePath).href;
|
|
28
28
|
|
|
29
|
-
// Use
|
|
30
|
-
|
|
31
|
-
const module = await
|
|
29
|
+
// Use standard import() so that tsx/loader hooks can
|
|
30
|
+
// resolve .ts files and workspace bare-specifiers.
|
|
31
|
+
const module = await import(fileUrl);
|
|
32
32
|
|
|
33
33
|
// Expect the collection to be the default export
|
|
34
34
|
if (module && module.default) {
|
package/src/init.ts
CHANGED
|
@@ -43,7 +43,7 @@ export interface RebaseAuthConfig {
|
|
|
43
43
|
*/
|
|
44
44
|
serviceKey?: string;
|
|
45
45
|
email?: EmailConfig;
|
|
46
|
-
google?: { clientId: string };
|
|
46
|
+
google?: { clientId: string; clientSecret?: string };
|
|
47
47
|
linkedin?: { clientId: string; clientSecret: string };
|
|
48
48
|
github?: { clientId: string; clientSecret: string };
|
|
49
49
|
microsoft?: { clientId: string; clientSecret: string; tenantId?: string };
|
|
@@ -355,7 +355,7 @@ collectionRegistry });
|
|
|
355
355
|
|
|
356
356
|
if (config.auth.google?.clientId) {
|
|
357
357
|
const { createGoogleProvider } = await import("./auth");
|
|
358
|
-
oauthProviders.push(createGoogleProvider(config.auth.google
|
|
358
|
+
oauthProviders.push(createGoogleProvider(config.auth.google));
|
|
359
359
|
}
|
|
360
360
|
|
|
361
361
|
if (config.auth.linkedin?.clientId && config.auth.linkedin?.clientSecret) {
|
|
@@ -582,6 +582,18 @@ collectionRegistry });
|
|
|
582
582
|
_initRebase(serverClient);
|
|
583
583
|
logger.info("Rebase singleton initialized");
|
|
584
584
|
|
|
585
|
+
// Retroactively inject the server client into the driver so that
|
|
586
|
+
// entity callbacks receive `context.client` at runtime.
|
|
587
|
+
// The driver is created before the client (which depends on the mounted
|
|
588
|
+
// Hono app), so we set it here, mirroring the historyService injection above.
|
|
589
|
+
if (defaultDriverResult.internals) {
|
|
590
|
+
const internals = defaultDriverResult.internals as Record<string, unknown>;
|
|
591
|
+
const driver = internals.driver as Record<string, unknown> | undefined;
|
|
592
|
+
if (driver && "client" in driver) {
|
|
593
|
+
driver.client = serverClient;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
585
597
|
// 5. Mount Custom Functions
|
|
586
598
|
if (config.functionsDir) {
|
|
587
599
|
const { loadFunctionsFromDirectory } = await import("./functions/function-loader");
|