@rebasepro/server-core 0.7.0 → 0.9.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 +2 -2
- package/dist/api/ast-schema-editor.d.ts +21 -0
- package/dist/api/collections_for_test/callbacks_test_collection.d.ts +2 -0
- package/dist/api/errors.d.ts +53 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/logs-routes.d.ts +37 -0
- package/dist/api/openapi-generator.d.ts +16 -0
- package/dist/api/rest/api-generator.d.ts +68 -0
- package/{src/api/rest/index.ts → dist/api/rest/index.d.ts} +0 -1
- package/dist/api/rest/query-parser.d.ts +6 -0
- package/dist/api/schema-editor-routes.d.ts +3 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/auth/adapter-middleware.d.ts +43 -0
- package/dist/auth/admin-roles-route.d.ts +18 -0
- package/dist/auth/admin-user-ops.d.ts +79 -0
- package/dist/auth/admin-users-route.d.ts +28 -0
- package/dist/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/{src/auth/api-keys/api-key-permission-guard.ts → dist/auth/api-keys/api-key-permission-guard.d.ts} +2 -34
- package/dist/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/auth/api-keys/api-key-store.d.ts +35 -0
- package/{src/auth/api-keys/api-key-types.ts → dist/auth/api-keys/api-key-types.d.ts} +0 -6
- package/{src/auth/api-keys/index.ts → dist/auth/api-keys/index.d.ts} +2 -22
- package/dist/auth/apple-oauth.d.ts +30 -0
- package/{src/auth/auth-hooks.ts → dist/auth/auth-hooks.d.ts} +15 -73
- package/dist/auth/bitbucket-oauth.d.ts +11 -0
- package/dist/auth/builtin-auth-adapter.d.ts +55 -0
- package/dist/auth/cookie-utils.d.ts +32 -0
- package/{src/auth/crypto-utils.ts → dist/auth/crypto-utils.d.ts} +1 -16
- package/dist/auth/custom-auth-adapter.d.ts +39 -0
- package/dist/auth/discord-oauth.d.ts +14 -0
- package/dist/auth/facebook-oauth.d.ts +14 -0
- package/dist/auth/github-oauth.d.ts +15 -0
- package/dist/auth/gitlab-oauth.d.ts +13 -0
- package/dist/auth/google-oauth.d.ts +47 -0
- package/{src/auth/index.ts → dist/auth/index.d.ts} +4 -22
- package/{src/auth/interfaces.ts → dist/auth/interfaces.d.ts} +20 -89
- package/dist/auth/jwt.d.ts +67 -0
- package/dist/auth/linkedin-oauth.d.ts +18 -0
- package/dist/auth/magic-link-routes.d.ts +32 -0
- package/dist/auth/mfa-crypto.d.ts +23 -0
- package/dist/auth/mfa-routes.d.ts +7 -0
- package/dist/auth/mfa.d.ts +49 -0
- package/dist/auth/microsoft-oauth.d.ts +16 -0
- package/dist/auth/middleware.d.ts +158 -0
- package/dist/auth/password.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/reset-password-admin.d.ts +29 -0
- package/dist/auth/rls-scope.d.ts +31 -0
- package/dist/auth/routes.d.ts +66 -0
- package/dist/auth/session-routes.d.ts +29 -0
- package/dist/auth/slack-oauth.d.ts +12 -0
- package/dist/auth/spotify-oauth.d.ts +12 -0
- package/dist/auth/twitter-oauth.d.ts +18 -0
- package/dist/collections/BackendCollectionRegistry.d.ts +13 -0
- package/dist/collections/loader.d.ts +5 -0
- package/dist/cron/cron-loader.d.ts +18 -0
- package/dist/cron/cron-routes.d.ts +14 -0
- package/dist/cron/cron-scheduler.d.ts +106 -0
- package/dist/cron/cron-store.d.ts +32 -0
- package/dist/cron/define-cron.d.ts +28 -0
- package/{src/cron/index.ts → dist/cron/index.d.ts} +1 -0
- package/dist/db/interfaces.d.ts +18 -0
- package/dist/dynamic-import-Dvh-K5fl.js +22 -0
- package/dist/dynamic-import-Dvh-K5fl.js.map +1 -0
- package/dist/email/index.d.ts +6 -0
- package/dist/email/smtp-email-service.d.ts +30 -0
- package/dist/email/templates.d.ts +50 -0
- package/{src/email/types.ts → dist/email/types.d.ts} +40 -36
- package/dist/env.d.ts +102 -0
- package/dist/functions/define-function.d.ts +55 -0
- package/dist/functions/function-loader.d.ts +18 -0
- package/dist/functions/function-routes.d.ts +10 -0
- package/{src/functions/index.ts → dist/functions/index.d.ts} +2 -0
- package/dist/history/history-routes.d.ts +23 -0
- package/{src/history/index.ts → dist/history/index.d.ts} +0 -1
- package/dist/index.d.ts +35 -0
- package/dist/index.es.js +18184 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +48629 -0
- package/dist/index.umd.js.map +1 -0
- 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 +65 -0
- package/dist/init/storage.d.ts +5 -0
- package/dist/init.d.ts +344 -0
- package/dist/jwt-BwIn8xmk.js +4200 -0
- package/dist/jwt-BwIn8xmk.js.map +1 -0
- package/dist/logger-BYU66ENZ.js +94 -0
- package/dist/logger-BYU66ENZ.js.map +1 -0
- package/dist/ms-DnYXB-Wd.js +162 -0
- package/dist/ms-DnYXB-Wd.js.map +1 -0
- package/dist/openapi-generator-Z9oYWLf_.js +586 -0
- package/dist/openapi-generator-Z9oYWLf_.js.map +1 -0
- package/dist/schema-editor-routes-D3ef8zu1.js +219 -0
- package/dist/schema-editor-routes-D3ef8zu1.js.map +1 -0
- package/dist/serve-spa.d.ts +36 -0
- package/dist/services/driver-registry.d.ts +78 -0
- package/dist/services/routed-realtime-service.d.ts +43 -0
- package/dist/services/webhook-service.d.ts +29 -0
- package/dist/singleton.d.ts +51 -0
- package/dist/src-B4OLmNVa.js +437 -0
- package/dist/src-B4OLmNVa.js.map +1 -0
- package/dist/src-B5WkP1Rv.js +24778 -0
- package/dist/src-B5WkP1Rv.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +43 -0
- package/dist/storage/LocalStorageController.d.ts +46 -0
- package/dist/storage/S3StorageController.d.ts +40 -0
- package/dist/storage/image-transform.d.ts +50 -0
- package/{src/storage/index.ts → dist/storage/index.d.ts} +6 -21
- package/dist/storage/routes.d.ts +70 -0
- package/dist/storage/storage-registry.d.ts +78 -0
- package/dist/storage/tus-handler.d.ts +53 -0
- package/{src/storage/types.ts → dist/storage/types.d.ts} +32 -38
- package/dist/types/index.d.ts +11 -0
- package/dist/utils/dev-port.d.ts +45 -0
- package/dist/utils/dynamic-import.d.ts +25 -0
- package/dist/utils/logger.d.ts +31 -0
- package/dist/utils/logging.d.ts +9 -0
- package/dist/utils/request-id.d.ts +4 -0
- package/dist/utils/request-logger.d.ts +19 -0
- package/{src/utils/sql.ts → dist/utils/sql.d.ts} +5 -16
- package/package.json +49 -22
- package/coverage/clover.xml +0 -3739
- package/coverage/coverage-final.json +0 -31
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +0 -266
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov-report/src/api/ast-schema-editor.ts.html +0 -952
- package/coverage/lcov-report/src/api/errors.ts.html +0 -472
- package/coverage/lcov-report/src/api/graphql/graphql-schema-generator.ts.html +0 -1069
- package/coverage/lcov-report/src/api/graphql/index.html +0 -116
- package/coverage/lcov-report/src/api/index.html +0 -176
- package/coverage/lcov-report/src/api/openapi-generator.ts.html +0 -565
- package/coverage/lcov-report/src/api/rest/api-generator.ts.html +0 -994
- package/coverage/lcov-report/src/api/rest/index.html +0 -131
- package/coverage/lcov-report/src/api/rest/query-parser.ts.html +0 -550
- package/coverage/lcov-report/src/api/schema-editor-routes.ts.html +0 -202
- package/coverage/lcov-report/src/api/server.ts.html +0 -823
- package/coverage/lcov-report/src/auth/admin-routes.ts.html +0 -973
- package/coverage/lcov-report/src/auth/index.html +0 -176
- package/coverage/lcov-report/src/auth/jwt.ts.html +0 -574
- package/coverage/lcov-report/src/auth/middleware.ts.html +0 -745
- package/coverage/lcov-report/src/auth/password.ts.html +0 -310
- package/coverage/lcov-report/src/auth/services.ts.html +0 -2074
- package/coverage/lcov-report/src/collections/index.html +0 -116
- package/coverage/lcov-report/src/collections/loader.ts.html +0 -232
- package/coverage/lcov-report/src/db/auth-schema.ts.html +0 -523
- package/coverage/lcov-report/src/db/data-transformer.ts.html +0 -1753
- package/coverage/lcov-report/src/db/entityService.ts.html +0 -700
- package/coverage/lcov-report/src/db/index.html +0 -146
- package/coverage/lcov-report/src/db/services/EntityFetchService.ts.html +0 -4048
- package/coverage/lcov-report/src/db/services/EntityPersistService.ts.html +0 -883
- package/coverage/lcov-report/src/db/services/RelationService.ts.html +0 -3121
- package/coverage/lcov-report/src/db/services/entity-helpers.ts.html +0 -442
- package/coverage/lcov-report/src/db/services/index.html +0 -176
- package/coverage/lcov-report/src/db/services/index.ts.html +0 -124
- package/coverage/lcov-report/src/generate-drizzle-schema-logic.ts.html +0 -1960
- package/coverage/lcov-report/src/index.html +0 -116
- package/coverage/lcov-report/src/services/driver-registry.ts.html +0 -631
- package/coverage/lcov-report/src/services/index.html +0 -131
- package/coverage/lcov-report/src/services/postgresDataDriver.ts.html +0 -3025
- package/coverage/lcov-report/src/storage/LocalStorageController.ts.html +0 -1189
- package/coverage/lcov-report/src/storage/S3StorageController.ts.html +0 -970
- package/coverage/lcov-report/src/storage/index.html +0 -161
- package/coverage/lcov-report/src/storage/storage-registry.ts.html +0 -646
- package/coverage/lcov-report/src/storage/types.ts.html +0 -451
- package/coverage/lcov-report/src/utils/drizzle-conditions.ts.html +0 -3082
- package/coverage/lcov-report/src/utils/index.html +0 -116
- package/coverage/lcov.info +0 -7179
- package/jest.config.cjs +0 -19
- package/src/api/ast-schema-editor.ts +0 -289
- package/src/api/collections_for_test/callbacks_test_collection.ts +0 -61
- package/src/api/errors.ts +0 -278
- package/src/api/graphql/graphql-schema-generator.ts +0 -383
- package/src/api/graphql/index.ts +0 -2
- package/src/api/index.ts +0 -11
- package/src/api/logs-routes.ts +0 -132
- package/src/api/openapi-generator.ts +0 -727
- package/src/api/rest/api-generator-count.test.ts +0 -126
- package/src/api/rest/api-generator.ts +0 -747
- package/src/api/rest/query-parser.ts +0 -300
- package/src/api/schema-editor-routes.ts +0 -41
- package/src/api/server.ts +0 -271
- package/src/api/types.ts +0 -104
- package/src/auth/adapter-middleware.ts +0 -114
- package/src/auth/admin-roles-route.ts +0 -36
- package/src/auth/admin-user-ops.ts +0 -236
- package/src/auth/admin-users-route.ts +0 -302
- package/src/auth/api-keys/api-key-middleware.ts +0 -133
- package/src/auth/api-keys/api-key-routes.ts +0 -193
- package/src/auth/api-keys/api-key-store.ts +0 -340
- package/src/auth/apple-oauth.ts +0 -122
- package/src/auth/bitbucket-oauth.ts +0 -84
- package/src/auth/builtin-auth-adapter.ts +0 -380
- package/src/auth/custom-auth-adapter.ts +0 -86
- package/src/auth/discord-oauth.ts +0 -85
- package/src/auth/facebook-oauth.ts +0 -74
- package/src/auth/github-oauth.ts +0 -112
- package/src/auth/gitlab-oauth.ts +0 -72
- package/src/auth/google-oauth.ts +0 -223
- package/src/auth/jwt.ts +0 -208
- package/src/auth/linkedin-oauth.ts +0 -83
- package/src/auth/magic-link-routes.ts +0 -167
- package/src/auth/mfa-crypto.ts +0 -91
- package/src/auth/mfa-routes.ts +0 -323
- package/src/auth/mfa.ts +0 -161
- package/src/auth/microsoft-oauth.ts +0 -90
- package/src/auth/middleware.ts +0 -369
- package/src/auth/password.ts +0 -77
- package/src/auth/rate-limiter.ts +0 -225
- package/src/auth/reset-password-admin.ts +0 -160
- package/src/auth/rls-scope.ts +0 -58
- package/src/auth/routes.ts +0 -802
- package/src/auth/session-routes.ts +0 -353
- package/src/auth/slack-oauth.ts +0 -73
- package/src/auth/spotify-oauth.ts +0 -69
- package/src/auth/twitter-oauth.ts +0 -128
- package/src/collections/BackendCollectionRegistry.ts +0 -20
- package/src/collections/loader.ts +0 -50
- package/src/cron/cron-loader.ts +0 -84
- package/src/cron/cron-routes.test.ts +0 -265
- package/src/cron/cron-routes.ts +0 -85
- package/src/cron/cron-scheduler.test.ts +0 -559
- package/src/cron/cron-scheduler.ts +0 -581
- package/src/cron/cron-store.ts +0 -166
- package/src/db/interfaces.ts +0 -60
- package/src/email/index.ts +0 -19
- package/src/email/smtp-email-service.ts +0 -123
- package/src/email/templates.ts +0 -470
- package/src/env.ts +0 -227
- package/src/functions/function-loader.ts +0 -116
- package/src/functions/function-routes.ts +0 -31
- package/src/history/history-routes.ts +0 -129
- package/src/index.ts +0 -70
- package/src/init/docs.ts +0 -47
- package/src/init/health.ts +0 -37
- package/src/init/middlewares.ts +0 -61
- package/src/init/shutdown.ts +0 -56
- package/src/init/storage.ts +0 -57
- package/src/init.ts +0 -1041
- package/src/serve-spa.ts +0 -89
- package/src/services/driver-registry.ts +0 -183
- package/src/services/routed-realtime-service.ts +0 -113
- package/src/services/webhook-service.ts +0 -155
- package/src/singleton.ts +0 -70
- package/src/storage/LocalStorageController.ts +0 -389
- package/src/storage/S3StorageController.ts +0 -300
- package/src/storage/image-transform.ts +0 -218
- package/src/storage/routes.ts +0 -368
- package/src/storage/storage-registry.ts +0 -188
- package/src/storage/tus-handler.ts +0 -315
- package/src/types/index.ts +0 -27
- package/src/utils/dev-port.ts +0 -196
- package/src/utils/logger.ts +0 -143
- package/src/utils/logging.ts +0 -40
- package/src/utils/request-id.ts +0 -40
- package/src/utils/request-logger.ts +0 -72
- package/test/api-generator.test.ts +0 -588
- package/test/api-key-permission-guard.test.ts +0 -144
- package/test/ast-schema-editor.test.ts +0 -89
- package/test/auth-middleware-hono.test.ts +0 -556
- package/test/auth-routes.test.ts +0 -1098
- package/test/backend-hooks-data.test.ts +0 -477
- package/test/custom-auth-adapter.test.ts +0 -189
- package/test/driver-registry.test.ts +0 -282
- package/test/email-templates.test.ts +0 -174
- package/test/env.test.ts +0 -128
- package/test/error-propagation.test.ts +0 -226
- package/test/errors-hono.test.ts +0 -133
- package/test/errors.test.ts +0 -155
- package/test/function-loader.test.ts +0 -127
- package/test/graphql-schema-generator.test.ts +0 -554
- package/test/jwt-security.test.ts +0 -182
- package/test/jwt.test.ts +0 -326
- package/test/mfa.test.ts +0 -197
- package/test/middleware.test.ts +0 -305
- package/test/multi-datasource-routing.test.ts +0 -113
- package/test/password.test.ts +0 -165
- package/test/query-parser.test.ts +0 -233
- package/test/rate-limiter.test.ts +0 -102
- package/test/reset-password-admin.test.ts +0 -113
- package/test/routed-realtime-service.test.ts +0 -86
- package/test/safe-compare.test.ts +0 -66
- package/test/singleton.test.ts +0 -59
- package/test/smtp-email-service.test.ts +0 -169
- package/test/storage-local.test.ts +0 -271
- package/test/storage-registry.test.ts +0 -282
- package/test/storage-routes.test.ts +0 -222
- package/test/storage-s3.test.ts +0 -304
- package/test/transform-auth-response.test.ts +0 -305
- package/test/webhook-service.test.ts +0 -260
- package/tsconfig.json +0 -54
- package/tsconfig.prod.json +0 -23
- package/vite.config.ts +0 -86
|
@@ -1,340 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database operations for Service API Keys.
|
|
3
|
-
*
|
|
4
|
-
* Uses the DataDriver's `admin.executeSql` capability (same pattern as
|
|
5
|
-
* the cron-store and ensure-tables modules). All data lives in the
|
|
6
|
-
* `rebase.api_keys` table.
|
|
7
|
-
*
|
|
8
|
-
* @module
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { randomBytes, createHash } from "crypto";
|
|
12
|
-
import type { DataDriver } from "@rebasepro/types";
|
|
13
|
-
import { isSQLAdmin } from "@rebasepro/types";
|
|
14
|
-
import { logger } from "../../utils/logger";
|
|
15
|
-
import type {
|
|
16
|
-
ApiKey,
|
|
17
|
-
ApiKeyMasked,
|
|
18
|
-
ApiKeyPermission,
|
|
19
|
-
ApiKeyWithSecret,
|
|
20
|
-
CreateApiKeyRequest,
|
|
21
|
-
UpdateApiKeyRequest
|
|
22
|
-
} from "./api-key-types";
|
|
23
|
-
|
|
24
|
-
const TABLE = "rebase.api_keys";
|
|
25
|
-
|
|
26
|
-
/** Characters used to generate the random portion of an API key. */
|
|
27
|
-
const HEX_CHARS = "abcdef0123456789";
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Generate a plaintext API key with the `rk_live_` prefix.
|
|
31
|
-
*
|
|
32
|
-
* Format: `rk_live_` + 32 random hex characters.
|
|
33
|
-
*/
|
|
34
|
-
function generateApiKey(): string {
|
|
35
|
-
const random = randomBytes(16).toString("hex"); // 32 hex chars
|
|
36
|
-
return `rk_live_${random}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* SHA-256 hash a plaintext API key for database storage.
|
|
41
|
-
*/
|
|
42
|
-
function hashKey(plaintext: string): string {
|
|
43
|
-
return createHash("sha256").update(plaintext).digest("hex");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Extract the display prefix from a plaintext key (first 12 chars).
|
|
48
|
-
*/
|
|
49
|
-
function keyPrefix(plaintext: string): string {
|
|
50
|
-
return plaintext.substring(0, 12);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Strip the `key_hash` field and return a safe-to-expose masked key.
|
|
55
|
-
*/
|
|
56
|
-
function toMasked(row: ApiKey): ApiKeyMasked {
|
|
57
|
-
return {
|
|
58
|
-
id: row.id,
|
|
59
|
-
name: row.name,
|
|
60
|
-
key_prefix: row.key_prefix,
|
|
61
|
-
permissions: row.permissions,
|
|
62
|
-
admin: row.admin,
|
|
63
|
-
rate_limit: row.rate_limit,
|
|
64
|
-
created_by: row.created_by,
|
|
65
|
-
created_at: row.created_at,
|
|
66
|
-
updated_at: row.updated_at,
|
|
67
|
-
last_used_at: row.last_used_at,
|
|
68
|
-
expires_at: row.expires_at,
|
|
69
|
-
revoked_at: row.revoked_at
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Parse a raw DB row into the typed `ApiKey` shape.
|
|
75
|
-
*/
|
|
76
|
-
function rowToApiKey(row: Record<string, unknown>): ApiKey {
|
|
77
|
-
let permissions = (row.permissions ?? []) as ApiKeyPermission[];
|
|
78
|
-
if (typeof row.permissions === "string") {
|
|
79
|
-
try {
|
|
80
|
-
permissions = JSON.parse(row.permissions) as ApiKeyPermission[];
|
|
81
|
-
} catch {
|
|
82
|
-
permissions = [];
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
id: row.id as string,
|
|
88
|
-
name: row.name as string,
|
|
89
|
-
key_prefix: row.key_prefix as string,
|
|
90
|
-
key_hash: row.key_hash as string,
|
|
91
|
-
permissions,
|
|
92
|
-
admin: Boolean(row.admin),
|
|
93
|
-
rate_limit: row.rate_limit !== null && row.rate_limit !== undefined
|
|
94
|
-
? Number(row.rate_limit)
|
|
95
|
-
: null,
|
|
96
|
-
created_by: row.created_by as string,
|
|
97
|
-
created_at: new Date(row.created_at as string).toISOString(),
|
|
98
|
-
updated_at: new Date(row.updated_at as string).toISOString(),
|
|
99
|
-
last_used_at: row.last_used_at ? new Date(row.last_used_at as string).toISOString() : null,
|
|
100
|
-
expires_at: row.expires_at ? new Date(row.expires_at as string).toISOString() : null,
|
|
101
|
-
revoked_at: row.revoked_at ? new Date(row.revoked_at as string).toISOString() : null
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ─── Public API ──────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
export interface ApiKeyStore {
|
|
108
|
-
/** Ensure the `rebase.api_keys` table exists. Called once on startup. */
|
|
109
|
-
ensureTable(): Promise<void>;
|
|
110
|
-
|
|
111
|
-
/** Create a new API key. Returns the full plaintext key exactly once. */
|
|
112
|
-
createApiKey(request: CreateApiKeyRequest, createdBy: string): Promise<ApiKeyWithSecret>;
|
|
113
|
-
|
|
114
|
-
/** Look up an API key by its SHA-256 hash. Returns `null` if not found. */
|
|
115
|
-
findByKeyHash(hash: string): Promise<ApiKey | null>;
|
|
116
|
-
|
|
117
|
-
/** List all API keys (masked, never includes hash). */
|
|
118
|
-
listApiKeys(): Promise<ApiKeyMasked[]>;
|
|
119
|
-
|
|
120
|
-
/** Get a single API key by ID (masked). */
|
|
121
|
-
getApiKeyById(id: string): Promise<ApiKeyMasked | null>;
|
|
122
|
-
|
|
123
|
-
/** Update name, permissions, rate_limit, or expires_at. */
|
|
124
|
-
updateApiKey(id: string, updates: UpdateApiKeyRequest): Promise<ApiKeyMasked | null>;
|
|
125
|
-
|
|
126
|
-
/** Soft-delete: set `revoked_at` to now. */
|
|
127
|
-
revokeApiKey(id: string): Promise<boolean>;
|
|
128
|
-
|
|
129
|
-
/** Touch `last_used_at` to the current timestamp. */
|
|
130
|
-
updateLastUsed(id: string): Promise<void>;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Create an `ApiKeyStore` backed by the driver's SQL admin capability.
|
|
135
|
-
*
|
|
136
|
-
* Returns `undefined` if the driver does not support `executeSql`.
|
|
137
|
-
*/
|
|
138
|
-
export function createApiKeyStore(driver: DataDriver): ApiKeyStore | undefined {
|
|
139
|
-
const admin = driver.admin;
|
|
140
|
-
if (!isSQLAdmin(admin)) {
|
|
141
|
-
logger.warn("⚠️ [api-key-store] DataDriver does not support SQL admin — API keys will not be available.");
|
|
142
|
-
return undefined;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const exec = (sqlText: string, options?: { params?: unknown[] }) =>
|
|
146
|
-
admin.executeSql(sqlText, options?.params ? { params: options.params } : undefined);
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
// ── Schema bootstrap ────────────────────────────────────────
|
|
150
|
-
async ensureTable(): Promise<void> {
|
|
151
|
-
try {
|
|
152
|
-
await exec("CREATE SCHEMA IF NOT EXISTS rebase");
|
|
153
|
-
await exec(`
|
|
154
|
-
CREATE TABLE IF NOT EXISTS ${TABLE} (
|
|
155
|
-
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
156
|
-
name TEXT NOT NULL,
|
|
157
|
-
key_prefix TEXT NOT NULL,
|
|
158
|
-
key_hash TEXT NOT NULL UNIQUE,
|
|
159
|
-
permissions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
160
|
-
admin BOOLEAN NOT NULL DEFAULT FALSE,
|
|
161
|
-
rate_limit INTEGER,
|
|
162
|
-
created_by TEXT NOT NULL,
|
|
163
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
164
|
-
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
165
|
-
last_used_at TIMESTAMPTZ,
|
|
166
|
-
expires_at TIMESTAMPTZ,
|
|
167
|
-
revoked_at TIMESTAMPTZ
|
|
168
|
-
)
|
|
169
|
-
`);
|
|
170
|
-
|
|
171
|
-
await exec(`
|
|
172
|
-
CREATE INDEX IF NOT EXISTS idx_api_keys_hash
|
|
173
|
-
ON ${TABLE}(key_hash)
|
|
174
|
-
`);
|
|
175
|
-
|
|
176
|
-
await exec(`
|
|
177
|
-
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix
|
|
178
|
-
ON ${TABLE}(key_prefix)
|
|
179
|
-
`);
|
|
180
|
-
|
|
181
|
-
// Migration: add admin column to existing tables
|
|
182
|
-
await exec(`
|
|
183
|
-
ALTER TABLE ${TABLE}
|
|
184
|
-
ADD COLUMN IF NOT EXISTS admin BOOLEAN NOT NULL DEFAULT FALSE
|
|
185
|
-
`);
|
|
186
|
-
|
|
187
|
-
logger.info("✅ API keys table ready");
|
|
188
|
-
} catch (err) {
|
|
189
|
-
logger.error("❌ Failed to create API keys table", { error: err });
|
|
190
|
-
logger.warn("⚠️ Continuing without API keys support.");
|
|
191
|
-
}
|
|
192
|
-
},
|
|
193
|
-
|
|
194
|
-
// ── Create ──────────────────────────────────────────────────
|
|
195
|
-
async createApiKey(request: CreateApiKeyRequest, createdBy: string): Promise<ApiKeyWithSecret> {
|
|
196
|
-
const plaintext = generateApiKey();
|
|
197
|
-
const hash = hashKey(plaintext);
|
|
198
|
-
const prefix = keyPrefix(plaintext);
|
|
199
|
-
const permissionsJson = JSON.stringify(request.permissions);
|
|
200
|
-
|
|
201
|
-
const rows = await exec(
|
|
202
|
-
`INSERT INTO ${TABLE} (name, key_prefix, key_hash, permissions, admin, rate_limit, created_by, expires_at)
|
|
203
|
-
VALUES ($1, $2, $3, $4::jsonb, $5, $6, $7, $8)
|
|
204
|
-
RETURNING *`,
|
|
205
|
-
{ params: [
|
|
206
|
-
request.name,
|
|
207
|
-
prefix,
|
|
208
|
-
hash,
|
|
209
|
-
permissionsJson,
|
|
210
|
-
request.admin ?? false,
|
|
211
|
-
request.rate_limit ?? null,
|
|
212
|
-
createdBy,
|
|
213
|
-
request.expires_at ?? null
|
|
214
|
-
]}
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
const apiKey = rowToApiKey(rows[0]);
|
|
218
|
-
return {
|
|
219
|
-
...toMasked(apiKey),
|
|
220
|
-
key: plaintext
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
|
|
224
|
-
// ── Lookup by hash ──────────────────────────────────────────
|
|
225
|
-
async findByKeyHash(hash: string): Promise<ApiKey | null> {
|
|
226
|
-
const rows = await exec(
|
|
227
|
-
`SELECT * FROM ${TABLE}
|
|
228
|
-
WHERE key_hash = $1
|
|
229
|
-
LIMIT 1`,
|
|
230
|
-
{ params: [hash] }
|
|
231
|
-
);
|
|
232
|
-
if (rows.length === 0) return null;
|
|
233
|
-
return rowToApiKey(rows[0]);
|
|
234
|
-
},
|
|
235
|
-
|
|
236
|
-
// ── List all (masked) ───────────────────────────────────────
|
|
237
|
-
async listApiKeys(): Promise<ApiKeyMasked[]> {
|
|
238
|
-
const rows = await exec(`
|
|
239
|
-
SELECT * FROM ${TABLE}
|
|
240
|
-
ORDER BY created_at DESC
|
|
241
|
-
`);
|
|
242
|
-
return rows.map(r => toMasked(rowToApiKey(r)));
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
// ── Get by ID (masked) ──────────────────────────────────────
|
|
246
|
-
async getApiKeyById(id: string): Promise<ApiKeyMasked | null> {
|
|
247
|
-
const rows = await exec(
|
|
248
|
-
`SELECT * FROM ${TABLE}
|
|
249
|
-
WHERE id = $1
|
|
250
|
-
LIMIT 1`,
|
|
251
|
-
{ params: [id] }
|
|
252
|
-
);
|
|
253
|
-
if (rows.length === 0) return null;
|
|
254
|
-
return toMasked(rowToApiKey(rows[0]));
|
|
255
|
-
},
|
|
256
|
-
|
|
257
|
-
// ── Update ──────────────────────────────────────────────────
|
|
258
|
-
async updateApiKey(id: string, updates: UpdateApiKeyRequest): Promise<ApiKeyMasked | null> {
|
|
259
|
-
const setClauses: string[] = [];
|
|
260
|
-
const params: unknown[] = [];
|
|
261
|
-
let paramIdx = 1;
|
|
262
|
-
|
|
263
|
-
if (updates.name !== undefined) {
|
|
264
|
-
setClauses.push(`name = $${paramIdx++}`);
|
|
265
|
-
params.push(updates.name);
|
|
266
|
-
}
|
|
267
|
-
if (updates.permissions !== undefined) {
|
|
268
|
-
setClauses.push(`permissions = $${paramIdx++}::jsonb`);
|
|
269
|
-
params.push(JSON.stringify(updates.permissions));
|
|
270
|
-
}
|
|
271
|
-
if (updates.admin !== undefined) {
|
|
272
|
-
setClauses.push(`admin = $${paramIdx++}`);
|
|
273
|
-
params.push(updates.admin);
|
|
274
|
-
}
|
|
275
|
-
if (updates.rate_limit !== undefined) {
|
|
276
|
-
if (updates.rate_limit !== null) {
|
|
277
|
-
setClauses.push(`rate_limit = $${paramIdx++}`);
|
|
278
|
-
params.push(updates.rate_limit);
|
|
279
|
-
} else {
|
|
280
|
-
setClauses.push("rate_limit = NULL");
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (updates.expires_at !== undefined) {
|
|
284
|
-
if (updates.expires_at !== null) {
|
|
285
|
-
setClauses.push(`expires_at = $${paramIdx++}`);
|
|
286
|
-
params.push(updates.expires_at);
|
|
287
|
-
} else {
|
|
288
|
-
setClauses.push("expires_at = NULL");
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
if (setClauses.length === 0) {
|
|
293
|
-
return this.getApiKeyById(id);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
setClauses.push("updated_at = NOW()");
|
|
297
|
-
|
|
298
|
-
// The WHERE id = $N uses the next available param index
|
|
299
|
-
params.push(id);
|
|
300
|
-
|
|
301
|
-
const rows = await exec(
|
|
302
|
-
`UPDATE ${TABLE}
|
|
303
|
-
SET ${setClauses.join(", ")}
|
|
304
|
-
WHERE id = $${paramIdx}
|
|
305
|
-
RETURNING *`,
|
|
306
|
-
{ params }
|
|
307
|
-
);
|
|
308
|
-
|
|
309
|
-
if (rows.length === 0) return null;
|
|
310
|
-
return toMasked(rowToApiKey(rows[0]));
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
// ── Revoke (soft-delete) ────────────────────────────────────
|
|
314
|
-
async revokeApiKey(id: string): Promise<boolean> {
|
|
315
|
-
const rows = await exec(
|
|
316
|
-
`UPDATE ${TABLE}
|
|
317
|
-
SET revoked_at = NOW(), updated_at = NOW()
|
|
318
|
-
WHERE id = $1 AND revoked_at IS NULL
|
|
319
|
-
RETURNING id`,
|
|
320
|
-
{ params: [id] }
|
|
321
|
-
);
|
|
322
|
-
return rows.length > 0;
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
// ── Touch last_used_at ──────────────────────────────────────
|
|
326
|
-
async updateLastUsed(id: string): Promise<void> {
|
|
327
|
-
try {
|
|
328
|
-
await exec(
|
|
329
|
-
`UPDATE ${TABLE}
|
|
330
|
-
SET last_used_at = NOW()
|
|
331
|
-
WHERE id = $1`,
|
|
332
|
-
{ params: [id] }
|
|
333
|
-
);
|
|
334
|
-
} catch (err) {
|
|
335
|
-
// Non-blocking — don't fail requests because of a usage timestamp update
|
|
336
|
-
logger.error("[api-key-store] Failed to update last_used_at", { error: err });
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
};
|
|
340
|
-
}
|
package/src/auth/apple-oauth.ts
DELETED
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import jwt from "jsonwebtoken";
|
|
4
|
-
import { logger } from "../utils/logger";
|
|
5
|
-
/**
|
|
6
|
-
* Creates an Apple Sign In OAuth Provider integration.
|
|
7
|
-
*
|
|
8
|
-
* Apple requires a client secret that is a signed JWT, regenerated on each
|
|
9
|
-
* token exchange (valid up to 6 months). This provider handles that automatically.
|
|
10
|
-
*
|
|
11
|
-
* Required Apple Developer configuration:
|
|
12
|
-
* - Services ID (clientId)
|
|
13
|
-
* - Key ID from the private key registered with Apple
|
|
14
|
-
* - Team ID from Apple Developer account
|
|
15
|
-
* - Private key (.p8 file contents) downloaded from Apple Developer portal
|
|
16
|
-
*/
|
|
17
|
-
export function createAppleProvider(config: {
|
|
18
|
-
clientId: string;
|
|
19
|
-
teamId: string;
|
|
20
|
-
keyId: string;
|
|
21
|
-
/** The raw PEM contents of the .p8 private key file */
|
|
22
|
-
privateKey: string;
|
|
23
|
-
}): OAuthProvider<{
|
|
24
|
-
code: string;
|
|
25
|
-
redirectUri: string;
|
|
26
|
-
user?: { name?: { firstName?: string; lastName?: string }; email?: string };
|
|
27
|
-
}> {
|
|
28
|
-
/**
|
|
29
|
-
* Generate a client_secret JWT signed with the Apple private key.
|
|
30
|
-
* Apple requires this instead of a static client_secret.
|
|
31
|
-
*/
|
|
32
|
-
async function generateClientSecret(): Promise<string> {
|
|
33
|
-
return jwt.sign({}, config.privateKey, {
|
|
34
|
-
algorithm: "ES256",
|
|
35
|
-
keyid: config.keyId,
|
|
36
|
-
issuer: config.teamId,
|
|
37
|
-
expiresIn: "180d",
|
|
38
|
-
audience: "https://appleid.apple.com",
|
|
39
|
-
subject: config.clientId
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
id: "apple",
|
|
45
|
-
schema: z.object({
|
|
46
|
-
code: z.string().min(1, "Auth code is required"),
|
|
47
|
-
redirectUri: z.string().url("Valid redirect URI is required"),
|
|
48
|
-
/** Apple sends user info only on first authorization; the frontend must forward it. */
|
|
49
|
-
user: z.object({
|
|
50
|
-
name: z.object({
|
|
51
|
-
firstName: z.string().optional(),
|
|
52
|
-
lastName: z.string().optional()
|
|
53
|
-
}).optional(),
|
|
54
|
-
email: z.string().email().optional()
|
|
55
|
-
}).optional()
|
|
56
|
-
}),
|
|
57
|
-
verify: async (payload: {
|
|
58
|
-
code: string;
|
|
59
|
-
redirectUri: string;
|
|
60
|
-
user?: { name?: { firstName?: string; lastName?: string }; email?: string };
|
|
61
|
-
}): Promise<OAuthProviderProfile | null> => {
|
|
62
|
-
try {
|
|
63
|
-
const clientSecret = await generateClientSecret();
|
|
64
|
-
|
|
65
|
-
// Exchange code for tokens
|
|
66
|
-
const tokenResponse = await fetch("https://appleid.apple.com/auth/token", {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
69
|
-
body: new URLSearchParams({
|
|
70
|
-
client_id: config.clientId,
|
|
71
|
-
client_secret: clientSecret,
|
|
72
|
-
code: payload.code,
|
|
73
|
-
grant_type: "authorization_code",
|
|
74
|
-
redirect_uri: payload.redirectUri
|
|
75
|
-
})
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (!tokenResponse.ok) {
|
|
79
|
-
logger.error("Failed to get Apple access token", { detail: await tokenResponse.text() });
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const tokenData = await tokenResponse.json() as { id_token: string };
|
|
84
|
-
|
|
85
|
-
// Decode the id_token (JWT) to get user info.
|
|
86
|
-
// Apple's id_token is a standard JWT — we only need the payload.
|
|
87
|
-
const [, payloadB64] = tokenData.id_token.split(".");
|
|
88
|
-
const decoded = JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8")) as {
|
|
89
|
-
sub: string;
|
|
90
|
-
email?: string;
|
|
91
|
-
email_verified?: string | boolean;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Apple only sends the user's name on the FIRST authorization.
|
|
95
|
-
// Subsequent logins only have the id_token. The frontend should pass
|
|
96
|
-
// the user object from the first auth for us to capture the name.
|
|
97
|
-
const email = decoded.email || payload.user?.email;
|
|
98
|
-
if (!email) {
|
|
99
|
-
logger.error("Apple user has no email");
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let displayName: string | null = null;
|
|
104
|
-
if (payload.user?.name) {
|
|
105
|
-
const parts = [payload.user.name.firstName, payload.user.name.lastName].filter(Boolean);
|
|
106
|
-
displayName = parts.length > 0 ? parts.join(" ") : null;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
providerId: decoded.sub,
|
|
111
|
-
email,
|
|
112
|
-
displayName,
|
|
113
|
-
photoUrl: null, // Apple does not provide a profile photo
|
|
114
|
-
emailVerified: decoded.email_verified === true || decoded.email_verified === "true"
|
|
115
|
-
};
|
|
116
|
-
} catch (error) {
|
|
117
|
-
logger.error("Apple OAuth error", { error: error });
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { OAuthProvider, OAuthProviderProfile } from "./interfaces";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import { logger } from "../utils/logger";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Creates a Bitbucket OAuth Provider integration (OAuth 2.0 consumer).
|
|
7
|
-
*/
|
|
8
|
-
export function createBitbucketProvider(config: { clientId: string; clientSecret: string }): OAuthProvider<{ code: string; redirectUri: string }> {
|
|
9
|
-
return {
|
|
10
|
-
id: "bitbucket",
|
|
11
|
-
schema: z.object({
|
|
12
|
-
code: z.string().min(1, "Auth code is required"),
|
|
13
|
-
redirectUri: z.string().url("Valid redirect URI is required")
|
|
14
|
-
}),
|
|
15
|
-
verify: async (payload: { code: string; redirectUri: string }): Promise<OAuthProviderProfile | null> => {
|
|
16
|
-
try {
|
|
17
|
-
const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
|
|
18
|
-
|
|
19
|
-
const tokenResponse = await fetch("https://bitbucket.org/site/oauth2/access_token", {
|
|
20
|
-
method: "POST",
|
|
21
|
-
headers: {
|
|
22
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
23
|
-
"Authorization": `Basic ${basicAuth}`
|
|
24
|
-
},
|
|
25
|
-
body: new URLSearchParams({
|
|
26
|
-
grant_type: "authorization_code",
|
|
27
|
-
code: payload.code,
|
|
28
|
-
redirect_uri: payload.redirectUri
|
|
29
|
-
})
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
if (!tokenResponse.ok) {
|
|
33
|
-
logger.error("Failed to get Bitbucket access token", { detail: await tokenResponse.text() });
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const tokenData = await tokenResponse.json() as { access_token: string };
|
|
38
|
-
const accessToken = tokenData.access_token;
|
|
39
|
-
|
|
40
|
-
const profileResponse = await fetch("https://api.bitbucket.org/2.0/user", {
|
|
41
|
-
headers: { "Authorization": `Bearer ${accessToken}` }
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (!profileResponse.ok) {
|
|
45
|
-
logger.error("Failed to get Bitbucket user info", { detail: await profileResponse.text() });
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const p = await profileResponse.json() as {
|
|
50
|
-
uuid: string; display_name?: string; username?: string;
|
|
51
|
-
links?: { avatar?: { href?: string } };
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// Bitbucket doesn't return email in user profile — fetch from /emails endpoint
|
|
55
|
-
const emailsResponse = await fetch("https://api.bitbucket.org/2.0/user/emails", {
|
|
56
|
-
headers: { "Authorization": `Bearer ${accessToken}` }
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
let email: string | null = null;
|
|
60
|
-
if (emailsResponse.ok) {
|
|
61
|
-
const emailData = await emailsResponse.json() as {
|
|
62
|
-
values: Array<{ email: string; is_primary: boolean; is_confirmed: boolean }>;
|
|
63
|
-
};
|
|
64
|
-
const primary = emailData.values.find(e => e.is_primary && e.is_confirmed)
|
|
65
|
-
|| emailData.values.find(e => e.is_confirmed);
|
|
66
|
-
email = primary?.email || null;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!email) { logger.error("Bitbucket user has no verified email"); return null; }
|
|
70
|
-
|
|
71
|
-
return {
|
|
72
|
-
providerId: p.uuid,
|
|
73
|
-
email,
|
|
74
|
-
displayName: p.display_name || p.username || null,
|
|
75
|
-
photoUrl: p.links?.avatar?.href || null,
|
|
76
|
-
emailVerified: true
|
|
77
|
-
};
|
|
78
|
-
} catch (error) {
|
|
79
|
-
logger.error("Bitbucket OAuth error", { error: error });
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
}
|