@rebasepro/server-core 0.0.1-canary.f81da60 → 0.1.2

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/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";
@@ -233,8 +233,16 @@ export function createAuthRoutes(config: AuthModuleConfig): Hono<HonoEnv> {
233
233
  displayName: displayName || undefined
234
234
  });
235
235
 
236
- // Assign configured default role (never auto-assign admin via registration)
237
- if (config.defaultRole) {
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
- const externalUser = await provider.verify(payload);
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
- // Assign configured default role (never auto-assign admin via registration)
324
- if (config.defaultRole) {
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 new Function to compile dynamic import natively and bypass tsc converting import() to require()
30
- const dynamicImport = new Function("url", "return import(url)");
31
- const module = await dynamicImport(fileUrl);
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.clientId));
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");
package/history_diff.log DELETED
@@ -1,385 +0,0 @@
1
- [recordHistory: posts/1 - update]
2
- CHANGED FIELDS: null
3
- PREVIOUS: {
4
- "title": "same"
5
- }
6
- NEW: {
7
- "title": "same"
8
- }
9
-
10
- [recordHistory: posts/1 - update]
11
- CHANGED FIELDS: ["title","tags"]
12
- PREVIOUS: {
13
- "title": "old",
14
- "tags": [
15
- {
16
- "id": 1
17
- }
18
- ]
19
- }
20
- NEW: {
21
- "title": "new",
22
- "tags": [
23
- {
24
- "id": 2
25
- }
26
- ]
27
- }
28
-
29
- [recordHistory: posts/1 - create]
30
- CHANGED FIELDS: null
31
- PREVIOUS: undefined
32
- NEW: {
33
- "title": "new"
34
- }
35
-
36
- [recordHistory: posts/1 - update]
37
- CHANGED FIELDS: null
38
- PREVIOUS: {
39
- "title": "same"
40
- }
41
- NEW: {
42
- "title": "same"
43
- }
44
-
45
- [recordHistory: posts/1 - update]
46
- CHANGED FIELDS: ["title","tags"]
47
- PREVIOUS: {
48
- "title": "old",
49
- "tags": [
50
- {
51
- "id": 1
52
- }
53
- ]
54
- }
55
- NEW: {
56
- "title": "new",
57
- "tags": [
58
- {
59
- "id": 2
60
- }
61
- ]
62
- }
63
-
64
- [recordHistory: posts/1 - create]
65
- CHANGED FIELDS: null
66
- PREVIOUS: undefined
67
- NEW: {
68
- "title": "new"
69
- }
70
-
71
- [recordHistory: posts/1 - update]
72
- CHANGED FIELDS: null
73
- PREVIOUS: {
74
- "title": "same"
75
- }
76
- NEW: {
77
- "title": "same"
78
- }
79
-
80
- [recordHistory: posts/1 - update]
81
- CHANGED FIELDS: ["title","tags"]
82
- PREVIOUS: {
83
- "title": "old",
84
- "tags": [
85
- {
86
- "id": 1
87
- }
88
- ]
89
- }
90
- NEW: {
91
- "title": "new",
92
- "tags": [
93
- {
94
- "id": 2
95
- }
96
- ]
97
- }
98
-
99
- [recordHistory: posts/1 - create]
100
- CHANGED FIELDS: null
101
- PREVIOUS: undefined
102
- NEW: {
103
- "title": "new"
104
- }
105
-
106
- [recordHistory: posts/1 - update]
107
- CHANGED FIELDS: null
108
- PREVIOUS: {
109
- "title": "same"
110
- }
111
- NEW: {
112
- "title": "same"
113
- }
114
-
115
- [recordHistory: posts/1 - update]
116
- CHANGED FIELDS: ["title","tags"]
117
- PREVIOUS: {
118
- "title": "old",
119
- "tags": [
120
- {
121
- "id": 1
122
- }
123
- ]
124
- }
125
- NEW: {
126
- "title": "new",
127
- "tags": [
128
- {
129
- "id": 2
130
- }
131
- ]
132
- }
133
-
134
- [recordHistory: posts/1 - create]
135
- CHANGED FIELDS: null
136
- PREVIOUS: undefined
137
- NEW: {
138
- "title": "new"
139
- }
140
-
141
- [recordHistory: posts/1 - update]
142
- CHANGED FIELDS: null
143
- PREVIOUS: {
144
- "title": "same"
145
- }
146
- NEW: {
147
- "title": "same"
148
- }
149
-
150
- [recordHistory: posts/1 - update]
151
- CHANGED FIELDS: ["title","tags"]
152
- PREVIOUS: {
153
- "title": "old",
154
- "tags": [
155
- {
156
- "id": 1
157
- }
158
- ]
159
- }
160
- NEW: {
161
- "title": "new",
162
- "tags": [
163
- {
164
- "id": 2
165
- }
166
- ]
167
- }
168
-
169
- [recordHistory: posts/1 - create]
170
- CHANGED FIELDS: null
171
- PREVIOUS: undefined
172
- NEW: {
173
- "title": "new"
174
- }
175
-
176
- [recordHistory: posts/1 - update]
177
- CHANGED FIELDS: null
178
- PREVIOUS: {
179
- "title": "same"
180
- }
181
- NEW: {
182
- "title": "same"
183
- }
184
-
185
- [recordHistory: posts/1 - update]
186
- CHANGED FIELDS: ["title","tags"]
187
- PREVIOUS: {
188
- "title": "old",
189
- "tags": [
190
- {
191
- "id": 1
192
- }
193
- ]
194
- }
195
- NEW: {
196
- "title": "new",
197
- "tags": [
198
- {
199
- "id": 2
200
- }
201
- ]
202
- }
203
-
204
- [recordHistory: posts/1 - create]
205
- CHANGED FIELDS: null
206
- PREVIOUS: undefined
207
- NEW: {
208
- "title": "new"
209
- }
210
-
211
- [recordHistory: posts/1 - update]
212
- CHANGED FIELDS: null
213
- PREVIOUS: {
214
- "title": "same"
215
- }
216
- NEW: {
217
- "title": "same"
218
- }
219
-
220
- [recordHistory: posts/1 - update]
221
- CHANGED FIELDS: ["title","tags"]
222
- PREVIOUS: {
223
- "title": "old",
224
- "tags": [
225
- {
226
- "id": 1
227
- }
228
- ]
229
- }
230
- NEW: {
231
- "title": "new",
232
- "tags": [
233
- {
234
- "id": 2
235
- }
236
- ]
237
- }
238
-
239
- [recordHistory: posts/1 - create]
240
- CHANGED FIELDS: null
241
- PREVIOUS: undefined
242
- NEW: {
243
- "title": "new"
244
- }
245
-
246
- [recordHistory: posts/1 - update]
247
- CHANGED FIELDS: null
248
- PREVIOUS: {
249
- "title": "same"
250
- }
251
- NEW: {
252
- "title": "same"
253
- }
254
-
255
- [recordHistory: posts/1 - update]
256
- CHANGED FIELDS: ["title","tags"]
257
- PREVIOUS: {
258
- "title": "old",
259
- "tags": [
260
- {
261
- "id": 1
262
- }
263
- ]
264
- }
265
- NEW: {
266
- "title": "new",
267
- "tags": [
268
- {
269
- "id": 2
270
- }
271
- ]
272
- }
273
-
274
- [recordHistory: posts/1 - create]
275
- CHANGED FIELDS: null
276
- PREVIOUS: undefined
277
- NEW: {
278
- "title": "new"
279
- }
280
-
281
- [recordHistory: posts/1 - update]
282
- CHANGED FIELDS: null
283
- PREVIOUS: {
284
- "title": "same"
285
- }
286
- NEW: {
287
- "title": "same"
288
- }
289
-
290
- [recordHistory: posts/1 - update]
291
- CHANGED FIELDS: ["title","tags"]
292
- PREVIOUS: {
293
- "title": "old",
294
- "tags": [
295
- {
296
- "id": 1
297
- }
298
- ]
299
- }
300
- NEW: {
301
- "title": "new",
302
- "tags": [
303
- {
304
- "id": 2
305
- }
306
- ]
307
- }
308
-
309
- [recordHistory: posts/1 - create]
310
- CHANGED FIELDS: null
311
- PREVIOUS: undefined
312
- NEW: {
313
- "title": "new"
314
- }
315
-
316
- [recordHistory: posts/1 - update]
317
- CHANGED FIELDS: null
318
- PREVIOUS: {
319
- "title": "same"
320
- }
321
- NEW: {
322
- "title": "same"
323
- }
324
-
325
- [recordHistory: posts/1 - update]
326
- CHANGED FIELDS: ["title","tags"]
327
- PREVIOUS: {
328
- "title": "old",
329
- "tags": [
330
- {
331
- "id": 1
332
- }
333
- ]
334
- }
335
- NEW: {
336
- "title": "new",
337
- "tags": [
338
- {
339
- "id": 2
340
- }
341
- ]
342
- }
343
-
344
- [recordHistory: posts/1 - create]
345
- CHANGED FIELDS: null
346
- PREVIOUS: undefined
347
- NEW: {
348
- "title": "new"
349
- }
350
-
351
- [recordHistory: posts/1 - update]
352
- CHANGED FIELDS: null
353
- PREVIOUS: {
354
- "title": "same"
355
- }
356
- NEW: {
357
- "title": "same"
358
- }
359
-
360
- [recordHistory: posts/1 - update]
361
- CHANGED FIELDS: ["title","tags"]
362
- PREVIOUS: {
363
- "title": "old",
364
- "tags": [
365
- {
366
- "id": 1
367
- }
368
- ]
369
- }
370
- NEW: {
371
- "title": "new",
372
- "tags": [
373
- {
374
- "id": 2
375
- }
376
- ]
377
- }
378
-
379
- [recordHistory: posts/1 - create]
380
- CHANGED FIELDS: null
381
- PREVIOUS: undefined
382
- NEW: {
383
- "title": "new"
384
- }
385
-
package/scratch.ts DELETED
@@ -1,9 +0,0 @@
1
- import { Hono } from "hono";
2
- const app = new Hono();
3
- const child = new Hono();
4
- child.get("/*", (c) => {
5
- return c.json({ star: c.req.param("*"),
6
- path: c.req.path });
7
- })
8
- app.route("/api", child);
9
- app.request(new Request("http://localhost/api/users/1/posts")).then(r => r.json()).then(console.log);
package/test-ast.ts DELETED
@@ -1,28 +0,0 @@
1
- import { AstSchemaEditor } from "./src/api/ast-schema-editor";
2
- import * as fs from "fs";
3
-
4
- const collectionsDir = __dirname + "/collections-test";
5
- fs.mkdirSync(collectionsDir, { recursive: true });
6
- fs.writeFileSync(collectionsDir + "/users.ts", `
7
- export default {
8
- name: "Users",
9
- slug: "users",
10
- properties: {},
11
- securityRules: [
12
- { name: "test", operation: "read", mode: "permissive", roles: ["public"] }
13
- ]
14
- };
15
- `);
16
-
17
- async function main() {
18
- const editor = new AstSchemaEditor(collectionsDir);
19
- await editor.saveCollection("users", {
20
- name: "Users",
21
- slug: "users",
22
- properties: {},
23
- securityRules: []
24
- });
25
- console.log("AFTER SAVE:", fs.readFileSync(collectionsDir + "/users.ts", "utf8"));
26
- }
27
-
28
- main().catch(console.error);