@rebasepro/server-core 0.2.4 → 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.
@@ -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";
@@ -131,8 +128,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
131
128
  // Resolve roles from the repository
132
129
  let roles: string[] = payload.roles || [];
133
130
  try {
134
- const userRoles = await authRepository.getUserRoles(payload.userId);
135
- roles = userRoles.map((r) => r.id);
131
+ roles = await authRepository.getUserRoleIds(payload.userId);
136
132
  } catch {
137
133
  // Fall back to token roles if repository lookup fails
138
134
  }
@@ -174,8 +170,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
174
170
 
175
171
  let roles: string[] = payload.roles || [];
176
172
  try {
177
- const userRoles = await authRepository.getUserRoles(payload.userId);
178
- roles = userRoles.map((r) => r.id);
173
+ roles = await authRepository.getUserRoleIds(payload.userId);
179
174
  } catch {
180
175
  // Fall back to token roles if repository lookup fails
181
176
  }
@@ -194,7 +189,7 @@ export function createBuiltinAuthAdapter(config: BuiltinAuthAdapterConfig): Auth
194
189
 
195
190
  userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
196
191
 
197
- roleManagement: createRoleManagementFromRepo(authRepository),
192
+
198
193
 
199
194
  createAuthRoutes(): Hono<HonoEnv> | undefined {
200
195
  return createAuthRoutes({
@@ -327,9 +322,8 @@ function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: Resolve
327
322
  }
328
323
  },
329
324
 
330
- async getUserRoles(userId: string): Promise<AuthRoleData[]> {
331
- const roles = await repo.getUserRoles(userId);
332
- return roles.map(toAuthRoleData);
325
+ async getUserRoles(userId: string): Promise<string[]> {
326
+ return repo.getUserRoleIds(userId);
333
327
  },
334
328
 
335
329
  async setUserRoles(userId: string, roleIds: string[]): Promise<void> {
@@ -338,40 +332,6 @@ function createUserManagementFromRepo(repo: AuthRepository, resolvedOps: Resolve
338
332
  };
339
333
  }
340
334
 
341
- function createRoleManagementFromRepo(repo: AuthRepository): RoleManagementAdapter {
342
- return {
343
- async listRoles(): Promise<AuthRoleData[]> {
344
- const roles = await repo.listRoles();
345
- return roles.map(toAuthRoleData);
346
- },
347
-
348
- async getRoleById(id: string): Promise<AuthRoleData | null> {
349
- const role = await repo.getRoleById(id);
350
- return role ? toAuthRoleData(role) : null;
351
- },
352
-
353
- async createRole(data: AuthCreateRoleData): Promise<AuthRoleData> {
354
- const role = await repo.createRole({
355
- id: data.id,
356
- name: data.name,
357
- isAdmin: data.isAdmin,
358
- defaultPermissions: data.defaultPermissions,
359
- collectionPermissions: data.collectionPermissions,
360
- });
361
- return toAuthRoleData(role);
362
- },
363
-
364
- async updateRole(id: string, data: Partial<AuthRoleData>): Promise<AuthRoleData | null> {
365
- const role = await repo.updateRole(id, data);
366
- return role ? toAuthRoleData(role) : null;
367
- },
368
-
369
- async deleteRole(id: string): Promise<void> {
370
- await repo.deleteRole(id);
371
- },
372
- };
373
- }
374
-
375
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 {
376
336
  return {
377
337
  id: user.id,
@@ -384,13 +344,3 @@ function toAuthUserData(user: { id: string; email: string; displayName?: string
384
344
  updatedAt: user.updatedAt,
385
345
  };
386
346
  }
387
-
388
- function toAuthRoleData(role: { id: string; name: string; isAdmin: boolean; defaultPermissions?: unknown; collectionPermissions?: unknown }): AuthRoleData {
389
- return {
390
- id: role.id,
391
- name: role.name,
392
- isAdmin: role.isAdmin,
393
- defaultPermissions: role.defaultPermissions as AuthRoleData["defaultPermissions"],
394
- collectionPermissions: role.collectionPermissions as AuthRoleData["collectionPermissions"],
395
- };
396
- }
@@ -76,7 +76,7 @@ export function createCustomAuthAdapter(options: CustomAuthAdapterOptions): Auth
76
76
 
77
77
  userManagement: options.userManagement,
78
78
 
79
- roleManagement: options.roleManagement,
79
+
80
80
 
81
81
  getCapabilities() {
82
82
  return defaultCapabilities;
package/src/init.ts CHANGED
@@ -23,6 +23,7 @@ import { createApiKeyRoutes } from "./auth/api-keys/api-key-routes";
23
23
  import type { ApiKeyStore } from "./auth/api-keys/api-key-store";
24
24
  import { createApiKeyRateLimiter } from "./auth/rate-limiter";
25
25
  import { createRebaseClient } from "@rebasepro/client";
26
+ import { defaultUsersCollection } from "@rebasepro/common";
26
27
  import { createHistoryRoutes } from "./history";
27
28
  import { EmailConfig, createEmailService } from "./email";
28
29
  import type { EmailService } from "./email";
@@ -292,6 +293,13 @@ async function _initializeRebaseBackend(config: RebaseBackendConfig): Promise<Re
292
293
  dir: config.collectionsDir });
293
294
  }
294
295
 
296
+ if (config.auth) {
297
+ // Prepend defaultUsersCollection if not overridden by the developer's collections
298
+ activeCollections = Array.from(
299
+ new Map([defaultUsersCollection, ...activeCollections].map(c => [c.slug, c])).values()
300
+ );
301
+ }
302
+
295
303
  const realtimeServices: Record<string, RealtimeProvider> = {};
296
304
  const delegates: Record<string, DataDriver> = {};
297
305
 
@@ -378,7 +386,6 @@ collectionRegistry });
378
386
  // (the return type still exposes `auth?: BootstrappedAuth`)
379
387
  authConfigResult = {
380
388
  userService: authAdapter.userManagement ?? {},
381
- roleService: authAdapter.roleManagement ?? {},
382
389
  };
383
390
  } else {
384
391
  // ── RebaseAuthConfig — wrap in built-in adapter ──
@@ -792,6 +799,17 @@ collectionRegistry });
792
799
  logger.info("Email service attached to singleton", { configured: emailService.isConfigured() });
793
800
  }
794
801
 
802
+ // Attach raw SQL capability when the driver supports it (Postgres, MySQL).
803
+ // Document databases (MongoDB, Firestore) won't have this.
804
+ const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
805
+ if (isSQLAdmin(driverAdmin)) {
806
+ Object.assign(serverClient, {
807
+ sql: (query: string, options?: { database?: string; role?: string }) =>
808
+ driverAdmin.executeSql(query, options)
809
+ });
810
+ logger.info("SQL capability attached to singleton");
811
+ }
812
+
795
813
  _initRebase(serverClient);
796
814
  logger.info("Rebase singleton initialized");
797
815
 
@@ -540,174 +540,5 @@ displayName: "Updated" }),
540
540
  });
541
541
  });
542
542
 
543
- // ── Role CRUD ───────────────────────────────────────────────────────
544
- describe("Role Management", () => {
545
- describe("GET /admin/roles", () => {
546
- it("returns list of roles", async () => {
547
- const app = createApp();
548
- mockAuthRepo.listRoles.mockResolvedValueOnce([
549
- mockRole("admin", true),
550
- mockRole("editor"),
551
- mockRole("viewer")
552
- ]);
553
-
554
- const res = await app.request("/admin/roles", { headers: { ...adminAuth() } });
555
- expect(res.status).toBe(200);
556
- const body = await res.json() as any;
557
- expect(body.roles).toHaveLength(3);
558
- expect(body.roles[0].isAdmin).toBe(true);
559
- });
560
- });
561
-
562
- describe("GET /admin/roles/:roleId", () => {
563
- it("returns role by ID", async () => {
564
- const app = createApp();
565
- mockAuthRepo.getRoleById.mockResolvedValueOnce(mockRole("admin", true));
566
-
567
- const res = await app.request("/admin/roles/admin", { headers: { ...adminAuth() } });
568
- expect(res.status).toBe(200);
569
- const body = await res.json() as any;
570
- expect(body.role.id).toBe("admin");
571
- expect(body.role.isAdmin).toBe(true);
572
- });
573
-
574
- it("returns 404 for non-existent role", async () => {
575
- const app = createApp();
576
- mockAuthRepo.getRoleById.mockResolvedValueOnce(null);
577
-
578
- const res = await app.request("/admin/roles/missing", { headers: { ...adminAuth() } });
579
- expect(res.status).toBe(404);
580
- });
581
- });
582
-
583
- describe("POST /admin/roles", () => {
584
- it("creates a new role", async () => {
585
- const app = createApp();
586
-
587
- const res = await app.request("/admin/roles", {
588
- ...json({ id: "custom",
589
- name: "Custom Role" }),
590
- headers: { ...json({}).headers,
591
- ...adminAuth() }
592
- });
593
- expect(res.status).toBe(201);
594
- const body = await res.json() as any;
595
- expect(body.role.id).toBe("custom");
596
- });
597
543
 
598
- it("returns 400 for missing id or name", async () => {
599
- const app = createApp();
600
-
601
- const res = await app.request("/admin/roles", {
602
- ...json({ id: "nope" }),
603
- headers: { ...json({}).headers,
604
- ...adminAuth() }
605
- });
606
- expect(res.status).toBe(400);
607
- });
608
-
609
- it("returns 409 when role already exists", async () => {
610
- const app = createApp();
611
- mockAuthRepo.getRoleById.mockResolvedValueOnce(mockRole("custom"));
612
-
613
- const res = await app.request("/admin/roles", {
614
- ...json({ id: "custom",
615
- name: "Dup" }),
616
- headers: { ...json({}).headers,
617
- ...adminAuth() }
618
- });
619
- expect(res.status).toBe(409);
620
- const body = await res.json() as any;
621
- expect(body.error.code).toBe("ROLE_EXISTS");
622
- });
623
- });
624
-
625
- describe("PUT /admin/roles/:roleId", () => {
626
- it("updates an existing role", async () => {
627
- const app = createApp();
628
- mockAuthRepo.getRoleById.mockResolvedValueOnce(mockRole("editor"));
629
-
630
- const res = await app.request("/admin/roles/editor", {
631
- method: "PUT",
632
- headers: { "Content-Type": "application/json",
633
- ...adminAuth() },
634
- body: JSON.stringify({ name: "Super Editor" })
635
- });
636
- expect(res.status).toBe(200);
637
- expect(mockAuthRepo.updateRole).toHaveBeenCalledWith("editor", expect.objectContaining({
638
- name: "Super Editor"
639
- }));
640
- });
641
-
642
- it("returns 404 for non-existent role", async () => {
643
- const app = createApp();
644
- mockAuthRepo.getRoleById.mockResolvedValueOnce(null);
645
-
646
- const res = await app.request("/admin/roles/missing", {
647
- method: "PUT",
648
- headers: { "Content-Type": "application/json",
649
- ...adminAuth() },
650
- body: JSON.stringify({ name: "Nope" })
651
- });
652
- expect(res.status).toBe(404);
653
- });
654
- });
655
-
656
- describe("DELETE /admin/roles/:roleId", () => {
657
- it("deletes a custom role", async () => {
658
- const app = createApp();
659
- mockAuthRepo.getRoleById.mockResolvedValueOnce(mockRole("custom"));
660
-
661
- const res = await app.request("/admin/roles/custom", {
662
- method: "DELETE",
663
- headers: { ...adminAuth() }
664
- });
665
- expect(res.status).toBe(200);
666
- expect(mockAuthRepo.deleteRole).toHaveBeenCalledWith("custom");
667
- });
668
-
669
- it("prevents deletion of built-in admin role", async () => {
670
- const app = createApp();
671
-
672
- const res = await app.request("/admin/roles/admin", {
673
- method: "DELETE",
674
- headers: { ...adminAuth() }
675
- });
676
- expect(res.status).toBe(400);
677
- const body = await res.json() as any;
678
- expect(body.error.code).toBe("BUILTIN_ROLE");
679
- });
680
-
681
- it("prevents deletion of built-in editor role", async () => {
682
- const app = createApp();
683
-
684
- const res = await app.request("/admin/roles/editor", {
685
- method: "DELETE",
686
- headers: { ...adminAuth() }
687
- });
688
- expect(res.status).toBe(400);
689
- });
690
-
691
- it("prevents deletion of built-in viewer role", async () => {
692
- const app = createApp();
693
-
694
- const res = await app.request("/admin/roles/viewer", {
695
- method: "DELETE",
696
- headers: { ...adminAuth() }
697
- });
698
- expect(res.status).toBe(400);
699
- });
700
-
701
- it("returns 404 for non-existent role", async () => {
702
- const app = createApp();
703
- mockAuthRepo.getRoleById.mockResolvedValueOnce(null);
704
-
705
- const res = await app.request("/admin/roles/ghost", {
706
- method: "DELETE",
707
- headers: { ...adminAuth() }
708
- });
709
- expect(res.status).toBe(404);
710
- });
711
- });
712
- });
713
544
  });
@@ -362,32 +362,7 @@ describe("BackendHooks — Admin Routes", () => {
362
362
  });
363
363
  });
364
364
 
365
- // ── roles.afterRead ─────────────────────────────────────────────────
366
- describe("roles.afterRead", () => {
367
- it("filters out roles from GET /admin/roles", async () => {
368
- const hooks: BackendHooks = {
369
- roles: {
370
- afterRead(role) {
371
- // Hide internal roles
372
- if (role.id === "internal") return null;
373
- return role;
374
- }
375
- }
376
- };
377
- const app = createApp(hooks);
378
- mockAuthRepo.listRoles.mockResolvedValueOnce([
379
- mockRole("admin", true),
380
- mockRole("internal"),
381
- mockRole("editor")
382
- ]);
383
365
 
384
- const res = await app.request("/admin/roles", { headers: { ...adminAuth() } });
385
- expect(res.status).toBe(200);
386
- const body = await res.json() as any;
387
- expect(body.roles).toHaveLength(2);
388
- expect(body.roles.map((r: any) => r.id)).toEqual(["admin", "editor"]);
389
- });
390
- });
391
366
 
392
367
  // ── no hooks (passthrough) ──────────────────────────────────────────
393
368
  describe("no hooks configured", () => {
@@ -143,7 +143,7 @@ describe("createCustomAuthAdapter", () => {
143
143
  expect(adapter.serviceKey).toBe("sk_live_123");
144
144
  });
145
145
 
146
- it("passes through userManagement and roleManagement when provided", () => {
146
+ it("passes through userManagement when provided", () => {
147
147
  const userMgmt = {
148
148
  getUser: jest.fn(),
149
149
  listUsers: jest.fn(),
@@ -151,27 +151,19 @@ describe("createCustomAuthAdapter", () => {
151
151
  updateUser: jest.fn(),
152
152
  deleteUser: jest.fn(),
153
153
  };
154
- const roleMgmt = {
155
- listRoles: jest.fn(),
156
- getUserRoles: jest.fn(),
157
- setUserRoles: jest.fn(),
158
- };
159
154
 
160
155
  const adapter = createCustomAuthAdapter({
161
156
  verifyRequest: async () => null,
162
157
  userManagement: userMgmt as unknown as CustomAuthAdapterOptions["userManagement"],
163
- roleManagement: roleMgmt as unknown as CustomAuthAdapterOptions["roleManagement"],
164
158
  });
165
159
 
166
160
  expect(adapter.userManagement).toBe(userMgmt);
167
- expect(adapter.roleManagement).toBe(roleMgmt);
168
161
  });
169
162
 
170
- it("omits userManagement and roleManagement when not provided", () => {
163
+ it("omits userManagement when not provided", () => {
171
164
  const adapter = createCustomAuthAdapter({
172
165
  verifyRequest: async () => null,
173
166
  });
174
167
  expect(adapter.userManagement).toBeUndefined();
175
- expect(adapter.roleManagement).toBeUndefined();
176
168
  });
177
169
  });
@@ -1,14 +0,0 @@
1
- export type Role = {
2
- /**
3
- * ID of the role
4
- */
5
- id: string;
6
- /**
7
- * Name of the role
8
- */
9
- name: string;
10
- /**
11
- * If this flag is true, the user can perform any action
12
- */
13
- isAdmin?: boolean;
14
- };