@rebasepro/server-core 0.2.3 → 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.
Files changed (116) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +9 -0
  2. package/dist/common/src/collections/index.d.ts +1 -0
  3. package/dist/common/src/util/permissions.d.ts +1 -0
  4. package/dist/{index-BZoAtuqi.js → index-Cr1D21av.js} +11 -3
  5. package/dist/index-Cr1D21av.js.map +1 -0
  6. package/dist/index.es.js +2309 -370
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/index.umd.js +2298 -355
  9. package/dist/index.umd.js.map +1 -1
  10. package/dist/server-core/src/api/logs-routes.d.ts +37 -0
  11. package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
  12. package/dist/server-core/src/api/types.d.ts +6 -1
  13. package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
  14. package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
  15. package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
  16. package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
  17. package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
  18. package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
  19. package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
  20. package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
  21. package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
  22. package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
  23. package/dist/server-core/src/auth/index.d.ts +5 -3
  24. package/dist/server-core/src/auth/interfaces.d.ts +93 -3
  25. package/dist/server-core/src/auth/jwt.d.ts +3 -1
  26. package/dist/server-core/src/auth/mfa.d.ts +49 -0
  27. package/dist/server-core/src/auth/middleware.d.ts +7 -0
  28. package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
  29. package/dist/server-core/src/auth/routes.d.ts +3 -3
  30. package/dist/server-core/src/env.d.ts +6 -0
  31. package/dist/server-core/src/index.d.ts +1 -0
  32. package/dist/server-core/src/init.d.ts +3 -3
  33. package/dist/server-core/src/services/webhook-service.d.ts +29 -0
  34. package/dist/server-core/src/storage/image-transform.d.ts +48 -0
  35. package/dist/server-core/src/storage/index.d.ts +3 -0
  36. package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
  37. package/dist/types/src/controllers/auth.d.ts +4 -26
  38. package/dist/types/src/controllers/client.d.ts +25 -43
  39. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  40. package/dist/types/src/controllers/data.d.ts +4 -0
  41. package/dist/types/src/controllers/data_driver.d.ts +23 -0
  42. package/dist/types/src/controllers/registry.d.ts +5 -4
  43. package/dist/types/src/rebase_context.d.ts +1 -1
  44. package/dist/types/src/types/auth_adapter.d.ts +5 -60
  45. package/dist/types/src/types/backend.d.ts +2 -2
  46. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  47. package/dist/types/src/types/collections.d.ts +0 -4
  48. package/dist/types/src/types/component_ref.d.ts +1 -1
  49. package/dist/types/src/types/cron.d.ts +1 -1
  50. package/dist/types/src/types/entity_views.d.ts +1 -0
  51. package/dist/types/src/types/export_import.d.ts +1 -1
  52. package/dist/types/src/types/formex.d.ts +2 -2
  53. package/dist/types/src/types/properties.d.ts +9 -7
  54. package/dist/types/src/types/translations.d.ts +28 -12
  55. package/dist/types/src/types/user_management_delegate.d.ts +22 -57
  56. package/dist/types/src/users/index.d.ts +0 -1
  57. package/dist/types/src/users/user.d.ts +0 -1
  58. package/package.json +8 -6
  59. package/src/api/ast-schema-editor.ts +4 -4
  60. package/src/api/errors.ts +16 -7
  61. package/src/api/logs-routes.ts +129 -0
  62. package/src/api/rest/api-generator.ts +42 -2
  63. package/src/api/rest/query-parser.ts +37 -1
  64. package/src/api/types.ts +6 -1
  65. package/src/auth/adapter-middleware.ts +20 -4
  66. package/src/auth/admin-routes.ts +36 -100
  67. package/src/auth/api-keys/api-key-middleware.ts +126 -0
  68. package/src/auth/api-keys/api-key-permission-guard.ts +64 -0
  69. package/src/auth/api-keys/api-key-routes.ts +183 -0
  70. package/src/auth/api-keys/api-key-store.ts +317 -0
  71. package/src/auth/api-keys/api-key-types.ts +94 -0
  72. package/src/auth/api-keys/index.ts +37 -0
  73. package/src/auth/{auth-overrides.ts → auth-hooks.ts} +81 -14
  74. package/src/auth/builtin-auth-adapter.ts +35 -73
  75. package/src/auth/custom-auth-adapter.ts +1 -1
  76. package/src/auth/index.ts +7 -3
  77. package/src/auth/interfaces.ts +111 -3
  78. package/src/auth/jwt.ts +19 -5
  79. package/src/auth/mfa.ts +160 -0
  80. package/src/auth/middleware.ts +20 -1
  81. package/src/auth/rate-limiter.ts +92 -0
  82. package/src/auth/routes.ts +455 -24
  83. package/src/cron/cron-loader.ts +5 -10
  84. package/src/cron/cron-scheduler.ts +11 -12
  85. package/src/cron/cron-store.ts +8 -7
  86. package/src/env.ts +2 -0
  87. package/src/functions/function-loader.ts +6 -9
  88. package/src/index.ts +1 -2
  89. package/src/init.ts +56 -8
  90. package/src/serve-spa.ts +5 -4
  91. package/src/services/webhook-service.ts +155 -0
  92. package/src/storage/image-transform.ts +202 -0
  93. package/src/storage/index.ts +3 -0
  94. package/src/storage/routes.ts +56 -3
  95. package/src/storage/tus-handler.ts +315 -0
  96. package/src/utils/dev-port.ts +14 -0
  97. package/src/utils/logging.ts +9 -7
  98. package/test/admin-routes.test.ts +62 -164
  99. package/test/api-generator.test.ts +0 -1
  100. package/test/api-key-permission-guard.test.ts +132 -0
  101. package/test/ast-schema-editor.test.ts +26 -0
  102. package/test/auth-routes.test.ts +1 -2
  103. package/test/backend-hooks-admin.test.ts +3 -29
  104. package/test/custom-auth-adapter.test.ts +2 -10
  105. package/test/email-templates.test.ts +169 -0
  106. package/test/function-loader.test.ts +124 -0
  107. package/test/jwt.test.ts +4 -2
  108. package/test/mfa.test.ts +197 -0
  109. package/test/middleware.test.ts +10 -5
  110. package/test/webhook-service.test.ts +249 -0
  111. package/vite.config.ts +3 -2
  112. package/dist/index-BZoAtuqi.js.map +0 -1
  113. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  114. package/dist/types/src/users/roles.d.ts +0 -22
  115. package/src/bootstrappers/index.ts +0 -1
  116. package/src/singleton.test.ts +0 -28
@@ -0,0 +1,249 @@
1
+ import { WebhookDispatcher, WebhookConfig } from "../src/services/webhook-service";
2
+ import { createHmac } from "crypto";
3
+
4
+ // Mock global fetch
5
+ const mockFetch = jest.fn();
6
+ global.fetch = mockFetch as unknown as typeof fetch;
7
+
8
+ beforeEach(() => {
9
+ mockFetch.mockReset();
10
+ });
11
+
12
+ function makeWebhook(overrides: Partial<WebhookConfig> = {}): WebhookConfig {
13
+ return {
14
+ id: "wh_1",
15
+ url: "https://example.com/webhook",
16
+ events: ["INSERT", "UPDATE", "DELETE"],
17
+ table: "users",
18
+ enabled: true,
19
+ ...overrides
20
+ };
21
+ }
22
+
23
+ describe("WebhookDispatcher", () => {
24
+ describe("setWebhooks", () => {
25
+ it("filters out disabled webhooks", () => {
26
+ const dispatcher = new WebhookDispatcher();
27
+ dispatcher.setWebhooks([
28
+ makeWebhook({ id: "wh_1", enabled: true }),
29
+ makeWebhook({ id: "wh_2", enabled: false }),
30
+ makeWebhook({ id: "wh_3", enabled: true })
31
+ ]);
32
+
33
+ // Disabled webhook should not trigger
34
+ mockFetch.mockResolvedValue({
35
+ status: 200,
36
+ text: () => Promise.resolve("OK")
37
+ });
38
+
39
+ // This should only match enabled webhooks
40
+ });
41
+ });
42
+
43
+ describe("onEntityChange", () => {
44
+ it("returns empty array when no webhooks match", async () => {
45
+ const dispatcher = new WebhookDispatcher();
46
+ dispatcher.setWebhooks([makeWebhook({ table: "posts" })]);
47
+
48
+ const results = await dispatcher.onEntityChange(
49
+ "users", "INSERT", "id_1", { id: "id_1", name: "Test" }
50
+ );
51
+ expect(results).toEqual([]);
52
+ });
53
+
54
+ it("matches webhooks by table AND event", async () => {
55
+ const dispatcher = new WebhookDispatcher();
56
+ dispatcher.setWebhooks([makeWebhook({ table: "users", events: ["INSERT"] })]);
57
+
58
+ mockFetch.mockResolvedValue({
59
+ status: 200,
60
+ text: () => Promise.resolve("OK")
61
+ });
62
+
63
+ const results = await dispatcher.onEntityChange(
64
+ "users", "INSERT", "id_1", { id: "id_1" }
65
+ );
66
+ expect(results).toHaveLength(1);
67
+ expect(results[0].success).toBe(true);
68
+ });
69
+
70
+ it("does not match different table", async () => {
71
+ const dispatcher = new WebhookDispatcher();
72
+ dispatcher.setWebhooks([makeWebhook({ table: "users" })]);
73
+
74
+ const results = await dispatcher.onEntityChange(
75
+ "posts", "INSERT", "id_1", { id: "id_1" }
76
+ );
77
+ expect(results).toEqual([]);
78
+ });
79
+
80
+ it("does not match different event", async () => {
81
+ const dispatcher = new WebhookDispatcher();
82
+ dispatcher.setWebhooks([makeWebhook({ events: ["INSERT"] })]);
83
+
84
+ const results = await dispatcher.onEntityChange(
85
+ "users", "DELETE", "id_1", { id: "id_1" }
86
+ );
87
+ expect(results).toEqual([]);
88
+ });
89
+
90
+ it("includes HMAC signature when webhook has secret", async () => {
91
+ const secret = "my-webhook-secret";
92
+ const dispatcher = new WebhookDispatcher();
93
+ dispatcher.setWebhooks([makeWebhook({ secret })]);
94
+
95
+ mockFetch.mockResolvedValue({
96
+ status: 200,
97
+ text: () => Promise.resolve("OK")
98
+ });
99
+
100
+ await dispatcher.onEntityChange(
101
+ "users", "INSERT", "id_1", { id: "id_1" }
102
+ );
103
+
104
+ expect(mockFetch).toHaveBeenCalledTimes(1);
105
+ const [, options] = mockFetch.mock.calls[0];
106
+ const headers = options.headers as Record<string, string>;
107
+ expect(headers["X-Webhook-Signature"]).toBeDefined();
108
+ expect(headers["X-Webhook-Signature"]).toMatch(/^sha256=/);
109
+
110
+ // Verify the signature is correct
111
+ const body = options.body as string;
112
+ const expectedSig = createHmac("sha256", secret).update(body).digest("hex");
113
+ expect(headers["X-Webhook-Signature"]).toBe(`sha256=${expectedSig}`);
114
+ });
115
+
116
+ it("does not include signature header when no secret", async () => {
117
+ const dispatcher = new WebhookDispatcher();
118
+ dispatcher.setWebhooks([makeWebhook({ secret: undefined })]);
119
+
120
+ mockFetch.mockResolvedValue({
121
+ status: 200,
122
+ text: () => Promise.resolve("OK")
123
+ });
124
+
125
+ await dispatcher.onEntityChange(
126
+ "users", "INSERT", "id_1", { id: "id_1" }
127
+ );
128
+
129
+ const [, options] = mockFetch.mock.calls[0];
130
+ const headers = options.headers as Record<string, string>;
131
+ expect(headers["X-Webhook-Signature"]).toBeUndefined();
132
+ });
133
+
134
+ it("payload includes type, table, record, and timestamp", async () => {
135
+ const dispatcher = new WebhookDispatcher();
136
+ dispatcher.setWebhooks([makeWebhook()]);
137
+
138
+ mockFetch.mockResolvedValue({
139
+ status: 200,
140
+ text: () => Promise.resolve("OK")
141
+ });
142
+
143
+ const entity = { id: "id_1", name: "Test User" };
144
+ await dispatcher.onEntityChange("users", "INSERT", "id_1", entity);
145
+
146
+ const [, options] = mockFetch.mock.calls[0];
147
+ const payload = JSON.parse(options.body as string);
148
+ expect(payload.type).toBe("INSERT");
149
+ expect(payload.table).toBe("users");
150
+ expect(payload.record).toEqual(entity);
151
+ expect(payload.timestamp).toBeTruthy();
152
+ expect(payload.schema).toBe("public");
153
+ });
154
+
155
+ it("UPDATE payload includes old_record", async () => {
156
+ const dispatcher = new WebhookDispatcher();
157
+ dispatcher.setWebhooks([makeWebhook()]);
158
+
159
+ mockFetch.mockResolvedValue({
160
+ status: 200,
161
+ text: () => Promise.resolve("OK")
162
+ });
163
+
164
+ const entity = { id: "id_1", name: "Updated" };
165
+ const previous = { id: "id_1", name: "Original" };
166
+ await dispatcher.onEntityChange("users", "UPDATE", "id_1", entity, previous);
167
+
168
+ const [, options] = mockFetch.mock.calls[0];
169
+ const payload = JSON.parse(options.body as string);
170
+ expect(payload.old_record).toEqual(previous);
171
+ });
172
+
173
+ it("returns success: false on fetch failure", async () => {
174
+ const dispatcher = new WebhookDispatcher();
175
+ dispatcher.setWebhooks([makeWebhook()]);
176
+
177
+ mockFetch.mockRejectedValue(new Error("Network error"));
178
+
179
+ const results = await dispatcher.onEntityChange(
180
+ "users", "INSERT", "id_1", { id: "id_1" }
181
+ );
182
+
183
+ // After retries, last result should be failure
184
+ expect(results).toHaveLength(1);
185
+ expect(results[0].success).toBe(false);
186
+ expect(results[0].responseBody).toContain("Network error");
187
+ }, 30000);
188
+
189
+ it("returns success: false for non-2xx status codes", async () => {
190
+ const dispatcher = new WebhookDispatcher();
191
+ dispatcher.setWebhooks([makeWebhook()]);
192
+
193
+ mockFetch.mockResolvedValue({
194
+ status: 500,
195
+ text: () => Promise.resolve("Internal Server Error")
196
+ });
197
+
198
+ const results = await dispatcher.onEntityChange(
199
+ "users", "INSERT", "id_1", { id: "id_1" }
200
+ );
201
+
202
+ expect(results).toHaveLength(1);
203
+ expect(results[0].success).toBe(false);
204
+ expect(results[0].statusCode).toBe(500);
205
+ }, 30000);
206
+
207
+ it("includes custom headers from webhook config", async () => {
208
+ const dispatcher = new WebhookDispatcher();
209
+ dispatcher.setWebhooks([
210
+ makeWebhook({
211
+ headers: { "X-Custom": "my-value", "Authorization": "Bearer token" }
212
+ })
213
+ ]);
214
+
215
+ mockFetch.mockResolvedValue({
216
+ status: 200,
217
+ text: () => Promise.resolve("OK")
218
+ });
219
+
220
+ await dispatcher.onEntityChange("users", "INSERT", "id_1", { id: "id_1" });
221
+
222
+ const [, options] = mockFetch.mock.calls[0];
223
+ const headers = options.headers as Record<string, string>;
224
+ expect(headers["X-Custom"]).toBe("my-value");
225
+ expect(headers["Authorization"]).toBe("Bearer token");
226
+ });
227
+
228
+ it("dispatches to multiple matching webhooks", async () => {
229
+ const dispatcher = new WebhookDispatcher();
230
+ dispatcher.setWebhooks([
231
+ makeWebhook({ id: "wh_1", url: "https://a.com/hook" }),
232
+ makeWebhook({ id: "wh_2", url: "https://b.com/hook" })
233
+ ]);
234
+
235
+ mockFetch.mockResolvedValue({
236
+ status: 200,
237
+ text: () => Promise.resolve("OK")
238
+ });
239
+
240
+ const results = await dispatcher.onEntityChange(
241
+ "users", "INSERT", "id_1", { id: "id_1" }
242
+ );
243
+
244
+ expect(results).toHaveLength(2);
245
+ expect(results[0].webhookId).toBe("wh_1");
246
+ expect(results[1].webhookId).toBe("wh_2");
247
+ });
248
+ });
249
+ });
package/vite.config.ts CHANGED
@@ -24,7 +24,8 @@ const CONSUMER_EXTERNALS = [
24
24
  "chokidar",
25
25
  "fsevents",
26
26
  "ws",
27
- "ts-morph"
27
+ "ts-morph",
28
+ "sharp"
28
29
  ];
29
30
  const isExternal = (id: string) => {
30
31
  if (id.startsWith(".") || path.isAbsolute(id)) return false;
@@ -33,7 +34,7 @@ const isExternal = (id: string) => {
33
34
  // Externalize only deps the consumer app explicitly installs
34
35
  if (CONSUMER_EXTERNALS.some(ext => id === ext || id.startsWith(ext + "/"))) return true;
35
36
  // Externalize Node built-ins
36
- if (["fs", "path", "url", "util", "crypto", "http", "https", "net", "tls", "stream", "events", "os", "child_process", "buffer", "assert", "node:"].some(b => id === b || id.startsWith("node:") || id.startsWith(b + "/"))) return true;
37
+ if (["fs", "path", "url", "util", "crypto", "http", "https", "net", "tls", "stream", "events", "os", "child_process", "buffer", "assert", "dns", "zlib", "querystring", "node:"].some(b => id === b || id.startsWith("node:") || id.startsWith(b + "/"))) return true;
37
38
  // Inline everything else (jsonwebtoken, ws, zod, etc.)
38
39
  return false;
39
40
  };
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-BZoAtuqi.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
File without changes
@@ -1,22 +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
- /**
15
- * Permissions related to editing the collections
16
- */
17
- config?: {
18
- createCollections?: boolean;
19
- editCollections?: boolean | "own";
20
- deleteCollections?: boolean | "own";
21
- };
22
- };
@@ -1 +0,0 @@
1
- // PostgresBootstrapper was moved to @rebasepro/server-postgresql
@@ -1,28 +0,0 @@
1
- import { rebase, _initRebase } from "./singleton";
2
- import type { RebaseClient } from "@rebasepro/types";
3
-
4
- describe("rebase singleton", () => {
5
- beforeEach(() => {
6
- // Reset the singleton before each test
7
- // By calling _initRebase with null (cast to bypass type checking for the reset)
8
- _initRebase(null as unknown as RebaseClient);
9
- });
10
-
11
- it("should throw an error if accessed before initialization", () => {
12
- expect(() => rebase.data).toThrow(
13
- "rebase.data: server not initialized yet. The singleton is available after Rebase starts — don't call it at import time."
14
- );
15
- });
16
-
17
- it("should return the correctly initialized instance properties", () => {
18
- const mockClient = {
19
- data: { test: "mockData" },
20
- auth: { test: "mockAuth" }
21
- } as unknown as RebaseClient;
22
-
23
- _initRebase(mockClient);
24
-
25
- expect(rebase.data).toEqual({ test: "mockData" });
26
- expect(rebase.auth).toEqual({ test: "mockAuth" });
27
- });
28
- });