@rebasepro/server-core 0.4.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/README.md +62 -25
- package/dist/{server-core/src/api → api}/errors.d.ts +20 -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/{server-core/src/auth → auth}/jwt.d.ts +10 -0
- 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 +60871 -50424
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +73151 -51256
- 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 +95 -20
- package/src/api/graphql/graphql-schema-generator.ts +15 -10
- package/src/api/logs-routes.ts +5 -2
- package/src/api/openapi-generator.ts +1 -1
- package/src/api/rest/api-generator-count.test.ts +21 -10
- package/src/api/rest/api-generator.ts +151 -36
- package/src/api/rest/query-parser.ts +18 -12
- package/src/api/server.ts +13 -2
- 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 +64 -54
- 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 +15 -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 -600
- 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 +93 -295
- 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 -6
- 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 -536
- 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 -941
- 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 -1181
- 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/test.ts +0 -6
- /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}/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
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
|
|
@@ -120,7 +131,7 @@ export class RebaseApiServer {
|
|
|
120
131
|
singularName: col.singularName,
|
|
121
132
|
description: col.description,
|
|
122
133
|
properties: Object.keys(col.properties),
|
|
123
|
-
relations:
|
|
134
|
+
relations: col.relations?.map((r: Relation) => ({
|
|
124
135
|
relationName: r.relationName,
|
|
125
136
|
target: typeof r.target === "function" ? r.target().slug : r.target,
|
|
126
137
|
cardinality: r.cardinality,
|
|
@@ -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();
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin User Operations
|
|
3
|
+
*
|
|
4
|
+
* Shared utilities and orchestration for admin-initiated user management
|
|
5
|
+
* (user creation via REST API, password reset via admin panel).
|
|
6
|
+
*
|
|
7
|
+
* Hook resolution order:
|
|
8
|
+
* 1. Collection-level hook (`auth.onCreateUser` on the collection) — closest to the data
|
|
9
|
+
* 2. Backend-level hook (`AuthHooks.onAdminCreateUser`) — global override
|
|
10
|
+
* 3. Built-in default — framework fallback
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { randomBytes, createHash, randomInt } from "node:crypto";
|
|
14
|
+
import type { AuthRepository } from "./interfaces";
|
|
15
|
+
import type { EmailService, EmailConfig } from "../email";
|
|
16
|
+
import type { ResolvedAuthHooks } from "./auth-hooks";
|
|
17
|
+
import type { AuthCollectionConfig, AuthCollectionContext } from "@rebasepro/types";
|
|
18
|
+
import { getPasswordResetTemplate } from "../email/templates";
|
|
19
|
+
import { logger } from "../utils/logger";
|
|
20
|
+
|
|
21
|
+
// ─── Shared Crypto Utilities ────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generate a cryptographically secure random password that meets strength requirements.
|
|
25
|
+
*
|
|
26
|
+
* 16 characters, guaranteed at least one uppercase, one lowercase, one digit.
|
|
27
|
+
* Ambiguous characters (0, O, 1, l, I) are excluded.
|
|
28
|
+
*/
|
|
29
|
+
export function generateSecurePassword(): string {
|
|
30
|
+
const upper = "ABCDEFGHJKLMNPQRSTUVWXYZ";
|
|
31
|
+
const lower = "abcdefghjkmnpqrstuvwxyz";
|
|
32
|
+
const digits = "23456789";
|
|
33
|
+
const all = upper + lower + digits;
|
|
34
|
+
|
|
35
|
+
const pick = (chars: string) => chars[randomInt(chars.length)];
|
|
36
|
+
const parts = [pick(upper), pick(lower), pick(digits)];
|
|
37
|
+
|
|
38
|
+
for (let i = parts.length; i < 16; i++) {
|
|
39
|
+
parts.push(pick(all));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Shuffle
|
|
43
|
+
for (let i = parts.length - 1; i > 0; i--) {
|
|
44
|
+
const j = randomInt(i + 1);
|
|
45
|
+
[parts[i], parts[j]] = [parts[j], parts[i]];
|
|
46
|
+
}
|
|
47
|
+
return parts.join("");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate a cryptographically secure random token (80 hex characters).
|
|
52
|
+
*/
|
|
53
|
+
export function generateSecureToken(): string {
|
|
54
|
+
return randomBytes(40).toString("hex");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Hash a token for database storage using SHA-256.
|
|
59
|
+
*/
|
|
60
|
+
export function hashToken(token: string): string {
|
|
61
|
+
return createHash("sha256").update(token).digest("hex");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Admin User Creation ────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Context needed by admin user creation / password reset operations.
|
|
68
|
+
*/
|
|
69
|
+
export interface AdminUserContext {
|
|
70
|
+
authRepo: AuthRepository;
|
|
71
|
+
emailService?: EmailService;
|
|
72
|
+
emailConfig?: EmailConfig;
|
|
73
|
+
resolvedHooks: ResolvedAuthHooks;
|
|
74
|
+
/** The parsed auth config from the collection (if `auth` is an object, not just `true`). */
|
|
75
|
+
collectionAuthConfig?: AuthCollectionConfig;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Result of preparing user values for admin-initiated creation.
|
|
80
|
+
*/
|
|
81
|
+
export interface AdminUserPrepareResult {
|
|
82
|
+
/** Values ready for `driver.saveEntity()`. */
|
|
83
|
+
values: Record<string, unknown>;
|
|
84
|
+
/** The cleartext password (for returning to admin or sending via email). */
|
|
85
|
+
clearPassword?: string;
|
|
86
|
+
/** Whether the hook already handled the invitation email. */
|
|
87
|
+
hookHandledEmail: boolean;
|
|
88
|
+
/** Whether an invitation was sent (only relevant when hookHandledEmail is true). */
|
|
89
|
+
invitationSent: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build the `AuthCollectionContext` facade from server internals.
|
|
94
|
+
*
|
|
95
|
+
* This is the simplified context exposed to collection-level auth hooks,
|
|
96
|
+
* keeping them decoupled from internal interfaces like `AuthRepository`.
|
|
97
|
+
*/
|
|
98
|
+
function buildCollectionContext(ctx: AdminUserContext): AuthCollectionContext {
|
|
99
|
+
const isEmailConfigured = !!(ctx.emailService && ctx.emailService.isConfigured());
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
hashPassword: (password: string) => ctx.resolvedHooks.hashPassword(password),
|
|
103
|
+
sendEmail: isEmailConfigured
|
|
104
|
+
? (options) => ctx.emailService!.send(options)
|
|
105
|
+
: undefined,
|
|
106
|
+
emailConfigured: isEmailConfigured,
|
|
107
|
+
appName: ctx.emailConfig?.appName || "Rebase",
|
|
108
|
+
resetPasswordUrl: ctx.emailConfig?.resetPasswordUrl || ""
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Prepare user values for an admin-initiated user creation.
|
|
114
|
+
*
|
|
115
|
+
* Resolution order:
|
|
116
|
+
* 1. Collection-level `auth.onCreateUser` — closest to the data
|
|
117
|
+
* 2. Backend-level `AuthHooks.onAdminCreateUser` — global override
|
|
118
|
+
* 3. Built-in default — generate password → hash → normalize email
|
|
119
|
+
*
|
|
120
|
+
* The caller is responsible for persisting (via `driver.saveEntity()`).
|
|
121
|
+
*/
|
|
122
|
+
export async function prepareAdminUserValues(
|
|
123
|
+
body: Record<string, unknown>,
|
|
124
|
+
ctx: AdminUserContext
|
|
125
|
+
): Promise<AdminUserPrepareResult> {
|
|
126
|
+
const { resolvedHooks, collectionAuthConfig } = ctx;
|
|
127
|
+
|
|
128
|
+
// 1. Collection-level hook (closest to the data)
|
|
129
|
+
if (collectionAuthConfig?.onCreateUser) {
|
|
130
|
+
const collectionCtx = buildCollectionContext(ctx);
|
|
131
|
+
const hookResult = await collectionAuthConfig.onCreateUser(body, collectionCtx);
|
|
132
|
+
return {
|
|
133
|
+
values: hookResult.values,
|
|
134
|
+
clearPassword: hookResult.temporaryPassword,
|
|
135
|
+
hookHandledEmail: true,
|
|
136
|
+
invitationSent: hookResult.invitationSent ?? false
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 2. Backend-level hook (global override)
|
|
141
|
+
if (resolvedHooks.onAdminCreateUser) {
|
|
142
|
+
const hookResult = await resolvedHooks.onAdminCreateUser(body, {
|
|
143
|
+
authRepo: ctx.authRepo,
|
|
144
|
+
emailService: ctx.emailService,
|
|
145
|
+
emailConfig: ctx.emailConfig,
|
|
146
|
+
hashPassword: (password: string) => resolvedHooks.hashPassword(password)
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
values: hookResult.values,
|
|
150
|
+
clearPassword: hookResult.temporaryPassword,
|
|
151
|
+
hookHandledEmail: true,
|
|
152
|
+
invitationSent: hookResult.invitationSent ?? false
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 3. Built-in default
|
|
157
|
+
const password = body.password as string | undefined;
|
|
158
|
+
const clearPassword = password || generateSecurePassword();
|
|
159
|
+
const passwordHash = await resolvedHooks.hashPassword(clearPassword);
|
|
160
|
+
|
|
161
|
+
const values = { ...body };
|
|
162
|
+
values.passwordHash = passwordHash;
|
|
163
|
+
if (values.email) {
|
|
164
|
+
values.email = (values.email as string).toLowerCase();
|
|
165
|
+
}
|
|
166
|
+
values.emailVerified = true;
|
|
167
|
+
delete values.password;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
values,
|
|
171
|
+
clearPassword: password ? undefined : clearPassword,
|
|
172
|
+
hookHandledEmail: false,
|
|
173
|
+
invitationSent: false
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Handle post-creation work for admin-created users.
|
|
179
|
+
*
|
|
180
|
+
* Sends an invitation email (password-reset link) if email is configured
|
|
181
|
+
* and no explicit password was provided. Falls back to returning the
|
|
182
|
+
* temporary password if email fails or is not configured.
|
|
183
|
+
*/
|
|
184
|
+
export async function finalizeAdminUserCreation(
|
|
185
|
+
entity: { id: string; values: Record<string, unknown> },
|
|
186
|
+
clearPassword: string | undefined,
|
|
187
|
+
ctx: AdminUserContext
|
|
188
|
+
): Promise<{
|
|
189
|
+
temporaryPassword?: string;
|
|
190
|
+
invitationSent: boolean;
|
|
191
|
+
}> {
|
|
192
|
+
// If an explicit password was provided (clearPassword is undefined), nothing to do
|
|
193
|
+
if (!clearPassword) {
|
|
194
|
+
return { invitationSent: false };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const isEmailConfigured = !!(ctx.emailService && ctx.emailService.isConfigured());
|
|
198
|
+
|
|
199
|
+
if (isEmailConfigured) {
|
|
200
|
+
try {
|
|
201
|
+
const token = generateSecureToken();
|
|
202
|
+
const tokenHash = hashToken(token);
|
|
203
|
+
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours
|
|
204
|
+
|
|
205
|
+
await ctx.authRepo.createPasswordResetToken(entity.id, tokenHash, expiresAt);
|
|
206
|
+
|
|
207
|
+
const baseUrl = ctx.emailConfig?.resetPasswordUrl || "";
|
|
208
|
+
const setPasswordUrl = `${baseUrl}/reset-password?token=${token}`;
|
|
209
|
+
|
|
210
|
+
const appName = ctx.emailConfig?.appName || "Rebase";
|
|
211
|
+
const templateFn = ctx.emailConfig?.templates?.passwordReset;
|
|
212
|
+
const emailContent = templateFn
|
|
213
|
+
? templateFn(setPasswordUrl, { email: entity.values.email as string,
|
|
214
|
+
displayName: entity.values.displayName as string })
|
|
215
|
+
: getPasswordResetTemplate(setPasswordUrl, { email: entity.values.email as string,
|
|
216
|
+
displayName: entity.values.displayName as string }, appName);
|
|
217
|
+
|
|
218
|
+
await ctx.emailService!.send({
|
|
219
|
+
to: entity.values.email as string,
|
|
220
|
+
subject: emailContent.subject,
|
|
221
|
+
html: emailContent.html,
|
|
222
|
+
text: emailContent.text
|
|
223
|
+
});
|
|
224
|
+
return { invitationSent: true };
|
|
225
|
+
} catch (emailError: unknown) {
|
|
226
|
+
logger.error("Failed to send reset email", { error: emailError instanceof Error ? emailError.message : emailError });
|
|
227
|
+
// Fall back to returning the temporary password
|
|
228
|
+
return { temporaryPassword: clearPassword,
|
|
229
|
+
invitationSent: false };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// No email service — return the temporary password
|
|
234
|
+
return { temporaryPassword: clearPassword,
|
|
235
|
+
invitationSent: false };
|
|
236
|
+
}
|
|
@@ -20,6 +20,7 @@ import type { HonoEnv } from "../../api/types";
|
|
|
20
20
|
import type { ApiKeyStore } from "./api-key-store";
|
|
21
21
|
import type { ApiKeyMasked } from "./api-key-types";
|
|
22
22
|
import { scopeDataDriver } from "../rls-scope";
|
|
23
|
+
import { logger } from "../../utils/logger";
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Check whether a token looks like a Rebase API key.
|
|
@@ -55,7 +56,7 @@ export interface ApiKeyAuthOptions {
|
|
|
55
56
|
export async function validateApiKey(
|
|
56
57
|
c: Context<HonoEnv>,
|
|
57
58
|
token: string,
|
|
58
|
-
options: ApiKeyAuthOptions
|
|
59
|
+
options: ApiKeyAuthOptions
|
|
59
60
|
): Promise<Response | true> {
|
|
60
61
|
const { store, driver } = options;
|
|
61
62
|
|
|
@@ -64,27 +65,31 @@ export async function validateApiKey(
|
|
|
64
65
|
|
|
65
66
|
if (!apiKey) {
|
|
66
67
|
return c.json({
|
|
67
|
-
error: { message: "Invalid API key",
|
|
68
|
+
error: { message: "Invalid API key",
|
|
69
|
+
code: "UNAUTHORIZED" }
|
|
68
70
|
}, 401);
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
// Check revocation
|
|
72
74
|
if (apiKey.revoked_at) {
|
|
73
75
|
return c.json({
|
|
74
|
-
error: { message: "API key has been revoked",
|
|
76
|
+
error: { message: "API key has been revoked",
|
|
77
|
+
code: "UNAUTHORIZED" }
|
|
75
78
|
}, 401);
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
// Check expiration
|
|
79
82
|
if (apiKey.expires_at && new Date(apiKey.expires_at) < new Date()) {
|
|
80
83
|
return c.json({
|
|
81
|
-
error: { message: "API key has expired",
|
|
84
|
+
error: { message: "API key has expired",
|
|
85
|
+
code: "UNAUTHORIZED" }
|
|
82
86
|
}, 401);
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
// Set user identity — API keys represent service accounts
|
|
86
90
|
const userId = `api-key:${apiKey.id}`;
|
|
87
|
-
c.set("user", { userId,
|
|
91
|
+
c.set("user", { userId,
|
|
92
|
+
roles: [] });
|
|
88
93
|
|
|
89
94
|
// Expose masked key metadata for downstream permission checks
|
|
90
95
|
const masked: ApiKeyMasked = {
|
|
@@ -98,7 +103,7 @@ export async function validateApiKey(
|
|
|
98
103
|
updated_at: apiKey.updated_at,
|
|
99
104
|
last_used_at: apiKey.last_used_at,
|
|
100
105
|
expires_at: apiKey.expires_at,
|
|
101
|
-
revoked_at: apiKey.revoked_at
|
|
106
|
+
revoked_at: apiKey.revoked_at
|
|
102
107
|
};
|
|
103
108
|
// Store apiKey in the context for permission checking in api-generator
|
|
104
109
|
c.set("apiKey", masked);
|
|
@@ -107,13 +112,14 @@ export async function validateApiKey(
|
|
|
107
112
|
try {
|
|
108
113
|
const scopedDriver = await scopeDataDriver(driver, {
|
|
109
114
|
uid: userId,
|
|
110
|
-
roles: ["service"]
|
|
115
|
+
roles: ["service"]
|
|
111
116
|
});
|
|
112
117
|
c.set("driver", scopedDriver);
|
|
113
118
|
} catch (error) {
|
|
114
|
-
|
|
119
|
+
logger.error("[AUTH] RLS scoping failed for API key", { error: error });
|
|
115
120
|
return c.json({
|
|
116
|
-
error: { message: "Internal authentication error",
|
|
121
|
+
error: { message: "Internal authentication error",
|
|
122
|
+
code: "INTERNAL_ERROR" }
|
|
117
123
|
}, 500);
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -52,7 +52,7 @@ export function httpMethodToOperation(method: string): ApiKeyOperation {
|
|
|
52
52
|
export function isOperationAllowed(
|
|
53
53
|
permissions: ApiKeyPermission[],
|
|
54
54
|
collection: string,
|
|
55
|
-
operation: ApiKeyOperation
|
|
55
|
+
operation: ApiKeyOperation
|
|
56
56
|
): boolean {
|
|
57
57
|
for (const perm of permissions) {
|
|
58
58
|
const collectionMatch = perm.collection === "*" || perm.collection === collection;
|
|
@@ -68,7 +68,7 @@ export function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv> {
|
|
|
68
68
|
if (!validatePermissions(permissions)) {
|
|
69
69
|
throw ApiError.badRequest(
|
|
70
70
|
"Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }",
|
|
71
|
-
"INVALID_INPUT"
|
|
71
|
+
"INVALID_INPUT"
|
|
72
72
|
);
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -97,7 +97,7 @@ export function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv> {
|
|
|
97
97
|
name: name.trim(),
|
|
98
98
|
permissions: permissions as ApiKeyPermission[],
|
|
99
99
|
rate_limit: (rate_limit as number | null) ?? null,
|
|
100
|
-
expires_at: (expires_at as string | null) ?? null
|
|
100
|
+
expires_at: (expires_at as string | null) ?? null
|
|
101
101
|
};
|
|
102
102
|
|
|
103
103
|
const keyWithSecret = await store.createApiKey(request, createdBy);
|
|
@@ -134,7 +134,7 @@ export function createApiKeyRoutes(options: ApiKeyRouteOptions): Hono<HonoEnv> {
|
|
|
134
134
|
if (!validatePermissions(permissions)) {
|
|
135
135
|
throw ApiError.badRequest(
|
|
136
136
|
"Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }",
|
|
137
|
-
"INVALID_INPUT"
|
|
137
|
+
"INVALID_INPUT"
|
|
138
138
|
);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -15,9 +15,10 @@ import { logger } from "../../utils/logger";
|
|
|
15
15
|
import type {
|
|
16
16
|
ApiKey,
|
|
17
17
|
ApiKeyMasked,
|
|
18
|
+
ApiKeyPermission,
|
|
18
19
|
ApiKeyWithSecret,
|
|
19
20
|
CreateApiKeyRequest,
|
|
20
|
-
UpdateApiKeyRequest
|
|
21
|
+
UpdateApiKeyRequest
|
|
21
22
|
} from "./api-key-types";
|
|
22
23
|
|
|
23
24
|
const TABLE = "rebase.api_keys";
|
|
@@ -64,7 +65,7 @@ function toMasked(row: ApiKey): ApiKeyMasked {
|
|
|
64
65
|
updated_at: row.updated_at,
|
|
65
66
|
last_used_at: row.last_used_at,
|
|
66
67
|
expires_at: row.expires_at,
|
|
67
|
-
revoked_at: row.revoked_at
|
|
68
|
+
revoked_at: row.revoked_at
|
|
68
69
|
};
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -72,9 +73,14 @@ function toMasked(row: ApiKey): ApiKeyMasked {
|
|
|
72
73
|
* Parse a raw DB row into the typed `ApiKey` shape.
|
|
73
74
|
*/
|
|
74
75
|
function rowToApiKey(row: Record<string, unknown>): ApiKey {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
let permissions = (row.permissions ?? []) as ApiKeyPermission[];
|
|
77
|
+
if (typeof row.permissions === "string") {
|
|
78
|
+
try {
|
|
79
|
+
permissions = JSON.parse(row.permissions) as ApiKeyPermission[];
|
|
80
|
+
} catch {
|
|
81
|
+
permissions = [];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
78
84
|
|
|
79
85
|
return {
|
|
80
86
|
id: row.id as string,
|
|
@@ -90,7 +96,7 @@ function rowToApiKey(row: Record<string, unknown>): ApiKey {
|
|
|
90
96
|
updated_at: new Date(row.updated_at as string).toISOString(),
|
|
91
97
|
last_used_at: row.last_used_at ? new Date(row.last_used_at as string).toISOString() : null,
|
|
92
98
|
expires_at: row.expires_at ? new Date(row.expires_at as string).toISOString() : null,
|
|
93
|
-
revoked_at: row.revoked_at ? new Date(row.revoked_at as string).toISOString() : null
|
|
99
|
+
revoked_at: row.revoked_at ? new Date(row.revoked_at as string).toISOString() : null
|
|
94
100
|
};
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -212,7 +218,7 @@ export function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined {
|
|
|
212
218
|
const apiKey = rowToApiKey(rows[0]);
|
|
213
219
|
return {
|
|
214
220
|
...toMasked(apiKey),
|
|
215
|
-
key: plaintext
|
|
221
|
+
key: plaintext
|
|
216
222
|
};
|
|
217
223
|
},
|
|
218
224
|
|
|
@@ -312,6 +318,6 @@ export function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined {
|
|
|
312
318
|
// Non-blocking — don't fail requests because of a usage timestamp update
|
|
313
319
|
logger.error("[api-key-store] Failed to update last_used_at", { error: err });
|
|
314
320
|
}
|
|
315
|
-
}
|
|
321
|
+
}
|
|
316
322
|
};
|
|
317
323
|
}
|
|
@@ -14,7 +14,7 @@ export type {
|
|
|
14
14
|
ApiKeyPermission,
|
|
15
15
|
ApiKeyWithSecret,
|
|
16
16
|
CreateApiKeyRequest,
|
|
17
|
-
UpdateApiKeyRequest
|
|
17
|
+
UpdateApiKeyRequest
|
|
18
18
|
} from "./api-key-types";
|
|
19
19
|
|
|
20
20
|
// Store
|
|
@@ -28,7 +28,7 @@ export type { ApiKeyAuthOptions } from "./api-key-middleware";
|
|
|
28
28
|
// Permission guard
|
|
29
29
|
export {
|
|
30
30
|
httpMethodToOperation,
|
|
31
|
-
isOperationAllowed
|
|
31
|
+
isOperationAllowed
|
|
32
32
|
} from "./api-key-permission-guard";
|
|
33
33
|
export type { ApiKeyOperation } from "./api-key-permission-guard";
|
|
34
34
|
|
package/src/auth/apple-oauth.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import jwt from "jsonwebtoken";
|
|
4
|
+
import { logger } from "../utils/logger";
|
|
4
5
|
/**
|
|
5
6
|
* Creates an Apple Sign In OAuth Provider integration.
|
|
6
7
|
*
|
|
@@ -75,7 +76,7 @@ export function createAppleProvider(config: {
|
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
if (!tokenResponse.ok) {
|
|
78
|
-
|
|
79
|
+
logger.error("Failed to get Apple access token", { detail: await tokenResponse.text() });
|
|
79
80
|
return null;
|
|
80
81
|
}
|
|
81
82
|
|
|
@@ -95,7 +96,7 @@ export function createAppleProvider(config: {
|
|
|
95
96
|
// the user object from the first auth for us to capture the name.
|
|
96
97
|
const email = decoded.email || payload.user?.email;
|
|
97
98
|
if (!email) {
|
|
98
|
-
|
|
99
|
+
logger.error("Apple user has no email");
|
|
99
100
|
return null;
|
|
100
101
|
}
|
|
101
102
|
|
|
@@ -112,7 +113,7 @@ export function createAppleProvider(config: {
|
|
|
112
113
|
photoUrl: null // Apple does not provide a profile photo
|
|
113
114
|
};
|
|
114
115
|
} catch (error) {
|
|
115
|
-
|
|
116
|
+
logger.error("Apple OAuth error", { error: error });
|
|
116
117
|
return null;
|
|
117
118
|
}
|
|
118
119
|
}
|