@rebasepro/server-core 0.5.0 → 0.6.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/dist/{server-core/src/api → api}/errors.d.ts +5 -2
- package/dist/{server-core/src/api → api}/rest/api-generator.d.ts +8 -2
- package/dist/{server-core/src/api → api}/types.d.ts +2 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/{server-core/src/auth → auth}/api-keys/index.d.ts +2 -2
- package/dist/{server-core/src/auth → auth}/auth-hooks.d.ts +37 -9
- package/dist/{server-core/src/auth → auth}/builtin-auth-adapter.d.ts +4 -4
- package/dist/{server-core/src/auth → auth}/index.d.ts +5 -2
- package/dist/auth/mfa-routes.d.ts +6 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/session-routes.d.ts +25 -0
- package/dist/backend-CIxN4FVm.js +15 -0
- package/dist/backend-CIxN4FVm.js.map +1 -0
- package/dist/chunk-Dze3rakg.js +42 -0
- package/dist/{server-core/src/cron → cron}/cron-scheduler.d.ts +1 -1
- package/dist/dist-CZKP-Xz4.js +832 -0
- package/dist/dist-CZKP-Xz4.js.map +1 -0
- package/dist/env.d.ts +102 -0
- package/dist/from-VbwD7xRf.js +3849 -0
- package/dist/from-VbwD7xRf.js.map +1 -0
- package/dist/{server-core/src/index.d.ts → index.d.ts} +1 -0
- package/dist/index.es.js +60867 -50462
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +73151 -51298
- package/dist/index.umd.js.map +1 -1
- package/dist/init/docs.d.ts +4 -0
- package/dist/init/health.d.ts +2 -0
- package/dist/init/middlewares.d.ts +10 -0
- package/dist/init/shutdown.d.ts +11 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/{server-core/src/init.d.ts → init.d.ts} +4 -4
- package/dist/jwt-DHcQRGC3.js +4168 -0
- package/dist/jwt-DHcQRGC3.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-BeBSuOXN.js +125 -0
- package/dist/ms-BeBSuOXN.js.map +1 -0
- package/dist/multipart-parser-CedBDOeC.js +299 -0
- package/dist/multipart-parser-CedBDOeC.js.map +1 -0
- package/dist/src-COaj0G3P.js +1182 -0
- package/dist/src-COaj0G3P.js.map +1 -0
- package/dist/{server-core/src/storage → storage}/image-transform.d.ts +3 -1
- package/dist/utils/request-id.d.ts +4 -0
- package/package.json +29 -29
- package/src/api/collections_for_test/callbacks_test_collection.ts +7 -6
- package/src/api/errors.ts +75 -19
- package/src/api/graphql/graphql-schema-generator.ts +15 -10
- package/src/api/logs-routes.ts +5 -2
- package/src/api/rest/api-generator-count.test.ts +21 -10
- package/src/api/rest/api-generator.ts +83 -24
- package/src/api/rest/query-parser.ts +18 -12
- package/src/api/server.ts +12 -1
- package/src/api/types.ts +2 -0
- package/src/auth/adapter-middleware.ts +17 -10
- package/src/auth/admin-user-ops.ts +236 -0
- package/src/auth/api-keys/api-key-middleware.ts +15 -9
- package/src/auth/api-keys/api-key-permission-guard.ts +1 -1
- package/src/auth/api-keys/api-key-routes.ts +3 -3
- package/src/auth/api-keys/api-key-store.ts +14 -8
- package/src/auth/api-keys/index.ts +2 -2
- package/src/auth/apple-oauth.ts +4 -3
- package/src/auth/auth-hooks.ts +50 -10
- package/src/auth/bitbucket-oauth.ts +5 -4
- package/src/auth/builtin-auth-adapter.ts +60 -39
- package/src/auth/custom-auth-adapter.ts +4 -5
- package/src/auth/discord-oauth.ts +5 -4
- package/src/auth/facebook-oauth.ts +5 -4
- package/src/auth/github-oauth.ts +6 -5
- package/src/auth/gitlab-oauth.ts +5 -4
- package/src/auth/google-oauth.ts +2 -1
- package/src/auth/index.ts +6 -2
- package/src/auth/jwt.ts +5 -4
- package/src/auth/linkedin-oauth.ts +4 -3
- package/src/auth/mfa-routes.ts +299 -0
- package/src/auth/mfa.ts +2 -1
- package/src/auth/microsoft-oauth.ts +5 -4
- package/src/auth/middleware.ts +5 -4
- package/src/auth/rate-limiter.ts +1 -1
- package/src/auth/reset-password-admin.ts +144 -0
- package/src/auth/rls-scope.ts +1 -1
- package/src/auth/routes.ts +52 -596
- package/src/auth/session-routes.ts +341 -0
- package/src/auth/slack-oauth.ts +6 -5
- package/src/auth/spotify-oauth.ts +5 -4
- package/src/auth/twitter-oauth.ts +4 -3
- package/src/collections/loader.ts +5 -4
- package/src/cron/cron-scheduler.test.ts +23 -11
- package/src/cron/cron-scheduler.ts +14 -8
- package/src/cron/cron-store.ts +1 -1
- package/src/email/smtp-email-service.ts +3 -2
- package/src/env.ts +11 -10
- package/src/index.ts +1 -0
- package/src/init/docs.ts +47 -0
- package/src/init/health.ts +37 -0
- package/src/init/middlewares.ts +61 -0
- package/src/init/shutdown.ts +56 -0
- package/src/init/storage.ts +57 -0
- package/src/init.ts +85 -279
- package/src/serve-spa.ts +12 -5
- package/src/services/driver-registry.ts +4 -3
- package/src/storage/S3StorageController.ts +1 -2
- package/src/storage/image-transform.ts +25 -10
- package/src/storage/routes.ts +30 -11
- package/src/storage/storage-registry.ts +4 -3
- package/src/storage/tus-handler.ts +12 -12
- package/src/utils/request-id.ts +40 -0
- package/src/utils/request-logger.ts +6 -0
- package/test/api-generator.test.ts +90 -2
- package/test/api-key-permission-guard.test.ts +24 -12
- package/test/auth-routes.test.ts +5 -3
- package/test/backend-hooks-data.test.ts +99 -30
- package/test/custom-auth-adapter.test.ts +16 -15
- package/test/email-templates.test.ts +10 -5
- package/test/env.test.ts +10 -10
- package/test/function-loader.test.ts +7 -4
- package/test/graphql-schema-generator.test.ts +554 -0
- package/test/jwt-security.test.ts +1 -1
- package/test/query-parser.test.ts +0 -1
- package/test/reset-password-admin.test.ts +113 -0
- package/test/singleton.test.ts +5 -5
- package/test/smtp-email-service.test.ts +21 -21
- package/test/webhook-service.test.ts +22 -11
- package/tsconfig.json +2 -0
- package/tsconfig.prod.json +3 -0
- package/vite.config.ts +5 -5
- package/dist/common/src/collections/CollectionRegistry.d.ts +0 -56
- package/dist/common/src/collections/default-collections.d.ts +0 -9
- package/dist/common/src/collections/index.d.ts +0 -2
- package/dist/common/src/data/buildRebaseData.d.ts +0 -14
- package/dist/common/src/data/query_builder.d.ts +0 -55
- package/dist/common/src/index.d.ts +0 -4
- package/dist/common/src/util/builders.d.ts +0 -57
- package/dist/common/src/util/callbacks.d.ts +0 -6
- package/dist/common/src/util/collections.d.ts +0 -11
- package/dist/common/src/util/common.d.ts +0 -2
- package/dist/common/src/util/conditions.d.ts +0 -26
- package/dist/common/src/util/entities.d.ts +0 -58
- package/dist/common/src/util/enums.d.ts +0 -3
- package/dist/common/src/util/index.d.ts +0 -16
- package/dist/common/src/util/navigation_from_path.d.ts +0 -34
- package/dist/common/src/util/navigation_utils.d.ts +0 -20
- package/dist/common/src/util/parent_references_from_path.d.ts +0 -6
- package/dist/common/src/util/paths.d.ts +0 -14
- package/dist/common/src/util/permissions.d.ts +0 -14
- package/dist/common/src/util/references.d.ts +0 -2
- package/dist/common/src/util/relations.d.ts +0 -22
- package/dist/common/src/util/resolutions.d.ts +0 -72
- package/dist/common/src/util/storage.d.ts +0 -24
- package/dist/index-Cr1D21av.js +0 -49
- package/dist/index-Cr1D21av.js.map +0 -1
- package/dist/server-core/src/auth/admin-routes.d.ts +0 -27
- package/dist/server-core/src/env.d.ts +0 -137
- package/dist/types/src/controllers/analytics_controller.d.ts +0 -7
- package/dist/types/src/controllers/auth.d.ts +0 -104
- package/dist/types/src/controllers/client.d.ts +0 -168
- package/dist/types/src/controllers/collection_registry.d.ts +0 -46
- package/dist/types/src/controllers/customization_controller.d.ts +0 -60
- package/dist/types/src/controllers/data.d.ts +0 -207
- package/dist/types/src/controllers/data_driver.d.ts +0 -218
- package/dist/types/src/controllers/database_admin.d.ts +0 -11
- package/dist/types/src/controllers/dialogs_controller.d.ts +0 -36
- package/dist/types/src/controllers/effective_role.d.ts +0 -4
- package/dist/types/src/controllers/email.d.ts +0 -36
- package/dist/types/src/controllers/index.d.ts +0 -18
- package/dist/types/src/controllers/local_config_persistence.d.ts +0 -20
- package/dist/types/src/controllers/navigation.d.ts +0 -225
- package/dist/types/src/controllers/registry.d.ts +0 -63
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +0 -67
- package/dist/types/src/controllers/side_entity_controller.d.ts +0 -97
- package/dist/types/src/controllers/snackbar.d.ts +0 -24
- package/dist/types/src/controllers/storage.d.ts +0 -171
- package/dist/types/src/index.d.ts +0 -4
- package/dist/types/src/rebase_context.d.ts +0 -122
- package/dist/types/src/types/auth_adapter.d.ts +0 -301
- package/dist/types/src/types/backend.d.ts +0 -571
- package/dist/types/src/types/backend_hooks.d.ts +0 -172
- package/dist/types/src/types/builders.d.ts +0 -15
- package/dist/types/src/types/chips.d.ts +0 -5
- package/dist/types/src/types/collections.d.ts +0 -961
- package/dist/types/src/types/component_ref.d.ts +0 -47
- package/dist/types/src/types/cron.d.ts +0 -102
- package/dist/types/src/types/data_source.d.ts +0 -64
- package/dist/types/src/types/database_adapter.d.ts +0 -94
- package/dist/types/src/types/entities.d.ts +0 -145
- package/dist/types/src/types/entity_actions.d.ts +0 -104
- package/dist/types/src/types/entity_callbacks.d.ts +0 -173
- package/dist/types/src/types/entity_link_builder.d.ts +0 -7
- package/dist/types/src/types/entity_overrides.d.ts +0 -10
- package/dist/types/src/types/entity_views.d.ts +0 -87
- package/dist/types/src/types/export_import.d.ts +0 -21
- package/dist/types/src/types/formex.d.ts +0 -40
- package/dist/types/src/types/index.d.ts +0 -28
- package/dist/types/src/types/locales.d.ts +0 -4
- package/dist/types/src/types/modify_collections.d.ts +0 -5
- package/dist/types/src/types/plugins.d.ts +0 -282
- package/dist/types/src/types/properties.d.ts +0 -1173
- package/dist/types/src/types/property_config.d.ts +0 -74
- package/dist/types/src/types/relations.d.ts +0 -336
- package/dist/types/src/types/slots.d.ts +0 -262
- package/dist/types/src/types/translations.d.ts +0 -900
- package/dist/types/src/types/user_management_delegate.d.ts +0 -86
- package/dist/types/src/types/websockets.d.ts +0 -78
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -50
- package/src/auth/admin-routes.ts +0 -534
- package/test/admin-routes.test.ts +0 -544
- package/test/backend-hooks-admin.test.ts +0 -388
- /package/dist/{server-core/src/api → api}/ast-schema-editor.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/collections_for_test/callbacks_test_collection.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/graphql-schema-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/graphql/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/logs-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/openapi-generator.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/index.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/rest/query-parser.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/schema-editor-routes.d.ts +0 -0
- /package/dist/{server-core/src/api → api}/server.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/adapter-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-permission-guard.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-store.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/api-keys/api-key-types.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/apple-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/bitbucket-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/crypto-utils.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/custom-auth-adapter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/discord-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/facebook-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/github-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/gitlab-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/google-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/jwt.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/linkedin-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/mfa.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/microsoft-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/middleware.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/password.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rate-limiter.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/rls-scope.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/routes.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/slack-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/spotify-oauth.d.ts +0 -0
- /package/dist/{server-core/src/auth → auth}/twitter-oauth.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/BackendCollectionRegistry.d.ts +0 -0
- /package/dist/{server-core/src/collections → collections}/loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-loader.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-routes.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/cron-store.d.ts +0 -0
- /package/dist/{server-core/src/cron → cron}/index.d.ts +0 -0
- /package/dist/{server-core/src/db → db}/interfaces.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/index.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/smtp-email-service.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/templates.d.ts +0 -0
- /package/dist/{server-core/src/email → email}/types.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-loader.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/function-routes.d.ts +0 -0
- /package/dist/{server-core/src/functions → functions}/index.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/history-routes.d.ts +0 -0
- /package/dist/{server-core/src/history → history}/index.d.ts +0 -0
- /package/dist/{server-core/src/serve-spa.d.ts → serve-spa.d.ts} +0 -0
- /package/dist/{server-core/src/services → services}/driver-registry.d.ts +0 -0
- /package/dist/{server-core/src/services → services}/webhook-service.d.ts +0 -0
- /package/dist/{server-core/src/singleton.d.ts → singleton.d.ts} +0 -0
- /package/dist/{server-core/src/storage → storage}/LocalStorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/S3StorageController.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/index.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/routes.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/storage-registry.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/tus-handler.d.ts +0 -0
- /package/dist/{server-core/src/storage → storage}/types.d.ts +0 -0
- /package/dist/{server-core/src/types → types}/index.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/dev-port.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/logging.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/request-logger.d.ts +0 -0
- /package/dist/{server-core/src/utils → utils}/sql.d.ts +0 -0
|
@@ -184,8 +184,9 @@ export class GraphQLSchemaGenerator {
|
|
|
184
184
|
id: { type: new GraphQLNonNull(GraphQLString) }
|
|
185
185
|
},
|
|
186
186
|
resolve: async (_, args, context: unknown) => {
|
|
187
|
-
const ctx = context as { driver
|
|
188
|
-
const ds = ctx
|
|
187
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
188
|
+
const ds = ctx?.driver;
|
|
189
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
189
190
|
const entity = await ds.fetchEntity({
|
|
190
191
|
path: collection.slug,
|
|
191
192
|
entityId: args.id,
|
|
@@ -207,8 +208,9 @@ defaultValue: 0 },
|
|
|
207
208
|
orderBy: { type: GraphQLString }
|
|
208
209
|
},
|
|
209
210
|
resolve: async (_, args, context: unknown) => {
|
|
210
|
-
const ctx = context as { driver
|
|
211
|
-
const ds = ctx
|
|
211
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
212
|
+
const ds = ctx?.driver;
|
|
213
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
212
214
|
let filter: FetchCollectionProps["filter"] | undefined;
|
|
213
215
|
if (args.where) {
|
|
214
216
|
try {
|
|
@@ -259,8 +261,9 @@ defaultValue: 0 },
|
|
|
259
261
|
input: { type: new GraphQLNonNull(inputType) }
|
|
260
262
|
},
|
|
261
263
|
resolve: async (_, args, context: unknown) => {
|
|
262
|
-
const ctx = context as { driver
|
|
263
|
-
const ds = ctx
|
|
264
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
265
|
+
const ds = ctx?.driver;
|
|
266
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
264
267
|
const path = collection.slug;
|
|
265
268
|
const entity = await ds.saveEntity({
|
|
266
269
|
path,
|
|
@@ -280,8 +283,9 @@ defaultValue: 0 },
|
|
|
280
283
|
input: { type: new GraphQLNonNull(inputType) }
|
|
281
284
|
},
|
|
282
285
|
resolve: async (_, args, context: unknown) => {
|
|
283
|
-
const ctx = context as { driver
|
|
284
|
-
const ds = ctx
|
|
286
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
287
|
+
const ds = ctx?.driver;
|
|
288
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
285
289
|
const entity = await ds.saveEntity({
|
|
286
290
|
path: collection.slug,
|
|
287
291
|
entityId: args.id,
|
|
@@ -301,8 +305,9 @@ defaultValue: 0 },
|
|
|
301
305
|
},
|
|
302
306
|
resolve: async (_, args, context: unknown) => {
|
|
303
307
|
try {
|
|
304
|
-
const ctx = context as { driver
|
|
305
|
-
const ds = ctx
|
|
308
|
+
const ctx = context as { driver?: DataDriver } | undefined;
|
|
309
|
+
const ds = ctx?.driver;
|
|
310
|
+
if (!ds) throw new Error("Scoped driver not available");
|
|
306
311
|
const existingEntity = await ds.fetchEntity({
|
|
307
312
|
path: collection.slug,
|
|
308
313
|
entityId: args.id,
|
package/src/api/logs-routes.ts
CHANGED
|
@@ -22,7 +22,8 @@ class LogRingBuffer {
|
|
|
22
22
|
|
|
23
23
|
push(entry: Omit<LogEntry, "id">): void {
|
|
24
24
|
const id = `log_${++this.idCounter}`;
|
|
25
|
-
this.buffer.push({ ...entry,
|
|
25
|
+
this.buffer.push({ ...entry,
|
|
26
|
+
id });
|
|
26
27
|
if (this.buffer.length > this.maxSize) {
|
|
27
28
|
this.buffer.shift();
|
|
28
29
|
}
|
|
@@ -95,11 +96,13 @@ export function logMiddleware(): MiddlewareHandler<HonoEnv> {
|
|
|
95
96
|
const start = Date.now();
|
|
96
97
|
await next();
|
|
97
98
|
const duration = Date.now() - start;
|
|
99
|
+
const reqId = c.get("requestId");
|
|
98
100
|
addLog("info", "api", `${c.req.method} ${c.req.path} ${c.res.status} ${duration}ms`, {
|
|
99
101
|
method: c.req.method,
|
|
100
102
|
path: c.req.path,
|
|
101
103
|
status: c.res.status,
|
|
102
|
-
duration
|
|
104
|
+
duration,
|
|
105
|
+
...(reqId && { requestId: reqId })
|
|
103
106
|
});
|
|
104
107
|
};
|
|
105
108
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jest } from "@jest/globals";
|
|
2
|
+
import { Hono } from "hono";
|
|
2
3
|
import { RestApiGenerator } from "./api-generator";
|
|
3
4
|
import type { DataDriver, Entity, EntityCollection, FetchCollectionProps } from "@rebasepro/types";
|
|
4
5
|
|
|
@@ -27,6 +28,21 @@ function createTestCollection(slug: string): EntityCollection {
|
|
|
27
28
|
} as unknown as EntityCollection;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Wraps generated routes in a parent Hono app that injects the driver
|
|
33
|
+
* into context — replicating what auth middleware does in production.
|
|
34
|
+
*/
|
|
35
|
+
function createApp(collections: EntityCollection[], driver: DataDriver): Hono {
|
|
36
|
+
const parent = new Hono();
|
|
37
|
+
parent.use("/*", async (c, next) => {
|
|
38
|
+
c.set("driver", driver);
|
|
39
|
+
await next();
|
|
40
|
+
});
|
|
41
|
+
const generator = new RestApiGenerator(collections, driver);
|
|
42
|
+
parent.route("/", generator.generateRoutes());
|
|
43
|
+
return parent;
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
describe("RestApiGenerator - Count Endpoint", () => {
|
|
31
47
|
let driver: DataDriver;
|
|
32
48
|
let collection: EntityCollection;
|
|
@@ -39,8 +55,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
39
55
|
});
|
|
40
56
|
|
|
41
57
|
it("GET /products/count should return a count object", async () => {
|
|
42
|
-
const
|
|
43
|
-
const app = generator.generateRoutes();
|
|
58
|
+
const app = createApp([collection], driver);
|
|
44
59
|
|
|
45
60
|
const res = await app.request("/products/count");
|
|
46
61
|
expect(res.status).toBe(200);
|
|
@@ -50,8 +65,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
50
65
|
});
|
|
51
66
|
|
|
52
67
|
it("should pass filters to countEntities driver", async () => {
|
|
53
|
-
const
|
|
54
|
-
const app = generator.generateRoutes();
|
|
68
|
+
const app = createApp([collection], driver);
|
|
55
69
|
|
|
56
70
|
const res = await app.request("/products/count?status=eq.active");
|
|
57
71
|
expect(res.status).toBe(200);
|
|
@@ -67,8 +81,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
67
81
|
});
|
|
68
82
|
|
|
69
83
|
it("should pass searchString to countEntities driver", async () => {
|
|
70
|
-
const
|
|
71
|
-
const app = generator.generateRoutes();
|
|
84
|
+
const app = createApp([collection], driver);
|
|
72
85
|
|
|
73
86
|
const res = await app.request("/products/count?searchString=widget");
|
|
74
87
|
expect(res.status).toBe(200);
|
|
@@ -83,8 +96,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
83
96
|
|
|
84
97
|
it("should return 0 when countEntities is not available on driver", async () => {
|
|
85
98
|
const driverWithoutCount = createMockDriver({ countEntities: undefined });
|
|
86
|
-
const
|
|
87
|
-
const app = generator.generateRoutes();
|
|
99
|
+
const app = createApp([collection], driverWithoutCount);
|
|
88
100
|
|
|
89
101
|
const res = await app.request("/products/count");
|
|
90
102
|
expect(res.status).toBe(200);
|
|
@@ -100,8 +112,7 @@ describe("RestApiGenerator - Count Endpoint", () => {
|
|
|
100
112
|
countEntities: jest.fn<NonNullable<DataDriver["countEntities"]>>().mockResolvedValue(99),
|
|
101
113
|
fetchEntity: fetchEntity as unknown as DataDriver["fetchEntity"]
|
|
102
114
|
});
|
|
103
|
-
const
|
|
104
|
-
const app = generator.generateRoutes();
|
|
115
|
+
const app = createApp([collection], driverCustom);
|
|
105
116
|
|
|
106
117
|
const res = await app.request("/products/count");
|
|
107
118
|
expect(res.status).toBe(200);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { DataDriver, Entity, EntityCollection, FetchCollectionProps, DataHooks, BackendHookContext, RestFetchService } from "@rebasepro/types";
|
|
2
|
+
import { AuthAdapter, DataDriver, Entity, EntityCollection, FetchCollectionProps, DataHooks, BackendHookContext, RestFetchService } from "@rebasepro/types";
|
|
3
3
|
import { QueryOptions, HonoEnv } from "../types";
|
|
4
4
|
import { ApiError, isRebaseApiError } from "../errors";
|
|
5
5
|
import { parseQueryOptions } from "./query-parser";
|
|
6
6
|
import { httpMethodToOperation, isOperationAllowed } from "../../auth/api-keys/api-key-permission-guard";
|
|
7
7
|
import type { ApiKeyMasked } from "../../auth/api-keys/api-key-types";
|
|
8
|
+
import { logger } from "../../utils/logger";
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -16,11 +17,18 @@ export class RestApiGenerator {
|
|
|
16
17
|
private router: Hono<HonoEnv>;
|
|
17
18
|
private driver: DataDriver;
|
|
18
19
|
private dataHooks?: DataHooks;
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
private authAdapter?: AuthAdapter;
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
collections: EntityCollection[],
|
|
24
|
+
driver: DataDriver,
|
|
25
|
+
dataHooks?: DataHooks,
|
|
26
|
+
authAdapter?: AuthAdapter
|
|
27
|
+
) {
|
|
21
28
|
this.collections = collections;
|
|
22
29
|
this.driver = driver;
|
|
23
30
|
this.dataHooks = dataHooks;
|
|
31
|
+
this.authAdapter = authAdapter;
|
|
24
32
|
this.router = new Hono<HonoEnv>();
|
|
25
33
|
}
|
|
26
34
|
|
|
@@ -28,7 +36,8 @@ export class RestApiGenerator {
|
|
|
28
36
|
private buildHookContext(c: { get: (key: string) => unknown }, method: BackendHookContext["method"]): BackendHookContext {
|
|
29
37
|
const user = c.get("user") as { userId: string; roles?: string[] } | undefined;
|
|
30
38
|
return {
|
|
31
|
-
requestUser: user ? { userId: user.userId,
|
|
39
|
+
requestUser: user ? { userId: user.userId,
|
|
40
|
+
roles: user.roles ?? [] } : undefined,
|
|
32
41
|
method
|
|
33
42
|
};
|
|
34
43
|
}
|
|
@@ -56,7 +65,7 @@ export class RestApiGenerator {
|
|
|
56
65
|
*/
|
|
57
66
|
private enforceApiKeyPermission(
|
|
58
67
|
c: { get: (key: string) => unknown; req: { method: string } },
|
|
59
|
-
collectionSlug: string
|
|
68
|
+
collectionSlug: string
|
|
60
69
|
): void {
|
|
61
70
|
const apiKey = c.get("apiKey") as ApiKeyMasked | undefined;
|
|
62
71
|
if (!apiKey) return; // Not an API key request — skip
|
|
@@ -65,11 +74,21 @@ export class RestApiGenerator {
|
|
|
65
74
|
if (!isOperationAllowed(apiKey.permissions, collectionSlug, operation)) {
|
|
66
75
|
throw ApiError.forbidden(
|
|
67
76
|
`API key does not have "${operation}" permission for collection "${collectionSlug}"`,
|
|
68
|
-
"API_KEY_FORBIDDEN"
|
|
77
|
+
"API_KEY_FORBIDDEN"
|
|
69
78
|
);
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Get the request-scoped driver. Throws if none is set — never falls
|
|
84
|
+
* back to the unscoped `this.driver` to avoid bypassing RLS/auth.
|
|
85
|
+
*/
|
|
86
|
+
private getScopedDriver(c: { get: (key: string) => unknown }): DataDriver {
|
|
87
|
+
const driver = c.get("driver") as DataDriver | undefined;
|
|
88
|
+
if (!driver) throw ApiError.internal("Scoped driver not available");
|
|
89
|
+
return driver;
|
|
90
|
+
}
|
|
91
|
+
|
|
73
92
|
/**
|
|
74
93
|
* Get the typed RestFetchService from a driver if it exposes one (for include support).
|
|
75
94
|
*/
|
|
@@ -90,7 +109,7 @@ export class RestApiGenerator {
|
|
|
90
109
|
const queryDict = c.req.queries();
|
|
91
110
|
const queryOptions = parseQueryOptions(queryDict);
|
|
92
111
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
93
|
-
const driver =
|
|
112
|
+
const driver = this.getScopedDriver(c);
|
|
94
113
|
|
|
95
114
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
96
115
|
return c.json({ count: total });
|
|
@@ -103,7 +122,7 @@ export class RestApiGenerator {
|
|
|
103
122
|
const queryOptions = parseQueryOptions(queryDict);
|
|
104
123
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
105
124
|
|
|
106
|
-
const driver =
|
|
125
|
+
const driver = this.getScopedDriver(c);
|
|
107
126
|
const fetchService = this.getFetchService(driver);
|
|
108
127
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
109
128
|
|
|
@@ -163,7 +182,7 @@ export class RestApiGenerator {
|
|
|
163
182
|
const id = c.req.param("id");
|
|
164
183
|
const queryDict = c.req.queries();
|
|
165
184
|
const queryOptions = parseQueryOptions(queryDict);
|
|
166
|
-
const driver =
|
|
185
|
+
const driver = this.getScopedDriver(c);
|
|
167
186
|
const fetchService = this.getFetchService(driver);
|
|
168
187
|
const hookCtx = this.buildHookContext(c, "GET");
|
|
169
188
|
|
|
@@ -207,7 +226,7 @@ export class RestApiGenerator {
|
|
|
207
226
|
this.router.post(basePath, async (c) => {
|
|
208
227
|
try {
|
|
209
228
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
210
|
-
const driver =
|
|
229
|
+
const driver = this.getScopedDriver(c);
|
|
211
230
|
const path = collection.slug;
|
|
212
231
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
213
232
|
|
|
@@ -217,6 +236,46 @@ export class RestApiGenerator {
|
|
|
217
236
|
body = await this.dataHooks.beforeSave(path, body, undefined, hookCtx);
|
|
218
237
|
}
|
|
219
238
|
|
|
239
|
+
const isAuth = collection.auth;
|
|
240
|
+
const isAuthCollection = isAuth === true || (isAuth && typeof isAuth === "object" && isAuth.enabled === true);
|
|
241
|
+
|
|
242
|
+
if (isAuthCollection && this.authAdapter?.prepareUserCreation) {
|
|
243
|
+
const collectionAuthConfig = typeof isAuth === "object" ? isAuth : undefined;
|
|
244
|
+
const prepared = await this.authAdapter.prepareUserCreation(body, collectionAuthConfig);
|
|
245
|
+
|
|
246
|
+
const entity = await driver.saveEntity({
|
|
247
|
+
path,
|
|
248
|
+
values: prepared.values,
|
|
249
|
+
collection: resolvedCollection,
|
|
250
|
+
status: "new"
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const result = prepared.hookHandledEmail
|
|
254
|
+
? { temporaryPassword: prepared.clearPassword,
|
|
255
|
+
invitationSent: prepared.invitationSent }
|
|
256
|
+
: this.authAdapter.finalizeUserCreation
|
|
257
|
+
? await this.authAdapter.finalizeUserCreation(
|
|
258
|
+
{ id: entity.id as string,
|
|
259
|
+
values: entity.values as Record<string, unknown> },
|
|
260
|
+
prepared.clearPassword
|
|
261
|
+
)
|
|
262
|
+
: { invitationSent: false };
|
|
263
|
+
|
|
264
|
+
const response = this.formatResponse(entity) as Record<string, unknown>;
|
|
265
|
+
|
|
266
|
+
if (this.dataHooks?.afterSave) {
|
|
267
|
+
Promise.resolve(this.dataHooks.afterSave(path, response, hookCtx)).catch(err => {
|
|
268
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return c.json({
|
|
273
|
+
...response,
|
|
274
|
+
invitationSent: result.invitationSent,
|
|
275
|
+
...(result.temporaryPassword ? { temporaryPassword: result.temporaryPassword } : {})
|
|
276
|
+
}, 201);
|
|
277
|
+
}
|
|
278
|
+
|
|
220
279
|
const entity = await driver.saveEntity({
|
|
221
280
|
path,
|
|
222
281
|
values: body,
|
|
@@ -228,7 +287,7 @@ export class RestApiGenerator {
|
|
|
228
287
|
|
|
229
288
|
if (this.dataHooks?.afterSave) {
|
|
230
289
|
Promise.resolve(this.dataHooks.afterSave(path, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
231
|
-
|
|
290
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
232
291
|
});
|
|
233
292
|
}
|
|
234
293
|
|
|
@@ -246,7 +305,7 @@ export class RestApiGenerator {
|
|
|
246
305
|
try {
|
|
247
306
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
248
307
|
const id = c.req.param("id");
|
|
249
|
-
const driver =
|
|
308
|
+
const driver = this.getScopedDriver(c);
|
|
250
309
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
251
310
|
|
|
252
311
|
const existingEntity = await driver.fetchEntity({
|
|
@@ -277,7 +336,7 @@ export class RestApiGenerator {
|
|
|
277
336
|
|
|
278
337
|
if (this.dataHooks?.afterSave) {
|
|
279
338
|
Promise.resolve(this.dataHooks.afterSave(collection.slug, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
280
|
-
|
|
339
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
281
340
|
});
|
|
282
341
|
}
|
|
283
342
|
|
|
@@ -294,7 +353,7 @@ export class RestApiGenerator {
|
|
|
294
353
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
295
354
|
this.enforceApiKeyPermission(c, collection.slug);
|
|
296
355
|
const id = c.req.param("id");
|
|
297
|
-
const driver =
|
|
356
|
+
const driver = this.getScopedDriver(c);
|
|
298
357
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
299
358
|
|
|
300
359
|
const existingEntity = await driver.fetchEntity({
|
|
@@ -318,7 +377,7 @@ export class RestApiGenerator {
|
|
|
318
377
|
|
|
319
378
|
if (this.dataHooks?.afterDelete) {
|
|
320
379
|
Promise.resolve(this.dataHooks.afterDelete(collection.slug, String(id), hookCtx)).catch(err => {
|
|
321
|
-
|
|
380
|
+
logger.error("[BackendHooks] data.afterDelete error", { error: err instanceof Error ? err.message : err });
|
|
322
381
|
});
|
|
323
382
|
}
|
|
324
383
|
|
|
@@ -384,7 +443,7 @@ entityId };
|
|
|
384
443
|
const parsed = parseSubPath(rawPath);
|
|
385
444
|
if (!parsed) return next();
|
|
386
445
|
|
|
387
|
-
const driver =
|
|
446
|
+
const driver = this.getScopedDriver(c);
|
|
388
447
|
|
|
389
448
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
390
449
|
|
|
@@ -395,13 +454,13 @@ entityId };
|
|
|
395
454
|
const queryDict = c.req.queries();
|
|
396
455
|
const queryOptions = parseQueryOptions(queryDict);
|
|
397
456
|
const searchString = Array.isArray(queryDict.searchString) ? queryDict.searchString[queryDict.searchString.length - 1] : undefined;
|
|
398
|
-
|
|
457
|
+
|
|
399
458
|
const total = driver.countEntities ? await driver.countEntities({
|
|
400
459
|
path: parsed.collectionPath,
|
|
401
460
|
filter: queryOptions.where as FetchCollectionProps["filter"],
|
|
402
461
|
searchString
|
|
403
462
|
}) : 0;
|
|
404
|
-
|
|
463
|
+
|
|
405
464
|
return c.json({ count: total });
|
|
406
465
|
} else if (parsed.entityId) {
|
|
407
466
|
// GET /parent/:parentId/child/:id — single entity
|
|
@@ -459,7 +518,7 @@ entityId };
|
|
|
459
518
|
const parsed = parseSubPath(rawPath);
|
|
460
519
|
if (!parsed || parsed.entityId) return next();
|
|
461
520
|
|
|
462
|
-
const driver =
|
|
521
|
+
const driver = this.getScopedDriver(c);
|
|
463
522
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
464
523
|
|
|
465
524
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
@@ -479,7 +538,7 @@ entityId };
|
|
|
479
538
|
|
|
480
539
|
if (this.dataHooks?.afterSave) {
|
|
481
540
|
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
482
|
-
|
|
541
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
483
542
|
});
|
|
484
543
|
}
|
|
485
544
|
|
|
@@ -494,7 +553,7 @@ entityId };
|
|
|
494
553
|
const parsed = parseSubPath(rawPath);
|
|
495
554
|
if (!parsed || !parsed.entityId) return next();
|
|
496
555
|
|
|
497
|
-
const driver =
|
|
556
|
+
const driver = this.getScopedDriver(c);
|
|
498
557
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
499
558
|
|
|
500
559
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
@@ -516,7 +575,7 @@ entityId };
|
|
|
516
575
|
|
|
517
576
|
if (this.dataHooks?.afterSave) {
|
|
518
577
|
Promise.resolve(this.dataHooks.afterSave(parsed.collectionPath, response as Record<string, unknown>, hookCtx)).catch(err => {
|
|
519
|
-
|
|
578
|
+
logger.error("[BackendHooks] data.afterSave error", { error: err instanceof Error ? err.message : err });
|
|
520
579
|
});
|
|
521
580
|
}
|
|
522
581
|
|
|
@@ -531,7 +590,7 @@ entityId };
|
|
|
531
590
|
const parsed = parseSubPath(rawPath);
|
|
532
591
|
if (!parsed || !parsed.entityId) return next();
|
|
533
592
|
|
|
534
|
-
const driver =
|
|
593
|
+
const driver = this.getScopedDriver(c);
|
|
535
594
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
536
595
|
|
|
537
596
|
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
@@ -551,7 +610,7 @@ entityId };
|
|
|
551
610
|
|
|
552
611
|
if (this.dataHooks?.afterDelete) {
|
|
553
612
|
Promise.resolve(this.dataHooks.afterDelete(parsed.collectionPath, parsed.entityId, hookCtx)).catch(err => {
|
|
554
|
-
|
|
613
|
+
logger.error("[BackendHooks] data.afterDelete error", { error: err instanceof Error ? err.message : err });
|
|
555
614
|
});
|
|
556
615
|
}
|
|
557
616
|
|
|
@@ -31,17 +31,21 @@ export function parseLogicalString(str: string): FilterCondition | LogicalCondit
|
|
|
31
31
|
str = str.trim();
|
|
32
32
|
if (str.startsWith("or(") && str.endsWith(")")) {
|
|
33
33
|
const inner = str.slice(3, -1);
|
|
34
|
-
return { type: "or",
|
|
34
|
+
return { type: "or",
|
|
35
|
+
conditions: parseLogicalList(inner) };
|
|
35
36
|
}
|
|
36
37
|
if (str.startsWith("and(") && str.endsWith(")")) {
|
|
37
38
|
const inner = str.slice(4, -1);
|
|
38
|
-
return { type: "and",
|
|
39
|
+
return { type: "and",
|
|
40
|
+
conditions: parseLogicalList(inner) };
|
|
39
41
|
}
|
|
40
|
-
|
|
42
|
+
|
|
41
43
|
// It's a leaf condition: field.op.val
|
|
42
44
|
const firstDot = str.indexOf(".");
|
|
43
45
|
if (firstDot === -1) {
|
|
44
|
-
return { column: str,
|
|
46
|
+
return { column: str,
|
|
47
|
+
operator: "==",
|
|
48
|
+
value: true };
|
|
45
49
|
}
|
|
46
50
|
const field = str.substring(0, firstDot);
|
|
47
51
|
const rest = str.substring(firstDot + 1);
|
|
@@ -55,7 +59,7 @@ export function parseLogicalString(str: string): FilterCondition | LogicalCondit
|
|
|
55
59
|
op = "eq";
|
|
56
60
|
valStr = rest;
|
|
57
61
|
}
|
|
58
|
-
|
|
62
|
+
|
|
59
63
|
const rebaseOp = (mapOperator(op) || "==") as FilterCondition["operator"];
|
|
60
64
|
let parsedVal: unknown = valStr;
|
|
61
65
|
if (valStr === "true") parsedVal = true;
|
|
@@ -73,20 +77,22 @@ export function parseLogicalString(str: string): FilterCondition | LogicalCondit
|
|
|
73
77
|
return trimmed;
|
|
74
78
|
});
|
|
75
79
|
}
|
|
76
|
-
|
|
77
|
-
return { column: field,
|
|
80
|
+
|
|
81
|
+
return { column: field,
|
|
82
|
+
operator: rebaseOp,
|
|
83
|
+
value: parsedVal };
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
function parseLogicalList(str: string): (FilterCondition | LogicalCondition)[] {
|
|
81
87
|
const list: (FilterCondition | LogicalCondition)[] = [];
|
|
82
88
|
let depth = 0;
|
|
83
89
|
let current = "";
|
|
84
|
-
|
|
90
|
+
|
|
85
91
|
for (let i = 0; i < str.length; i++) {
|
|
86
92
|
const char = str[i];
|
|
87
93
|
if (char === "(") depth++;
|
|
88
94
|
if (char === ")") depth--;
|
|
89
|
-
|
|
95
|
+
|
|
90
96
|
if (char === "," && depth === 0) {
|
|
91
97
|
list.push(parseLogicalString(current));
|
|
92
98
|
current = "";
|
|
@@ -109,10 +115,10 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
|
|
|
109
115
|
// Pagination
|
|
110
116
|
const limitVal = getLastValue(query.limit);
|
|
111
117
|
if (limitVal) options.limit = parseInt(String(limitVal));
|
|
112
|
-
|
|
118
|
+
|
|
113
119
|
const offsetVal = getLastValue(query.offset);
|
|
114
120
|
if (offsetVal) options.offset = parseInt(String(offsetVal));
|
|
115
|
-
|
|
121
|
+
|
|
116
122
|
const pageVal = getLastValue(query.page);
|
|
117
123
|
if (pageVal) {
|
|
118
124
|
const page = parseInt(String(pageVal));
|
|
@@ -275,7 +281,7 @@ export function parseQueryOptions(query: Record<string, unknown>): QueryOptions
|
|
|
275
281
|
const vectorSearch: VectorSearchParams = {
|
|
276
282
|
property: String(vectorSearchVal),
|
|
277
283
|
vector: queryVector,
|
|
278
|
-
distance: distanceParam
|
|
284
|
+
distance: distanceParam
|
|
279
285
|
};
|
|
280
286
|
|
|
281
287
|
const thresholdVal = getLastValue(query.vector_threshold);
|
package/src/api/server.ts
CHANGED
|
@@ -12,6 +12,8 @@ import { createSchemaEditorRoutes } from "./schema-editor-routes";
|
|
|
12
12
|
import { createAuthMiddleware, requireAuth, requireAdmin } from "../auth/middleware";
|
|
13
13
|
import { errorHandler } from "./errors";
|
|
14
14
|
import { generateOpenApiSpec } from "./openapi-generator";
|
|
15
|
+
import { requestId } from "../utils/request-id";
|
|
16
|
+
import { logger } from "../utils/logger";
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Simplified API server that leverages existing Rebase infrastructure
|
|
@@ -69,6 +71,9 @@ export class RebaseApiServer {
|
|
|
69
71
|
* Setup Hono middleware
|
|
70
72
|
*/
|
|
71
73
|
private setupMiddleware(): void {
|
|
74
|
+
// Request ID — must be first so all downstream middleware can access it
|
|
75
|
+
this.router.use("/*", requestId());
|
|
76
|
+
|
|
72
77
|
// Security headers — use same-origin-allow-popups for COOP so that
|
|
73
78
|
// OAuth popup flows (Google, etc.) can postMessage back to the opener.
|
|
74
79
|
this.router.use("/*", secureHeaders({
|
|
@@ -86,6 +91,12 @@ export class RebaseApiServer {
|
|
|
86
91
|
: (origin ?? "*"),
|
|
87
92
|
credentials: this.config.cors.credentials ?? false
|
|
88
93
|
}));
|
|
94
|
+
} else if (process.env.NODE_ENV !== "production") {
|
|
95
|
+
logger.warn(
|
|
96
|
+
"⚠️ [RebaseApiServer] No CORS configuration provided. " +
|
|
97
|
+
"The API will accept requests from any origin. " +
|
|
98
|
+
"Pass a `cors` option or configure CORS on your Hono app."
|
|
99
|
+
);
|
|
89
100
|
}
|
|
90
101
|
|
|
91
102
|
// Auth middleware
|
|
@@ -179,7 +190,7 @@ export class RebaseApiServer {
|
|
|
179
190
|
// Schema Editor endpoints
|
|
180
191
|
if (this.config.collectionsDir) {
|
|
181
192
|
if (process.env.NODE_ENV === "production") {
|
|
182
|
-
|
|
193
|
+
logger.warn("[RebaseApiServer] Schema Editor is disabled in production environments for security.");
|
|
183
194
|
} else {
|
|
184
195
|
// Auth middlewares applied to schema-editor via the router prefix
|
|
185
196
|
// MUST be declared before .route() so they execute first
|
package/src/api/types.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type HonoEnv = {
|
|
|
14
14
|
driver?: DataDriver;
|
|
15
15
|
/** Set when the request is authenticated via a Service API Key. */
|
|
16
16
|
apiKey?: ApiKeyMasked;
|
|
17
|
+
/** Unique request correlation ID (generated or propagated from X-Request-ID header). */
|
|
18
|
+
requestId?: string;
|
|
17
19
|
}
|
|
18
20
|
};
|
|
19
21
|
|
|
@@ -21,6 +21,7 @@ import type { HonoEnv } from "../api/types";
|
|
|
21
21
|
import type { ApiKeyStore } from "./api-keys/api-key-store";
|
|
22
22
|
import { scopeDataDriver } from "./rls-scope";
|
|
23
23
|
import { validateApiKey } from "./api-keys/api-key-middleware";
|
|
24
|
+
import { logger } from "../utils/logger";
|
|
24
25
|
|
|
25
26
|
export interface AdapterAuthMiddlewareOptions {
|
|
26
27
|
/** The auth adapter to delegate verification to. */
|
|
@@ -48,7 +49,8 @@ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOption
|
|
|
48
49
|
const authHeader = c.req.header("authorization") || "";
|
|
49
50
|
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
50
51
|
if (token.startsWith("rk_")) {
|
|
51
|
-
const result = await validateApiKey(c, token, { store: apiKeyStore,
|
|
52
|
+
const result = await validateApiKey(c, token, { store: apiKeyStore,
|
|
53
|
+
driver });
|
|
52
54
|
if (result === true) return next();
|
|
53
55
|
return result;
|
|
54
56
|
}
|
|
@@ -60,7 +62,8 @@ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOption
|
|
|
60
62
|
authenticatedUser = await adapter.verifyRequest(c.req.raw);
|
|
61
63
|
} catch (error) {
|
|
62
64
|
// adapter.verifyRequest() threw — reject the request (fail closed)
|
|
63
|
-
return c.json({ error: { message: "Unauthorized",
|
|
65
|
+
return c.json({ error: { message: "Unauthorized",
|
|
66
|
+
code: "UNAUTHORIZED" } }, 401);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
if (authenticatedUser) {
|
|
@@ -68,30 +71,34 @@ export function createAdapterAuthMiddleware(options: AdapterAuthMiddlewareOption
|
|
|
68
71
|
c.set("user", {
|
|
69
72
|
userId: authenticatedUser.uid,
|
|
70
73
|
email: authenticatedUser.email,
|
|
71
|
-
roles: authenticatedUser.roles
|
|
74
|
+
roles: authenticatedUser.roles
|
|
72
75
|
});
|
|
73
76
|
try {
|
|
74
77
|
c.set("driver", await scopeDataDriver(driver, {
|
|
75
78
|
uid: authenticatedUser.uid,
|
|
76
|
-
roles: authenticatedUser.roles
|
|
79
|
+
roles: authenticatedUser.roles
|
|
77
80
|
}));
|
|
78
81
|
} catch (error) {
|
|
79
|
-
|
|
80
|
-
return c.json({ error: { message: "Internal authentication error",
|
|
82
|
+
logger.error("[AUTH-ADAPTER] RLS scoping failed for authenticated user", { error: error });
|
|
83
|
+
return c.json({ error: { message: "Internal authentication error",
|
|
84
|
+
code: "INTERNAL_ERROR" } }, 500);
|
|
81
85
|
}
|
|
82
86
|
} else {
|
|
83
87
|
// Not authenticated — scope as anon for RLS evaluation
|
|
84
88
|
try {
|
|
85
|
-
c.set("driver", await scopeDataDriver(driver, { uid: "anon",
|
|
89
|
+
c.set("driver", await scopeDataDriver(driver, { uid: "anon",
|
|
90
|
+
roles: ["anon"] }));
|
|
86
91
|
} catch (error) {
|
|
87
|
-
|
|
88
|
-
return c.json({ error: { message: "Server configuration error",
|
|
92
|
+
logger.error("[AUTH-ADAPTER] Failed to create anon-scoped driver", { error: error });
|
|
93
|
+
return c.json({ error: { message: "Server configuration error",
|
|
94
|
+
code: "INTERNAL_ERROR" } }, 500);
|
|
89
95
|
}
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
// Enforce auth if required
|
|
93
99
|
if (enforceAuth && !c.get("user")) {
|
|
94
|
-
return c.json({ error: { message: "Unauthorized: Authentication required",
|
|
100
|
+
return c.json({ error: { message: "Unauthorized: Authentication required",
|
|
101
|
+
code: "UNAUTHORIZED" } }, 401);
|
|
95
102
|
}
|
|
96
103
|
|
|
97
104
|
return next();
|