@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.
Files changed (148) hide show
  1. package/LICENSE +22 -6
  2. package/dist/common/src/util/entities.d.ts +2 -2
  3. package/dist/common/src/util/relations.d.ts +1 -1
  4. package/dist/{index-DXVBFp5V.js → index-BZoAtuqi.js} +6 -2
  5. package/dist/index-BZoAtuqi.js.map +1 -0
  6. package/dist/index.es.js +15909 -16083
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +15847 -16017
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-core/src/auth/adapter-middleware.d.ts +33 -0
  11. package/dist/server-core/src/auth/admin-routes.d.ts +6 -0
  12. package/dist/server-core/src/auth/auth-overrides.d.ts +139 -0
  13. package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +49 -0
  14. package/dist/server-core/src/auth/crypto-utils.d.ts +16 -0
  15. package/dist/server-core/src/auth/custom-auth-adapter.d.ts +39 -0
  16. package/dist/server-core/src/auth/index.d.ts +7 -0
  17. package/dist/server-core/src/auth/interfaces.d.ts +2 -0
  18. package/dist/server-core/src/auth/middleware.d.ts +18 -0
  19. package/dist/server-core/src/auth/rls-scope.d.ts +31 -0
  20. package/dist/server-core/src/auth/routes.d.ts +7 -1
  21. package/dist/server-core/src/env.d.ts +131 -0
  22. package/dist/server-core/src/index.d.ts +2 -0
  23. package/dist/server-core/src/init.d.ts +62 -3
  24. package/dist/types/src/controllers/auth.d.ts +9 -8
  25. package/dist/types/src/controllers/client.d.ts +3 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +356 -0
  27. package/dist/types/src/types/collections.d.ts +67 -2
  28. package/dist/types/src/types/database_adapter.d.ts +94 -0
  29. package/dist/types/src/types/entity_actions.d.ts +7 -1
  30. package/dist/types/src/types/entity_callbacks.d.ts +1 -1
  31. package/dist/types/src/types/entity_views.d.ts +36 -1
  32. package/dist/types/src/types/index.d.ts +2 -0
  33. package/dist/types/src/types/plugins.d.ts +1 -1
  34. package/dist/types/src/types/properties.d.ts +24 -5
  35. package/dist/types/src/types/property_config.d.ts +6 -2
  36. package/dist/types/src/types/relations.d.ts +1 -1
  37. package/dist/types/src/types/translations.d.ts +8 -0
  38. package/dist/types/src/users/user.d.ts +5 -0
  39. package/package.json +26 -26
  40. package/src/api/errors.ts +1 -1
  41. package/src/api/graphql/graphql-schema-generator.ts +7 -0
  42. package/src/api/openapi-generator.ts +13 -1
  43. package/src/api/rest/api-generator-count.test.ts +14 -12
  44. package/src/api/rest/query-parser.ts +2 -20
  45. package/src/auth/adapter-middleware.ts +83 -0
  46. package/src/auth/admin-routes.ts +36 -43
  47. package/src/auth/auth-overrides.ts +172 -0
  48. package/src/auth/builtin-auth-adapter.ts +384 -0
  49. package/src/auth/crypto-utils.ts +31 -0
  50. package/src/auth/custom-auth-adapter.ts +85 -0
  51. package/src/auth/index.ts +10 -0
  52. package/src/auth/interfaces.ts +2 -0
  53. package/src/auth/jwt.ts +3 -1
  54. package/src/auth/middleware.ts +2 -46
  55. package/src/auth/rls-scope.ts +58 -0
  56. package/src/auth/routes.ts +74 -32
  57. package/src/cron/cron-scheduler.test.ts +9 -9
  58. package/src/cron/cron-scheduler.ts +1 -1
  59. package/src/env.ts +224 -0
  60. package/src/index.ts +4 -0
  61. package/src/init.ts +355 -135
  62. package/src/storage/routes.ts +1 -19
  63. package/src/utils/logging.ts +3 -3
  64. package/test/admin-routes.test.ts +10 -4
  65. package/test/auth-routes.test.ts +2 -2
  66. package/test/backend-hooks-admin.test.ts +32 -12
  67. package/test/custom-auth-adapter.test.ts +177 -0
  68. package/test/env.test.ts +138 -0
  69. package/test/query-parser.test.ts +0 -29
  70. package/tsconfig.json +3 -0
  71. package/app/frontend/node_modules/esbuild/LICENSE.md +0 -21
  72. package/app/frontend/node_modules/esbuild/README.md +0 -3
  73. package/app/frontend/node_modules/esbuild/bin/esbuild +0 -220
  74. package/app/frontend/node_modules/esbuild/install.js +0 -285
  75. package/app/frontend/node_modules/esbuild/lib/main.d.ts +0 -705
  76. package/app/frontend/node_modules/esbuild/lib/main.js +0 -2239
  77. package/app/frontend/node_modules/esbuild/package.json +0 -46
  78. package/dist/index-DXVBFp5V.js.map +0 -1
  79. package/examples/firebase/node_modules/esbuild/LICENSE.md +0 -21
  80. package/examples/firebase/node_modules/esbuild/README.md +0 -3
  81. package/examples/firebase/node_modules/esbuild/bin/esbuild +0 -220
  82. package/examples/firebase/node_modules/esbuild/install.js +0 -285
  83. package/examples/firebase/node_modules/esbuild/lib/main.d.ts +0 -705
  84. package/examples/firebase/node_modules/esbuild/lib/main.js +0 -2239
  85. package/examples/firebase/node_modules/esbuild/package.json +0 -46
  86. package/examples/medmot-staging/frontend/node_modules/esbuild/LICENSE.md +0 -21
  87. package/examples/medmot-staging/frontend/node_modules/esbuild/README.md +0 -3
  88. package/examples/medmot-staging/frontend/node_modules/esbuild/bin/esbuild +0 -220
  89. package/examples/medmot-staging/frontend/node_modules/esbuild/install.js +0 -285
  90. package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.d.ts +0 -705
  91. package/examples/medmot-staging/frontend/node_modules/esbuild/lib/main.js +0 -2239
  92. package/examples/medmot-staging/frontend/node_modules/esbuild/package.json +0 -46
  93. package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +0 -21
  94. package/examples/sdk-demo/node_modules/esbuild/README.md +0 -3
  95. package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +0 -223
  96. package/examples/sdk-demo/node_modules/esbuild/install.js +0 -289
  97. package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +0 -716
  98. package/examples/sdk-demo/node_modules/esbuild/lib/main.js +0 -2242
  99. package/examples/sdk-demo/node_modules/esbuild/package.json +0 -49
  100. package/packages/client/node_modules/esbuild/LICENSE.md +0 -21
  101. package/packages/client/node_modules/esbuild/README.md +0 -3
  102. package/packages/client/node_modules/esbuild/bin/esbuild +0 -220
  103. package/packages/client/node_modules/esbuild/install.js +0 -285
  104. package/packages/client/node_modules/esbuild/lib/main.d.ts +0 -705
  105. package/packages/client/node_modules/esbuild/lib/main.js +0 -2239
  106. package/packages/client/node_modules/esbuild/package.json +0 -46
  107. package/packages/client-postgresql/node_modules/esbuild/LICENSE.md +0 -21
  108. package/packages/client-postgresql/node_modules/esbuild/README.md +0 -3
  109. package/packages/client-postgresql/node_modules/esbuild/bin/esbuild +0 -220
  110. package/packages/client-postgresql/node_modules/esbuild/install.js +0 -285
  111. package/packages/client-postgresql/node_modules/esbuild/lib/main.d.ts +0 -705
  112. package/packages/client-postgresql/node_modules/esbuild/lib/main.js +0 -2239
  113. package/packages/client-postgresql/node_modules/esbuild/package.json +0 -46
  114. package/packages/common/node_modules/esbuild/LICENSE.md +0 -21
  115. package/packages/common/node_modules/esbuild/README.md +0 -3
  116. package/packages/common/node_modules/esbuild/bin/esbuild +0 -220
  117. package/packages/common/node_modules/esbuild/install.js +0 -285
  118. package/packages/common/node_modules/esbuild/lib/main.d.ts +0 -705
  119. package/packages/common/node_modules/esbuild/lib/main.js +0 -2239
  120. package/packages/common/node_modules/esbuild/package.json +0 -46
  121. package/packages/server-mongodb/node_modules/esbuild/LICENSE.md +0 -21
  122. package/packages/server-mongodb/node_modules/esbuild/README.md +0 -3
  123. package/packages/server-mongodb/node_modules/esbuild/bin/esbuild +0 -220
  124. package/packages/server-mongodb/node_modules/esbuild/install.js +0 -285
  125. package/packages/server-mongodb/node_modules/esbuild/lib/main.d.ts +0 -705
  126. package/packages/server-mongodb/node_modules/esbuild/lib/main.js +0 -2239
  127. package/packages/server-mongodb/node_modules/esbuild/package.json +0 -46
  128. package/packages/server-postgresql/node_modules/esbuild/LICENSE.md +0 -21
  129. package/packages/server-postgresql/node_modules/esbuild/README.md +0 -3
  130. package/packages/server-postgresql/node_modules/esbuild/bin/esbuild +0 -220
  131. package/packages/server-postgresql/node_modules/esbuild/install.js +0 -285
  132. package/packages/server-postgresql/node_modules/esbuild/lib/main.d.ts +0 -705
  133. package/packages/server-postgresql/node_modules/esbuild/lib/main.js +0 -2239
  134. package/packages/server-postgresql/node_modules/esbuild/package.json +0 -46
  135. package/packages/types/node_modules/esbuild/LICENSE.md +0 -21
  136. package/packages/types/node_modules/esbuild/README.md +0 -3
  137. package/packages/types/node_modules/esbuild/bin/esbuild +0 -220
  138. package/packages/types/node_modules/esbuild/install.js +0 -285
  139. package/packages/types/node_modules/esbuild/lib/main.d.ts +0 -705
  140. package/packages/types/node_modules/esbuild/lib/main.js +0 -2239
  141. package/packages/types/node_modules/esbuild/package.json +0 -46
  142. package/packages/utils/node_modules/esbuild/LICENSE.md +0 -21
  143. package/packages/utils/node_modules/esbuild/README.md +0 -3
  144. package/packages/utils/node_modules/esbuild/bin/esbuild +0 -220
  145. package/packages/utils/node_modules/esbuild/install.js +0 -285
  146. package/packages/utils/node_modules/esbuild/lib/main.d.ts +0 -705
  147. package/packages/utils/node_modules/esbuild/lib/main.js +0 -2239
  148. package/packages/utils/node_modules/esbuild/package.json +0 -46
@@ -94,25 +94,7 @@ export function createStorageRoutes(config: StorageRoutesConfig): Hono<HonoEnv>
94
94
  const key = typeof body["key"] === "string" ? body["key"] : "";
95
95
  const bucket = typeof body["bucket"] === "string" ? body["bucket"] : undefined;
96
96
 
97
- // Backward compatibility support for older clients sending path and fileName
98
- const legacyPath = typeof body["path"] === "string" ? body["path"] : "";
99
- const legacyFileName = typeof body["fileName"] === "string" ? body["fileName"] : undefined;
100
-
101
- let finalKey = key;
102
- if (!finalKey) {
103
- if (legacyPath || legacyFileName) {
104
- const parts = [];
105
- if (legacyPath) parts.push(legacyPath);
106
- if (legacyFileName) {
107
- parts.push(legacyFileName);
108
- } else {
109
- parts.push(uploadedFile.name || "unnamed");
110
- }
111
- finalKey = parts.join("/");
112
- } else {
113
- finalKey = uploadedFile.name || "unnamed";
114
- }
115
- }
97
+ const finalKey = key || uploadedFile.name || "unnamed";
116
98
 
117
99
  // Extract custom metadata from request body
118
100
  const metadata: Record<string, unknown> = {};
@@ -21,8 +21,8 @@ debug: 3 };
21
21
  */
22
22
  export function resetConsole() {
23
23
  // Store original methods if not already stored
24
- if (!(global as unknown as Record<string, unknown>).__originalConsole) {
25
- (global as unknown as Record<string, unknown>).__originalConsole = {
24
+ if (!(global as Record<string, unknown>).__originalConsole) {
25
+ (global as Record<string, unknown>).__originalConsole = {
26
26
  log: console.log,
27
27
  warn: console.warn,
28
28
  error: console.error,
@@ -30,7 +30,7 @@ export function resetConsole() {
30
30
  };
31
31
  }
32
32
 
33
- const original = (global as unknown as Record<string, unknown>).__originalConsole as Console;
33
+ const original = (global as Record<string, unknown>).__originalConsole as Console;
34
34
  console.log = original.log;
35
35
  console.warn = original.warn;
36
36
  console.error = original.error;
@@ -63,6 +63,7 @@ displayName: data.displayName,
63
63
  passwordHash: data.passwordHash }))
64
64
  ),
65
65
  listUsers: jest.fn().mockResolvedValue([]),
66
+ listUsersPaginated: jest.fn().mockResolvedValue({ users: [], total: 0, limit: 25, offset: 0 }),
66
67
  getUserRoles: jest.fn().mockResolvedValue([mockRole("editor")]),
67
68
  getUserRoleIds: jest.fn().mockResolvedValue(["editor"]),
68
69
  assignDefaultRole: jest.fn().mockResolvedValue(undefined),
@@ -226,12 +227,17 @@ accessExpiresIn: "1h" });
226
227
  describe("GET /admin/users", () => {
227
228
  it("returns list of users with roles", async () => {
228
229
  const app = createApp();
229
- mockAuthRepo.listUsers.mockResolvedValueOnce([
230
- mockUser({ id: "u1",
230
+ mockAuthRepo.listUsersPaginated.mockResolvedValueOnce({
231
+ users: [
232
+ mockUser({ id: "u1",
231
233
  email: "a@test.com" }),
232
- mockUser({ id: "u2",
234
+ mockUser({ id: "u2",
233
235
  email: "b@test.com" })
234
- ]);
236
+ ],
237
+ total: 2,
238
+ limit: 25,
239
+ offset: 0
240
+ });
235
241
  mockAuthRepo.getUserRoleIds
236
242
  .mockResolvedValueOnce(["admin"])
237
243
  .mockResolvedValueOnce(["editor"]);
@@ -916,7 +916,7 @@ withEmail: false });
916
916
  const body = await res.json() as any;
917
917
  expect(body.needsSetup).toBe(false);
918
918
  expect(body.registrationEnabled).toBe(false);
919
- expect(body.googleEnabled).toBe(false);
919
+ expect(body.enabledProviders).toEqual([]);
920
920
  });
921
921
 
922
922
  it("reports Google enabled when configured", async () => {
@@ -925,7 +925,7 @@ withEmail: false });
925
925
 
926
926
  const res = await app.request("/auth/config");
927
927
  const body = await res.json() as any;
928
- expect(body.googleEnabled).toBe(true);
928
+ expect(body.enabledProviders).toContain("google");
929
929
  });
930
930
  });
931
931
 
@@ -159,11 +159,16 @@ describe("BackendHooks — Admin Routes", () => {
159
159
  }
160
160
  };
161
161
  const app = createApp(hooks);
162
- mockAuthRepo.listUsers.mockResolvedValueOnce([
163
- mockUser({ id: "u1", email: "alice@test.com" }),
164
- mockUser({ id: "u2", email: "bot@system.internal" }),
165
- mockUser({ id: "u3", email: "bob@test.com" })
166
- ]);
162
+ mockAuthRepo.listUsersPaginated.mockResolvedValueOnce({
163
+ users: [
164
+ mockUser({ id: "u1", email: "alice@test.com" }),
165
+ mockUser({ id: "u2", email: "bot@system.internal" }),
166
+ mockUser({ id: "u3", email: "bob@test.com" })
167
+ ],
168
+ total: 3,
169
+ limit: 25,
170
+ offset: 0
171
+ });
167
172
  mockAuthRepo.getUserRoleIds
168
173
  .mockResolvedValueOnce(["editor"])
169
174
  .mockResolvedValueOnce(["editor"])
@@ -186,9 +191,14 @@ describe("BackendHooks — Admin Routes", () => {
186
191
  }
187
192
  };
188
193
  const app = createApp(hooks);
189
- mockAuthRepo.listUsers.mockResolvedValueOnce([
190
- mockUser({ id: "u1", email: "alice@secret.com" })
191
- ]);
194
+ mockAuthRepo.listUsersPaginated.mockResolvedValueOnce({
195
+ users: [
196
+ mockUser({ id: "u1", email: "alice@secret.com" })
197
+ ],
198
+ total: 1,
199
+ limit: 25,
200
+ offset: 0
201
+ });
192
202
  mockAuthRepo.getUserRoleIds.mockResolvedValueOnce(["editor"]);
193
203
 
194
204
  const res = await app.request("/admin/users", { headers: { ...adminAuth() } });
@@ -219,7 +229,12 @@ describe("BackendHooks — Admin Routes", () => {
219
229
  const afterReadSpy = jest.fn((user, ctx) => user);
220
230
  const hooks: BackendHooks = { users: { afterRead: afterReadSpy } };
221
231
  const app = createApp(hooks);
222
- mockAuthRepo.listUsers.mockResolvedValueOnce([mockUser({ id: "u1" })]);
232
+ mockAuthRepo.listUsersPaginated.mockResolvedValueOnce({
233
+ users: [mockUser({ id: "u1" })],
234
+ total: 1,
235
+ limit: 25,
236
+ offset: 0
237
+ });
223
238
  mockAuthRepo.getUserRoleIds.mockResolvedValueOnce(["editor"]);
224
239
 
225
240
  await app.request("/admin/users", { headers: { ...adminAuth("admin-42") } });
@@ -379,9 +394,14 @@ describe("BackendHooks — Admin Routes", () => {
379
394
  describe("no hooks configured", () => {
380
395
  it("returns data unchanged when no hooks are provided", async () => {
381
396
  const app = createApp(); // no hooks
382
- mockAuthRepo.listUsers.mockResolvedValueOnce([
383
- mockUser({ id: "u1", email: "alice@test.com" })
384
- ]);
397
+ mockAuthRepo.listUsersPaginated.mockResolvedValueOnce({
398
+ users: [
399
+ mockUser({ id: "u1", email: "alice@test.com" })
400
+ ],
401
+ total: 1,
402
+ limit: 25,
403
+ offset: 0
404
+ });
385
405
  mockAuthRepo.getUserRoleIds.mockResolvedValueOnce(["editor"]);
386
406
 
387
407
  const res = await app.request("/admin/users", { headers: { ...adminAuth() } });
@@ -0,0 +1,177 @@
1
+ import { createCustomAuthAdapter } from "../src/auth/custom-auth-adapter";
2
+ import type { AuthenticatedUser, CustomAuthAdapterOptions } from "@rebasepro/types";
3
+
4
+ const TEST_USER: AuthenticatedUser = {
5
+ uid: "user-123",
6
+ email: "test@example.com",
7
+ displayName: "Test User",
8
+ roles: ["editor"],
9
+ isAdmin: false,
10
+ rawToken: "tok_abc",
11
+ };
12
+
13
+ const ADMIN_USER: AuthenticatedUser = {
14
+ uid: "admin-1",
15
+ email: "admin@example.com",
16
+ roles: ["admin"],
17
+ isAdmin: true,
18
+ };
19
+
20
+ describe("createCustomAuthAdapter", () => {
21
+ it("sets the adapter id to 'custom'", () => {
22
+ const adapter = createCustomAuthAdapter({
23
+ verifyRequest: async () => null,
24
+ });
25
+ expect(adapter.id).toBe("custom");
26
+ });
27
+
28
+ it("delegates verifyRequest to the provided function", async () => {
29
+ const verifyRequest = jest.fn(async () => TEST_USER);
30
+ const adapter = createCustomAuthAdapter({ verifyRequest });
31
+ const req = new Request("http://localhost/api", {
32
+ headers: { Authorization: "Bearer test-token" },
33
+ });
34
+ const result = await adapter.verifyRequest(req);
35
+ expect(verifyRequest).toHaveBeenCalledWith(req);
36
+ expect(result).toBe(TEST_USER);
37
+ });
38
+
39
+ it("returns null from verifyRequest when the user function returns null", async () => {
40
+ const adapter = createCustomAuthAdapter({
41
+ verifyRequest: async () => null,
42
+ });
43
+ const result = await adapter.verifyRequest(new Request("http://localhost/"));
44
+ expect(result).toBeNull();
45
+ });
46
+
47
+ // ── verifyToken (fallback via synthetic Request) ──────────────────
48
+
49
+ it("synthesizes a Request when verifyToken is not provided", async () => {
50
+ const verifyRequest = jest.fn(async (request: Request) => {
51
+ const auth = request.headers.get("authorization");
52
+ if (auth === "Bearer my-token") return TEST_USER;
53
+ return null;
54
+ });
55
+ const adapter = createCustomAuthAdapter({ verifyRequest });
56
+
57
+ expect(adapter.verifyToken).toBeDefined();
58
+
59
+ const result = await adapter.verifyToken!("my-token");
60
+ expect(result).toBe(TEST_USER);
61
+
62
+ // Verify the synthetic request was created correctly
63
+ expect(verifyRequest).toHaveBeenCalledTimes(1);
64
+ const calledRequest = verifyRequest.mock.calls[0][0] as Request;
65
+ expect(calledRequest.headers.get("authorization")).toBe("Bearer my-token");
66
+ expect(calledRequest.url).toContain("_ws_auth");
67
+ });
68
+
69
+ it("returns null from fallback verifyToken for invalid token", async () => {
70
+ const adapter = createCustomAuthAdapter({
71
+ verifyRequest: async () => null,
72
+ });
73
+ const result = await adapter.verifyToken!("bad-token");
74
+ expect(result).toBeNull();
75
+ });
76
+
77
+ // ── verifyToken (direct passthrough) ──────────────────────────────
78
+
79
+ it("uses the user-provided verifyToken when given", async () => {
80
+ const customVerifyToken = jest.fn(async (token: string) => {
81
+ if (token === "direct-token") return ADMIN_USER;
82
+ return null;
83
+ });
84
+ const verifyRequest = jest.fn(async () => null);
85
+ const adapter = createCustomAuthAdapter({
86
+ verifyRequest,
87
+ verifyToken: customVerifyToken,
88
+ });
89
+
90
+ const result = await adapter.verifyToken!("direct-token");
91
+ expect(result).toBe(ADMIN_USER);
92
+
93
+ // verifyRequest should NOT be called — verifyToken takes precedence
94
+ expect(verifyRequest).not.toHaveBeenCalled();
95
+ });
96
+
97
+ it("returns null from user-provided verifyToken for unknown token", async () => {
98
+ const adapter = createCustomAuthAdapter({
99
+ verifyRequest: async () => null,
100
+ verifyToken: async () => null,
101
+ });
102
+ const result = await adapter.verifyToken!("unknown");
103
+ expect(result).toBeNull();
104
+ });
105
+
106
+ // ── Capabilities ──────────────────────────────────────────────────
107
+
108
+ it("returns default capabilities when none are overridden", async () => {
109
+ const adapter = createCustomAuthAdapter({
110
+ verifyRequest: async () => null,
111
+ });
112
+ const caps = await adapter.getCapabilities!();
113
+ expect(caps).toEqual({
114
+ hasBuiltInAuthRoutes: false,
115
+ emailPasswordLogin: false,
116
+ registration: false,
117
+ passwordReset: false,
118
+ sessionManagement: false,
119
+ profileUpdate: false,
120
+ emailVerification: false,
121
+ enabledProviders: [],
122
+ });
123
+ });
124
+
125
+ it("merges user-provided capabilities with defaults", async () => {
126
+ const adapter = createCustomAuthAdapter({
127
+ verifyRequest: async () => null,
128
+ capabilities: { emailPasswordLogin: true, registration: true },
129
+ });
130
+ const caps = await adapter.getCapabilities!();
131
+ expect(caps.emailPasswordLogin).toBe(true);
132
+ expect(caps.registration).toBe(true);
133
+ expect(caps.hasBuiltInAuthRoutes).toBe(false);
134
+ });
135
+
136
+ // ── Optional fields ───────────────────────────────────────────────
137
+
138
+ it("passes through serviceKey", () => {
139
+ const adapter = createCustomAuthAdapter({
140
+ verifyRequest: async () => null,
141
+ serviceKey: "sk_live_123",
142
+ });
143
+ expect(adapter.serviceKey).toBe("sk_live_123");
144
+ });
145
+
146
+ it("passes through userManagement and roleManagement when provided", () => {
147
+ const userMgmt = {
148
+ getUser: jest.fn(),
149
+ listUsers: jest.fn(),
150
+ createUser: jest.fn(),
151
+ updateUser: jest.fn(),
152
+ deleteUser: jest.fn(),
153
+ };
154
+ const roleMgmt = {
155
+ listRoles: jest.fn(),
156
+ getUserRoles: jest.fn(),
157
+ setUserRoles: jest.fn(),
158
+ };
159
+
160
+ const adapter = createCustomAuthAdapter({
161
+ verifyRequest: async () => null,
162
+ userManagement: userMgmt as unknown as CustomAuthAdapterOptions["userManagement"],
163
+ roleManagement: roleMgmt as unknown as CustomAuthAdapterOptions["roleManagement"],
164
+ });
165
+
166
+ expect(adapter.userManagement).toBe(userMgmt);
167
+ expect(adapter.roleManagement).toBe(roleMgmt);
168
+ });
169
+
170
+ it("omits userManagement and roleManagement when not provided", () => {
171
+ const adapter = createCustomAuthAdapter({
172
+ verifyRequest: async () => null,
173
+ });
174
+ expect(adapter.userManagement).toBeUndefined();
175
+ expect(adapter.roleManagement).toBeUndefined();
176
+ });
177
+ });
@@ -0,0 +1,138 @@
1
+ import { z } from "zod";
2
+ import { loadEnv } from "../src/env";
3
+
4
+ describe("env configuration and localhost validation", () => {
5
+ let originalEnv: NodeJS.ProcessEnv;
6
+
7
+ beforeEach(() => {
8
+ // Save a backup of the original process.env
9
+ originalEnv = { ...process.env };
10
+ // Clear env vars that might interfere with tests
11
+ delete process.env.NODE_ENV;
12
+ delete process.env.DATABASE_URL;
13
+ delete process.env.ADMIN_CONNECTION_STRING;
14
+ delete process.env.JWT_SECRET;
15
+ delete process.env.FRONTEND_URL;
16
+ delete process.env.CORS_ORIGINS;
17
+ delete process.env.ALLOW_LOCALHOST_IN_PRODUCTION;
18
+ });
19
+
20
+ afterEach(() => {
21
+ // Restore process.env after each test
22
+ process.env = originalEnv;
23
+ });
24
+
25
+ it("should allow localhost URLs in development mode", () => {
26
+ process.env.NODE_ENV = "development";
27
+ process.env.DATABASE_URL = "postgresql://localhost:5432/rebase";
28
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
29
+
30
+ expect(() => loadEnv()).not.toThrow();
31
+ const env = loadEnv();
32
+ expect(env.DATABASE_URL).toBe("postgresql://localhost:5432/rebase");
33
+ });
34
+
35
+ it("should fail validation in production if DATABASE_URL contains localhost", () => {
36
+ process.env.NODE_ENV = "production";
37
+ process.env.DATABASE_URL = "postgresql://localhost:5432/rebase";
38
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
39
+ process.env.FRONTEND_URL = "https://my-app.com";
40
+
41
+ expect(() => loadEnv()).toThrowError(/postgresql:\/\/localhost:5432\/rebase/);
42
+ });
43
+
44
+ it("should fail validation in production if DATABASE_URL contains 127.0.0.1", () => {
45
+ process.env.NODE_ENV = "production";
46
+ process.env.DATABASE_URL = "postgresql://127.0.0.1:5432/rebase";
47
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
48
+ process.env.FRONTEND_URL = "https://my-app.com";
49
+
50
+ expect(() => loadEnv()).toThrowError(/postgresql:\/\/127\.0\.0\.1:5432\/rebase/);
51
+ });
52
+
53
+ it("should fail validation in production if DATABASE_URL contains an IPv6 loopback [::1]", () => {
54
+ process.env.NODE_ENV = "production";
55
+ process.env.DATABASE_URL = "postgresql://[::1]:5432/rebase";
56
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
57
+ process.env.FRONTEND_URL = "https://my-app.com";
58
+
59
+ expect(() => loadEnv()).toThrowError(/postgresql:\/\/\[::1\]:5432\/rebase/);
60
+ });
61
+
62
+ it("should fail validation in production if DATABASE_URL contains a loopback in the 127.x.x.x range", () => {
63
+ process.env.NODE_ENV = "production";
64
+ process.env.DATABASE_URL = "postgresql://127.0.0.2:5432/rebase";
65
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
66
+ process.env.FRONTEND_URL = "https://my-app.com";
67
+
68
+ expect(() => loadEnv()).toThrowError(/postgresql:\/\/127\.0\.0\.2:5432\/rebase/);
69
+ });
70
+
71
+ it("should succeed validation in production with a non-localhost DATABASE_URL", () => {
72
+ process.env.NODE_ENV = "production";
73
+ process.env.DATABASE_URL = "postgresql://db.my-app.com:5432/rebase";
74
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
75
+ process.env.FRONTEND_URL = "https://my-app.com";
76
+
77
+ expect(() => loadEnv()).not.toThrow();
78
+ const env = loadEnv();
79
+ expect(env.DATABASE_URL).toBe("postgresql://db.my-app.com:5432/rebase");
80
+ });
81
+
82
+ it("should allow localhost URLs in production if ALLOW_LOCALHOST_IN_PRODUCTION is set to true", () => {
83
+ process.env.NODE_ENV = "production";
84
+ process.env.DATABASE_URL = "postgresql://localhost:5432/rebase";
85
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
86
+ process.env.FRONTEND_URL = "https://my-app.com";
87
+ process.env.ALLOW_LOCALHOST_IN_PRODUCTION = "true";
88
+
89
+ expect(() => loadEnv()).not.toThrow();
90
+ const env = loadEnv();
91
+ expect(env.DATABASE_URL).toBe("postgresql://localhost:5432/rebase");
92
+ });
93
+
94
+ it("should not block localhost URLs in CORS_ORIGINS in production mode", () => {
95
+ process.env.NODE_ENV = "production";
96
+ process.env.DATABASE_URL = "postgresql://db.my-app.com:5432/rebase";
97
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
98
+ process.env.FRONTEND_URL = "https://my-app.com";
99
+ process.env.CORS_ORIGINS = "http://localhost:3000,https://my-app.com";
100
+
101
+ expect(() => loadEnv()).not.toThrow();
102
+ const env = loadEnv();
103
+ expect(env.CORS_ORIGINS).toBe("http://localhost:3000,https://my-app.com");
104
+ });
105
+
106
+ it("should validate and block localhost in extended variables", () => {
107
+ process.env.NODE_ENV = "production";
108
+ process.env.DATABASE_URL = "postgresql://db.my-app.com:5432/rebase";
109
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
110
+ process.env.FRONTEND_URL = "https://my-app.com";
111
+ // Extended URL points to localhost
112
+ process.env.EXTERNAL_SERVICE_URL = "https://localhost:8080/api";
113
+
114
+ const extension = {
115
+ extend: z.object({
116
+ EXTERNAL_SERVICE_URL: z.string().url(),
117
+ }),
118
+ };
119
+
120
+ expect(() => loadEnv(extension)).toThrowError(/https:\/\/localhost:8080\/api/);
121
+ });
122
+
123
+ it("should validate and block plain host string matching localhost", () => {
124
+ process.env.NODE_ENV = "production";
125
+ process.env.DATABASE_URL = "postgresql://db.my-app.com:5432/rebase";
126
+ process.env.JWT_SECRET = "12345678901234567890123456789012";
127
+ process.env.FRONTEND_URL = "https://my-app.com";
128
+ process.env.DB_HOST = "localhost";
129
+
130
+ const extension = {
131
+ extend: z.object({
132
+ DB_HOST: z.string(),
133
+ }),
134
+ };
135
+
136
+ expect(() => loadEnv(extension)).toThrowError(/localhost/);
137
+ });
138
+ });
@@ -156,36 +156,7 @@ describe("parseQueryOptions — PostgREST filters", () => {
156
156
  });
157
157
  });
158
158
 
159
- // ─────────────────────────────────────────────────────────────
160
- // parseQueryOptions — Legacy JSON where
161
- // ─────────────────────────────────────────────────────────────
162
- describe("parseQueryOptions — legacy JSON where", () => {
163
- it("parses JSON where string", () => {
164
- const result = parseQueryOptions({
165
- where: JSON.stringify({ status: ["==", "published"] })
166
- });
167
- expect(result.where?.status).toEqual(["==", "published"]);
168
- });
169
-
170
- it("accepts object where directly", () => {
171
- const result = parseQueryOptions({
172
- where: { status: ["==", "draft"] }
173
- });
174
- expect(result.where?.status).toEqual(["==", "draft"]);
175
- });
176
-
177
- it("throws for malformed JSON where", () => {
178
- expect(() => parseQueryOptions({ where: "not valid json{" })).toThrow("Invalid 'where' filter");
179
- });
180
159
 
181
- it("throws for array where", () => {
182
- expect(() => parseQueryOptions({ where: JSON.stringify([1, 2]) })).toThrow("Filter must be a JSON object");
183
- });
184
-
185
- it("throws for null where", () => {
186
- expect(() => parseQueryOptions({ where: JSON.stringify(null) })).toThrow("Filter must be a JSON object");
187
- });
188
- });
189
160
 
190
161
  // ─────────────────────────────────────────────────────────────
191
162
  // parseQueryOptions — Sorting
package/tsconfig.json CHANGED
@@ -35,6 +35,9 @@
35
35
  ],
36
36
  "@rebasepro/common": [
37
37
  "../common/src"
38
+ ],
39
+ "hono": [
40
+ "node_modules/hono"
38
41
  ]
39
42
  }
40
43
  },
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2020 Evan Wallace
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,3 +0,0 @@
1
- # esbuild
2
-
3
- This is a JavaScript bundler and minifier. See https://github.com/evanw/esbuild and the [JavaScript API documentation](https://esbuild.github.io/api/) for details.