@rebasepro/server-core 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/src/collections/default-collections.d.ts +9 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/util/permissions.d.ts +1 -0
- package/dist/{index-BZoAtuqi.js → index-Cr1D21av.js} +11 -3
- package/dist/index-Cr1D21av.js.map +1 -0
- package/dist/index.es.js +2309 -370
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2298 -355
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/logs-routes.d.ts +37 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
- package/dist/server-core/src/api/types.d.ts +6 -1
- package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
- package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
- package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
- package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
- package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
- package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
- package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
- package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
- package/dist/server-core/src/auth/index.d.ts +5 -3
- package/dist/server-core/src/auth/interfaces.d.ts +93 -3
- package/dist/server-core/src/auth/jwt.d.ts +3 -1
- package/dist/server-core/src/auth/mfa.d.ts +49 -0
- package/dist/server-core/src/auth/middleware.d.ts +7 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
- package/dist/server-core/src/auth/routes.d.ts +3 -3
- package/dist/server-core/src/env.d.ts +6 -0
- package/dist/server-core/src/index.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +3 -3
- package/dist/server-core/src/services/webhook-service.d.ts +29 -0
- package/dist/server-core/src/storage/image-transform.d.ts +48 -0
- package/dist/server-core/src/storage/index.d.ts +3 -0
- package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
- package/dist/types/src/controllers/auth.d.ts +4 -26
- package/dist/types/src/controllers/client.d.ts +25 -43
- package/dist/types/src/controllers/collection_registry.d.ts +1 -1
- package/dist/types/src/controllers/data.d.ts +4 -0
- package/dist/types/src/controllers/data_driver.d.ts +23 -0
- package/dist/types/src/controllers/registry.d.ts +5 -4
- package/dist/types/src/rebase_context.d.ts +1 -1
- package/dist/types/src/types/auth_adapter.d.ts +5 -60
- package/dist/types/src/types/backend.d.ts +2 -2
- package/dist/types/src/types/backend_hooks.d.ts +2 -17
- package/dist/types/src/types/collections.d.ts +0 -4
- package/dist/types/src/types/component_ref.d.ts +1 -1
- package/dist/types/src/types/cron.d.ts +1 -1
- package/dist/types/src/types/entity_views.d.ts +1 -0
- package/dist/types/src/types/export_import.d.ts +1 -1
- package/dist/types/src/types/formex.d.ts +2 -2
- package/dist/types/src/types/properties.d.ts +9 -7
- package/dist/types/src/types/translations.d.ts +28 -12
- package/dist/types/src/types/user_management_delegate.d.ts +22 -57
- package/dist/types/src/users/index.d.ts +0 -1
- package/dist/types/src/users/user.d.ts +0 -1
- package/package.json +8 -6
- package/src/api/ast-schema-editor.ts +4 -4
- package/src/api/errors.ts +16 -7
- package/src/api/logs-routes.ts +129 -0
- package/src/api/rest/api-generator.ts +42 -2
- package/src/api/rest/query-parser.ts +37 -1
- package/src/api/types.ts +6 -1
- package/src/auth/adapter-middleware.ts +20 -4
- package/src/auth/admin-routes.ts +36 -100
- package/src/auth/api-keys/api-key-middleware.ts +126 -0
- package/src/auth/api-keys/api-key-permission-guard.ts +64 -0
- package/src/auth/api-keys/api-key-routes.ts +183 -0
- package/src/auth/api-keys/api-key-store.ts +317 -0
- package/src/auth/api-keys/api-key-types.ts +94 -0
- package/src/auth/api-keys/index.ts +37 -0
- package/src/auth/{auth-overrides.ts → auth-hooks.ts} +81 -14
- package/src/auth/builtin-auth-adapter.ts +35 -73
- package/src/auth/custom-auth-adapter.ts +1 -1
- package/src/auth/index.ts +7 -3
- package/src/auth/interfaces.ts +111 -3
- package/src/auth/jwt.ts +19 -5
- package/src/auth/mfa.ts +160 -0
- package/src/auth/middleware.ts +20 -1
- package/src/auth/rate-limiter.ts +92 -0
- package/src/auth/routes.ts +455 -24
- package/src/cron/cron-loader.ts +5 -10
- package/src/cron/cron-scheduler.ts +11 -12
- package/src/cron/cron-store.ts +8 -7
- package/src/env.ts +2 -0
- package/src/functions/function-loader.ts +6 -9
- package/src/index.ts +1 -2
- package/src/init.ts +56 -8
- package/src/serve-spa.ts +5 -4
- package/src/services/webhook-service.ts +155 -0
- package/src/storage/image-transform.ts +202 -0
- package/src/storage/index.ts +3 -0
- package/src/storage/routes.ts +56 -3
- package/src/storage/tus-handler.ts +315 -0
- package/src/utils/dev-port.ts +14 -0
- package/src/utils/logging.ts +9 -7
- package/test/admin-routes.test.ts +62 -164
- package/test/api-generator.test.ts +0 -1
- package/test/api-key-permission-guard.test.ts +132 -0
- package/test/ast-schema-editor.test.ts +26 -0
- package/test/auth-routes.test.ts +1 -2
- package/test/backend-hooks-admin.test.ts +3 -29
- package/test/custom-auth-adapter.test.ts +2 -10
- package/test/email-templates.test.ts +169 -0
- package/test/function-loader.test.ts +124 -0
- package/test/jwt.test.ts +4 -2
- package/test/mfa.test.ts +197 -0
- package/test/middleware.test.ts +10 -5
- package/test/webhook-service.test.ts +249 -0
- package/vite.config.ts +3 -2
- package/dist/index-BZoAtuqi.js.map +0 -1
- package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
- package/dist/types/src/users/roles.d.ts +0 -22
- package/src/bootstrappers/index.ts +0 -1
- package/src/singleton.test.ts +0 -28
package/dist/index.es.js
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
1
|
import * as fs$4 from "fs";
|
|
2
|
-
import fs__default from "fs";
|
|
2
|
+
import fs__default, { existsSync } from "fs";
|
|
3
3
|
import * as path$3 from "path";
|
|
4
|
-
import path__default from "path";
|
|
4
|
+
import path__default, { join as join$1 } from "path";
|
|
5
5
|
import require$$0$5, { pathToFileURL } from "url";
|
|
6
6
|
import { Hono } from "hono";
|
|
7
7
|
import require$$0$3 from "buffer";
|
|
8
8
|
import require$$0$4 from "stream";
|
|
9
9
|
import require$$5, { promisify } from "util";
|
|
10
10
|
import * as require$$0$2 from "crypto";
|
|
11
|
-
import require$$0__default, { randomBytes, createHash, timingSafeEqual as timingSafeEqual$1, scrypt } from "crypto";
|
|
11
|
+
import require$$0__default, { randomBytes, createHash, timingSafeEqual as timingSafeEqual$1, scrypt, createHmac, randomUUID as randomUUID$1 } from "crypto";
|
|
12
12
|
import { bodyLimit } from "hono/body-limit";
|
|
13
13
|
import { csrf } from "hono/csrf";
|
|
14
14
|
import require$$0$a from "child_process";
|
|
15
15
|
import require$$1 from "https";
|
|
16
|
+
import require$$1$2 from "querystring";
|
|
16
17
|
import require$$0$7 from "net";
|
|
17
18
|
import require$$1$1 from "tls";
|
|
18
19
|
import require$$2 from "assert";
|
|
19
20
|
import require$$0$6 from "http";
|
|
20
|
-
import require$$1$
|
|
21
|
+
import require$$1$3 from "os";
|
|
21
22
|
import require$$0$8 from "node:events";
|
|
22
|
-
import require$$1$
|
|
23
|
+
import require$$1$4 from "node:process";
|
|
23
24
|
import require$$2$1 from "node:util";
|
|
24
25
|
import require$$0$9 from "events";
|
|
25
26
|
import { S3Client, PutObjectCommand, HeadObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
|
|
26
27
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
28
|
+
import { mkdir as mkdir$1, unlink as unlink$1, writeFile as writeFile$1, open } from "fs/promises";
|
|
29
|
+
import require$$3 from "zlib";
|
|
30
|
+
import require$$4$1 from "dns";
|
|
27
31
|
import { cors } from "hono/cors";
|
|
28
32
|
import { secureHeaders } from "hono/secure-headers";
|
|
29
33
|
import { serve } from "@hono/node-server";
|
|
@@ -211,30 +215,6 @@ var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof win
|
|
|
211
215
|
function getDefaultExportFromCjs(x) {
|
|
212
216
|
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
|
|
213
217
|
}
|
|
214
|
-
function getAugmentedNamespace(n) {
|
|
215
|
-
if (n.__esModule) return n;
|
|
216
|
-
var f2 = n.default;
|
|
217
|
-
if (typeof f2 == "function") {
|
|
218
|
-
var a2 = function a3() {
|
|
219
|
-
if (this instanceof a3) {
|
|
220
|
-
return Reflect.construct(f2, arguments, this.constructor);
|
|
221
|
-
}
|
|
222
|
-
return f2.apply(this, arguments);
|
|
223
|
-
};
|
|
224
|
-
a2.prototype = f2.prototype;
|
|
225
|
-
} else a2 = {};
|
|
226
|
-
Object.defineProperty(a2, "__esModule", { value: true });
|
|
227
|
-
Object.keys(n).forEach(function(k) {
|
|
228
|
-
var d2 = Object.getOwnPropertyDescriptor(n, k);
|
|
229
|
-
Object.defineProperty(a2, k, d2.get ? d2 : {
|
|
230
|
-
enumerable: true,
|
|
231
|
-
get: function() {
|
|
232
|
-
return n[k];
|
|
233
|
-
}
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
return a2;
|
|
237
|
-
}
|
|
238
218
|
function commonjsRequire(path2) {
|
|
239
219
|
throw new Error('Could not dynamically require "' + path2 + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
|
|
240
220
|
}
|
|
@@ -1066,6 +1046,9 @@ function mergeDeep(target, source, ignoreUndefined = false) {
|
|
|
1066
1046
|
return output;
|
|
1067
1047
|
}
|
|
1068
1048
|
for (const key in source) {
|
|
1049
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1069
1052
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
1070
1053
|
const sourceValue = source[key];
|
|
1071
1054
|
const outputValue = output[key];
|
|
@@ -1254,8 +1237,8 @@ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
|
1254
1237
|
} catch (e) {
|
|
1255
1238
|
}
|
|
1256
1239
|
if (!foundForeignKey) {
|
|
1257
|
-
const
|
|
1258
|
-
newRelation.foreignKeyOnTarget = generateForeignKeyName(
|
|
1240
|
+
const keyPrefix2 = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
|
|
1241
|
+
newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix2);
|
|
1259
1242
|
}
|
|
1260
1243
|
}
|
|
1261
1244
|
} else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
|
|
@@ -2564,6 +2547,142 @@ class CollectionRegistry {
|
|
|
2564
2547
|
};
|
|
2565
2548
|
}
|
|
2566
2549
|
}
|
|
2550
|
+
const defaultUsersCollection = {
|
|
2551
|
+
name: "Users",
|
|
2552
|
+
singularName: "User",
|
|
2553
|
+
slug: "users",
|
|
2554
|
+
table: "users",
|
|
2555
|
+
schema: "rebase",
|
|
2556
|
+
icon: "Users",
|
|
2557
|
+
group: "Settings",
|
|
2558
|
+
openEntityMode: "dialog",
|
|
2559
|
+
disableDefaultActions: ["copy"],
|
|
2560
|
+
sort: ["createdAt", "desc"],
|
|
2561
|
+
properties: {
|
|
2562
|
+
id: {
|
|
2563
|
+
name: "ID",
|
|
2564
|
+
type: "string",
|
|
2565
|
+
isId: "uuid",
|
|
2566
|
+
ui: {
|
|
2567
|
+
readOnly: true
|
|
2568
|
+
}
|
|
2569
|
+
},
|
|
2570
|
+
email: {
|
|
2571
|
+
name: "Email",
|
|
2572
|
+
type: "string",
|
|
2573
|
+
validation: {
|
|
2574
|
+
required: true,
|
|
2575
|
+
unique: true
|
|
2576
|
+
}
|
|
2577
|
+
},
|
|
2578
|
+
displayName: {
|
|
2579
|
+
name: "Name",
|
|
2580
|
+
type: "string",
|
|
2581
|
+
columnName: "display_name",
|
|
2582
|
+
validation: {
|
|
2583
|
+
required: true
|
|
2584
|
+
}
|
|
2585
|
+
},
|
|
2586
|
+
photoURL: {
|
|
2587
|
+
name: "Photo URL",
|
|
2588
|
+
type: "string",
|
|
2589
|
+
columnName: "photo_url",
|
|
2590
|
+
url: "image"
|
|
2591
|
+
},
|
|
2592
|
+
roles: {
|
|
2593
|
+
name: "Roles",
|
|
2594
|
+
type: "array",
|
|
2595
|
+
columnType: "text[]",
|
|
2596
|
+
of: {
|
|
2597
|
+
name: "Role",
|
|
2598
|
+
type: "string",
|
|
2599
|
+
enum: {
|
|
2600
|
+
admin: "Admin",
|
|
2601
|
+
editor: "Editor",
|
|
2602
|
+
viewer: "Viewer"
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
},
|
|
2606
|
+
passwordHash: {
|
|
2607
|
+
name: "Password Hash",
|
|
2608
|
+
type: "string",
|
|
2609
|
+
columnName: "password_hash",
|
|
2610
|
+
ui: {
|
|
2611
|
+
hideFromCollection: true,
|
|
2612
|
+
disabled: {
|
|
2613
|
+
hidden: true
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
},
|
|
2617
|
+
emailVerified: {
|
|
2618
|
+
name: "Email Verified",
|
|
2619
|
+
type: "boolean",
|
|
2620
|
+
columnName: "email_verified",
|
|
2621
|
+
defaultValue: false,
|
|
2622
|
+
ui: {
|
|
2623
|
+
hideFromCollection: true,
|
|
2624
|
+
disabled: {
|
|
2625
|
+
hidden: true
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
},
|
|
2629
|
+
emailVerificationToken: {
|
|
2630
|
+
name: "Email Verification Token",
|
|
2631
|
+
type: "string",
|
|
2632
|
+
columnName: "email_verification_token",
|
|
2633
|
+
ui: {
|
|
2634
|
+
hideFromCollection: true,
|
|
2635
|
+
disabled: {
|
|
2636
|
+
hidden: true
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
},
|
|
2640
|
+
emailVerificationSentAt: {
|
|
2641
|
+
name: "Email Verification Sent At",
|
|
2642
|
+
type: "date",
|
|
2643
|
+
columnName: "email_verification_sent_at",
|
|
2644
|
+
ui: {
|
|
2645
|
+
hideFromCollection: true,
|
|
2646
|
+
disabled: {
|
|
2647
|
+
hidden: true
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
},
|
|
2651
|
+
metadata: {
|
|
2652
|
+
name: "Metadata",
|
|
2653
|
+
type: "map",
|
|
2654
|
+
defaultValue: {},
|
|
2655
|
+
ui: {
|
|
2656
|
+
hideFromCollection: true,
|
|
2657
|
+
disabled: {
|
|
2658
|
+
hidden: true
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
},
|
|
2662
|
+
createdAt: {
|
|
2663
|
+
name: "Created At",
|
|
2664
|
+
type: "date",
|
|
2665
|
+
columnName: "created_at",
|
|
2666
|
+
ui: {
|
|
2667
|
+
readOnly: true
|
|
2668
|
+
}
|
|
2669
|
+
},
|
|
2670
|
+
updatedAt: {
|
|
2671
|
+
name: "Updated At",
|
|
2672
|
+
type: "date",
|
|
2673
|
+
columnName: "updated_at",
|
|
2674
|
+
autoValue: "on_update",
|
|
2675
|
+
ui: {
|
|
2676
|
+
hideFromCollection: true,
|
|
2677
|
+
disabled: {
|
|
2678
|
+
hidden: true
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
},
|
|
2683
|
+
listProperties: ["displayName", "email", "roles", "createdAt"],
|
|
2684
|
+
propertiesOrder: ["id", "email", "displayName", "roles", "createdAt"]
|
|
2685
|
+
};
|
|
2567
2686
|
function mapOperator$1(op) {
|
|
2568
2687
|
switch (op) {
|
|
2569
2688
|
case "==":
|
|
@@ -2855,10 +2974,15 @@ const errorHandler = (err, c) => {
|
|
|
2855
2974
|
const issue = error2.code === "42703" ? "column" : "table";
|
|
2856
2975
|
logMessage = `Database schema mismatch (${issue} missing): ${error2.message}. Did you forget to run migrations ('pnpm db:push' or 'pnpm db:migrate')?`;
|
|
2857
2976
|
}
|
|
2858
|
-
console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2859
2977
|
const causePg = error2.cause && typeof error2.cause === "object" ? error2.cause : void 0;
|
|
2860
2978
|
const pgErrorCode = causePg?.code || error2.code;
|
|
2861
|
-
const
|
|
2979
|
+
const isDbSchemaMismatch = pgErrorCode === "42703" || pgErrorCode === "42P01";
|
|
2980
|
+
if (isDbSchemaMismatch) {
|
|
2981
|
+
console.warn(`⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2982
|
+
} else {
|
|
2983
|
+
console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2984
|
+
}
|
|
2985
|
+
const suppressStack = isDbSchemaMismatch || statusCode < 500 && code2 === "BAD_REQUEST";
|
|
2862
2986
|
if (!suppressStack) {
|
|
2863
2987
|
console.error(error2.stack || error2);
|
|
2864
2988
|
}
|
|
@@ -2939,7 +3063,7 @@ function parseQueryOptions(query) {
|
|
|
2939
3063
|
options2.offset = (page - 1) * limit;
|
|
2940
3064
|
}
|
|
2941
3065
|
options2.where = {};
|
|
2942
|
-
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString"];
|
|
3066
|
+
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
|
|
2943
3067
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
2944
3068
|
if (reservedQueryKeys.includes(key)) continue;
|
|
2945
3069
|
const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
|
|
@@ -3011,8 +3135,63 @@ function parseQueryOptions(query) {
|
|
|
3011
3135
|
const fieldsStr = String(query.fields).trim();
|
|
3012
3136
|
options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
3013
3137
|
}
|
|
3138
|
+
if (query.vector_search && query.vector) {
|
|
3139
|
+
const vectorStr = String(query.vector);
|
|
3140
|
+
let queryVector;
|
|
3141
|
+
try {
|
|
3142
|
+
queryVector = JSON.parse(vectorStr);
|
|
3143
|
+
if (!Array.isArray(queryVector) || !queryVector.every((v) => typeof v === "number")) {
|
|
3144
|
+
throw new Error("Expected array of numbers");
|
|
3145
|
+
}
|
|
3146
|
+
} catch {
|
|
3147
|
+
throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
|
|
3148
|
+
}
|
|
3149
|
+
const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
|
|
3150
|
+
if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
|
|
3151
|
+
throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
|
|
3152
|
+
}
|
|
3153
|
+
const vectorSearch = {
|
|
3154
|
+
property: String(query.vector_search),
|
|
3155
|
+
vector: queryVector,
|
|
3156
|
+
distance: distanceParam
|
|
3157
|
+
};
|
|
3158
|
+
if (query.vector_threshold) {
|
|
3159
|
+
const threshold = parseFloat(String(query.vector_threshold));
|
|
3160
|
+
if (isNaN(threshold)) {
|
|
3161
|
+
throw new Error("Invalid vector_threshold. Expected a number.");
|
|
3162
|
+
}
|
|
3163
|
+
vectorSearch.threshold = threshold;
|
|
3164
|
+
}
|
|
3165
|
+
options2.vectorSearch = vectorSearch;
|
|
3166
|
+
}
|
|
3014
3167
|
return options2;
|
|
3015
3168
|
}
|
|
3169
|
+
function httpMethodToOperation(method) {
|
|
3170
|
+
const upper = method.toUpperCase();
|
|
3171
|
+
switch (upper) {
|
|
3172
|
+
case "GET":
|
|
3173
|
+
case "HEAD":
|
|
3174
|
+
case "OPTIONS":
|
|
3175
|
+
return "read";
|
|
3176
|
+
case "POST":
|
|
3177
|
+
case "PUT":
|
|
3178
|
+
case "PATCH":
|
|
3179
|
+
return "write";
|
|
3180
|
+
case "DELETE":
|
|
3181
|
+
return "delete";
|
|
3182
|
+
default:
|
|
3183
|
+
return "read";
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
function isOperationAllowed(permissions, collection, operation) {
|
|
3187
|
+
for (const perm of permissions) {
|
|
3188
|
+
const collectionMatch = perm.collection === "*" || perm.collection === collection;
|
|
3189
|
+
if (collectionMatch && perm.operations.includes(operation)) {
|
|
3190
|
+
return true;
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
return false;
|
|
3194
|
+
}
|
|
3016
3195
|
class RestApiGenerator {
|
|
3017
3196
|
collections;
|
|
3018
3197
|
router;
|
|
@@ -3045,6 +3224,19 @@ class RestApiGenerator {
|
|
|
3045
3224
|
this.createSubcollectionRoutes();
|
|
3046
3225
|
return this.router;
|
|
3047
3226
|
}
|
|
3227
|
+
/**
|
|
3228
|
+
* Check API key permissions for a collection operation.
|
|
3229
|
+
* Throws 403 if the key doesn't have the required permission.
|
|
3230
|
+
* No-ops if the request is not authenticated via an API key.
|
|
3231
|
+
*/
|
|
3232
|
+
enforceApiKeyPermission(c, collectionSlug) {
|
|
3233
|
+
const apiKey = c.get("apiKey");
|
|
3234
|
+
if (!apiKey) return;
|
|
3235
|
+
const operation = httpMethodToOperation(c.req.method);
|
|
3236
|
+
if (!isOperationAllowed(apiKey.permissions, collectionSlug, operation)) {
|
|
3237
|
+
throw ApiError.forbidden(`API key does not have "${operation}" permission for collection "${collectionSlug}"`, "API_KEY_FORBIDDEN");
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3048
3240
|
/**
|
|
3049
3241
|
* Get the typed RestFetchService from a driver if it exposes one (for include support).
|
|
3050
3242
|
*/
|
|
@@ -3058,6 +3250,7 @@ class RestApiGenerator {
|
|
|
3058
3250
|
const basePath = `/${collection.slug}`;
|
|
3059
3251
|
const resolvedCollection = collection;
|
|
3060
3252
|
this.router.get(`${basePath}/count`, async (c) => {
|
|
3253
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3061
3254
|
const queryDict = c.req.query();
|
|
3062
3255
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3063
3256
|
const searchString = queryDict.searchString;
|
|
@@ -3068,6 +3261,7 @@ class RestApiGenerator {
|
|
|
3068
3261
|
});
|
|
3069
3262
|
});
|
|
3070
3263
|
this.router.get(basePath, async (c) => {
|
|
3264
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3071
3265
|
const queryDict = c.req.query();
|
|
3072
3266
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3073
3267
|
const searchString = queryDict.searchString;
|
|
@@ -3082,7 +3276,8 @@ class RestApiGenerator {
|
|
|
3082
3276
|
offset: queryOptions.offset,
|
|
3083
3277
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3084
3278
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3085
|
-
searchString
|
|
3279
|
+
searchString,
|
|
3280
|
+
vectorSearch: queryOptions.vectorSearch
|
|
3086
3281
|
}, queryOptions.include);
|
|
3087
3282
|
entities2 = await this.applyAfterReadBatch(collection.slug, entities2, hookCtx);
|
|
3088
3283
|
const total2 = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
@@ -3110,6 +3305,7 @@ class RestApiGenerator {
|
|
|
3110
3305
|
});
|
|
3111
3306
|
});
|
|
3112
3307
|
this.router.get(`${basePath}/:id`, async (c) => {
|
|
3308
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3113
3309
|
const id = c.req.param("id");
|
|
3114
3310
|
const queryDict = c.req.query();
|
|
3115
3311
|
const queryOptions = parseQueryOptions(queryDict);
|
|
@@ -3140,6 +3336,7 @@ class RestApiGenerator {
|
|
|
3140
3336
|
});
|
|
3141
3337
|
this.router.post(basePath, async (c) => {
|
|
3142
3338
|
try {
|
|
3339
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3143
3340
|
const driver = c.get("driver") || this.driver;
|
|
3144
3341
|
const path2 = collection.slug;
|
|
3145
3342
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
@@ -3168,6 +3365,7 @@ class RestApiGenerator {
|
|
|
3168
3365
|
});
|
|
3169
3366
|
this.router.put(`${basePath}/:id`, async (c) => {
|
|
3170
3367
|
try {
|
|
3368
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3171
3369
|
const id = c.req.param("id");
|
|
3172
3370
|
const driver = c.get("driver") || this.driver;
|
|
3173
3371
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
@@ -3204,6 +3402,7 @@ class RestApiGenerator {
|
|
|
3204
3402
|
}
|
|
3205
3403
|
});
|
|
3206
3404
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
3405
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3207
3406
|
const id = c.req.param("id");
|
|
3208
3407
|
const driver = c.get("driver") || this.driver;
|
|
3209
3408
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
@@ -3275,6 +3474,7 @@ class RestApiGenerator {
|
|
|
3275
3474
|
const parsed = parseSubPath(rawPath);
|
|
3276
3475
|
if (!parsed) return next();
|
|
3277
3476
|
const driver = c.get("driver") || this.driver;
|
|
3477
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3278
3478
|
if (parsed.entityId === "count") {
|
|
3279
3479
|
const queryDict = c.req.query();
|
|
3280
3480
|
const queryOptions = parseQueryOptions(queryDict);
|
|
@@ -3322,6 +3522,7 @@ class RestApiGenerator {
|
|
|
3322
3522
|
const parsed = parseSubPath(rawPath);
|
|
3323
3523
|
if (!parsed || parsed.entityId) return next();
|
|
3324
3524
|
const driver = c.get("driver") || this.driver;
|
|
3525
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3325
3526
|
const body = await c.req.json().catch(() => ({}));
|
|
3326
3527
|
const entity = await driver.saveEntity({
|
|
3327
3528
|
path: parsed.collectionPath,
|
|
@@ -3337,6 +3538,7 @@ class RestApiGenerator {
|
|
|
3337
3538
|
const parsed = parseSubPath(rawPath);
|
|
3338
3539
|
if (!parsed || !parsed.entityId) return next();
|
|
3339
3540
|
const driver = c.get("driver") || this.driver;
|
|
3541
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3340
3542
|
const body = await c.req.json().catch(() => ({}));
|
|
3341
3543
|
const entity = await driver.saveEntity({
|
|
3342
3544
|
path: parsed.collectionPath,
|
|
@@ -3353,6 +3555,7 @@ class RestApiGenerator {
|
|
|
3353
3555
|
const parsed = parseSubPath(rawPath);
|
|
3354
3556
|
if (!parsed || !parsed.entityId) return next();
|
|
3355
3557
|
const driver = c.get("driver") || this.driver;
|
|
3558
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3356
3559
|
const existingEntity = await driver.fetchEntity({
|
|
3357
3560
|
path: parsed.collectionPath,
|
|
3358
3561
|
entityId: parsed.entityId
|
|
@@ -3418,7 +3621,8 @@ class RestApiGenerator {
|
|
|
3418
3621
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3419
3622
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3420
3623
|
startAfter: queryOptions.offset ? String(queryOptions.offset) : void 0,
|
|
3421
|
-
searchString
|
|
3624
|
+
searchString,
|
|
3625
|
+
vectorSearch: queryOptions.vectorSearch
|
|
3422
3626
|
});
|
|
3423
3627
|
return entities.map((entity) => this.flattenEntity(entity));
|
|
3424
3628
|
}
|
|
@@ -4944,7 +5148,7 @@ var cmp_1 = cmp$1;
|
|
|
4944
5148
|
const SemVer$6 = semver$4;
|
|
4945
5149
|
const parse$6 = parse_1;
|
|
4946
5150
|
const { safeRe: re, t } = reExports;
|
|
4947
|
-
const coerce$
|
|
5151
|
+
const coerce$2 = (version2, options2) => {
|
|
4948
5152
|
if (version2 instanceof SemVer$6) {
|
|
4949
5153
|
return version2;
|
|
4950
5154
|
}
|
|
@@ -4979,7 +5183,7 @@ const coerce$1 = (version2, options2) => {
|
|
|
4979
5183
|
const build = options2.includePrerelease && match[6] ? `+${match[6]}` : "";
|
|
4980
5184
|
return parse$6(`${major2}.${minor2}.${patch2}${prerelease2}${build}`, options2);
|
|
4981
5185
|
};
|
|
4982
|
-
var coerce_1 = coerce$
|
|
5186
|
+
var coerce_1 = coerce$2;
|
|
4983
5187
|
const parse$5 = parse_1;
|
|
4984
5188
|
const constants$1 = constants$2;
|
|
4985
5189
|
const SemVer$5 = semver$4;
|
|
@@ -5265,19 +5469,19 @@ function requireRange() {
|
|
|
5265
5469
|
const replaceCaret = (comp, options2) => {
|
|
5266
5470
|
debug2("caret", comp, options2);
|
|
5267
5471
|
const r = options2.loose ? re2[t2.CARETLOOSE] : re2[t2.CARET];
|
|
5268
|
-
const
|
|
5472
|
+
const z2 = options2.includePrerelease ? "-0" : "";
|
|
5269
5473
|
return comp.replace(r, (_, M, m2, p, pr) => {
|
|
5270
5474
|
debug2("caret", comp, _, M, m2, p, pr);
|
|
5271
5475
|
let ret;
|
|
5272
5476
|
if (isX(M)) {
|
|
5273
5477
|
ret = "";
|
|
5274
5478
|
} else if (isX(m2)) {
|
|
5275
|
-
ret = `>=${M}.0.0${
|
|
5479
|
+
ret = `>=${M}.0.0${z2} <${+M + 1}.0.0-0`;
|
|
5276
5480
|
} else if (isX(p)) {
|
|
5277
5481
|
if (M === "0") {
|
|
5278
|
-
ret = `>=${M}.${m2}.0${
|
|
5482
|
+
ret = `>=${M}.${m2}.0${z2} <${M}.${+m2 + 1}.0-0`;
|
|
5279
5483
|
} else {
|
|
5280
|
-
ret = `>=${M}.${m2}.0${
|
|
5484
|
+
ret = `>=${M}.${m2}.0${z2} <${+M + 1}.0.0-0`;
|
|
5281
5485
|
}
|
|
5282
5486
|
} else if (pr) {
|
|
5283
5487
|
debug2("replaceCaret pr", pr);
|
|
@@ -5294,9 +5498,9 @@ function requireRange() {
|
|
|
5294
5498
|
debug2("no pr");
|
|
5295
5499
|
if (M === "0") {
|
|
5296
5500
|
if (m2 === "0") {
|
|
5297
|
-
ret = `>=${M}.${m2}.${p}${
|
|
5501
|
+
ret = `>=${M}.${m2}.${p}${z2} <${M}.${m2}.${+p + 1}-0`;
|
|
5298
5502
|
} else {
|
|
5299
|
-
ret = `>=${M}.${m2}.${p}${
|
|
5503
|
+
ret = `>=${M}.${m2}.${p}${z2} <${M}.${+m2 + 1}.0-0`;
|
|
5300
5504
|
}
|
|
5301
5505
|
} else {
|
|
5302
5506
|
ret = `>=${M}.${m2}.${p} <${+M + 1}.0.0-0`;
|
|
@@ -5953,7 +6157,7 @@ const neq = neq_1;
|
|
|
5953
6157
|
const gte = gte_1;
|
|
5954
6158
|
const lte = lte_1;
|
|
5955
6159
|
const cmp = cmp_1;
|
|
5956
|
-
const coerce = coerce_1;
|
|
6160
|
+
const coerce$1 = coerce_1;
|
|
5957
6161
|
const truncate = truncate_1;
|
|
5958
6162
|
const Comparator = requireComparator();
|
|
5959
6163
|
const Range = requireRange();
|
|
@@ -5992,7 +6196,7 @@ var semver$3 = {
|
|
|
5992
6196
|
gte,
|
|
5993
6197
|
lte,
|
|
5994
6198
|
cmp,
|
|
5995
|
-
coerce,
|
|
6199
|
+
coerce: coerce$1,
|
|
5996
6200
|
truncate,
|
|
5997
6201
|
Comparator,
|
|
5998
6202
|
Range,
|
|
@@ -6873,7 +7077,7 @@ var jsonwebtoken = {
|
|
|
6873
7077
|
NotBeforeError: NotBeforeError_1,
|
|
6874
7078
|
TokenExpiredError: TokenExpiredError_1
|
|
6875
7079
|
};
|
|
6876
|
-
const jwt = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
|
|
7080
|
+
const jwt$1 = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
|
|
6877
7081
|
let jwtConfig = {
|
|
6878
7082
|
secret: "",
|
|
6879
7083
|
accessExpiresIn: "1h",
|
|
@@ -6892,15 +7096,17 @@ function configureJwt(config) {
|
|
|
6892
7096
|
...config
|
|
6893
7097
|
};
|
|
6894
7098
|
}
|
|
6895
|
-
function generateAccessToken(userId, roles) {
|
|
7099
|
+
function generateAccessToken(userId, roles, aal = "aal1", customClaims) {
|
|
6896
7100
|
if (!jwtConfig.secret) {
|
|
6897
7101
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
6898
7102
|
}
|
|
6899
7103
|
const payload = {
|
|
6900
7104
|
userId,
|
|
6901
|
-
roles
|
|
7105
|
+
roles,
|
|
7106
|
+
aal,
|
|
7107
|
+
...customClaims
|
|
6902
7108
|
};
|
|
6903
|
-
return jwt.sign(payload, jwtConfig.secret, {
|
|
7109
|
+
return jwt$1.sign(payload, jwtConfig.secret, {
|
|
6904
7110
|
expiresIn: jwtConfig.accessExpiresIn,
|
|
6905
7111
|
algorithm: "HS256"
|
|
6906
7112
|
});
|
|
@@ -6934,7 +7140,7 @@ function verifyAccessToken(token) {
|
|
|
6934
7140
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
6935
7141
|
}
|
|
6936
7142
|
try {
|
|
6937
|
-
const decoded = jwt.verify(token, jwtConfig.secret, {
|
|
7143
|
+
const decoded = jwt$1.verify(token, jwtConfig.secret, {
|
|
6938
7144
|
algorithms: ["HS256"]
|
|
6939
7145
|
});
|
|
6940
7146
|
const id = decoded.userId || decoded.uid || decoded.sub;
|
|
@@ -6942,9 +7148,11 @@ function verifyAccessToken(token) {
|
|
|
6942
7148
|
console.error("[JWT] Verification failed: missing id in payload", decoded);
|
|
6943
7149
|
return null;
|
|
6944
7150
|
}
|
|
7151
|
+
const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
|
|
6945
7152
|
return {
|
|
6946
7153
|
userId: id,
|
|
6947
|
-
roles: decoded.roles || []
|
|
7154
|
+
roles: decoded.roles || [],
|
|
7155
|
+
aal
|
|
6948
7156
|
};
|
|
6949
7157
|
} catch (error2) {
|
|
6950
7158
|
console.error("[JWT] Verification failed:", error2, "Token start:", token.substring(0, 15));
|
|
@@ -6984,6 +7192,17 @@ function getRefreshTokenExpiry() {
|
|
|
6984
7192
|
}
|
|
6985
7193
|
return new Date(Date.now() + ms2);
|
|
6986
7194
|
}
|
|
7195
|
+
const jwt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
7196
|
+
__proto__: null,
|
|
7197
|
+
configureJwt,
|
|
7198
|
+
generateAccessToken,
|
|
7199
|
+
generateRefreshToken,
|
|
7200
|
+
getAccessTokenExpiry,
|
|
7201
|
+
getAccessTokenExpiryMs,
|
|
7202
|
+
getRefreshTokenExpiry,
|
|
7203
|
+
hashRefreshToken,
|
|
7204
|
+
verifyAccessToken
|
|
7205
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
6987
7206
|
function isRLSScopedDriver(driver) {
|
|
6988
7207
|
return "withAuth" in driver && typeof driver.withAuth === "function";
|
|
6989
7208
|
}
|
|
@@ -7006,6 +7225,81 @@ function safeCompare(a2, b) {
|
|
|
7006
7225
|
return false;
|
|
7007
7226
|
}
|
|
7008
7227
|
}
|
|
7228
|
+
function isApiKeyToken(token) {
|
|
7229
|
+
return token.startsWith("rk_");
|
|
7230
|
+
}
|
|
7231
|
+
function hashToken$2(token) {
|
|
7232
|
+
return createHash("sha256").update(token).digest("hex");
|
|
7233
|
+
}
|
|
7234
|
+
async function validateApiKey(c, token, options2) {
|
|
7235
|
+
const {
|
|
7236
|
+
store,
|
|
7237
|
+
driver
|
|
7238
|
+
} = options2;
|
|
7239
|
+
const hash = hashToken$2(token);
|
|
7240
|
+
const apiKey = await store.findByKeyHash(hash);
|
|
7241
|
+
if (!apiKey) {
|
|
7242
|
+
return c.json({
|
|
7243
|
+
error: {
|
|
7244
|
+
message: "Invalid API key",
|
|
7245
|
+
code: "UNAUTHORIZED"
|
|
7246
|
+
}
|
|
7247
|
+
}, 401);
|
|
7248
|
+
}
|
|
7249
|
+
if (apiKey.revoked_at) {
|
|
7250
|
+
return c.json({
|
|
7251
|
+
error: {
|
|
7252
|
+
message: "API key has been revoked",
|
|
7253
|
+
code: "UNAUTHORIZED"
|
|
7254
|
+
}
|
|
7255
|
+
}, 401);
|
|
7256
|
+
}
|
|
7257
|
+
if (apiKey.expires_at && new Date(apiKey.expires_at) < /* @__PURE__ */ new Date()) {
|
|
7258
|
+
return c.json({
|
|
7259
|
+
error: {
|
|
7260
|
+
message: "API key has expired",
|
|
7261
|
+
code: "UNAUTHORIZED"
|
|
7262
|
+
}
|
|
7263
|
+
}, 401);
|
|
7264
|
+
}
|
|
7265
|
+
const userId = `api-key:${apiKey.id}`;
|
|
7266
|
+
c.set("user", {
|
|
7267
|
+
userId,
|
|
7268
|
+
roles: []
|
|
7269
|
+
});
|
|
7270
|
+
const masked = {
|
|
7271
|
+
id: apiKey.id,
|
|
7272
|
+
name: apiKey.name,
|
|
7273
|
+
key_prefix: apiKey.key_prefix,
|
|
7274
|
+
permissions: apiKey.permissions,
|
|
7275
|
+
rate_limit: apiKey.rate_limit,
|
|
7276
|
+
created_by: apiKey.created_by,
|
|
7277
|
+
created_at: apiKey.created_at,
|
|
7278
|
+
updated_at: apiKey.updated_at,
|
|
7279
|
+
last_used_at: apiKey.last_used_at,
|
|
7280
|
+
expires_at: apiKey.expires_at,
|
|
7281
|
+
revoked_at: apiKey.revoked_at
|
|
7282
|
+
};
|
|
7283
|
+
c.set("apiKey", masked);
|
|
7284
|
+
try {
|
|
7285
|
+
const scopedDriver = await scopeDataDriver(driver, {
|
|
7286
|
+
uid: userId,
|
|
7287
|
+
roles: ["service"]
|
|
7288
|
+
});
|
|
7289
|
+
c.set("driver", scopedDriver);
|
|
7290
|
+
} catch (error2) {
|
|
7291
|
+
console.error("[AUTH] RLS scoping failed for API key:", error2);
|
|
7292
|
+
return c.json({
|
|
7293
|
+
error: {
|
|
7294
|
+
message: "Internal authentication error",
|
|
7295
|
+
code: "INTERNAL_ERROR"
|
|
7296
|
+
}
|
|
7297
|
+
}, 500);
|
|
7298
|
+
}
|
|
7299
|
+
store.updateLastUsed(apiKey.id).catch(() => {
|
|
7300
|
+
});
|
|
7301
|
+
return true;
|
|
7302
|
+
}
|
|
7009
7303
|
const requireAuth = async (c, next) => {
|
|
7010
7304
|
const authHeader = c.req.header("authorization");
|
|
7011
7305
|
const queryToken = c.req.query("token");
|
|
@@ -7112,7 +7406,8 @@ function createAuthMiddleware(options2) {
|
|
|
7112
7406
|
driver,
|
|
7113
7407
|
requireAuth: enforceAuth = true,
|
|
7114
7408
|
validator,
|
|
7115
|
-
serviceKey
|
|
7409
|
+
serviceKey,
|
|
7410
|
+
apiKeyStore
|
|
7116
7411
|
} = options2;
|
|
7117
7412
|
return async (c, next) => {
|
|
7118
7413
|
if (validator) {
|
|
@@ -7187,6 +7482,14 @@ function createAuthMiddleware(options2) {
|
|
|
7187
7482
|
}
|
|
7188
7483
|
}, 500);
|
|
7189
7484
|
}
|
|
7485
|
+
} else if (apiKeyStore && isApiKeyToken(token)) {
|
|
7486
|
+
const result = await validateApiKey(c, token, {
|
|
7487
|
+
store: apiKeyStore,
|
|
7488
|
+
driver
|
|
7489
|
+
});
|
|
7490
|
+
if (result !== true) {
|
|
7491
|
+
return result;
|
|
7492
|
+
}
|
|
7190
7493
|
} else {
|
|
7191
7494
|
const payload = extractUserFromToken(token);
|
|
7192
7495
|
if (payload) {
|
|
@@ -7247,9 +7550,22 @@ function createAdapterAuthMiddleware(options2) {
|
|
|
7247
7550
|
const {
|
|
7248
7551
|
adapter,
|
|
7249
7552
|
driver,
|
|
7250
|
-
requireAuth: enforceAuth = true
|
|
7553
|
+
requireAuth: enforceAuth = true,
|
|
7554
|
+
apiKeyStore
|
|
7251
7555
|
} = options2;
|
|
7252
7556
|
return async (c, next) => {
|
|
7557
|
+
if (apiKeyStore) {
|
|
7558
|
+
const authHeader = c.req.header("authorization") || "";
|
|
7559
|
+
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
7560
|
+
if (token.startsWith("rk_")) {
|
|
7561
|
+
const result = await validateApiKey(c, token, {
|
|
7562
|
+
store: apiKeyStore,
|
|
7563
|
+
driver
|
|
7564
|
+
});
|
|
7565
|
+
if (result === true) return next();
|
|
7566
|
+
return result;
|
|
7567
|
+
}
|
|
7568
|
+
}
|
|
7253
7569
|
let authenticatedUser = null;
|
|
7254
7570
|
try {
|
|
7255
7571
|
authenticatedUser = await adapter.verifyRequest(c.req.raw);
|
|
@@ -7345,11 +7661,11 @@ async function verifyPassword(password, storedHash) {
|
|
|
7345
7661
|
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
|
|
7346
7662
|
return timingSafeEqual$1(derivedKey, storedKey);
|
|
7347
7663
|
}
|
|
7348
|
-
function
|
|
7664
|
+
function resolveAuthHooks(hooks) {
|
|
7349
7665
|
return {
|
|
7350
|
-
hashPassword:
|
|
7351
|
-
verifyPassword:
|
|
7352
|
-
validatePasswordStrength:
|
|
7666
|
+
hashPassword: hooks?.hashPassword ?? hashPassword,
|
|
7667
|
+
verifyPassword: hooks?.verifyPassword ?? verifyPassword,
|
|
7668
|
+
validatePasswordStrength: hooks?.validatePasswordStrength ?? validatePasswordStrength
|
|
7353
7669
|
};
|
|
7354
7670
|
}
|
|
7355
7671
|
function getGreeting(user) {
|
|
@@ -7751,6 +8067,63 @@ const strictAuthLimiter = createRateLimiter({
|
|
|
7751
8067
|
limit: 50,
|
|
7752
8068
|
message: "Too many requests to this sensitive endpoint, please try again later."
|
|
7753
8069
|
});
|
|
8070
|
+
function apiKeyKeyGenerator(c) {
|
|
8071
|
+
const apiKey = c.get("apiKey");
|
|
8072
|
+
if (apiKey) {
|
|
8073
|
+
return `api-key:${apiKey.id}`;
|
|
8074
|
+
}
|
|
8075
|
+
return defaultKeyGenerator(c);
|
|
8076
|
+
}
|
|
8077
|
+
function createApiKeyRateLimiter(defaultLimit = 1e3, windowMs = 15 * 60 * 1e3) {
|
|
8078
|
+
const store = /* @__PURE__ */ new Map();
|
|
8079
|
+
const cleanupInterval = setInterval(() => {
|
|
8080
|
+
const now = Date.now();
|
|
8081
|
+
for (const [key, entry] of store.entries()) {
|
|
8082
|
+
entry.timestamps = entry.timestamps.filter((t2) => now - t2 < windowMs);
|
|
8083
|
+
if (entry.timestamps.length === 0) {
|
|
8084
|
+
store.delete(key);
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
}, windowMs);
|
|
8088
|
+
if (cleanupInterval.unref) {
|
|
8089
|
+
cleanupInterval.unref();
|
|
8090
|
+
}
|
|
8091
|
+
return async (c, next) => {
|
|
8092
|
+
const apiKey = c.get("apiKey");
|
|
8093
|
+
if (!apiKey) {
|
|
8094
|
+
return next();
|
|
8095
|
+
}
|
|
8096
|
+
const limit = apiKey.rate_limit ?? defaultLimit;
|
|
8097
|
+
const key = `api-key:${apiKey.id}`;
|
|
8098
|
+
const now = Date.now();
|
|
8099
|
+
let entry = store.get(key);
|
|
8100
|
+
if (!entry) {
|
|
8101
|
+
entry = {
|
|
8102
|
+
timestamps: []
|
|
8103
|
+
};
|
|
8104
|
+
store.set(key, entry);
|
|
8105
|
+
}
|
|
8106
|
+
entry.timestamps = entry.timestamps.filter((t2) => now - t2 < windowMs);
|
|
8107
|
+
if (entry.timestamps.length >= limit) {
|
|
8108
|
+
const retryAfterMs = entry.timestamps[0] + windowMs - now;
|
|
8109
|
+
const retryAfterSec = Math.ceil(retryAfterMs / 1e3);
|
|
8110
|
+
c.header("Retry-After", String(retryAfterSec));
|
|
8111
|
+
c.header("X-RateLimit-Limit", String(limit));
|
|
8112
|
+
c.header("X-RateLimit-Remaining", "0");
|
|
8113
|
+
c.header("X-RateLimit-Reset", String(Math.ceil((now + retryAfterMs) / 1e3)));
|
|
8114
|
+
return c.json({
|
|
8115
|
+
error: {
|
|
8116
|
+
message: "API key rate limit exceeded, please try again later.",
|
|
8117
|
+
code: "RATE_LIMITED"
|
|
8118
|
+
}
|
|
8119
|
+
}, 429);
|
|
8120
|
+
}
|
|
8121
|
+
entry.timestamps.push(now);
|
|
8122
|
+
c.header("X-RateLimit-Limit", String(limit));
|
|
8123
|
+
c.header("X-RateLimit-Remaining", String(limit - entry.timestamps.length));
|
|
8124
|
+
return next();
|
|
8125
|
+
};
|
|
8126
|
+
}
|
|
7754
8127
|
var util$3;
|
|
7755
8128
|
(function(util2) {
|
|
7756
8129
|
util2.assertEqual = (_) => {
|
|
@@ -7901,6 +8274,10 @@ const ZodIssueCode = util$3.arrayToEnum([
|
|
|
7901
8274
|
"not_multiple_of",
|
|
7902
8275
|
"not_finite"
|
|
7903
8276
|
]);
|
|
8277
|
+
const quotelessJson = (obj) => {
|
|
8278
|
+
const json = JSON.stringify(obj, null, 2);
|
|
8279
|
+
return json.replace(/"([^"]+)":/g, "$1:");
|
|
8280
|
+
};
|
|
7904
8281
|
class ZodError extends Error {
|
|
7905
8282
|
get errors() {
|
|
7906
8283
|
return this.issues;
|
|
@@ -8096,6 +8473,9 @@ const errorMap = (issue, _ctx) => {
|
|
|
8096
8473
|
return { message };
|
|
8097
8474
|
};
|
|
8098
8475
|
let overrideErrorMap = errorMap;
|
|
8476
|
+
function setErrorMap(map2) {
|
|
8477
|
+
overrideErrorMap = map2;
|
|
8478
|
+
}
|
|
8099
8479
|
function getErrorMap() {
|
|
8100
8480
|
return overrideErrorMap;
|
|
8101
8481
|
}
|
|
@@ -8124,6 +8504,7 @@ const makeIssue = (params) => {
|
|
|
8124
8504
|
message: errorMessage
|
|
8125
8505
|
};
|
|
8126
8506
|
};
|
|
8507
|
+
const EMPTY_PATH = [];
|
|
8127
8508
|
function addIssueToContext(ctx, issueData) {
|
|
8128
8509
|
const overrideMap = getErrorMap();
|
|
8129
8510
|
const issue = makeIssue({
|
|
@@ -10414,6 +10795,113 @@ ZodUnion.create = (types2, params) => {
|
|
|
10414
10795
|
...processCreateParams(params)
|
|
10415
10796
|
});
|
|
10416
10797
|
};
|
|
10798
|
+
const getDiscriminator = (type) => {
|
|
10799
|
+
if (type instanceof ZodLazy) {
|
|
10800
|
+
return getDiscriminator(type.schema);
|
|
10801
|
+
} else if (type instanceof ZodEffects) {
|
|
10802
|
+
return getDiscriminator(type.innerType());
|
|
10803
|
+
} else if (type instanceof ZodLiteral) {
|
|
10804
|
+
return [type.value];
|
|
10805
|
+
} else if (type instanceof ZodEnum) {
|
|
10806
|
+
return type.options;
|
|
10807
|
+
} else if (type instanceof ZodNativeEnum) {
|
|
10808
|
+
return util$3.objectValues(type.enum);
|
|
10809
|
+
} else if (type instanceof ZodDefault) {
|
|
10810
|
+
return getDiscriminator(type._def.innerType);
|
|
10811
|
+
} else if (type instanceof ZodUndefined) {
|
|
10812
|
+
return [void 0];
|
|
10813
|
+
} else if (type instanceof ZodNull) {
|
|
10814
|
+
return [null];
|
|
10815
|
+
} else if (type instanceof ZodOptional) {
|
|
10816
|
+
return [void 0, ...getDiscriminator(type.unwrap())];
|
|
10817
|
+
} else if (type instanceof ZodNullable) {
|
|
10818
|
+
return [null, ...getDiscriminator(type.unwrap())];
|
|
10819
|
+
} else if (type instanceof ZodBranded) {
|
|
10820
|
+
return getDiscriminator(type.unwrap());
|
|
10821
|
+
} else if (type instanceof ZodReadonly) {
|
|
10822
|
+
return getDiscriminator(type.unwrap());
|
|
10823
|
+
} else if (type instanceof ZodCatch) {
|
|
10824
|
+
return getDiscriminator(type._def.innerType);
|
|
10825
|
+
} else {
|
|
10826
|
+
return [];
|
|
10827
|
+
}
|
|
10828
|
+
};
|
|
10829
|
+
class ZodDiscriminatedUnion extends ZodType {
|
|
10830
|
+
_parse(input) {
|
|
10831
|
+
const { ctx } = this._processInputParams(input);
|
|
10832
|
+
if (ctx.parsedType !== ZodParsedType.object) {
|
|
10833
|
+
addIssueToContext(ctx, {
|
|
10834
|
+
code: ZodIssueCode.invalid_type,
|
|
10835
|
+
expected: ZodParsedType.object,
|
|
10836
|
+
received: ctx.parsedType
|
|
10837
|
+
});
|
|
10838
|
+
return INVALID;
|
|
10839
|
+
}
|
|
10840
|
+
const discriminator = this.discriminator;
|
|
10841
|
+
const discriminatorValue = ctx.data[discriminator];
|
|
10842
|
+
const option = this.optionsMap.get(discriminatorValue);
|
|
10843
|
+
if (!option) {
|
|
10844
|
+
addIssueToContext(ctx, {
|
|
10845
|
+
code: ZodIssueCode.invalid_union_discriminator,
|
|
10846
|
+
options: Array.from(this.optionsMap.keys()),
|
|
10847
|
+
path: [discriminator]
|
|
10848
|
+
});
|
|
10849
|
+
return INVALID;
|
|
10850
|
+
}
|
|
10851
|
+
if (ctx.common.async) {
|
|
10852
|
+
return option._parseAsync({
|
|
10853
|
+
data: ctx.data,
|
|
10854
|
+
path: ctx.path,
|
|
10855
|
+
parent: ctx
|
|
10856
|
+
});
|
|
10857
|
+
} else {
|
|
10858
|
+
return option._parseSync({
|
|
10859
|
+
data: ctx.data,
|
|
10860
|
+
path: ctx.path,
|
|
10861
|
+
parent: ctx
|
|
10862
|
+
});
|
|
10863
|
+
}
|
|
10864
|
+
}
|
|
10865
|
+
get discriminator() {
|
|
10866
|
+
return this._def.discriminator;
|
|
10867
|
+
}
|
|
10868
|
+
get options() {
|
|
10869
|
+
return this._def.options;
|
|
10870
|
+
}
|
|
10871
|
+
get optionsMap() {
|
|
10872
|
+
return this._def.optionsMap;
|
|
10873
|
+
}
|
|
10874
|
+
/**
|
|
10875
|
+
* The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor.
|
|
10876
|
+
* However, it only allows a union of objects, all of which need to share a discriminator property. This property must
|
|
10877
|
+
* have a different value for each object in the union.
|
|
10878
|
+
* @param discriminator the name of the discriminator property
|
|
10879
|
+
* @param types an array of object schemas
|
|
10880
|
+
* @param params
|
|
10881
|
+
*/
|
|
10882
|
+
static create(discriminator, options2, params) {
|
|
10883
|
+
const optionsMap = /* @__PURE__ */ new Map();
|
|
10884
|
+
for (const type of options2) {
|
|
10885
|
+
const discriminatorValues = getDiscriminator(type.shape[discriminator]);
|
|
10886
|
+
if (!discriminatorValues.length) {
|
|
10887
|
+
throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`);
|
|
10888
|
+
}
|
|
10889
|
+
for (const value of discriminatorValues) {
|
|
10890
|
+
if (optionsMap.has(value)) {
|
|
10891
|
+
throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);
|
|
10892
|
+
}
|
|
10893
|
+
optionsMap.set(value, type);
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
return new ZodDiscriminatedUnion({
|
|
10897
|
+
typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,
|
|
10898
|
+
discriminator,
|
|
10899
|
+
options: options2,
|
|
10900
|
+
optionsMap,
|
|
10901
|
+
...processCreateParams(params)
|
|
10902
|
+
});
|
|
10903
|
+
}
|
|
10904
|
+
}
|
|
10417
10905
|
function mergeValues(a2, b) {
|
|
10418
10906
|
const aType = getParsedType(a2);
|
|
10419
10907
|
const bType = getParsedType(b);
|
|
@@ -10572,6 +11060,59 @@ ZodTuple.create = (schemas, params) => {
|
|
|
10572
11060
|
...processCreateParams(params)
|
|
10573
11061
|
});
|
|
10574
11062
|
};
|
|
11063
|
+
class ZodRecord extends ZodType {
|
|
11064
|
+
get keySchema() {
|
|
11065
|
+
return this._def.keyType;
|
|
11066
|
+
}
|
|
11067
|
+
get valueSchema() {
|
|
11068
|
+
return this._def.valueType;
|
|
11069
|
+
}
|
|
11070
|
+
_parse(input) {
|
|
11071
|
+
const { status, ctx } = this._processInputParams(input);
|
|
11072
|
+
if (ctx.parsedType !== ZodParsedType.object) {
|
|
11073
|
+
addIssueToContext(ctx, {
|
|
11074
|
+
code: ZodIssueCode.invalid_type,
|
|
11075
|
+
expected: ZodParsedType.object,
|
|
11076
|
+
received: ctx.parsedType
|
|
11077
|
+
});
|
|
11078
|
+
return INVALID;
|
|
11079
|
+
}
|
|
11080
|
+
const pairs = [];
|
|
11081
|
+
const keyType = this._def.keyType;
|
|
11082
|
+
const valueType = this._def.valueType;
|
|
11083
|
+
for (const key in ctx.data) {
|
|
11084
|
+
pairs.push({
|
|
11085
|
+
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)),
|
|
11086
|
+
value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)),
|
|
11087
|
+
alwaysSet: key in ctx.data
|
|
11088
|
+
});
|
|
11089
|
+
}
|
|
11090
|
+
if (ctx.common.async) {
|
|
11091
|
+
return ParseStatus.mergeObjectAsync(status, pairs);
|
|
11092
|
+
} else {
|
|
11093
|
+
return ParseStatus.mergeObjectSync(status, pairs);
|
|
11094
|
+
}
|
|
11095
|
+
}
|
|
11096
|
+
get element() {
|
|
11097
|
+
return this._def.valueType;
|
|
11098
|
+
}
|
|
11099
|
+
static create(first, second, third) {
|
|
11100
|
+
if (second instanceof ZodType) {
|
|
11101
|
+
return new ZodRecord({
|
|
11102
|
+
keyType: first,
|
|
11103
|
+
valueType: second,
|
|
11104
|
+
typeName: ZodFirstPartyTypeKind.ZodRecord,
|
|
11105
|
+
...processCreateParams(third)
|
|
11106
|
+
});
|
|
11107
|
+
}
|
|
11108
|
+
return new ZodRecord({
|
|
11109
|
+
keyType: ZodString.create(),
|
|
11110
|
+
valueType: first,
|
|
11111
|
+
typeName: ZodFirstPartyTypeKind.ZodRecord,
|
|
11112
|
+
...processCreateParams(second)
|
|
11113
|
+
});
|
|
11114
|
+
}
|
|
11115
|
+
}
|
|
10575
11116
|
class ZodMap extends ZodType {
|
|
10576
11117
|
get keySchema() {
|
|
10577
11118
|
return this._def.keyType;
|
|
@@ -10723,6 +11264,111 @@ ZodSet.create = (valueType, params) => {
|
|
|
10723
11264
|
...processCreateParams(params)
|
|
10724
11265
|
});
|
|
10725
11266
|
};
|
|
11267
|
+
class ZodFunction extends ZodType {
|
|
11268
|
+
constructor() {
|
|
11269
|
+
super(...arguments);
|
|
11270
|
+
this.validate = this.implement;
|
|
11271
|
+
}
|
|
11272
|
+
_parse(input) {
|
|
11273
|
+
const { ctx } = this._processInputParams(input);
|
|
11274
|
+
if (ctx.parsedType !== ZodParsedType.function) {
|
|
11275
|
+
addIssueToContext(ctx, {
|
|
11276
|
+
code: ZodIssueCode.invalid_type,
|
|
11277
|
+
expected: ZodParsedType.function,
|
|
11278
|
+
received: ctx.parsedType
|
|
11279
|
+
});
|
|
11280
|
+
return INVALID;
|
|
11281
|
+
}
|
|
11282
|
+
function makeArgsIssue(args, error2) {
|
|
11283
|
+
return makeIssue({
|
|
11284
|
+
data: args,
|
|
11285
|
+
path: ctx.path,
|
|
11286
|
+
errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), errorMap].filter((x) => !!x),
|
|
11287
|
+
issueData: {
|
|
11288
|
+
code: ZodIssueCode.invalid_arguments,
|
|
11289
|
+
argumentsError: error2
|
|
11290
|
+
}
|
|
11291
|
+
});
|
|
11292
|
+
}
|
|
11293
|
+
function makeReturnsIssue(returns, error2) {
|
|
11294
|
+
return makeIssue({
|
|
11295
|
+
data: returns,
|
|
11296
|
+
path: ctx.path,
|
|
11297
|
+
errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), errorMap].filter((x) => !!x),
|
|
11298
|
+
issueData: {
|
|
11299
|
+
code: ZodIssueCode.invalid_return_type,
|
|
11300
|
+
returnTypeError: error2
|
|
11301
|
+
}
|
|
11302
|
+
});
|
|
11303
|
+
}
|
|
11304
|
+
const params = { errorMap: ctx.common.contextualErrorMap };
|
|
11305
|
+
const fn = ctx.data;
|
|
11306
|
+
if (this._def.returns instanceof ZodPromise) {
|
|
11307
|
+
const me = this;
|
|
11308
|
+
return OK(async function(...args) {
|
|
11309
|
+
const error2 = new ZodError([]);
|
|
11310
|
+
const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => {
|
|
11311
|
+
error2.addIssue(makeArgsIssue(args, e));
|
|
11312
|
+
throw error2;
|
|
11313
|
+
});
|
|
11314
|
+
const result = await Reflect.apply(fn, this, parsedArgs);
|
|
11315
|
+
const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e) => {
|
|
11316
|
+
error2.addIssue(makeReturnsIssue(result, e));
|
|
11317
|
+
throw error2;
|
|
11318
|
+
});
|
|
11319
|
+
return parsedReturns;
|
|
11320
|
+
});
|
|
11321
|
+
} else {
|
|
11322
|
+
const me = this;
|
|
11323
|
+
return OK(function(...args) {
|
|
11324
|
+
const parsedArgs = me._def.args.safeParse(args, params);
|
|
11325
|
+
if (!parsedArgs.success) {
|
|
11326
|
+
throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);
|
|
11327
|
+
}
|
|
11328
|
+
const result = Reflect.apply(fn, this, parsedArgs.data);
|
|
11329
|
+
const parsedReturns = me._def.returns.safeParse(result, params);
|
|
11330
|
+
if (!parsedReturns.success) {
|
|
11331
|
+
throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);
|
|
11332
|
+
}
|
|
11333
|
+
return parsedReturns.data;
|
|
11334
|
+
});
|
|
11335
|
+
}
|
|
11336
|
+
}
|
|
11337
|
+
parameters() {
|
|
11338
|
+
return this._def.args;
|
|
11339
|
+
}
|
|
11340
|
+
returnType() {
|
|
11341
|
+
return this._def.returns;
|
|
11342
|
+
}
|
|
11343
|
+
args(...items) {
|
|
11344
|
+
return new ZodFunction({
|
|
11345
|
+
...this._def,
|
|
11346
|
+
args: ZodTuple.create(items).rest(ZodUnknown.create())
|
|
11347
|
+
});
|
|
11348
|
+
}
|
|
11349
|
+
returns(returnType) {
|
|
11350
|
+
return new ZodFunction({
|
|
11351
|
+
...this._def,
|
|
11352
|
+
returns: returnType
|
|
11353
|
+
});
|
|
11354
|
+
}
|
|
11355
|
+
implement(func) {
|
|
11356
|
+
const validatedFunc = this.parse(func);
|
|
11357
|
+
return validatedFunc;
|
|
11358
|
+
}
|
|
11359
|
+
strictImplement(func) {
|
|
11360
|
+
const validatedFunc = this.parse(func);
|
|
11361
|
+
return validatedFunc;
|
|
11362
|
+
}
|
|
11363
|
+
static create(args, returns, params) {
|
|
11364
|
+
return new ZodFunction({
|
|
11365
|
+
args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()),
|
|
11366
|
+
returns: returns || ZodUnknown.create(),
|
|
11367
|
+
typeName: ZodFirstPartyTypeKind.ZodFunction,
|
|
11368
|
+
...processCreateParams(params)
|
|
11369
|
+
});
|
|
11370
|
+
}
|
|
11371
|
+
}
|
|
10726
11372
|
class ZodLazy extends ZodType {
|
|
10727
11373
|
get schema() {
|
|
10728
11374
|
return this._def.getter();
|
|
@@ -11180,6 +11826,7 @@ ZodNaN.create = (params) => {
|
|
|
11180
11826
|
...processCreateParams(params)
|
|
11181
11827
|
});
|
|
11182
11828
|
};
|
|
11829
|
+
const BRAND = Symbol("zod_brand");
|
|
11183
11830
|
class ZodBranded extends ZodType {
|
|
11184
11831
|
_parse(input) {
|
|
11185
11832
|
const { ctx } = this._processInputParams(input);
|
|
@@ -11271,6 +11918,36 @@ ZodReadonly.create = (type, params) => {
|
|
|
11271
11918
|
...processCreateParams(params)
|
|
11272
11919
|
});
|
|
11273
11920
|
};
|
|
11921
|
+
function cleanParams(params, data) {
|
|
11922
|
+
const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
|
|
11923
|
+
const p2 = typeof p === "string" ? { message: p } : p;
|
|
11924
|
+
return p2;
|
|
11925
|
+
}
|
|
11926
|
+
function custom(check, _params = {}, fatal) {
|
|
11927
|
+
if (check)
|
|
11928
|
+
return ZodAny.create().superRefine((data, ctx) => {
|
|
11929
|
+
const r = check(data);
|
|
11930
|
+
if (r instanceof Promise) {
|
|
11931
|
+
return r.then((r2) => {
|
|
11932
|
+
if (!r2) {
|
|
11933
|
+
const params = cleanParams(_params, data);
|
|
11934
|
+
const _fatal = params.fatal ?? fatal ?? true;
|
|
11935
|
+
ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
|
|
11936
|
+
}
|
|
11937
|
+
});
|
|
11938
|
+
}
|
|
11939
|
+
if (!r) {
|
|
11940
|
+
const params = cleanParams(_params, data);
|
|
11941
|
+
const _fatal = params.fatal ?? fatal ?? true;
|
|
11942
|
+
ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
|
|
11943
|
+
}
|
|
11944
|
+
return;
|
|
11945
|
+
});
|
|
11946
|
+
return ZodAny.create();
|
|
11947
|
+
}
|
|
11948
|
+
const late = {
|
|
11949
|
+
object: ZodObject.lazycreate
|
|
11950
|
+
};
|
|
11274
11951
|
var ZodFirstPartyTypeKind;
|
|
11275
11952
|
(function(ZodFirstPartyTypeKind2) {
|
|
11276
11953
|
ZodFirstPartyTypeKind2["ZodString"] = "ZodString";
|
|
@@ -11310,17 +11987,251 @@ var ZodFirstPartyTypeKind;
|
|
|
11310
11987
|
ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
|
|
11311
11988
|
ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
|
|
11312
11989
|
})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
11990
|
+
const instanceOfType = (cls, params = {
|
|
11991
|
+
message: `Input not instance of ${cls.name}`
|
|
11992
|
+
}) => custom((data) => data instanceof cls, params);
|
|
11313
11993
|
const stringType = ZodString.create;
|
|
11314
|
-
|
|
11315
|
-
|
|
11994
|
+
const numberType = ZodNumber.create;
|
|
11995
|
+
const nanType = ZodNaN.create;
|
|
11996
|
+
const bigIntType = ZodBigInt.create;
|
|
11997
|
+
const booleanType = ZodBoolean.create;
|
|
11998
|
+
const dateType = ZodDate.create;
|
|
11999
|
+
const symbolType = ZodSymbol.create;
|
|
12000
|
+
const undefinedType = ZodUndefined.create;
|
|
12001
|
+
const nullType = ZodNull.create;
|
|
12002
|
+
const anyType = ZodAny.create;
|
|
12003
|
+
const unknownType = ZodUnknown.create;
|
|
12004
|
+
const neverType = ZodNever.create;
|
|
12005
|
+
const voidType = ZodVoid.create;
|
|
12006
|
+
const arrayType = ZodArray.create;
|
|
11316
12007
|
const objectType = ZodObject.create;
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
12008
|
+
const strictObjectType = ZodObject.strictCreate;
|
|
12009
|
+
const unionType = ZodUnion.create;
|
|
12010
|
+
const discriminatedUnionType = ZodDiscriminatedUnion.create;
|
|
12011
|
+
const intersectionType = ZodIntersection.create;
|
|
12012
|
+
const tupleType = ZodTuple.create;
|
|
12013
|
+
const recordType = ZodRecord.create;
|
|
12014
|
+
const mapType = ZodMap.create;
|
|
12015
|
+
const setType = ZodSet.create;
|
|
12016
|
+
const functionType = ZodFunction.create;
|
|
12017
|
+
const lazyType = ZodLazy.create;
|
|
12018
|
+
const literalType = ZodLiteral.create;
|
|
11320
12019
|
const enumType = ZodEnum.create;
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
12020
|
+
const nativeEnumType = ZodNativeEnum.create;
|
|
12021
|
+
const promiseType = ZodPromise.create;
|
|
12022
|
+
const effectsType = ZodEffects.create;
|
|
12023
|
+
const optionalType = ZodOptional.create;
|
|
12024
|
+
const nullableType = ZodNullable.create;
|
|
12025
|
+
const preprocessType = ZodEffects.createWithPreprocess;
|
|
12026
|
+
const pipelineType = ZodPipeline.create;
|
|
12027
|
+
const ostring = () => stringType().optional();
|
|
12028
|
+
const onumber = () => numberType().optional();
|
|
12029
|
+
const oboolean = () => booleanType().optional();
|
|
12030
|
+
const coerce = {
|
|
12031
|
+
string: (arg) => ZodString.create({ ...arg, coerce: true }),
|
|
12032
|
+
number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
|
|
12033
|
+
boolean: (arg) => ZodBoolean.create({
|
|
12034
|
+
...arg,
|
|
12035
|
+
coerce: true
|
|
12036
|
+
}),
|
|
12037
|
+
bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
|
|
12038
|
+
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
12039
|
+
};
|
|
12040
|
+
const NEVER = INVALID;
|
|
12041
|
+
const z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12042
|
+
__proto__: null,
|
|
12043
|
+
BRAND,
|
|
12044
|
+
DIRTY,
|
|
12045
|
+
EMPTY_PATH,
|
|
12046
|
+
INVALID,
|
|
12047
|
+
NEVER,
|
|
12048
|
+
OK,
|
|
12049
|
+
ParseStatus,
|
|
12050
|
+
Schema: ZodType,
|
|
12051
|
+
ZodAny,
|
|
12052
|
+
ZodArray,
|
|
12053
|
+
ZodBigInt,
|
|
12054
|
+
ZodBoolean,
|
|
12055
|
+
ZodBranded,
|
|
12056
|
+
ZodCatch,
|
|
12057
|
+
ZodDate,
|
|
12058
|
+
ZodDefault,
|
|
12059
|
+
ZodDiscriminatedUnion,
|
|
12060
|
+
ZodEffects,
|
|
12061
|
+
ZodEnum,
|
|
12062
|
+
ZodError,
|
|
12063
|
+
get ZodFirstPartyTypeKind() {
|
|
12064
|
+
return ZodFirstPartyTypeKind;
|
|
12065
|
+
},
|
|
12066
|
+
ZodFunction,
|
|
12067
|
+
ZodIntersection,
|
|
12068
|
+
ZodIssueCode,
|
|
12069
|
+
ZodLazy,
|
|
12070
|
+
ZodLiteral,
|
|
12071
|
+
ZodMap,
|
|
12072
|
+
ZodNaN,
|
|
12073
|
+
ZodNativeEnum,
|
|
12074
|
+
ZodNever,
|
|
12075
|
+
ZodNull,
|
|
12076
|
+
ZodNullable,
|
|
12077
|
+
ZodNumber,
|
|
12078
|
+
ZodObject,
|
|
12079
|
+
ZodOptional,
|
|
12080
|
+
ZodParsedType,
|
|
12081
|
+
ZodPipeline,
|
|
12082
|
+
ZodPromise,
|
|
12083
|
+
ZodReadonly,
|
|
12084
|
+
ZodRecord,
|
|
12085
|
+
ZodSchema: ZodType,
|
|
12086
|
+
ZodSet,
|
|
12087
|
+
ZodString,
|
|
12088
|
+
ZodSymbol,
|
|
12089
|
+
ZodTransformer: ZodEffects,
|
|
12090
|
+
ZodTuple,
|
|
12091
|
+
ZodType,
|
|
12092
|
+
ZodUndefined,
|
|
12093
|
+
ZodUnion,
|
|
12094
|
+
ZodUnknown,
|
|
12095
|
+
ZodVoid,
|
|
12096
|
+
addIssueToContext,
|
|
12097
|
+
any: anyType,
|
|
12098
|
+
array: arrayType,
|
|
12099
|
+
bigint: bigIntType,
|
|
12100
|
+
boolean: booleanType,
|
|
12101
|
+
coerce,
|
|
12102
|
+
custom,
|
|
12103
|
+
date: dateType,
|
|
12104
|
+
datetimeRegex,
|
|
12105
|
+
defaultErrorMap: errorMap,
|
|
12106
|
+
discriminatedUnion: discriminatedUnionType,
|
|
12107
|
+
effect: effectsType,
|
|
12108
|
+
enum: enumType,
|
|
12109
|
+
function: functionType,
|
|
12110
|
+
getErrorMap,
|
|
12111
|
+
getParsedType,
|
|
12112
|
+
instanceof: instanceOfType,
|
|
12113
|
+
intersection: intersectionType,
|
|
12114
|
+
isAborted,
|
|
12115
|
+
isAsync,
|
|
12116
|
+
isDirty,
|
|
12117
|
+
isValid,
|
|
12118
|
+
late,
|
|
12119
|
+
lazy: lazyType,
|
|
12120
|
+
literal: literalType,
|
|
12121
|
+
makeIssue,
|
|
12122
|
+
map: mapType,
|
|
12123
|
+
nan: nanType,
|
|
12124
|
+
nativeEnum: nativeEnumType,
|
|
12125
|
+
never: neverType,
|
|
12126
|
+
null: nullType,
|
|
12127
|
+
nullable: nullableType,
|
|
12128
|
+
number: numberType,
|
|
12129
|
+
object: objectType,
|
|
12130
|
+
get objectUtil() {
|
|
12131
|
+
return objectUtil;
|
|
12132
|
+
},
|
|
12133
|
+
oboolean,
|
|
12134
|
+
onumber,
|
|
12135
|
+
optional: optionalType,
|
|
12136
|
+
ostring,
|
|
12137
|
+
pipeline: pipelineType,
|
|
12138
|
+
preprocess: preprocessType,
|
|
12139
|
+
promise: promiseType,
|
|
12140
|
+
quotelessJson,
|
|
12141
|
+
record: recordType,
|
|
12142
|
+
set: setType,
|
|
12143
|
+
setErrorMap,
|
|
12144
|
+
strictObject: strictObjectType,
|
|
12145
|
+
string: stringType,
|
|
12146
|
+
symbol: symbolType,
|
|
12147
|
+
transformer: effectsType,
|
|
12148
|
+
tuple: tupleType,
|
|
12149
|
+
undefined: undefinedType,
|
|
12150
|
+
union: unionType,
|
|
12151
|
+
unknown: unknownType,
|
|
12152
|
+
get util() {
|
|
12153
|
+
return util$3;
|
|
12154
|
+
},
|
|
12155
|
+
void: voidType
|
|
12156
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
12157
|
+
const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
12158
|
+
function base32Encode(buffer) {
|
|
12159
|
+
let bits = 0;
|
|
12160
|
+
let value = 0;
|
|
12161
|
+
let output = "";
|
|
12162
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
12163
|
+
value = value << 8 | buffer[i];
|
|
12164
|
+
bits += 8;
|
|
12165
|
+
while (bits >= 5) {
|
|
12166
|
+
output += BASE32_ALPHABET[value >>> bits - 5 & 31];
|
|
12167
|
+
bits -= 5;
|
|
12168
|
+
}
|
|
12169
|
+
}
|
|
12170
|
+
if (bits > 0) {
|
|
12171
|
+
output += BASE32_ALPHABET[value << 5 - bits & 31];
|
|
12172
|
+
}
|
|
12173
|
+
return output;
|
|
12174
|
+
}
|
|
12175
|
+
function base32Decode(encoded) {
|
|
12176
|
+
const cleanInput = encoded.replace(/=+$/, "").toUpperCase();
|
|
12177
|
+
const bytes = [];
|
|
12178
|
+
let bits = 0;
|
|
12179
|
+
let value = 0;
|
|
12180
|
+
for (let i = 0; i < cleanInput.length; i++) {
|
|
12181
|
+
const index = BASE32_ALPHABET.indexOf(cleanInput[i]);
|
|
12182
|
+
if (index === -1) continue;
|
|
12183
|
+
value = value << 5 | index;
|
|
12184
|
+
bits += 5;
|
|
12185
|
+
if (bits >= 8) {
|
|
12186
|
+
bytes.push(value >>> bits - 8 & 255);
|
|
12187
|
+
bits -= 8;
|
|
12188
|
+
}
|
|
12189
|
+
}
|
|
12190
|
+
return Buffer.from(bytes);
|
|
12191
|
+
}
|
|
12192
|
+
function generateHotp(secret, counter) {
|
|
12193
|
+
const hmac = createHmac("sha1", secret);
|
|
12194
|
+
const counterBuffer = Buffer.alloc(8);
|
|
12195
|
+
counterBuffer.writeBigInt64BE(counter);
|
|
12196
|
+
hmac.update(counterBuffer);
|
|
12197
|
+
const hash = hmac.digest();
|
|
12198
|
+
const offset = hash[hash.length - 1] & 15;
|
|
12199
|
+
const code2 = ((hash[offset] & 127) << 24 | hash[offset + 1] << 16 | hash[offset + 2] << 8 | hash[offset + 3]) % 1e6;
|
|
12200
|
+
return code2.toString().padStart(6, "0");
|
|
12201
|
+
}
|
|
12202
|
+
function verifyTotp(secret, token, window2 = 1) {
|
|
12203
|
+
const timeStep = 30;
|
|
12204
|
+
const counter = BigInt(Math.floor(Date.now() / 1e3 / timeStep));
|
|
12205
|
+
for (let i = -window2; i <= window2; i++) {
|
|
12206
|
+
if (generateHotp(secret, counter + BigInt(i)) === token) {
|
|
12207
|
+
return true;
|
|
12208
|
+
}
|
|
12209
|
+
}
|
|
12210
|
+
return false;
|
|
12211
|
+
}
|
|
12212
|
+
function generateTotpSecret(issuer, accountName) {
|
|
12213
|
+
const secretBuffer = randomBytes(20);
|
|
12214
|
+
const secret = base32Encode(secretBuffer);
|
|
12215
|
+
const encodedIssuer = encodeURIComponent(issuer);
|
|
12216
|
+
const encodedAccount = encodeURIComponent(accountName);
|
|
12217
|
+
const uri = `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
|
|
12218
|
+
return {
|
|
12219
|
+
secret,
|
|
12220
|
+
uri
|
|
12221
|
+
};
|
|
12222
|
+
}
|
|
12223
|
+
function generateRecoveryCodes(count = 10) {
|
|
12224
|
+
return Array.from({
|
|
12225
|
+
length: count
|
|
12226
|
+
}, () => {
|
|
12227
|
+
const raw = randomBytes(5).toString("hex").toUpperCase();
|
|
12228
|
+
const parts = raw.match(/.{1,5}/g);
|
|
12229
|
+
return parts ? parts.join("-") : raw;
|
|
12230
|
+
});
|
|
12231
|
+
}
|
|
12232
|
+
function hashRecoveryCode(code2) {
|
|
12233
|
+
return createHash("sha256").update(code2.replace(/-/g, "").toUpperCase()).digest("hex");
|
|
12234
|
+
}
|
|
11324
12235
|
function buildAuthResponse(user, roleIds, accessToken, refreshToken) {
|
|
11325
12236
|
return {
|
|
11326
12237
|
user: {
|
|
@@ -11358,9 +12269,9 @@ function createAuthRoutes(config) {
|
|
|
11358
12269
|
emailService,
|
|
11359
12270
|
emailConfig,
|
|
11360
12271
|
allowRegistration = false,
|
|
11361
|
-
|
|
12272
|
+
authHooks
|
|
11362
12273
|
} = config;
|
|
11363
|
-
const ops =
|
|
12274
|
+
const ops = resolveAuthHooks(authHooks);
|
|
11364
12275
|
const registerSchema = objectType({
|
|
11365
12276
|
email: stringType().email("Invalid email address").max(255),
|
|
11366
12277
|
password: stringType().min(1, "Password is required").max(128),
|
|
@@ -11424,7 +12335,19 @@ function createAuthRoutes(config) {
|
|
|
11424
12335
|
async function createSessionAndTokens(userId, userAgent, ipAddress) {
|
|
11425
12336
|
const roles = await authRepo.getUserRoles(userId);
|
|
11426
12337
|
const roleIds = roles.map((r) => r.id);
|
|
11427
|
-
|
|
12338
|
+
let customClaims;
|
|
12339
|
+
if (authHooks?.customizeAccessToken) {
|
|
12340
|
+
const user = await authRepo.getUserById(userId);
|
|
12341
|
+
if (user) {
|
|
12342
|
+
const defaultClaims = {
|
|
12343
|
+
userId,
|
|
12344
|
+
roles: roleIds,
|
|
12345
|
+
aal: "aal1"
|
|
12346
|
+
};
|
|
12347
|
+
customClaims = await authHooks.customizeAccessToken(defaultClaims, user);
|
|
12348
|
+
}
|
|
12349
|
+
}
|
|
12350
|
+
const accessToken = generateAccessToken(userId, roleIds, "aal1", customClaims);
|
|
11428
12351
|
const refreshToken = generateRefreshToken();
|
|
11429
12352
|
await authRepo.createRefreshToken(userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), userAgent, ipAddress);
|
|
11430
12353
|
return {
|
|
@@ -11459,8 +12382,8 @@ function createAuthRoutes(config) {
|
|
|
11459
12382
|
passwordHash,
|
|
11460
12383
|
displayName: displayName || void 0
|
|
11461
12384
|
};
|
|
11462
|
-
if (
|
|
11463
|
-
createData = await
|
|
12385
|
+
if (authHooks?.beforeUserCreate) {
|
|
12386
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
11464
12387
|
}
|
|
11465
12388
|
const user = await authRepo.createUser(createData);
|
|
11466
12389
|
const existingUsers = await authRepo.listUsers();
|
|
@@ -11479,14 +12402,16 @@ function createAuthRoutes(config) {
|
|
|
11479
12402
|
email: user.email,
|
|
11480
12403
|
displayName: user.displayName
|
|
11481
12404
|
});
|
|
11482
|
-
if (
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
})
|
|
12405
|
+
if (authHooks?.afterUserCreate) {
|
|
12406
|
+
try {
|
|
12407
|
+
await authHooks.afterUserCreate(user);
|
|
12408
|
+
} catch (err) {
|
|
12409
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
12410
|
+
}
|
|
11486
12411
|
}
|
|
11487
|
-
if (
|
|
11488
|
-
|
|
11489
|
-
console.error("[
|
|
12412
|
+
if (authHooks?.onAuthenticated) {
|
|
12413
|
+
authHooks.onAuthenticated(user, "register").catch((err) => {
|
|
12414
|
+
console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
|
|
11490
12415
|
});
|
|
11491
12416
|
}
|
|
11492
12417
|
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
|
|
@@ -11496,9 +12421,12 @@ function createAuthRoutes(config) {
|
|
|
11496
12421
|
email,
|
|
11497
12422
|
password
|
|
11498
12423
|
} = parseBody2(loginSchema, await c.req.json());
|
|
12424
|
+
if (authHooks?.beforeLogin) {
|
|
12425
|
+
await authHooks.beforeLogin(email, "login");
|
|
12426
|
+
}
|
|
11499
12427
|
let user;
|
|
11500
|
-
if (
|
|
11501
|
-
user = await
|
|
12428
|
+
if (authHooks?.verifyCredentials) {
|
|
12429
|
+
user = await authHooks.verifyCredentials(email, password, authRepo);
|
|
11502
12430
|
if (!user) {
|
|
11503
12431
|
throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
|
|
11504
12432
|
}
|
|
@@ -11520,9 +12448,9 @@ function createAuthRoutes(config) {
|
|
|
11520
12448
|
accessToken,
|
|
11521
12449
|
refreshToken
|
|
11522
12450
|
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
11523
|
-
if (
|
|
11524
|
-
|
|
11525
|
-
console.error("[
|
|
12451
|
+
if (authHooks?.onAuthenticated) {
|
|
12452
|
+
authHooks.onAuthenticated(user, "login").catch((err) => {
|
|
12453
|
+
console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
|
|
11526
12454
|
});
|
|
11527
12455
|
}
|
|
11528
12456
|
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken));
|
|
@@ -11561,6 +12489,13 @@ function createAuthRoutes(config) {
|
|
|
11561
12489
|
await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, {
|
|
11562
12490
|
email: externalUser.email
|
|
11563
12491
|
});
|
|
12492
|
+
if (authHooks?.afterUserCreate) {
|
|
12493
|
+
try {
|
|
12494
|
+
await authHooks.afterUserCreate(user);
|
|
12495
|
+
} catch (err) {
|
|
12496
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
12497
|
+
}
|
|
12498
|
+
}
|
|
11564
12499
|
const allUsers = await authRepo.listUsers();
|
|
11565
12500
|
const isFirstUser = allUsers.length === 1 && allUsers[0].id === user.id;
|
|
11566
12501
|
if (isFirstUser) {
|
|
@@ -11646,6 +12581,11 @@ function createAuthRoutes(config) {
|
|
|
11646
12581
|
await authRepo.updatePassword(storedToken.userId, passwordHash);
|
|
11647
12582
|
await authRepo.markPasswordResetTokenUsed(tokenHash);
|
|
11648
12583
|
await authRepo.deleteAllRefreshTokensForUser(storedToken.userId);
|
|
12584
|
+
if (authHooks?.onPasswordReset) {
|
|
12585
|
+
authHooks.onPasswordReset(storedToken.userId).catch((err) => {
|
|
12586
|
+
console.error("[AuthHooks] onPasswordReset error:", err instanceof Error ? err.message : err);
|
|
12587
|
+
});
|
|
12588
|
+
}
|
|
11649
12589
|
return c.json({
|
|
11650
12590
|
success: true,
|
|
11651
12591
|
message: "Password has been reset successfully"
|
|
@@ -11749,7 +12689,19 @@ function createAuthRoutes(config) {
|
|
|
11749
12689
|
}
|
|
11750
12690
|
const roles = await authRepo.getUserRoles(storedToken.userId);
|
|
11751
12691
|
const roleIds = roles.map((r) => r.id);
|
|
11752
|
-
|
|
12692
|
+
let customClaims;
|
|
12693
|
+
if (authHooks?.customizeAccessToken) {
|
|
12694
|
+
const user = await authRepo.getUserById(storedToken.userId);
|
|
12695
|
+
if (user) {
|
|
12696
|
+
const defaultClaims = {
|
|
12697
|
+
userId: storedToken.userId,
|
|
12698
|
+
roles: roleIds,
|
|
12699
|
+
aal: "aal1"
|
|
12700
|
+
};
|
|
12701
|
+
customClaims = await authHooks.customizeAccessToken(defaultClaims, user);
|
|
12702
|
+
}
|
|
12703
|
+
}
|
|
12704
|
+
const newAccessToken = generateAccessToken(storedToken.userId, roleIds, "aal1", customClaims);
|
|
11753
12705
|
const newRefreshToken = generateRefreshToken();
|
|
11754
12706
|
const userAgent = c.req.header("user-agent") || "unknown";
|
|
11755
12707
|
const ipAddress = c.req.header("x-forwarded-for") || "unknown";
|
|
@@ -11771,6 +12723,18 @@ function createAuthRoutes(config) {
|
|
|
11771
12723
|
const tokenHash = hashRefreshToken(refreshToken);
|
|
11772
12724
|
await authRepo.deleteRefreshToken(tokenHash);
|
|
11773
12725
|
}
|
|
12726
|
+
const authHeader = c.req.header("authorization");
|
|
12727
|
+
if (authHooks?.afterLogout && authHeader?.startsWith("Bearer ")) {
|
|
12728
|
+
const {
|
|
12729
|
+
verifyAccessToken: verifyAccessToken2
|
|
12730
|
+
} = await Promise.resolve().then(() => jwt);
|
|
12731
|
+
const payload = verifyAccessToken2(authHeader.substring(7));
|
|
12732
|
+
if (payload) {
|
|
12733
|
+
authHooks.afterLogout(payload.userId).catch((err) => {
|
|
12734
|
+
console.error("[AuthHooks] afterLogout error:", err instanceof Error ? err.message : err);
|
|
12735
|
+
});
|
|
12736
|
+
}
|
|
12737
|
+
}
|
|
11774
12738
|
return c.json({
|
|
11775
12739
|
success: true
|
|
11776
12740
|
});
|
|
@@ -11890,6 +12854,270 @@ function createAuthRoutes(config) {
|
|
|
11890
12854
|
enabledProviders
|
|
11891
12855
|
});
|
|
11892
12856
|
});
|
|
12857
|
+
router.post("/anonymous", strictAuthLimiter, async (c) => {
|
|
12858
|
+
const anonId = randomBytes(16).toString("hex");
|
|
12859
|
+
const anonEmail = `anon_${anonId.slice(0, 8)}@anonymous.local`;
|
|
12860
|
+
let createData = {
|
|
12861
|
+
email: anonEmail,
|
|
12862
|
+
emailVerified: false,
|
|
12863
|
+
isAnonymous: true
|
|
12864
|
+
};
|
|
12865
|
+
if (authHooks?.beforeUserCreate) {
|
|
12866
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
12867
|
+
}
|
|
12868
|
+
const user = await authRepo.createUser(createData);
|
|
12869
|
+
if (config.defaultRole) {
|
|
12870
|
+
await authRepo.assignDefaultRole(user.id, config.defaultRole);
|
|
12871
|
+
}
|
|
12872
|
+
const {
|
|
12873
|
+
roleIds,
|
|
12874
|
+
accessToken,
|
|
12875
|
+
refreshToken
|
|
12876
|
+
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
12877
|
+
if (authHooks?.afterUserCreate) {
|
|
12878
|
+
authHooks.afterUserCreate(user).catch((err) => {
|
|
12879
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
12880
|
+
});
|
|
12881
|
+
}
|
|
12882
|
+
if (authHooks?.onAuthenticated) {
|
|
12883
|
+
authHooks.onAuthenticated(user, "anonymous").catch((err) => {
|
|
12884
|
+
console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
|
|
12885
|
+
});
|
|
12886
|
+
}
|
|
12887
|
+
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
|
|
12888
|
+
});
|
|
12889
|
+
router.post("/anonymous/link", requireAuth, async (c) => {
|
|
12890
|
+
const userCtx = c.get("user");
|
|
12891
|
+
if (!userCtx) {
|
|
12892
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12893
|
+
}
|
|
12894
|
+
const user = await authRepo.getUserById(userCtx.userId);
|
|
12895
|
+
if (!user?.isAnonymous) {
|
|
12896
|
+
throw ApiError.badRequest("User is not anonymous", "NOT_ANONYMOUS");
|
|
12897
|
+
}
|
|
12898
|
+
const linkSchema = objectType({
|
|
12899
|
+
email: stringType().email("Invalid email address").max(255),
|
|
12900
|
+
password: stringType().min(1, "Password is required").max(128)
|
|
12901
|
+
});
|
|
12902
|
+
const {
|
|
12903
|
+
email,
|
|
12904
|
+
password
|
|
12905
|
+
} = parseBody2(linkSchema, await c.req.json());
|
|
12906
|
+
const passwordValidation = ops.validatePasswordStrength(password);
|
|
12907
|
+
if (!passwordValidation.valid) {
|
|
12908
|
+
throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
|
|
12909
|
+
}
|
|
12910
|
+
const existingUser = await authRepo.getUserByEmail(email.toLowerCase());
|
|
12911
|
+
if (existingUser) {
|
|
12912
|
+
throw ApiError.conflict("Email already registered", "EMAIL_EXISTS");
|
|
12913
|
+
}
|
|
12914
|
+
const passwordHash = await ops.hashPassword(password);
|
|
12915
|
+
const updatedUser = await authRepo.updateUser(user.id, {
|
|
12916
|
+
email: email.toLowerCase(),
|
|
12917
|
+
passwordHash,
|
|
12918
|
+
isAnonymous: false
|
|
12919
|
+
});
|
|
12920
|
+
if (!updatedUser) {
|
|
12921
|
+
throw ApiError.notFound("User not found");
|
|
12922
|
+
}
|
|
12923
|
+
const {
|
|
12924
|
+
roleIds,
|
|
12925
|
+
accessToken,
|
|
12926
|
+
refreshToken
|
|
12927
|
+
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
12928
|
+
return c.json(buildAuthResponse(updatedUser, roleIds, accessToken, refreshToken));
|
|
12929
|
+
});
|
|
12930
|
+
router.post("/mfa/enroll", requireAuth, async (c) => {
|
|
12931
|
+
const userCtx = c.get("user");
|
|
12932
|
+
if (!userCtx) {
|
|
12933
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12934
|
+
}
|
|
12935
|
+
const body = await c.req.json().catch(() => ({}));
|
|
12936
|
+
const friendlyName = typeof body.friendlyName === "string" ? body.friendlyName : void 0;
|
|
12937
|
+
const issuer = typeof body.issuer === "string" ? body.issuer : emailConfig?.appName || "Rebase";
|
|
12938
|
+
const user = await authRepo.getUserById(userCtx.userId);
|
|
12939
|
+
if (!user) {
|
|
12940
|
+
throw ApiError.notFound("User not found");
|
|
12941
|
+
}
|
|
12942
|
+
const {
|
|
12943
|
+
secret,
|
|
12944
|
+
uri
|
|
12945
|
+
} = generateTotpSecret(issuer, user.email);
|
|
12946
|
+
const factor = await authRepo.createMfaFactor(
|
|
12947
|
+
user.id,
|
|
12948
|
+
"totp",
|
|
12949
|
+
secret,
|
|
12950
|
+
// In production, encrypt this before storage
|
|
12951
|
+
friendlyName
|
|
12952
|
+
);
|
|
12953
|
+
const codes = generateRecoveryCodes(10);
|
|
12954
|
+
const codeHashes = codes.map(hashRecoveryCode);
|
|
12955
|
+
await authRepo.createRecoveryCodes(user.id, codeHashes);
|
|
12956
|
+
return c.json({
|
|
12957
|
+
factor: {
|
|
12958
|
+
id: factor.id,
|
|
12959
|
+
factorType: factor.factorType,
|
|
12960
|
+
friendlyName: factor.friendlyName
|
|
12961
|
+
},
|
|
12962
|
+
totp: {
|
|
12963
|
+
secret,
|
|
12964
|
+
uri,
|
|
12965
|
+
qrUri: uri
|
|
12966
|
+
// Client can use a QR library to render this
|
|
12967
|
+
},
|
|
12968
|
+
recoveryCodes: codes
|
|
12969
|
+
}, 201);
|
|
12970
|
+
});
|
|
12971
|
+
router.post("/mfa/verify", requireAuth, async (c) => {
|
|
12972
|
+
const userCtx = c.get("user");
|
|
12973
|
+
if (!userCtx) {
|
|
12974
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12975
|
+
}
|
|
12976
|
+
const verifySchema = objectType({
|
|
12977
|
+
factorId: stringType().min(1, "Factor ID is required"),
|
|
12978
|
+
code: stringType().length(6, "Code must be 6 digits")
|
|
12979
|
+
});
|
|
12980
|
+
const {
|
|
12981
|
+
factorId,
|
|
12982
|
+
code: code2
|
|
12983
|
+
} = parseBody2(verifySchema, await c.req.json());
|
|
12984
|
+
const factor = await authRepo.getMfaFactorById(factorId);
|
|
12985
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
12986
|
+
throw ApiError.notFound("MFA factor not found");
|
|
12987
|
+
}
|
|
12988
|
+
if (factor.verified) {
|
|
12989
|
+
throw ApiError.badRequest("Factor is already verified", "ALREADY_VERIFIED");
|
|
12990
|
+
}
|
|
12991
|
+
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
12992
|
+
const isValid2 = verifyTotp(secretBuffer, code2);
|
|
12993
|
+
if (!isValid2) {
|
|
12994
|
+
throw ApiError.unauthorized("Invalid TOTP code", "INVALID_CODE");
|
|
12995
|
+
}
|
|
12996
|
+
await authRepo.verifyMfaFactor(factorId);
|
|
12997
|
+
return c.json({
|
|
12998
|
+
success: true,
|
|
12999
|
+
message: "MFA factor verified and enrolled"
|
|
13000
|
+
});
|
|
13001
|
+
});
|
|
13002
|
+
router.post("/mfa/challenge", requireAuth, async (c) => {
|
|
13003
|
+
const userCtx = c.get("user");
|
|
13004
|
+
if (!userCtx) {
|
|
13005
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
13006
|
+
}
|
|
13007
|
+
const challengeSchema = objectType({
|
|
13008
|
+
factorId: stringType().min(1, "Factor ID is required")
|
|
13009
|
+
});
|
|
13010
|
+
const {
|
|
13011
|
+
factorId
|
|
13012
|
+
} = parseBody2(challengeSchema, await c.req.json());
|
|
13013
|
+
const factor = await authRepo.getMfaFactorById(factorId);
|
|
13014
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
13015
|
+
throw ApiError.notFound("MFA factor not found");
|
|
13016
|
+
}
|
|
13017
|
+
if (!factor.verified) {
|
|
13018
|
+
throw ApiError.badRequest("MFA factor is not yet verified", "FACTOR_NOT_VERIFIED");
|
|
13019
|
+
}
|
|
13020
|
+
const ipAddress = c.req.header("x-forwarded-for") || "unknown";
|
|
13021
|
+
const challenge = await authRepo.createMfaChallenge(factorId, ipAddress);
|
|
13022
|
+
return c.json({
|
|
13023
|
+
challengeId: challenge.id,
|
|
13024
|
+
factorId: challenge.factorId,
|
|
13025
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3).toISOString()
|
|
13026
|
+
});
|
|
13027
|
+
});
|
|
13028
|
+
router.post("/mfa/challenge/verify", requireAuth, async (c) => {
|
|
13029
|
+
const userCtx = c.get("user");
|
|
13030
|
+
if (!userCtx) {
|
|
13031
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
13032
|
+
}
|
|
13033
|
+
const challengeVerifySchema = objectType({
|
|
13034
|
+
challengeId: stringType().min(1, "Challenge ID is required"),
|
|
13035
|
+
code: stringType().min(1, "Code is required")
|
|
13036
|
+
});
|
|
13037
|
+
const {
|
|
13038
|
+
challengeId,
|
|
13039
|
+
code: code2
|
|
13040
|
+
} = parseBody2(challengeVerifySchema, await c.req.json());
|
|
13041
|
+
const challenge = await authRepo.getMfaChallengeById(challengeId);
|
|
13042
|
+
if (!challenge) {
|
|
13043
|
+
throw ApiError.badRequest("Invalid or expired challenge", "INVALID_CHALLENGE");
|
|
13044
|
+
}
|
|
13045
|
+
const factor = await authRepo.getMfaFactorById(challenge.factorId);
|
|
13046
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
13047
|
+
throw ApiError.notFound("MFA factor not found");
|
|
13048
|
+
}
|
|
13049
|
+
const isRecoveryCode = code2.length > 6;
|
|
13050
|
+
let isValid2 = false;
|
|
13051
|
+
if (isRecoveryCode) {
|
|
13052
|
+
const codeHash = hashRecoveryCode(code2);
|
|
13053
|
+
isValid2 = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
|
|
13054
|
+
} else {
|
|
13055
|
+
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
13056
|
+
isValid2 = verifyTotp(secretBuffer, code2);
|
|
13057
|
+
}
|
|
13058
|
+
if (!isValid2) {
|
|
13059
|
+
throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
|
|
13060
|
+
}
|
|
13061
|
+
await authRepo.verifyMfaChallenge(challengeId);
|
|
13062
|
+
const roles = await authRepo.getUserRoles(userCtx.userId);
|
|
13063
|
+
const roleIds = roles.map((r) => r.id);
|
|
13064
|
+
const accessToken = generateAccessToken(userCtx.userId, roleIds, "aal2");
|
|
13065
|
+
const refreshToken = generateRefreshToken();
|
|
13066
|
+
await authRepo.createRefreshToken(userCtx.userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
13067
|
+
if (authHooks?.onMfaVerified) {
|
|
13068
|
+
authHooks.onMfaVerified(userCtx.userId, factor.id).catch((err) => {
|
|
13069
|
+
console.error("[AuthHooks] onMfaVerified error:", err instanceof Error ? err.message : err);
|
|
13070
|
+
});
|
|
13071
|
+
}
|
|
13072
|
+
return c.json({
|
|
13073
|
+
tokens: {
|
|
13074
|
+
accessToken,
|
|
13075
|
+
refreshToken,
|
|
13076
|
+
accessTokenExpiresAt: getAccessTokenExpiry()
|
|
13077
|
+
}
|
|
13078
|
+
});
|
|
13079
|
+
});
|
|
13080
|
+
router.get("/mfa/factors", requireAuth, async (c) => {
|
|
13081
|
+
const userCtx = c.get("user");
|
|
13082
|
+
if (!userCtx) {
|
|
13083
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
13084
|
+
}
|
|
13085
|
+
const factors = await authRepo.getMfaFactors(userCtx.userId);
|
|
13086
|
+
return c.json({
|
|
13087
|
+
factors: factors.map((f2) => ({
|
|
13088
|
+
id: f2.id,
|
|
13089
|
+
factorType: f2.factorType,
|
|
13090
|
+
friendlyName: f2.friendlyName,
|
|
13091
|
+
verified: f2.verified,
|
|
13092
|
+
createdAt: f2.createdAt
|
|
13093
|
+
}))
|
|
13094
|
+
});
|
|
13095
|
+
});
|
|
13096
|
+
router.delete("/mfa/unenroll", requireAuth, async (c) => {
|
|
13097
|
+
const userCtx = c.get("user");
|
|
13098
|
+
if (!userCtx) {
|
|
13099
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
13100
|
+
}
|
|
13101
|
+
const unenrollSchema = objectType({
|
|
13102
|
+
factorId: stringType().min(1, "Factor ID is required")
|
|
13103
|
+
});
|
|
13104
|
+
const {
|
|
13105
|
+
factorId
|
|
13106
|
+
} = parseBody2(unenrollSchema, await c.req.json());
|
|
13107
|
+
const factor = await authRepo.getMfaFactorById(factorId);
|
|
13108
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
13109
|
+
throw ApiError.notFound("MFA factor not found");
|
|
13110
|
+
}
|
|
13111
|
+
await authRepo.deleteMfaFactor(factorId, userCtx.userId);
|
|
13112
|
+
const hasFactors = await authRepo.hasVerifiedMfaFactors(userCtx.userId);
|
|
13113
|
+
if (!hasFactors) {
|
|
13114
|
+
await authRepo.deleteAllRecoveryCodes(userCtx.userId);
|
|
13115
|
+
}
|
|
13116
|
+
return c.json({
|
|
13117
|
+
success: true,
|
|
13118
|
+
message: "MFA factor removed"
|
|
13119
|
+
});
|
|
13120
|
+
});
|
|
11893
13121
|
return router;
|
|
11894
13122
|
}
|
|
11895
13123
|
function generateSecurePassword() {
|
|
@@ -11921,9 +13149,9 @@ function createAdminRoutes(config) {
|
|
|
11921
13149
|
emailService,
|
|
11922
13150
|
emailConfig,
|
|
11923
13151
|
hooks,
|
|
11924
|
-
|
|
13152
|
+
authHooks
|
|
11925
13153
|
} = config;
|
|
11926
|
-
const ops =
|
|
13154
|
+
const ops = resolveAuthHooks(authHooks);
|
|
11927
13155
|
function buildHookContext(c, method) {
|
|
11928
13156
|
const user = c.get("user");
|
|
11929
13157
|
return {
|
|
@@ -11943,11 +13171,6 @@ function createAdminRoutes(config) {
|
|
|
11943
13171
|
const results = await Promise.all(users.map((u) => applyUserAfterRead(u, ctx)));
|
|
11944
13172
|
return results.filter((u) => u !== null);
|
|
11945
13173
|
}
|
|
11946
|
-
async function applyRoleAfterReadBatch(roles, ctx) {
|
|
11947
|
-
if (!hooks?.roles?.afterRead) return roles;
|
|
11948
|
-
const results = await Promise.all(roles.map((r) => hooks.roles.afterRead(r, ctx)));
|
|
11949
|
-
return results.filter((r) => r !== null);
|
|
11950
|
-
}
|
|
11951
13174
|
function toAdminUser(u, roles) {
|
|
11952
13175
|
return {
|
|
11953
13176
|
uid: u.id,
|
|
@@ -11991,6 +13214,10 @@ function createAdminRoutes(config) {
|
|
|
11991
13214
|
if (!userId) {
|
|
11992
13215
|
throw ApiError.unauthorized("User ID not found in auth context");
|
|
11993
13216
|
}
|
|
13217
|
+
const caller = await authRepo.getUserById(userId);
|
|
13218
|
+
if (!caller) {
|
|
13219
|
+
throw ApiError.notFound("Authenticated user does not exist in the database. Please sign out and sign in/register again.", "USER_NOT_FOUND");
|
|
13220
|
+
}
|
|
11994
13221
|
await authRepo.setUserRoles(userId, ["admin"]);
|
|
11995
13222
|
if (config.setBootstrapCompleted) {
|
|
11996
13223
|
await config.setBootstrapCompleted();
|
|
@@ -12242,6 +13469,18 @@ function createAdminRoutes(config) {
|
|
|
12242
13469
|
await authRepo.updateUser(userId, updates);
|
|
12243
13470
|
}
|
|
12244
13471
|
if (roles !== void 0 && Array.isArray(roles)) {
|
|
13472
|
+
const currentRoles = await authRepo.getUserRoleIds(userId);
|
|
13473
|
+
const wasAdmin = currentRoles.includes("admin");
|
|
13474
|
+
const willBeAdmin = roles.includes("admin");
|
|
13475
|
+
if (wasAdmin && !willBeAdmin) {
|
|
13476
|
+
const adminUsers = await authRepo.listUsersPaginated({
|
|
13477
|
+
roleId: "admin",
|
|
13478
|
+
limit: 1
|
|
13479
|
+
});
|
|
13480
|
+
if (adminUsers.total <= 1) {
|
|
13481
|
+
throw ApiError.forbidden("Cannot demote the last administrator", "LAST_ADMIN");
|
|
13482
|
+
}
|
|
13483
|
+
}
|
|
12245
13484
|
await authRepo.setUserRoles(userId, roles);
|
|
12246
13485
|
}
|
|
12247
13486
|
const result = await authRepo.getUserWithRoles(userId);
|
|
@@ -12266,6 +13505,16 @@ function createAdminRoutes(config) {
|
|
|
12266
13505
|
if (!existing) {
|
|
12267
13506
|
throw ApiError.notFound("User not found");
|
|
12268
13507
|
}
|
|
13508
|
+
const roles = await authRepo.getUserRoleIds(userId);
|
|
13509
|
+
if (roles.includes("admin")) {
|
|
13510
|
+
const adminUsers = await authRepo.listUsersPaginated({
|
|
13511
|
+
roleId: "admin",
|
|
13512
|
+
limit: 1
|
|
13513
|
+
});
|
|
13514
|
+
if (adminUsers.total <= 1) {
|
|
13515
|
+
throw ApiError.forbidden("Cannot delete the last administrator", "LAST_ADMIN");
|
|
13516
|
+
}
|
|
13517
|
+
}
|
|
12269
13518
|
const hookCtx = buildHookContext(c, "DELETE");
|
|
12270
13519
|
if (hooks?.users?.beforeDelete) {
|
|
12271
13520
|
await hooks.users.beforeDelete(userId, hookCtx);
|
|
@@ -12280,95 +13529,6 @@ function createAdminRoutes(config) {
|
|
|
12280
13529
|
success: true
|
|
12281
13530
|
});
|
|
12282
13531
|
});
|
|
12283
|
-
router.get("/roles", requireAdmin, async (c) => {
|
|
12284
|
-
const roles = await authRepo.listRoles();
|
|
12285
|
-
const hookCtx = buildHookContext(c, "GET");
|
|
12286
|
-
let adminRoles = roles.map((r) => ({
|
|
12287
|
-
id: r.id,
|
|
12288
|
-
name: r.name,
|
|
12289
|
-
isAdmin: r.isAdmin,
|
|
12290
|
-
defaultPermissions: r.defaultPermissions,
|
|
12291
|
-
config: r.config
|
|
12292
|
-
}));
|
|
12293
|
-
adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
|
|
12294
|
-
return c.json({
|
|
12295
|
-
roles: adminRoles
|
|
12296
|
-
});
|
|
12297
|
-
});
|
|
12298
|
-
router.get("/roles/:roleId", requireAdmin, async (c) => {
|
|
12299
|
-
const roleId = c.req.param("roleId");
|
|
12300
|
-
const role = await authRepo.getRoleById(roleId);
|
|
12301
|
-
if (!role) {
|
|
12302
|
-
throw ApiError.notFound("Role not found");
|
|
12303
|
-
}
|
|
12304
|
-
return c.json({
|
|
12305
|
-
role
|
|
12306
|
-
});
|
|
12307
|
-
});
|
|
12308
|
-
router.post("/roles", requireAdmin, async (c) => {
|
|
12309
|
-
const body = await c.req.json();
|
|
12310
|
-
const {
|
|
12311
|
-
id,
|
|
12312
|
-
name: name2,
|
|
12313
|
-
isAdmin,
|
|
12314
|
-
defaultPermissions,
|
|
12315
|
-
config: config2
|
|
12316
|
-
} = body;
|
|
12317
|
-
if (!id || !name2) {
|
|
12318
|
-
throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
|
|
12319
|
-
}
|
|
12320
|
-
const existing = await authRepo.getRoleById(id);
|
|
12321
|
-
if (existing) {
|
|
12322
|
-
throw ApiError.conflict("Role already exists", "ROLE_EXISTS");
|
|
12323
|
-
}
|
|
12324
|
-
const role = await authRepo.createRole({
|
|
12325
|
-
id,
|
|
12326
|
-
name: name2,
|
|
12327
|
-
isAdmin: isAdmin ?? false,
|
|
12328
|
-
defaultPermissions: defaultPermissions ?? null,
|
|
12329
|
-
config: config2 ?? null
|
|
12330
|
-
});
|
|
12331
|
-
return c.json({
|
|
12332
|
-
role
|
|
12333
|
-
}, 201);
|
|
12334
|
-
});
|
|
12335
|
-
router.put("/roles/:roleId", requireAdmin, async (c) => {
|
|
12336
|
-
const roleId = c.req.param("roleId");
|
|
12337
|
-
const body = await c.req.json();
|
|
12338
|
-
const {
|
|
12339
|
-
name: name2,
|
|
12340
|
-
isAdmin,
|
|
12341
|
-
defaultPermissions,
|
|
12342
|
-
config: config2
|
|
12343
|
-
} = body;
|
|
12344
|
-
const existing = await authRepo.getRoleById(roleId);
|
|
12345
|
-
if (!existing) {
|
|
12346
|
-
throw ApiError.notFound("Role not found");
|
|
12347
|
-
}
|
|
12348
|
-
const role = await authRepo.updateRole(roleId, {
|
|
12349
|
-
name: name2,
|
|
12350
|
-
isAdmin,
|
|
12351
|
-
defaultPermissions,
|
|
12352
|
-
config: config2
|
|
12353
|
-
});
|
|
12354
|
-
return c.json({
|
|
12355
|
-
role
|
|
12356
|
-
});
|
|
12357
|
-
});
|
|
12358
|
-
router.delete("/roles/:roleId", requireAdmin, async (c) => {
|
|
12359
|
-
const roleId = c.req.param("roleId");
|
|
12360
|
-
if (["admin", "editor", "viewer"].includes(roleId)) {
|
|
12361
|
-
throw ApiError.badRequest("Cannot delete built-in roles", "BUILTIN_ROLE");
|
|
12362
|
-
}
|
|
12363
|
-
const existing = await authRepo.getRoleById(roleId);
|
|
12364
|
-
if (!existing) {
|
|
12365
|
-
throw ApiError.notFound("Role not found");
|
|
12366
|
-
}
|
|
12367
|
-
await authRepo.deleteRole(roleId);
|
|
12368
|
-
return c.json({
|
|
12369
|
-
success: true
|
|
12370
|
-
});
|
|
12371
|
-
});
|
|
12372
13532
|
return router;
|
|
12373
13533
|
}
|
|
12374
13534
|
function createBuiltinAuthAdapter(config) {
|
|
@@ -12381,9 +13541,9 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12381
13541
|
oauthProviders = [],
|
|
12382
13542
|
serviceKey,
|
|
12383
13543
|
hooks,
|
|
12384
|
-
|
|
13544
|
+
authHooks
|
|
12385
13545
|
} = config;
|
|
12386
|
-
const resolvedOps =
|
|
13546
|
+
const resolvedOps = resolveAuthHooks(authHooks);
|
|
12387
13547
|
const adapter = {
|
|
12388
13548
|
id: "rebase-builtin",
|
|
12389
13549
|
serviceKey,
|
|
@@ -12412,8 +13572,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12412
13572
|
const extendedPayload = payload;
|
|
12413
13573
|
let roles = payload.roles || [];
|
|
12414
13574
|
try {
|
|
12415
|
-
|
|
12416
|
-
roles = userRoles.map((r) => r.id);
|
|
13575
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
12417
13576
|
} catch {
|
|
12418
13577
|
}
|
|
12419
13578
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
@@ -12443,8 +13602,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12443
13602
|
const extendedPayload = payload;
|
|
12444
13603
|
let roles = payload.roles || [];
|
|
12445
13604
|
try {
|
|
12446
|
-
|
|
12447
|
-
roles = userRoles.map((r) => r.id);
|
|
13605
|
+
roles = await authRepository.getUserRoleIds(payload.userId);
|
|
12448
13606
|
} catch {
|
|
12449
13607
|
}
|
|
12450
13608
|
const isAdmin = roles.some((r) => r === "admin" || r === "schema-admin");
|
|
@@ -12457,8 +13615,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12457
13615
|
rawToken: token
|
|
12458
13616
|
};
|
|
12459
13617
|
},
|
|
12460
|
-
userManagement: createUserManagementFromRepo(authRepository, resolvedOps,
|
|
12461
|
-
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
13618
|
+
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
|
|
12462
13619
|
createAuthRoutes() {
|
|
12463
13620
|
return createAuthRoutes({
|
|
12464
13621
|
authRepo: authRepository,
|
|
@@ -12467,7 +13624,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12467
13624
|
allowRegistration,
|
|
12468
13625
|
defaultRole,
|
|
12469
13626
|
oauthProviders,
|
|
12470
|
-
|
|
13627
|
+
authHooks
|
|
12471
13628
|
});
|
|
12472
13629
|
},
|
|
12473
13630
|
createAdminRoutes() {
|
|
@@ -12477,7 +13634,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12477
13634
|
emailConfig,
|
|
12478
13635
|
serviceKey,
|
|
12479
13636
|
hooks,
|
|
12480
|
-
|
|
13637
|
+
authHooks
|
|
12481
13638
|
});
|
|
12482
13639
|
},
|
|
12483
13640
|
async getCapabilities() {
|
|
@@ -12506,7 +13663,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12506
13663
|
};
|
|
12507
13664
|
return adapter;
|
|
12508
13665
|
}
|
|
12509
|
-
function createUserManagementFromRepo(repo, resolvedOps,
|
|
13666
|
+
function createUserManagementFromRepo(repo, resolvedOps, authHooks) {
|
|
12510
13667
|
return {
|
|
12511
13668
|
async listUsers(options2) {
|
|
12512
13669
|
const result = await repo.listUsersPaginated({
|
|
@@ -12537,14 +13694,16 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
|
|
|
12537
13694
|
photoUrl: data.photoUrl,
|
|
12538
13695
|
metadata: data.metadata
|
|
12539
13696
|
};
|
|
12540
|
-
if (
|
|
12541
|
-
createData = await
|
|
13697
|
+
if (authHooks?.beforeUserCreate) {
|
|
13698
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
12542
13699
|
}
|
|
12543
13700
|
const user = await repo.createUser(createData);
|
|
12544
|
-
if (
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
})
|
|
13701
|
+
if (authHooks?.afterUserCreate) {
|
|
13702
|
+
try {
|
|
13703
|
+
await authHooks.afterUserCreate(user);
|
|
13704
|
+
} catch (err) {
|
|
13705
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
13706
|
+
}
|
|
12548
13707
|
}
|
|
12549
13708
|
return toAuthUserData(user);
|
|
12550
13709
|
},
|
|
@@ -12561,47 +13720,24 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
|
|
|
12561
13720
|
return user ? toAuthUserData(user) : null;
|
|
12562
13721
|
},
|
|
12563
13722
|
async deleteUser(id) {
|
|
13723
|
+
if (authHooks?.beforeUserDelete) {
|
|
13724
|
+
await authHooks.beforeUserDelete(id);
|
|
13725
|
+
}
|
|
12564
13726
|
await repo.deleteUser(id);
|
|
13727
|
+
if (authHooks?.afterUserDelete) {
|
|
13728
|
+
authHooks.afterUserDelete(id).catch((err) => {
|
|
13729
|
+
console.error("[AuthHooks] afterUserDelete error:", err instanceof Error ? err.message : err);
|
|
13730
|
+
});
|
|
13731
|
+
}
|
|
12565
13732
|
},
|
|
12566
13733
|
async getUserRoles(userId) {
|
|
12567
|
-
|
|
12568
|
-
return roles.map(toAuthRoleData);
|
|
13734
|
+
return repo.getUserRoleIds(userId);
|
|
12569
13735
|
},
|
|
12570
13736
|
async setUserRoles(userId, roleIds) {
|
|
12571
13737
|
await repo.setUserRoles(userId, roleIds);
|
|
12572
13738
|
}
|
|
12573
13739
|
};
|
|
12574
13740
|
}
|
|
12575
|
-
function createRoleManagementFromRepo(repo) {
|
|
12576
|
-
return {
|
|
12577
|
-
async listRoles() {
|
|
12578
|
-
const roles = await repo.listRoles();
|
|
12579
|
-
return roles.map(toAuthRoleData);
|
|
12580
|
-
},
|
|
12581
|
-
async getRoleById(id) {
|
|
12582
|
-
const role = await repo.getRoleById(id);
|
|
12583
|
-
return role ? toAuthRoleData(role) : null;
|
|
12584
|
-
},
|
|
12585
|
-
async createRole(data) {
|
|
12586
|
-
const role = await repo.createRole({
|
|
12587
|
-
id: data.id,
|
|
12588
|
-
name: data.name,
|
|
12589
|
-
isAdmin: data.isAdmin,
|
|
12590
|
-
defaultPermissions: data.defaultPermissions,
|
|
12591
|
-
collectionPermissions: data.collectionPermissions,
|
|
12592
|
-
config: data.config
|
|
12593
|
-
});
|
|
12594
|
-
return toAuthRoleData(role);
|
|
12595
|
-
},
|
|
12596
|
-
async updateRole(id, data) {
|
|
12597
|
-
const role = await repo.updateRole(id, data);
|
|
12598
|
-
return role ? toAuthRoleData(role) : null;
|
|
12599
|
-
},
|
|
12600
|
-
async deleteRole(id) {
|
|
12601
|
-
await repo.deleteRole(id);
|
|
12602
|
-
}
|
|
12603
|
-
};
|
|
12604
|
-
}
|
|
12605
13741
|
function toAuthUserData(user) {
|
|
12606
13742
|
return {
|
|
12607
13743
|
id: user.id,
|
|
@@ -12614,16 +13750,6 @@ function toAuthUserData(user) {
|
|
|
12614
13750
|
updatedAt: user.updatedAt
|
|
12615
13751
|
};
|
|
12616
13752
|
}
|
|
12617
|
-
function toAuthRoleData(role) {
|
|
12618
|
-
return {
|
|
12619
|
-
id: role.id,
|
|
12620
|
-
name: role.name,
|
|
12621
|
-
isAdmin: role.isAdmin,
|
|
12622
|
-
defaultPermissions: role.defaultPermissions,
|
|
12623
|
-
collectionPermissions: role.collectionPermissions,
|
|
12624
|
-
config: role.config
|
|
12625
|
-
};
|
|
12626
|
-
}
|
|
12627
13753
|
function configureLogLevel(logLevel) {
|
|
12628
13754
|
const LOG_LEVEL = logLevel || process.env.LOG_LEVEL || "info";
|
|
12629
13755
|
const logLevels = {
|
|
@@ -12642,20 +13768,20 @@ function configureLogLevel(logLevel) {
|
|
|
12642
13768
|
if (currentLevel < 0) console.error = () => {
|
|
12643
13769
|
};
|
|
12644
13770
|
}
|
|
13771
|
+
let originalConsole;
|
|
12645
13772
|
function resetConsole() {
|
|
12646
|
-
if (!
|
|
12647
|
-
|
|
13773
|
+
if (!originalConsole) {
|
|
13774
|
+
originalConsole = {
|
|
12648
13775
|
log: console.log,
|
|
12649
13776
|
warn: console.warn,
|
|
12650
13777
|
error: console.error,
|
|
12651
13778
|
debug: console.debug
|
|
12652
13779
|
};
|
|
12653
13780
|
}
|
|
12654
|
-
|
|
12655
|
-
console.
|
|
12656
|
-
console.
|
|
12657
|
-
console.
|
|
12658
|
-
console.debug = original.debug;
|
|
13781
|
+
console.log = originalConsole.log;
|
|
13782
|
+
console.warn = originalConsole.warn;
|
|
13783
|
+
console.error = originalConsole.error;
|
|
13784
|
+
console.debug = originalConsole.debug;
|
|
12659
13785
|
}
|
|
12660
13786
|
const GCP_SEVERITY = {
|
|
12661
13787
|
debug: "DEBUG",
|
|
@@ -12897,12 +14023,6 @@ var browser$1 = { exports: {} };
|
|
|
12897
14023
|
exports.Response = globalObject.Response;
|
|
12898
14024
|
})(browser$1, browser$1.exports);
|
|
12899
14025
|
var browserExports = browser$1.exports;
|
|
12900
|
-
const __viteBrowserExternal = {};
|
|
12901
|
-
const __viteBrowserExternal$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12902
|
-
__proto__: null,
|
|
12903
|
-
default: __viteBrowserExternal
|
|
12904
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
12905
|
-
const require$$11 = /* @__PURE__ */ getAugmentedNamespace(__viteBrowserExternal$1);
|
|
12906
14026
|
const isStream = (stream2) => stream2 !== null && typeof stream2 === "object" && typeof stream2.pipe === "function";
|
|
12907
14027
|
isStream.writable = (stream2) => isStream(stream2) && stream2.writable !== false && typeof stream2._write === "function" && typeof stream2._writableState === "object";
|
|
12908
14028
|
isStream.readable = (stream2) => isStream(stream2) && stream2.readable !== false && typeof stream2._read === "function" && typeof stream2._readableState === "object";
|
|
@@ -13687,16 +14807,16 @@ Object.defineProperty(sha1$1, "__esModule", {
|
|
|
13687
14807
|
value: true
|
|
13688
14808
|
});
|
|
13689
14809
|
sha1$1.default = void 0;
|
|
13690
|
-
function f(s2, x, y2,
|
|
14810
|
+
function f(s2, x, y2, z2) {
|
|
13691
14811
|
switch (s2) {
|
|
13692
14812
|
case 0:
|
|
13693
|
-
return x & y2 ^ ~x &
|
|
14813
|
+
return x & y2 ^ ~x & z2;
|
|
13694
14814
|
case 1:
|
|
13695
|
-
return x ^ y2 ^
|
|
14815
|
+
return x ^ y2 ^ z2;
|
|
13696
14816
|
case 2:
|
|
13697
|
-
return x & y2 ^ x &
|
|
14817
|
+
return x & y2 ^ x & z2 ^ y2 & z2;
|
|
13698
14818
|
case 3:
|
|
13699
|
-
return x ^ y2 ^
|
|
14819
|
+
return x ^ y2 ^ z2;
|
|
13700
14820
|
}
|
|
13701
14821
|
}
|
|
13702
14822
|
function ROTL(x, n) {
|
|
@@ -14745,7 +15865,7 @@ gaxios.Gaxios = void 0;
|
|
|
14745
15865
|
const extend_1 = __importDefault(extend);
|
|
14746
15866
|
const https_1 = require$$1;
|
|
14747
15867
|
const node_fetch_1 = __importDefault(browserExports);
|
|
14748
|
-
const querystring_1$1 = __importDefault(require$$
|
|
15868
|
+
const querystring_1$1 = __importDefault(require$$1$2);
|
|
14749
15869
|
const is_stream_1 = __importDefault(isStream_1);
|
|
14750
15870
|
const url_1 = require$$0$5;
|
|
14751
15871
|
const common_1 = common$1;
|
|
@@ -16423,11 +17543,11 @@ var bignumber = { exports: {} };
|
|
|
16423
17543
|
return n > 0 || n === i ? i : i - 1;
|
|
16424
17544
|
}
|
|
16425
17545
|
function coeffToString(a2) {
|
|
16426
|
-
var s2,
|
|
17546
|
+
var s2, z2, i = 1, j = a2.length, r = a2[0] + "";
|
|
16427
17547
|
for (; i < j; ) {
|
|
16428
17548
|
s2 = a2[i++] + "";
|
|
16429
|
-
|
|
16430
|
-
for (;
|
|
17549
|
+
z2 = LOG_BASE - s2.length;
|
|
17550
|
+
for (; z2--; s2 = "0" + s2) ;
|
|
16431
17551
|
r += s2;
|
|
16432
17552
|
}
|
|
16433
17553
|
for (j = r.length; r.charCodeAt(--j) === 48; ) ;
|
|
@@ -16460,15 +17580,15 @@ var bignumber = { exports: {} };
|
|
|
16460
17580
|
function toExponential(str, e) {
|
|
16461
17581
|
return (str.length > 1 ? str.charAt(0) + "." + str.slice(1) : str) + (e < 0 ? "e" : "e+") + e;
|
|
16462
17582
|
}
|
|
16463
|
-
function toFixedPoint(str, e,
|
|
17583
|
+
function toFixedPoint(str, e, z2) {
|
|
16464
17584
|
var len, zs;
|
|
16465
17585
|
if (e < 0) {
|
|
16466
|
-
for (zs =
|
|
17586
|
+
for (zs = z2 + "."; ++e; zs += z2) ;
|
|
16467
17587
|
str = zs + str;
|
|
16468
17588
|
} else {
|
|
16469
17589
|
len = str.length;
|
|
16470
17590
|
if (++e > len) {
|
|
16471
|
-
for (zs =
|
|
17591
|
+
for (zs = z2, e -= len; --e; zs += z2) ;
|
|
16472
17592
|
str += zs;
|
|
16473
17593
|
} else if (e < len) {
|
|
16474
17594
|
str = str.slice(0, e) + "." + str.slice(e);
|
|
@@ -16887,7 +18007,7 @@ var gcpResidency = {};
|
|
|
16887
18007
|
exports.isGoogleComputeEngine = isGoogleComputeEngine;
|
|
16888
18008
|
exports.detectGCPResidency = detectGCPResidency;
|
|
16889
18009
|
const fs_1 = fs__default;
|
|
16890
|
-
const os_1 = require$$1$
|
|
18010
|
+
const os_1 = require$$1$3;
|
|
16891
18011
|
exports.GCE_LINUX_BIOS_PATHS = {
|
|
16892
18012
|
BIOS_DATE: "/sys/class/dmi/id/bios_date",
|
|
16893
18013
|
BIOS_VENDOR: "/sys/class/dmi/id/bios_vendor"
|
|
@@ -17020,7 +18140,7 @@ Colours.refresh();
|
|
|
17020
18140
|
exports.setBackend = setBackend;
|
|
17021
18141
|
exports.log = log;
|
|
17022
18142
|
const node_events_1 = require$$0$8;
|
|
17023
|
-
const process2 = __importStar2(require$$1$
|
|
18143
|
+
const process2 = __importStar2(require$$1$4);
|
|
17024
18144
|
const util2 = __importStar2(require$$2$1);
|
|
17025
18145
|
const colours_1 = colours;
|
|
17026
18146
|
var LogSeverity;
|
|
@@ -18092,7 +19212,7 @@ loginticket.LoginTicket = LoginTicket;
|
|
|
18092
19212
|
Object.defineProperty(oauth2client, "__esModule", { value: true });
|
|
18093
19213
|
oauth2client.OAuth2Client = oauth2client.ClientAuthentication = oauth2client.CertificateFormat = oauth2client.CodeChallengeMethod = void 0;
|
|
18094
19214
|
const gaxios_1$5 = src$2;
|
|
18095
|
-
const querystring$2 = require$$
|
|
19215
|
+
const querystring$2 = require$$1$2;
|
|
18096
19216
|
const stream$4 = require$$0$4;
|
|
18097
19217
|
const formatEcdsa = ecdsaSigFormatter;
|
|
18098
19218
|
const crypto_1$2 = requireCrypto();
|
|
@@ -19580,7 +20700,7 @@ var refreshclient = {};
|
|
|
19580
20700
|
Object.defineProperty(refreshclient, "__esModule", { value: true });
|
|
19581
20701
|
refreshclient.UserRefreshClient = refreshclient.USER_REFRESH_ACCOUNT_TYPE = void 0;
|
|
19582
20702
|
const oauth2client_1$1 = oauth2client;
|
|
19583
|
-
const querystring_1 = require$$
|
|
20703
|
+
const querystring_1 = require$$1$2;
|
|
19584
20704
|
refreshclient.USER_REFRESH_ACCOUNT_TYPE = "authorized_user";
|
|
19585
20705
|
class UserRefreshClient extends oauth2client_1$1.OAuth2Client {
|
|
19586
20706
|
constructor(optionsOrClientId, clientSecret, refreshToken, eagerRefreshThresholdMillis, forceRefreshOnFailure) {
|
|
@@ -19855,7 +20975,7 @@ var oauth2common = {};
|
|
|
19855
20975
|
Object.defineProperty(oauth2common, "__esModule", { value: true });
|
|
19856
20976
|
oauth2common.OAuthClientAuthHandler = void 0;
|
|
19857
20977
|
oauth2common.getErrorFromOAuthErrorResponse = getErrorFromOAuthErrorResponse;
|
|
19858
|
-
const querystring$1 = require$$
|
|
20978
|
+
const querystring$1 = require$$1$2;
|
|
19859
20979
|
const crypto_1$1 = requireCrypto();
|
|
19860
20980
|
const METHODS_SUPPORTING_REQUEST_BODY = ["PUT", "POST", "PATCH"];
|
|
19861
20981
|
class OAuthClientAuthHandler {
|
|
@@ -20001,7 +21121,7 @@ function getErrorFromOAuthErrorResponse(resp, err) {
|
|
|
20001
21121
|
Object.defineProperty(stscredentials, "__esModule", { value: true });
|
|
20002
21122
|
stscredentials.StsCredentials = void 0;
|
|
20003
21123
|
const gaxios_1$1 = src$2;
|
|
20004
|
-
const querystring = require$$
|
|
21124
|
+
const querystring = require$$1$2;
|
|
20005
21125
|
const transporters_1 = transporters;
|
|
20006
21126
|
const oauth2common_1$1 = oauth2common;
|
|
20007
21127
|
class StsCredentials extends oauth2common_1$1.OAuthClientAuthHandler {
|
|
@@ -21607,7 +22727,7 @@ externalAccountAuthorizedUserClient.ExternalAccountAuthorizedUserClient = Extern
|
|
|
21607
22727
|
const child_process_1 = require$$0$a;
|
|
21608
22728
|
const fs2 = fs__default;
|
|
21609
22729
|
const gcpMetadata2 = src$3;
|
|
21610
|
-
const os2 = require$$1$
|
|
22730
|
+
const os2 = require$$1$3;
|
|
21611
22731
|
const path2 = path__default;
|
|
21612
22732
|
const crypto_12 = requireCrypto();
|
|
21613
22733
|
const transporters_12 = transporters;
|
|
@@ -22971,7 +24091,7 @@ function createMicrosoftProvider(config) {
|
|
|
22971
24091
|
}
|
|
22972
24092
|
function createAppleProvider(config) {
|
|
22973
24093
|
async function generateClientSecret() {
|
|
22974
|
-
return jwt.sign({}, config.privateKey, {
|
|
24094
|
+
return jwt$1.sign({}, config.privateKey, {
|
|
22975
24095
|
algorithm: "ES256",
|
|
22976
24096
|
keyid: config.keyId,
|
|
22977
24097
|
issuer: config.teamId,
|
|
@@ -23451,6 +24571,341 @@ function createSpotifyProvider(config) {
|
|
|
23451
24571
|
}
|
|
23452
24572
|
};
|
|
23453
24573
|
}
|
|
24574
|
+
const TABLE$1 = "rebase.api_keys";
|
|
24575
|
+
function generateApiKey() {
|
|
24576
|
+
const random = randomBytes(16).toString("hex");
|
|
24577
|
+
return `rk_live_${random}`;
|
|
24578
|
+
}
|
|
24579
|
+
function hashKey(plaintext) {
|
|
24580
|
+
return createHash("sha256").update(plaintext).digest("hex");
|
|
24581
|
+
}
|
|
24582
|
+
function keyPrefix(plaintext) {
|
|
24583
|
+
return plaintext.substring(0, 12);
|
|
24584
|
+
}
|
|
24585
|
+
function toMasked(row) {
|
|
24586
|
+
return {
|
|
24587
|
+
id: row.id,
|
|
24588
|
+
name: row.name,
|
|
24589
|
+
key_prefix: row.key_prefix,
|
|
24590
|
+
permissions: row.permissions,
|
|
24591
|
+
rate_limit: row.rate_limit,
|
|
24592
|
+
created_by: row.created_by,
|
|
24593
|
+
created_at: row.created_at,
|
|
24594
|
+
updated_at: row.updated_at,
|
|
24595
|
+
last_used_at: row.last_used_at,
|
|
24596
|
+
expires_at: row.expires_at,
|
|
24597
|
+
revoked_at: row.revoked_at
|
|
24598
|
+
};
|
|
24599
|
+
}
|
|
24600
|
+
function rowToApiKey(row) {
|
|
24601
|
+
const permissions = typeof row.permissions === "string" ? JSON.parse(row.permissions) : row.permissions ?? [];
|
|
24602
|
+
return {
|
|
24603
|
+
id: row.id,
|
|
24604
|
+
name: row.name,
|
|
24605
|
+
key_prefix: row.key_prefix,
|
|
24606
|
+
key_hash: row.key_hash,
|
|
24607
|
+
permissions,
|
|
24608
|
+
rate_limit: row.rate_limit !== null && row.rate_limit !== void 0 ? Number(row.rate_limit) : null,
|
|
24609
|
+
created_by: row.created_by,
|
|
24610
|
+
created_at: new Date(row.created_at).toISOString(),
|
|
24611
|
+
updated_at: new Date(row.updated_at).toISOString(),
|
|
24612
|
+
last_used_at: row.last_used_at ? new Date(row.last_used_at).toISOString() : null,
|
|
24613
|
+
expires_at: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
24614
|
+
revoked_at: row.revoked_at ? new Date(row.revoked_at).toISOString() : null
|
|
24615
|
+
};
|
|
24616
|
+
}
|
|
24617
|
+
function esc(value) {
|
|
24618
|
+
return value.replace(/'/g, "''");
|
|
24619
|
+
}
|
|
24620
|
+
function createApiKeyStore(driver) {
|
|
24621
|
+
const admin = driver.admin;
|
|
24622
|
+
if (!isSQLAdmin(admin)) {
|
|
24623
|
+
logger.warn("⚠️ [api-key-store] DataDriver does not support SQL admin — API keys will not be available.");
|
|
24624
|
+
return void 0;
|
|
24625
|
+
}
|
|
24626
|
+
const exec = admin.executeSql.bind(admin);
|
|
24627
|
+
return {
|
|
24628
|
+
// ── Schema bootstrap ────────────────────────────────────────
|
|
24629
|
+
async ensureTable() {
|
|
24630
|
+
try {
|
|
24631
|
+
await exec("CREATE SCHEMA IF NOT EXISTS rebase");
|
|
24632
|
+
await exec(`
|
|
24633
|
+
CREATE TABLE IF NOT EXISTS ${TABLE$1} (
|
|
24634
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
24635
|
+
name TEXT NOT NULL,
|
|
24636
|
+
key_prefix TEXT NOT NULL,
|
|
24637
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
24638
|
+
permissions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
24639
|
+
rate_limit INTEGER,
|
|
24640
|
+
created_by TEXT NOT NULL,
|
|
24641
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
24642
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
24643
|
+
last_used_at TIMESTAMPTZ,
|
|
24644
|
+
expires_at TIMESTAMPTZ,
|
|
24645
|
+
revoked_at TIMESTAMPTZ
|
|
24646
|
+
)
|
|
24647
|
+
`);
|
|
24648
|
+
await exec(`
|
|
24649
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash
|
|
24650
|
+
ON ${TABLE$1}(key_hash)
|
|
24651
|
+
`);
|
|
24652
|
+
await exec(`
|
|
24653
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix
|
|
24654
|
+
ON ${TABLE$1}(key_prefix)
|
|
24655
|
+
`);
|
|
24656
|
+
logger.info("✅ API keys table ready");
|
|
24657
|
+
} catch (err) {
|
|
24658
|
+
logger.error("❌ Failed to create API keys table", {
|
|
24659
|
+
error: err
|
|
24660
|
+
});
|
|
24661
|
+
logger.warn("⚠️ Continuing without API keys support.");
|
|
24662
|
+
}
|
|
24663
|
+
},
|
|
24664
|
+
// ── Create ──────────────────────────────────────────────────
|
|
24665
|
+
async createApiKey(request, createdBy) {
|
|
24666
|
+
const plaintext = generateApiKey();
|
|
24667
|
+
const hash = hashKey(plaintext);
|
|
24668
|
+
const prefix = keyPrefix(plaintext);
|
|
24669
|
+
const permissionsJson = JSON.stringify(request.permissions);
|
|
24670
|
+
const rateLimitSql = request.rate_limit !== null && request.rate_limit !== void 0 ? String(request.rate_limit) : "NULL";
|
|
24671
|
+
const expiresAtSql = request.expires_at ? `'${esc(request.expires_at)}'` : "NULL";
|
|
24672
|
+
const rows = await exec(`
|
|
24673
|
+
INSERT INTO ${TABLE$1} (name, key_prefix, key_hash, permissions, rate_limit, created_by, expires_at)
|
|
24674
|
+
VALUES (
|
|
24675
|
+
'${esc(request.name)}',
|
|
24676
|
+
'${esc(prefix)}',
|
|
24677
|
+
'${esc(hash)}',
|
|
24678
|
+
'${esc(permissionsJson)}'::jsonb,
|
|
24679
|
+
${rateLimitSql},
|
|
24680
|
+
'${esc(createdBy)}',
|
|
24681
|
+
${expiresAtSql}
|
|
24682
|
+
)
|
|
24683
|
+
RETURNING *
|
|
24684
|
+
`);
|
|
24685
|
+
const apiKey = rowToApiKey(rows[0]);
|
|
24686
|
+
return {
|
|
24687
|
+
...toMasked(apiKey),
|
|
24688
|
+
key: plaintext
|
|
24689
|
+
};
|
|
24690
|
+
},
|
|
24691
|
+
// ── Lookup by hash ──────────────────────────────────────────
|
|
24692
|
+
async findByKeyHash(hash) {
|
|
24693
|
+
const rows = await exec(`
|
|
24694
|
+
SELECT * FROM ${TABLE$1}
|
|
24695
|
+
WHERE key_hash = '${esc(hash)}'
|
|
24696
|
+
LIMIT 1
|
|
24697
|
+
`);
|
|
24698
|
+
if (rows.length === 0) return null;
|
|
24699
|
+
return rowToApiKey(rows[0]);
|
|
24700
|
+
},
|
|
24701
|
+
// ── List all (masked) ───────────────────────────────────────
|
|
24702
|
+
async listApiKeys() {
|
|
24703
|
+
const rows = await exec(`
|
|
24704
|
+
SELECT * FROM ${TABLE$1}
|
|
24705
|
+
ORDER BY created_at DESC
|
|
24706
|
+
`);
|
|
24707
|
+
return rows.map((r) => toMasked(rowToApiKey(r)));
|
|
24708
|
+
},
|
|
24709
|
+
// ── Get by ID (masked) ──────────────────────────────────────
|
|
24710
|
+
async getApiKeyById(id) {
|
|
24711
|
+
const rows = await exec(`
|
|
24712
|
+
SELECT * FROM ${TABLE$1}
|
|
24713
|
+
WHERE id = '${esc(id)}'
|
|
24714
|
+
LIMIT 1
|
|
24715
|
+
`);
|
|
24716
|
+
if (rows.length === 0) return null;
|
|
24717
|
+
return toMasked(rowToApiKey(rows[0]));
|
|
24718
|
+
},
|
|
24719
|
+
// ── Update ──────────────────────────────────────────────────
|
|
24720
|
+
async updateApiKey(id, updates) {
|
|
24721
|
+
const setClauses = [];
|
|
24722
|
+
if (updates.name !== void 0) {
|
|
24723
|
+
setClauses.push(`name = '${esc(updates.name)}'`);
|
|
24724
|
+
}
|
|
24725
|
+
if (updates.permissions !== void 0) {
|
|
24726
|
+
setClauses.push(`permissions = '${esc(JSON.stringify(updates.permissions))}'::jsonb`);
|
|
24727
|
+
}
|
|
24728
|
+
if (updates.rate_limit !== void 0) {
|
|
24729
|
+
setClauses.push(updates.rate_limit !== null ? `rate_limit = ${updates.rate_limit}` : "rate_limit = NULL");
|
|
24730
|
+
}
|
|
24731
|
+
if (updates.expires_at !== void 0) {
|
|
24732
|
+
setClauses.push(updates.expires_at !== null ? `expires_at = '${esc(updates.expires_at)}'` : "expires_at = NULL");
|
|
24733
|
+
}
|
|
24734
|
+
if (setClauses.length === 0) {
|
|
24735
|
+
return this.getApiKeyById(id);
|
|
24736
|
+
}
|
|
24737
|
+
setClauses.push("updated_at = NOW()");
|
|
24738
|
+
const rows = await exec(`
|
|
24739
|
+
UPDATE ${TABLE$1}
|
|
24740
|
+
SET ${setClauses.join(", ")}
|
|
24741
|
+
WHERE id = '${esc(id)}'
|
|
24742
|
+
RETURNING *
|
|
24743
|
+
`);
|
|
24744
|
+
if (rows.length === 0) return null;
|
|
24745
|
+
return toMasked(rowToApiKey(rows[0]));
|
|
24746
|
+
},
|
|
24747
|
+
// ── Revoke (soft-delete) ────────────────────────────────────
|
|
24748
|
+
async revokeApiKey(id) {
|
|
24749
|
+
const rows = await exec(`
|
|
24750
|
+
UPDATE ${TABLE$1}
|
|
24751
|
+
SET revoked_at = NOW(), updated_at = NOW()
|
|
24752
|
+
WHERE id = '${esc(id)}' AND revoked_at IS NULL
|
|
24753
|
+
RETURNING id
|
|
24754
|
+
`);
|
|
24755
|
+
return rows.length > 0;
|
|
24756
|
+
},
|
|
24757
|
+
// ── Touch last_used_at ──────────────────────────────────────
|
|
24758
|
+
async updateLastUsed(id) {
|
|
24759
|
+
try {
|
|
24760
|
+
await exec(`
|
|
24761
|
+
UPDATE ${TABLE$1}
|
|
24762
|
+
SET last_used_at = NOW()
|
|
24763
|
+
WHERE id = '${esc(id)}'
|
|
24764
|
+
`);
|
|
24765
|
+
} catch (err) {
|
|
24766
|
+
logger.error("[api-key-store] Failed to update last_used_at", {
|
|
24767
|
+
error: err
|
|
24768
|
+
});
|
|
24769
|
+
}
|
|
24770
|
+
}
|
|
24771
|
+
};
|
|
24772
|
+
}
|
|
24773
|
+
function validatePermissions(permissions) {
|
|
24774
|
+
if (!Array.isArray(permissions)) return false;
|
|
24775
|
+
for (const perm of permissions) {
|
|
24776
|
+
if (typeof perm !== "object" || perm === null) return false;
|
|
24777
|
+
if (typeof perm.collection !== "string" || perm.collection.length === 0) return false;
|
|
24778
|
+
if (!Array.isArray(perm.operations) || perm.operations.length === 0) return false;
|
|
24779
|
+
const validOps = /* @__PURE__ */ new Set(["read", "write", "delete"]);
|
|
24780
|
+
for (const op of perm.operations) {
|
|
24781
|
+
if (!validOps.has(op)) return false;
|
|
24782
|
+
}
|
|
24783
|
+
}
|
|
24784
|
+
return true;
|
|
24785
|
+
}
|
|
24786
|
+
function createApiKeyRoutes(options2) {
|
|
24787
|
+
const {
|
|
24788
|
+
store,
|
|
24789
|
+
serviceKey
|
|
24790
|
+
} = options2;
|
|
24791
|
+
const router = new Hono();
|
|
24792
|
+
router.onError(errorHandler);
|
|
24793
|
+
router.use("/*", createRequireAuth({
|
|
24794
|
+
serviceKey
|
|
24795
|
+
}));
|
|
24796
|
+
router.use("/*", requireAdmin);
|
|
24797
|
+
router.get("/", async (c) => {
|
|
24798
|
+
const keys2 = await store.listApiKeys();
|
|
24799
|
+
return c.json({
|
|
24800
|
+
keys: keys2
|
|
24801
|
+
});
|
|
24802
|
+
});
|
|
24803
|
+
router.post("/", async (c) => {
|
|
24804
|
+
const body = await c.req.json();
|
|
24805
|
+
const {
|
|
24806
|
+
name: name2,
|
|
24807
|
+
permissions,
|
|
24808
|
+
rate_limit,
|
|
24809
|
+
expires_at
|
|
24810
|
+
} = body;
|
|
24811
|
+
if (!name2 || typeof name2 !== "string" || name2.trim().length === 0) {
|
|
24812
|
+
throw ApiError.badRequest("Name is required", "INVALID_INPUT");
|
|
24813
|
+
}
|
|
24814
|
+
if (!validatePermissions(permissions)) {
|
|
24815
|
+
throw ApiError.badRequest("Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }", "INVALID_INPUT");
|
|
24816
|
+
}
|
|
24817
|
+
if (rate_limit !== void 0 && rate_limit !== null) {
|
|
24818
|
+
if (typeof rate_limit !== "number" || rate_limit < 1 || !Number.isInteger(rate_limit)) {
|
|
24819
|
+
throw ApiError.badRequest("rate_limit must be a positive integer or null", "INVALID_INPUT");
|
|
24820
|
+
}
|
|
24821
|
+
}
|
|
24822
|
+
if (expires_at !== void 0 && expires_at !== null) {
|
|
24823
|
+
const parsed = new Date(expires_at);
|
|
24824
|
+
if (isNaN(parsed.getTime())) {
|
|
24825
|
+
throw ApiError.badRequest("expires_at must be a valid ISO-8601 date", "INVALID_INPUT");
|
|
24826
|
+
}
|
|
24827
|
+
if (parsed <= /* @__PURE__ */ new Date()) {
|
|
24828
|
+
throw ApiError.badRequest("expires_at must be in the future", "INVALID_INPUT");
|
|
24829
|
+
}
|
|
24830
|
+
}
|
|
24831
|
+
const user = c.get("user");
|
|
24832
|
+
const createdBy = user && typeof user === "object" && "userId" in user ? user.userId : "unknown";
|
|
24833
|
+
const request = {
|
|
24834
|
+
name: name2.trim(),
|
|
24835
|
+
permissions,
|
|
24836
|
+
rate_limit: rate_limit ?? null,
|
|
24837
|
+
expires_at: expires_at ?? null
|
|
24838
|
+
};
|
|
24839
|
+
const keyWithSecret = await store.createApiKey(request, createdBy);
|
|
24840
|
+
return c.json({
|
|
24841
|
+
key: keyWithSecret
|
|
24842
|
+
}, 201);
|
|
24843
|
+
});
|
|
24844
|
+
router.get("/:id", async (c) => {
|
|
24845
|
+
const id = c.req.param("id");
|
|
24846
|
+
const key = await store.getApiKeyById(id);
|
|
24847
|
+
if (!key) {
|
|
24848
|
+
throw ApiError.notFound("API key not found");
|
|
24849
|
+
}
|
|
24850
|
+
return c.json({
|
|
24851
|
+
key
|
|
24852
|
+
});
|
|
24853
|
+
});
|
|
24854
|
+
router.put("/:id", async (c) => {
|
|
24855
|
+
const id = c.req.param("id");
|
|
24856
|
+
const body = await c.req.json();
|
|
24857
|
+
const {
|
|
24858
|
+
name: name2,
|
|
24859
|
+
permissions,
|
|
24860
|
+
rate_limit,
|
|
24861
|
+
expires_at
|
|
24862
|
+
} = body;
|
|
24863
|
+
if (name2 !== void 0) {
|
|
24864
|
+
if (typeof name2 !== "string" || name2.trim().length === 0) {
|
|
24865
|
+
throw ApiError.badRequest("Name must be a non-empty string", "INVALID_INPUT");
|
|
24866
|
+
}
|
|
24867
|
+
}
|
|
24868
|
+
if (permissions !== void 0) {
|
|
24869
|
+
if (!validatePermissions(permissions)) {
|
|
24870
|
+
throw ApiError.badRequest("Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }", "INVALID_INPUT");
|
|
24871
|
+
}
|
|
24872
|
+
}
|
|
24873
|
+
if (rate_limit !== void 0 && rate_limit !== null) {
|
|
24874
|
+
if (typeof rate_limit !== "number" || rate_limit < 1 || !Number.isInteger(rate_limit)) {
|
|
24875
|
+
throw ApiError.badRequest("rate_limit must be a positive integer or null", "INVALID_INPUT");
|
|
24876
|
+
}
|
|
24877
|
+
}
|
|
24878
|
+
if (expires_at !== void 0 && expires_at !== null) {
|
|
24879
|
+
const parsed = new Date(expires_at);
|
|
24880
|
+
if (isNaN(parsed.getTime())) {
|
|
24881
|
+
throw ApiError.badRequest("expires_at must be a valid ISO-8601 date", "INVALID_INPUT");
|
|
24882
|
+
}
|
|
24883
|
+
}
|
|
24884
|
+
const updates = {};
|
|
24885
|
+
if (name2 !== void 0) updates.name = name2.trim();
|
|
24886
|
+
if (permissions !== void 0) updates.permissions = permissions;
|
|
24887
|
+
if (rate_limit !== void 0) updates.rate_limit = rate_limit;
|
|
24888
|
+
if (expires_at !== void 0) updates.expires_at = expires_at;
|
|
24889
|
+
const key = await store.updateApiKey(id, updates);
|
|
24890
|
+
if (!key) {
|
|
24891
|
+
throw ApiError.notFound("API key not found");
|
|
24892
|
+
}
|
|
24893
|
+
return c.json({
|
|
24894
|
+
key
|
|
24895
|
+
});
|
|
24896
|
+
});
|
|
24897
|
+
router.delete("/:id", async (c) => {
|
|
24898
|
+
const id = c.req.param("id");
|
|
24899
|
+
const revoked = await store.revokeApiKey(id);
|
|
24900
|
+
if (!revoked) {
|
|
24901
|
+
throw ApiError.notFound("API key not found or already revoked");
|
|
24902
|
+
}
|
|
24903
|
+
return c.json({
|
|
24904
|
+
success: true
|
|
24905
|
+
});
|
|
24906
|
+
});
|
|
24907
|
+
return router;
|
|
24908
|
+
}
|
|
23454
24909
|
function createCustomAuthAdapter(options2) {
|
|
23455
24910
|
const defaultCapabilities = {
|
|
23456
24911
|
hasBuiltInAuthRoutes: false,
|
|
@@ -23477,7 +24932,6 @@ function createCustomAuthAdapter(options2) {
|
|
|
23477
24932
|
verifyRequest: options2.verifyRequest,
|
|
23478
24933
|
verifyToken: resolvedVerifyToken,
|
|
23479
24934
|
userManagement: options2.userManagement,
|
|
23480
|
-
roleManagement: options2.roleManagement,
|
|
23481
24935
|
getCapabilities() {
|
|
23482
24936
|
return defaultCapabilities;
|
|
23483
24937
|
}
|
|
@@ -24003,6 +25457,409 @@ class S3StorageController {
|
|
|
24003
25457
|
};
|
|
24004
25458
|
}
|
|
24005
25459
|
}
|
|
25460
|
+
let sharpFactory;
|
|
25461
|
+
async function getSharp() {
|
|
25462
|
+
if (!sharpFactory) {
|
|
25463
|
+
try {
|
|
25464
|
+
const mod = await import("sharp");
|
|
25465
|
+
sharpFactory = mod.default;
|
|
25466
|
+
} catch (err) {
|
|
25467
|
+
throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
|
|
25468
|
+
}
|
|
25469
|
+
}
|
|
25470
|
+
if (!sharpFactory) {
|
|
25471
|
+
throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
|
|
25472
|
+
}
|
|
25473
|
+
return sharpFactory;
|
|
25474
|
+
}
|
|
25475
|
+
const MAX_DIMENSION = 4096;
|
|
25476
|
+
const MAX_QUALITY = 100;
|
|
25477
|
+
const MIN_QUALITY = 1;
|
|
25478
|
+
const VALID_FORMATS = /* @__PURE__ */ new Set(["webp", "avif", "jpeg", "png"]);
|
|
25479
|
+
const VALID_FITS = /* @__PURE__ */ new Set(["cover", "contain", "fill", "inside", "outside"]);
|
|
25480
|
+
function parseTransformOptions(query) {
|
|
25481
|
+
const opts = {};
|
|
25482
|
+
let hasTransform = false;
|
|
25483
|
+
if (query.width) {
|
|
25484
|
+
const w2 = parseInt(query.width, 10);
|
|
25485
|
+
if (!Number.isNaN(w2) && w2 > 0) {
|
|
25486
|
+
opts.width = Math.min(w2, MAX_DIMENSION);
|
|
25487
|
+
hasTransform = true;
|
|
25488
|
+
}
|
|
25489
|
+
}
|
|
25490
|
+
if (query.height) {
|
|
25491
|
+
const h2 = parseInt(query.height, 10);
|
|
25492
|
+
if (!Number.isNaN(h2) && h2 > 0) {
|
|
25493
|
+
opts.height = Math.min(h2, MAX_DIMENSION);
|
|
25494
|
+
hasTransform = true;
|
|
25495
|
+
}
|
|
25496
|
+
}
|
|
25497
|
+
if (query.quality) {
|
|
25498
|
+
const q = parseInt(query.quality, 10);
|
|
25499
|
+
if (!Number.isNaN(q)) {
|
|
25500
|
+
opts.quality = Math.min(Math.max(q, MIN_QUALITY), MAX_QUALITY);
|
|
25501
|
+
hasTransform = true;
|
|
25502
|
+
}
|
|
25503
|
+
}
|
|
25504
|
+
if (query.format && VALID_FORMATS.has(query.format)) {
|
|
25505
|
+
opts.format = query.format;
|
|
25506
|
+
hasTransform = true;
|
|
25507
|
+
}
|
|
25508
|
+
if (query.fit && VALID_FITS.has(query.fit)) {
|
|
25509
|
+
opts.fit = query.fit;
|
|
25510
|
+
hasTransform = true;
|
|
25511
|
+
}
|
|
25512
|
+
return hasTransform ? opts : null;
|
|
25513
|
+
}
|
|
25514
|
+
const FORMAT_CONTENT_TYPES = {
|
|
25515
|
+
webp: "image/webp",
|
|
25516
|
+
avif: "image/avif",
|
|
25517
|
+
jpeg: "image/jpeg",
|
|
25518
|
+
png: "image/png"
|
|
25519
|
+
};
|
|
25520
|
+
function isTransformableImage(contentType) {
|
|
25521
|
+
return contentType.startsWith("image/") && !contentType.includes("svg") && !contentType.includes("gif");
|
|
25522
|
+
}
|
|
25523
|
+
async function transformImage(buffer, options2) {
|
|
25524
|
+
const sharp = await getSharp();
|
|
25525
|
+
let pipeline = sharp(buffer);
|
|
25526
|
+
if (options2.width || options2.height) {
|
|
25527
|
+
pipeline = pipeline.resize({
|
|
25528
|
+
width: options2.width,
|
|
25529
|
+
height: options2.height,
|
|
25530
|
+
fit: options2.fit || "cover",
|
|
25531
|
+
withoutEnlargement: true
|
|
25532
|
+
});
|
|
25533
|
+
}
|
|
25534
|
+
const format = options2.format || "webp";
|
|
25535
|
+
const quality = options2.quality || 80;
|
|
25536
|
+
switch (format) {
|
|
25537
|
+
case "webp":
|
|
25538
|
+
pipeline = pipeline.webp({
|
|
25539
|
+
quality
|
|
25540
|
+
});
|
|
25541
|
+
break;
|
|
25542
|
+
case "avif":
|
|
25543
|
+
pipeline = pipeline.avif({
|
|
25544
|
+
quality
|
|
25545
|
+
});
|
|
25546
|
+
break;
|
|
25547
|
+
case "jpeg":
|
|
25548
|
+
pipeline = pipeline.jpeg({
|
|
25549
|
+
quality
|
|
25550
|
+
});
|
|
25551
|
+
break;
|
|
25552
|
+
case "png":
|
|
25553
|
+
pipeline = pipeline.png({
|
|
25554
|
+
quality
|
|
25555
|
+
});
|
|
25556
|
+
break;
|
|
25557
|
+
}
|
|
25558
|
+
const data = await pipeline.toBuffer();
|
|
25559
|
+
return {
|
|
25560
|
+
data,
|
|
25561
|
+
contentType: FORMAT_CONTENT_TYPES[format]
|
|
25562
|
+
};
|
|
25563
|
+
}
|
|
25564
|
+
class TransformCache {
|
|
25565
|
+
cache = /* @__PURE__ */ new Map();
|
|
25566
|
+
maxEntries;
|
|
25567
|
+
maxAgeMs;
|
|
25568
|
+
constructor(maxEntries = 500, maxAgeMs = 36e5) {
|
|
25569
|
+
this.maxEntries = maxEntries;
|
|
25570
|
+
this.maxAgeMs = maxAgeMs;
|
|
25571
|
+
}
|
|
25572
|
+
/** Build a deterministic cache key from file key + transform options. */
|
|
25573
|
+
buildKey(fileKey, options2) {
|
|
25574
|
+
return `${fileKey}::${JSON.stringify(options2)}`;
|
|
25575
|
+
}
|
|
25576
|
+
get(cacheKey) {
|
|
25577
|
+
const entry = this.cache.get(cacheKey);
|
|
25578
|
+
if (!entry) return null;
|
|
25579
|
+
if (Date.now() - entry.timestamp > this.maxAgeMs) {
|
|
25580
|
+
this.cache.delete(cacheKey);
|
|
25581
|
+
return null;
|
|
25582
|
+
}
|
|
25583
|
+
this.cache.delete(cacheKey);
|
|
25584
|
+
this.cache.set(cacheKey, entry);
|
|
25585
|
+
return {
|
|
25586
|
+
data: entry.data,
|
|
25587
|
+
contentType: entry.contentType
|
|
25588
|
+
};
|
|
25589
|
+
}
|
|
25590
|
+
set(cacheKey, data, contentType) {
|
|
25591
|
+
if (this.cache.size >= this.maxEntries) {
|
|
25592
|
+
const oldest = this.cache.keys().next().value;
|
|
25593
|
+
if (oldest !== void 0) this.cache.delete(oldest);
|
|
25594
|
+
}
|
|
25595
|
+
this.cache.set(cacheKey, {
|
|
25596
|
+
data,
|
|
25597
|
+
contentType,
|
|
25598
|
+
timestamp: Date.now()
|
|
25599
|
+
});
|
|
25600
|
+
}
|
|
25601
|
+
}
|
|
25602
|
+
const MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024;
|
|
25603
|
+
const UPLOAD_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
25604
|
+
class TusHandler {
|
|
25605
|
+
constructor(storageBaseDir, storageController) {
|
|
25606
|
+
this.storageController = storageController;
|
|
25607
|
+
this.tusDir = join$1(storageBaseDir, ".tus-uploads");
|
|
25608
|
+
}
|
|
25609
|
+
uploads = /* @__PURE__ */ new Map();
|
|
25610
|
+
tusDir;
|
|
25611
|
+
cleanupTimer;
|
|
25612
|
+
/** Ensure the temp directory exists. */
|
|
25613
|
+
async ensureDir() {
|
|
25614
|
+
if (!existsSync(this.tusDir)) {
|
|
25615
|
+
await mkdir$1(this.tusDir, {
|
|
25616
|
+
recursive: true
|
|
25617
|
+
});
|
|
25618
|
+
}
|
|
25619
|
+
}
|
|
25620
|
+
/** Start periodic cleanup of stale uploads. */
|
|
25621
|
+
startCleanup() {
|
|
25622
|
+
if (this.cleanupTimer) return;
|
|
25623
|
+
this.cleanupTimer = setInterval(() => {
|
|
25624
|
+
void this.cleanupStale();
|
|
25625
|
+
}, 6e4);
|
|
25626
|
+
}
|
|
25627
|
+
/** Remove uploads that have been idle for longer than UPLOAD_EXPIRY_MS. */
|
|
25628
|
+
async cleanupStale() {
|
|
25629
|
+
const now = Date.now();
|
|
25630
|
+
for (const [id, upload] of this.uploads) {
|
|
25631
|
+
if (now - upload.createdAt > UPLOAD_EXPIRY_MS && !upload.completed) {
|
|
25632
|
+
try {
|
|
25633
|
+
await unlink$1(upload.filePath);
|
|
25634
|
+
} catch {
|
|
25635
|
+
}
|
|
25636
|
+
this.uploads.delete(id);
|
|
25637
|
+
}
|
|
25638
|
+
}
|
|
25639
|
+
}
|
|
25640
|
+
// -----------------------------------------------------------------------
|
|
25641
|
+
// TUS Metadata Parsing
|
|
25642
|
+
// -----------------------------------------------------------------------
|
|
25643
|
+
/**
|
|
25644
|
+
* Parse the `Upload-Metadata` header.
|
|
25645
|
+
*
|
|
25646
|
+
* Format: `key base64value,key2 base64value2`
|
|
25647
|
+
*/
|
|
25648
|
+
parseMetadata(header) {
|
|
25649
|
+
const metadata = {};
|
|
25650
|
+
if (!header) return metadata;
|
|
25651
|
+
for (const pair of header.split(",")) {
|
|
25652
|
+
const trimmed = pair.trim();
|
|
25653
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
25654
|
+
if (spaceIdx === -1) {
|
|
25655
|
+
metadata[trimmed] = "";
|
|
25656
|
+
} else {
|
|
25657
|
+
const key = trimmed.substring(0, spaceIdx);
|
|
25658
|
+
const value = Buffer.from(trimmed.substring(spaceIdx + 1), "base64").toString("utf-8");
|
|
25659
|
+
metadata[key] = value;
|
|
25660
|
+
}
|
|
25661
|
+
}
|
|
25662
|
+
return metadata;
|
|
25663
|
+
}
|
|
25664
|
+
// -----------------------------------------------------------------------
|
|
25665
|
+
// Protocol Endpoints
|
|
25666
|
+
// -----------------------------------------------------------------------
|
|
25667
|
+
/** `OPTIONS /tus` — TUS capability discovery. */
|
|
25668
|
+
options() {
|
|
25669
|
+
return new Response(null, {
|
|
25670
|
+
status: 204,
|
|
25671
|
+
headers: {
|
|
25672
|
+
"Tus-Resumable": "1.0.0",
|
|
25673
|
+
"Tus-Version": "1.0.0",
|
|
25674
|
+
"Tus-Extension": "creation,termination",
|
|
25675
|
+
"Tus-Max-Size": String(MAX_UPLOAD_SIZE)
|
|
25676
|
+
}
|
|
25677
|
+
});
|
|
25678
|
+
}
|
|
25679
|
+
/** `POST /tus` — Create a new upload. */
|
|
25680
|
+
async create(c) {
|
|
25681
|
+
await this.ensureDir();
|
|
25682
|
+
const uploadLengthHeader = c.req.header("Upload-Length");
|
|
25683
|
+
if (!uploadLengthHeader) {
|
|
25684
|
+
return c.json({
|
|
25685
|
+
error: "Upload-Length header is required"
|
|
25686
|
+
}, 400);
|
|
25687
|
+
}
|
|
25688
|
+
const uploadLength = parseInt(uploadLengthHeader, 10);
|
|
25689
|
+
if (Number.isNaN(uploadLength) || uploadLength <= 0) {
|
|
25690
|
+
return c.json({
|
|
25691
|
+
error: "Invalid Upload-Length"
|
|
25692
|
+
}, 400);
|
|
25693
|
+
}
|
|
25694
|
+
if (uploadLength > MAX_UPLOAD_SIZE) {
|
|
25695
|
+
return c.json({
|
|
25696
|
+
error: `Upload-Length exceeds maximum of ${MAX_UPLOAD_SIZE} bytes`
|
|
25697
|
+
}, 413);
|
|
25698
|
+
}
|
|
25699
|
+
const metadata = this.parseMetadata(c.req.header("Upload-Metadata") || "");
|
|
25700
|
+
const id = randomUUID$1();
|
|
25701
|
+
const filePath = join$1(this.tusDir, id);
|
|
25702
|
+
await writeFile$1(filePath, Buffer.alloc(0));
|
|
25703
|
+
const upload = {
|
|
25704
|
+
id,
|
|
25705
|
+
size: uploadLength,
|
|
25706
|
+
offset: 0,
|
|
25707
|
+
metadata,
|
|
25708
|
+
createdAt: Date.now(),
|
|
25709
|
+
filePath,
|
|
25710
|
+
bucket: metadata.bucket || void 0,
|
|
25711
|
+
key: metadata.key || metadata.filename || void 0,
|
|
25712
|
+
completed: false
|
|
25713
|
+
};
|
|
25714
|
+
this.uploads.set(id, upload);
|
|
25715
|
+
const reqUrl = new URL(c.req.url);
|
|
25716
|
+
const location = `${reqUrl.origin}${reqUrl.pathname}/${id}`;
|
|
25717
|
+
return new Response(null, {
|
|
25718
|
+
status: 201,
|
|
25719
|
+
headers: {
|
|
25720
|
+
Location: location,
|
|
25721
|
+
"Tus-Resumable": "1.0.0",
|
|
25722
|
+
"Upload-Offset": "0"
|
|
25723
|
+
}
|
|
25724
|
+
});
|
|
25725
|
+
}
|
|
25726
|
+
/** `HEAD /tus/:id` — Query upload progress. */
|
|
25727
|
+
head(c, id) {
|
|
25728
|
+
const upload = this.uploads.get(id);
|
|
25729
|
+
if (!upload) {
|
|
25730
|
+
return c.json({
|
|
25731
|
+
error: "Upload not found"
|
|
25732
|
+
}, 404);
|
|
25733
|
+
}
|
|
25734
|
+
return new Response(null, {
|
|
25735
|
+
status: 200,
|
|
25736
|
+
headers: {
|
|
25737
|
+
"Tus-Resumable": "1.0.0",
|
|
25738
|
+
"Upload-Offset": String(upload.offset),
|
|
25739
|
+
"Upload-Length": String(upload.size),
|
|
25740
|
+
"Cache-Control": "no-store"
|
|
25741
|
+
}
|
|
25742
|
+
});
|
|
25743
|
+
}
|
|
25744
|
+
/** `PATCH /tus/:id` — Append data to an upload. */
|
|
25745
|
+
async patch(c, id) {
|
|
25746
|
+
const upload = this.uploads.get(id);
|
|
25747
|
+
if (!upload) {
|
|
25748
|
+
return c.json({
|
|
25749
|
+
error: "Upload not found"
|
|
25750
|
+
}, 404);
|
|
25751
|
+
}
|
|
25752
|
+
if (upload.completed) {
|
|
25753
|
+
return c.json({
|
|
25754
|
+
error: "Upload already completed"
|
|
25755
|
+
}, 400);
|
|
25756
|
+
}
|
|
25757
|
+
const offsetHeader = c.req.header("Upload-Offset");
|
|
25758
|
+
if (!offsetHeader) {
|
|
25759
|
+
return c.json({
|
|
25760
|
+
error: "Upload-Offset header is required"
|
|
25761
|
+
}, 400);
|
|
25762
|
+
}
|
|
25763
|
+
const offset = parseInt(offsetHeader, 10);
|
|
25764
|
+
if (offset !== upload.offset) {
|
|
25765
|
+
return c.json({
|
|
25766
|
+
error: "Offset mismatch"
|
|
25767
|
+
}, 409);
|
|
25768
|
+
}
|
|
25769
|
+
const contentType = c.req.header("Content-Type");
|
|
25770
|
+
if (contentType !== "application/offset+octet-stream") {
|
|
25771
|
+
return c.json({
|
|
25772
|
+
error: "Content-Type must be application/offset+octet-stream"
|
|
25773
|
+
}, 415);
|
|
25774
|
+
}
|
|
25775
|
+
const body = await c.req.arrayBuffer();
|
|
25776
|
+
const chunk = Buffer.from(body);
|
|
25777
|
+
if (upload.offset + chunk.length > upload.size) {
|
|
25778
|
+
return c.json({
|
|
25779
|
+
error: "Chunk exceeds declared Upload-Length"
|
|
25780
|
+
}, 413);
|
|
25781
|
+
}
|
|
25782
|
+
const fh = await open(upload.filePath, "a");
|
|
25783
|
+
try {
|
|
25784
|
+
await fh.write(chunk);
|
|
25785
|
+
} finally {
|
|
25786
|
+
await fh.close();
|
|
25787
|
+
}
|
|
25788
|
+
upload.offset += chunk.length;
|
|
25789
|
+
if (upload.offset >= upload.size) {
|
|
25790
|
+
await this.finalize(upload);
|
|
25791
|
+
}
|
|
25792
|
+
return new Response(null, {
|
|
25793
|
+
status: 204,
|
|
25794
|
+
headers: {
|
|
25795
|
+
"Tus-Resumable": "1.0.0",
|
|
25796
|
+
"Upload-Offset": String(upload.offset)
|
|
25797
|
+
}
|
|
25798
|
+
});
|
|
25799
|
+
}
|
|
25800
|
+
/** `DELETE /tus/:id` — Cancel and remove an upload. */
|
|
25801
|
+
async delete(c, id) {
|
|
25802
|
+
const upload = this.uploads.get(id);
|
|
25803
|
+
if (!upload) {
|
|
25804
|
+
return c.json({
|
|
25805
|
+
error: "Upload not found"
|
|
25806
|
+
}, 404);
|
|
25807
|
+
}
|
|
25808
|
+
try {
|
|
25809
|
+
await unlink$1(upload.filePath);
|
|
25810
|
+
} catch {
|
|
25811
|
+
}
|
|
25812
|
+
this.uploads.delete(id);
|
|
25813
|
+
return new Response(null, {
|
|
25814
|
+
status: 204,
|
|
25815
|
+
headers: {
|
|
25816
|
+
"Tus-Resumable": "1.0.0"
|
|
25817
|
+
}
|
|
25818
|
+
});
|
|
25819
|
+
}
|
|
25820
|
+
// -----------------------------------------------------------------------
|
|
25821
|
+
// Finalization
|
|
25822
|
+
// -----------------------------------------------------------------------
|
|
25823
|
+
/**
|
|
25824
|
+
* Move a completed upload into the storage controller.
|
|
25825
|
+
*/
|
|
25826
|
+
async finalize(upload) {
|
|
25827
|
+
upload.completed = true;
|
|
25828
|
+
if (!this.storageController) {
|
|
25829
|
+
logger.warn("[TUS] Upload completed but no StorageController configured. Temp file remains:", {
|
|
25830
|
+
filePath: upload.filePath
|
|
25831
|
+
});
|
|
25832
|
+
return;
|
|
25833
|
+
}
|
|
25834
|
+
try {
|
|
25835
|
+
const {
|
|
25836
|
+
readFile: readFile2
|
|
25837
|
+
} = await import("fs/promises");
|
|
25838
|
+
const data = await readFile2(upload.filePath);
|
|
25839
|
+
const fileName = upload.key || upload.metadata.filename || upload.id;
|
|
25840
|
+
const mimeType = upload.metadata.contentType || upload.metadata.filetype || "application/octet-stream";
|
|
25841
|
+
const file = new File([data], fileName, {
|
|
25842
|
+
type: mimeType
|
|
25843
|
+
});
|
|
25844
|
+
await this.storageController.putObject({
|
|
25845
|
+
file,
|
|
25846
|
+
key: fileName,
|
|
25847
|
+
bucket: upload.bucket
|
|
25848
|
+
});
|
|
25849
|
+
try {
|
|
25850
|
+
await unlink$1(upload.filePath);
|
|
25851
|
+
} catch {
|
|
25852
|
+
}
|
|
25853
|
+
this.uploads.delete(upload.id);
|
|
25854
|
+
logger.info(`[TUS] Upload ${upload.id} finalized → ${fileName}`);
|
|
25855
|
+
} catch (err) {
|
|
25856
|
+
logger.error(`[TUS] Failed to finalize upload ${upload.id}`, {
|
|
25857
|
+
error: err
|
|
25858
|
+
});
|
|
25859
|
+
}
|
|
25860
|
+
}
|
|
25861
|
+
}
|
|
25862
|
+
const transformCache = new TransformCache();
|
|
24006
25863
|
function extractWildcardPath(c) {
|
|
24007
25864
|
const routePath = c.req.routePath;
|
|
24008
25865
|
const prefix = routePath.replace("/*", "");
|
|
@@ -24066,6 +25923,7 @@ function createStorageRoutes(config) {
|
|
|
24066
25923
|
throw ApiError.notFound("File not found");
|
|
24067
25924
|
}
|
|
24068
25925
|
const filePath = decodeURIComponent(rawPath);
|
|
25926
|
+
const transformOpts = parseTransformOptions(c.req.query());
|
|
24069
25927
|
if (controller.getType() === "local") {
|
|
24070
25928
|
const localController = controller;
|
|
24071
25929
|
const {
|
|
@@ -24085,8 +25943,19 @@ function createStorageRoutes(config) {
|
|
|
24085
25943
|
} catch {
|
|
24086
25944
|
}
|
|
24087
25945
|
}
|
|
24088
|
-
c.header("Content-Type", contentType);
|
|
24089
25946
|
const fileContent = fs$4.readFileSync(absolutePath);
|
|
25947
|
+
if (transformOpts && isTransformableImage(contentType)) {
|
|
25948
|
+
const cacheKey = transformCache.buildKey(filePath, transformOpts);
|
|
25949
|
+
let cached = transformCache.get(cacheKey);
|
|
25950
|
+
if (!cached) {
|
|
25951
|
+
cached = await transformImage(Buffer.from(fileContent), transformOpts);
|
|
25952
|
+
transformCache.set(cacheKey, cached.data, cached.contentType);
|
|
25953
|
+
}
|
|
25954
|
+
c.header("Content-Type", cached.contentType);
|
|
25955
|
+
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
25956
|
+
return c.body(new Uint8Array(cached.data));
|
|
25957
|
+
}
|
|
25958
|
+
c.header("Content-Type", contentType);
|
|
24090
25959
|
return c.body(new Uint8Array(fileContent));
|
|
24091
25960
|
}
|
|
24092
25961
|
const {
|
|
@@ -24097,7 +25966,20 @@ function createStorageRoutes(config) {
|
|
|
24097
25966
|
if (!fileObject) {
|
|
24098
25967
|
throw ApiError.notFound("File not found");
|
|
24099
25968
|
}
|
|
24100
|
-
|
|
25969
|
+
const remoteContentType = fileObject.type || "application/octet-stream";
|
|
25970
|
+
if (transformOpts && isTransformableImage(remoteContentType)) {
|
|
25971
|
+
const cacheKey = transformCache.buildKey(filePath, transformOpts);
|
|
25972
|
+
let cached = transformCache.get(cacheKey);
|
|
25973
|
+
if (!cached) {
|
|
25974
|
+
const buf2 = Buffer.from(await fileObject.arrayBuffer());
|
|
25975
|
+
cached = await transformImage(buf2, transformOpts);
|
|
25976
|
+
transformCache.set(cacheKey, cached.data, cached.contentType);
|
|
25977
|
+
}
|
|
25978
|
+
c.header("Content-Type", cached.contentType);
|
|
25979
|
+
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
25980
|
+
return c.body(new Uint8Array(cached.data));
|
|
25981
|
+
}
|
|
25982
|
+
c.header("Content-Type", remoteContentType);
|
|
24101
25983
|
c.header("Cache-Control", "public, max-age=3600, immutable");
|
|
24102
25984
|
const buf = await fileObject.arrayBuffer();
|
|
24103
25985
|
return c.body(new Uint8Array(buf));
|
|
@@ -24193,6 +26075,14 @@ function createStorageRoutes(config) {
|
|
|
24193
26075
|
message: "Folder created"
|
|
24194
26076
|
}, 201);
|
|
24195
26077
|
});
|
|
26078
|
+
const tusBaseDir = controller.getType() === "local" ? controller.getBasePath() : process.env.STORAGE_PATH || "./uploads";
|
|
26079
|
+
const tusHandler = new TusHandler(tusBaseDir, controller);
|
|
26080
|
+
tusHandler.startCleanup();
|
|
26081
|
+
router.options("/tus", (_c2) => tusHandler.options());
|
|
26082
|
+
router.post("/tus", writeAuthMiddleware, async (c) => tusHandler.create(c));
|
|
26083
|
+
router.get("/tus/:id", readAuthMiddleware, (c) => tusHandler.head(c, c.req.param("id")));
|
|
26084
|
+
router.patch("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.patch(c, c.req.param("id")));
|
|
26085
|
+
router.delete("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.delete(c, c.req.param("id")));
|
|
24196
26086
|
return router;
|
|
24197
26087
|
}
|
|
24198
26088
|
const DEFAULT_STORAGE_ID = "(default)";
|
|
@@ -24416,6 +26306,13 @@ function createTransport$1(config) {
|
|
|
24416
26306
|
} catch (e) {
|
|
24417
26307
|
}
|
|
24418
26308
|
}
|
|
26309
|
+
const getErrorField = (obj, field) => {
|
|
26310
|
+
const err = obj?.error;
|
|
26311
|
+
if (err && typeof err === "object" && err !== null && field in err) {
|
|
26312
|
+
return err[field];
|
|
26313
|
+
}
|
|
26314
|
+
return obj?.[field];
|
|
26315
|
+
};
|
|
24419
26316
|
if (res.status === 401 && onUnauthorizedHandler) {
|
|
24420
26317
|
const retried = await onUnauthorizedHandler();
|
|
24421
26318
|
if (retried) {
|
|
@@ -24449,7 +26346,7 @@ function createTransport$1(config) {
|
|
|
24449
26346
|
const method = init?.method || "GET";
|
|
24450
26347
|
fallbackMessage = `Endpoint not found (${method} ${path2}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;
|
|
24451
26348
|
}
|
|
24452
|
-
throw new RebaseApiError(retryRes.status, retryBody
|
|
26349
|
+
throw new RebaseApiError(retryRes.status, String(getErrorField(retryBody, "message") || fallbackMessage || `Request failed with status ${retryRes.status}`), getErrorField(retryBody, "code"), getErrorField(retryBody, "details"));
|
|
24453
26350
|
}
|
|
24454
26351
|
return retryBody;
|
|
24455
26352
|
}
|
|
@@ -24460,7 +26357,7 @@ function createTransport$1(config) {
|
|
|
24460
26357
|
const method = init?.method || "GET";
|
|
24461
26358
|
fallbackMessage = `Endpoint not found (${method} ${path2}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;
|
|
24462
26359
|
}
|
|
24463
|
-
throw new RebaseApiError(res.status, body
|
|
26360
|
+
throw new RebaseApiError(res.status, String(getErrorField(body, "message") || fallbackMessage || `Request failed with status ${res.status}`), getErrorField(body, "code"), getErrorField(body, "details"));
|
|
24464
26361
|
}
|
|
24465
26362
|
return body;
|
|
24466
26363
|
}
|
|
@@ -25034,33 +26931,6 @@ function createAdmin(transport, options2) {
|
|
|
25034
26931
|
method: "DELETE"
|
|
25035
26932
|
});
|
|
25036
26933
|
}
|
|
25037
|
-
async function listRoles() {
|
|
25038
|
-
return transport.request(adminPath + "/roles", {
|
|
25039
|
-
method: "GET"
|
|
25040
|
-
});
|
|
25041
|
-
}
|
|
25042
|
-
async function getRole(roleId) {
|
|
25043
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
25044
|
-
method: "GET"
|
|
25045
|
-
});
|
|
25046
|
-
}
|
|
25047
|
-
async function createRole(data) {
|
|
25048
|
-
return transport.request(adminPath + "/roles", {
|
|
25049
|
-
method: "POST",
|
|
25050
|
-
body: JSON.stringify(data)
|
|
25051
|
-
});
|
|
25052
|
-
}
|
|
25053
|
-
async function updateRole(roleId, data) {
|
|
25054
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
25055
|
-
method: "PUT",
|
|
25056
|
-
body: JSON.stringify(data)
|
|
25057
|
-
});
|
|
25058
|
-
}
|
|
25059
|
-
async function deleteRole(roleId) {
|
|
25060
|
-
return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
|
|
25061
|
-
method: "DELETE"
|
|
25062
|
-
});
|
|
25063
|
-
}
|
|
25064
26934
|
async function bootstrap() {
|
|
25065
26935
|
return transport.request(adminPath + "/bootstrap", {
|
|
25066
26936
|
method: "POST"
|
|
@@ -25073,11 +26943,6 @@ function createAdmin(transport, options2) {
|
|
|
25073
26943
|
createUser,
|
|
25074
26944
|
updateUser,
|
|
25075
26945
|
deleteUser,
|
|
25076
|
-
listRoles,
|
|
25077
|
-
getRole,
|
|
25078
|
-
createRole,
|
|
25079
|
-
updateRole,
|
|
25080
|
-
deleteRole,
|
|
25081
26946
|
bootstrap
|
|
25082
26947
|
};
|
|
25083
26948
|
}
|
|
@@ -25716,7 +27581,7 @@ const require$$9 = {
|
|
|
25716
27581
|
const http = require$$0$6;
|
|
25717
27582
|
const https = require$$1;
|
|
25718
27583
|
const urllib$2 = require$$0$5;
|
|
25719
|
-
const zlib = require$$
|
|
27584
|
+
const zlib = require$$3;
|
|
25720
27585
|
const PassThrough$3 = require$$0$4.PassThrough;
|
|
25721
27586
|
const Cookies2 = cookies;
|
|
25722
27587
|
const packageData$7 = require$$9;
|
|
@@ -25951,9 +27816,9 @@ var fetchExports = fetch$1.exports;
|
|
|
25951
27816
|
const util2 = require$$5;
|
|
25952
27817
|
const fs2 = fs__default;
|
|
25953
27818
|
const nmfetch2 = fetchExports;
|
|
25954
|
-
const dns2 = require$$
|
|
27819
|
+
const dns2 = require$$4$1;
|
|
25955
27820
|
const net2 = require$$0$7;
|
|
25956
|
-
const os2 = require$$1$
|
|
27821
|
+
const os2 = require$$1$3;
|
|
25957
27822
|
const DNS_TTL = 5 * 60 * 1e3;
|
|
25958
27823
|
let networkInterfaces;
|
|
25959
27824
|
try {
|
|
@@ -32099,7 +33964,7 @@ const urllib = require$$0$5;
|
|
|
32099
33964
|
const packageData$6 = require$$9;
|
|
32100
33965
|
const MailMessage2 = mailMessage;
|
|
32101
33966
|
const net$1 = require$$0$7;
|
|
32102
|
-
const dns = require$$
|
|
33967
|
+
const dns = require$$4$1;
|
|
32103
33968
|
const crypto$3 = require$$0__default;
|
|
32104
33969
|
class Mail extends EventEmitter$5 {
|
|
32105
33970
|
constructor(transporter, options2, defaults) {
|
|
@@ -32535,7 +34400,7 @@ const packageInfo = require$$9;
|
|
|
32535
34400
|
const EventEmitter$4 = require$$0$9.EventEmitter;
|
|
32536
34401
|
const net = require$$0$7;
|
|
32537
34402
|
const tls = require$$1$1;
|
|
32538
|
-
const os = require$$1$
|
|
34403
|
+
const os = require$$1$3;
|
|
32539
34404
|
const crypto$2 = require$$0__default;
|
|
32540
34405
|
const DataStream2 = dataStream;
|
|
32541
34406
|
const PassThrough = require$$0$4.PassThrough;
|
|
@@ -36622,6 +38487,9 @@ async function _initializeRebaseBackend(config) {
|
|
|
36622
38487
|
dir: config.collectionsDir
|
|
36623
38488
|
});
|
|
36624
38489
|
}
|
|
38490
|
+
if (config.auth) {
|
|
38491
|
+
activeCollections = Array.from(new Map([defaultUsersCollection, ...activeCollections].map((c) => [c.slug, c])).values());
|
|
38492
|
+
}
|
|
36625
38493
|
const realtimeServices = {};
|
|
36626
38494
|
const delegates = {};
|
|
36627
38495
|
let bootstrappers = config.bootstrappers || [];
|
|
@@ -36690,8 +38558,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36690
38558
|
id: authAdapter.id
|
|
36691
38559
|
});
|
|
36692
38560
|
authConfigResult = {
|
|
36693
|
-
userService: authAdapter.userManagement ?? {}
|
|
36694
|
-
roleService: authAdapter.roleManagement ?? {}
|
|
38561
|
+
userService: authAdapter.userManagement ?? {}
|
|
36695
38562
|
};
|
|
36696
38563
|
} else {
|
|
36697
38564
|
const safeAuthConfig = config.auth;
|
|
@@ -36723,7 +38590,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36723
38590
|
oauthProviders,
|
|
36724
38591
|
serviceKey,
|
|
36725
38592
|
hooks: config.hooks,
|
|
36726
|
-
|
|
38593
|
+
authHooks: safeAuthConfig.hooks
|
|
36727
38594
|
});
|
|
36728
38595
|
}
|
|
36729
38596
|
logger.info("Authentication initialized");
|
|
@@ -36790,73 +38657,73 @@ async function _initializeRebaseBackend(config) {
|
|
|
36790
38657
|
if (safeAuthConfig.google?.clientId) {
|
|
36791
38658
|
const {
|
|
36792
38659
|
createGoogleProvider: createGoogleProvider2
|
|
36793
|
-
} = await import("./index-
|
|
38660
|
+
} = await import("./index-Cr1D21av.js");
|
|
36794
38661
|
oauthProviders.push(createGoogleProvider2(safeAuthConfig.google));
|
|
36795
38662
|
}
|
|
36796
38663
|
if (safeAuthConfig.linkedin?.clientId && safeAuthConfig.linkedin?.clientSecret) {
|
|
36797
38664
|
const {
|
|
36798
38665
|
createLinkedinProvider: createLinkedinProvider2
|
|
36799
|
-
} = await import("./index-
|
|
38666
|
+
} = await import("./index-Cr1D21av.js");
|
|
36800
38667
|
oauthProviders.push(createLinkedinProvider2(safeAuthConfig.linkedin));
|
|
36801
38668
|
}
|
|
36802
38669
|
if (safeAuthConfig.github?.clientId && safeAuthConfig.github?.clientSecret) {
|
|
36803
38670
|
const {
|
|
36804
38671
|
createGitHubProvider: createGitHubProvider2
|
|
36805
|
-
} = await import("./index-
|
|
38672
|
+
} = await import("./index-Cr1D21av.js");
|
|
36806
38673
|
oauthProviders.push(createGitHubProvider2(safeAuthConfig.github));
|
|
36807
38674
|
}
|
|
36808
38675
|
if (safeAuthConfig.microsoft?.clientId && safeAuthConfig.microsoft?.clientSecret) {
|
|
36809
38676
|
const {
|
|
36810
38677
|
createMicrosoftProvider: createMicrosoftProvider2
|
|
36811
|
-
} = await import("./index-
|
|
38678
|
+
} = await import("./index-Cr1D21av.js");
|
|
36812
38679
|
oauthProviders.push(createMicrosoftProvider2(safeAuthConfig.microsoft));
|
|
36813
38680
|
}
|
|
36814
38681
|
if (safeAuthConfig.apple?.clientId && safeAuthConfig.apple?.teamId && safeAuthConfig.apple?.keyId && safeAuthConfig.apple?.privateKey) {
|
|
36815
38682
|
const {
|
|
36816
38683
|
createAppleProvider: createAppleProvider2
|
|
36817
|
-
} = await import("./index-
|
|
38684
|
+
} = await import("./index-Cr1D21av.js");
|
|
36818
38685
|
oauthProviders.push(createAppleProvider2(safeAuthConfig.apple));
|
|
36819
38686
|
}
|
|
36820
38687
|
if (safeAuthConfig.facebook?.clientId && safeAuthConfig.facebook?.clientSecret) {
|
|
36821
38688
|
const {
|
|
36822
38689
|
createFacebookProvider: createFacebookProvider2
|
|
36823
|
-
} = await import("./index-
|
|
38690
|
+
} = await import("./index-Cr1D21av.js");
|
|
36824
38691
|
oauthProviders.push(createFacebookProvider2(safeAuthConfig.facebook));
|
|
36825
38692
|
}
|
|
36826
38693
|
if (safeAuthConfig.twitter?.clientId && safeAuthConfig.twitter?.clientSecret) {
|
|
36827
38694
|
const {
|
|
36828
38695
|
createTwitterProvider: createTwitterProvider2
|
|
36829
|
-
} = await import("./index-
|
|
38696
|
+
} = await import("./index-Cr1D21av.js");
|
|
36830
38697
|
oauthProviders.push(createTwitterProvider2(safeAuthConfig.twitter));
|
|
36831
38698
|
}
|
|
36832
38699
|
if (safeAuthConfig.discord?.clientId && safeAuthConfig.discord?.clientSecret) {
|
|
36833
38700
|
const {
|
|
36834
38701
|
createDiscordProvider: createDiscordProvider2
|
|
36835
|
-
} = await import("./index-
|
|
38702
|
+
} = await import("./index-Cr1D21av.js");
|
|
36836
38703
|
oauthProviders.push(createDiscordProvider2(safeAuthConfig.discord));
|
|
36837
38704
|
}
|
|
36838
38705
|
if (safeAuthConfig.gitlab?.clientId && safeAuthConfig.gitlab?.clientSecret) {
|
|
36839
38706
|
const {
|
|
36840
38707
|
createGitLabProvider: createGitLabProvider2
|
|
36841
|
-
} = await import("./index-
|
|
38708
|
+
} = await import("./index-Cr1D21av.js");
|
|
36842
38709
|
oauthProviders.push(createGitLabProvider2(safeAuthConfig.gitlab));
|
|
36843
38710
|
}
|
|
36844
38711
|
if (safeAuthConfig.bitbucket?.clientId && safeAuthConfig.bitbucket?.clientSecret) {
|
|
36845
38712
|
const {
|
|
36846
38713
|
createBitbucketProvider: createBitbucketProvider2
|
|
36847
|
-
} = await import("./index-
|
|
38714
|
+
} = await import("./index-Cr1D21av.js");
|
|
36848
38715
|
oauthProviders.push(createBitbucketProvider2(safeAuthConfig.bitbucket));
|
|
36849
38716
|
}
|
|
36850
38717
|
if (safeAuthConfig.slack?.clientId && safeAuthConfig.slack?.clientSecret) {
|
|
36851
38718
|
const {
|
|
36852
38719
|
createSlackProvider: createSlackProvider2
|
|
36853
|
-
} = await import("./index-
|
|
38720
|
+
} = await import("./index-Cr1D21av.js");
|
|
36854
38721
|
oauthProviders.push(createSlackProvider2(safeAuthConfig.slack));
|
|
36855
38722
|
}
|
|
36856
38723
|
if (safeAuthConfig.spotify?.clientId && safeAuthConfig.spotify?.clientSecret) {
|
|
36857
38724
|
const {
|
|
36858
38725
|
createSpotifyProvider: createSpotifyProvider2
|
|
36859
|
-
} = await import("./index-
|
|
38726
|
+
} = await import("./index-Cr1D21av.js");
|
|
36860
38727
|
oauthProviders.push(createSpotifyProvider2(safeAuthConfig.spotify));
|
|
36861
38728
|
}
|
|
36862
38729
|
authAdapter = createBuiltinAuthAdapter({
|
|
@@ -36868,7 +38735,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36868
38735
|
oauthProviders,
|
|
36869
38736
|
serviceKey,
|
|
36870
38737
|
hooks: config.hooks,
|
|
36871
|
-
|
|
38738
|
+
authHooks: safeAuthConfig.hooks
|
|
36872
38739
|
});
|
|
36873
38740
|
}
|
|
36874
38741
|
if (authAdapter.createAuthRoutes) {
|
|
@@ -36890,6 +38757,21 @@ async function _initializeRebaseBackend(config) {
|
|
|
36890
38757
|
}
|
|
36891
38758
|
}
|
|
36892
38759
|
}
|
|
38760
|
+
let apiKeyStore;
|
|
38761
|
+
const apiKeyStoreResult = createApiKeyStore(defaultDriver);
|
|
38762
|
+
if (apiKeyStoreResult) {
|
|
38763
|
+
apiKeyStore = apiKeyStoreResult;
|
|
38764
|
+
await apiKeyStore.ensureTable();
|
|
38765
|
+
logger.info("Service API Keys initialized");
|
|
38766
|
+
const apiKeyRoutes = createApiKeyRoutes({
|
|
38767
|
+
store: apiKeyStore,
|
|
38768
|
+
serviceKey
|
|
38769
|
+
});
|
|
38770
|
+
config.app.route(`${basePath}/admin/api-keys`, apiKeyRoutes);
|
|
38771
|
+
logger.info("API key admin routes mounted", {
|
|
38772
|
+
path: `${basePath}/admin/api-keys`
|
|
38773
|
+
});
|
|
38774
|
+
}
|
|
36893
38775
|
if (config.collectionsDir) {
|
|
36894
38776
|
if (process.env.NODE_ENV !== "production") {
|
|
36895
38777
|
const {
|
|
@@ -36940,15 +38822,20 @@ async function _initializeRebaseBackend(config) {
|
|
|
36940
38822
|
dataRouter.use("/*", createAdapterAuthMiddleware({
|
|
36941
38823
|
adapter: authAdapter,
|
|
36942
38824
|
driver: defaultDriver,
|
|
36943
|
-
requireAuth: dataRequireAuth
|
|
38825
|
+
requireAuth: dataRequireAuth,
|
|
38826
|
+
apiKeyStore
|
|
36944
38827
|
}));
|
|
36945
38828
|
} else {
|
|
36946
38829
|
dataRouter.use("/*", createAuthMiddleware({
|
|
36947
38830
|
driver: defaultDriver,
|
|
36948
38831
|
requireAuth: dataRequireAuth,
|
|
36949
|
-
serviceKey
|
|
38832
|
+
serviceKey,
|
|
38833
|
+
apiKeyStore
|
|
36950
38834
|
}));
|
|
36951
38835
|
}
|
|
38836
|
+
if (apiKeyStore) {
|
|
38837
|
+
dataRouter.use("/*", createApiKeyRateLimiter());
|
|
38838
|
+
}
|
|
36952
38839
|
if (historyConfigResult && historyConfigResult.historyService) {
|
|
36953
38840
|
const historyRoutes = createHistoryRoutes({
|
|
36954
38841
|
historyService: historyConfigResult.historyService,
|
|
@@ -37017,6 +38904,13 @@ async function _initializeRebaseBackend(config) {
|
|
|
37017
38904
|
configured: emailService.isConfigured()
|
|
37018
38905
|
});
|
|
37019
38906
|
}
|
|
38907
|
+
const driverAdmin = defaultBootstrapper.getAdmin?.(defaultDriverResult);
|
|
38908
|
+
if (isSQLAdmin(driverAdmin)) {
|
|
38909
|
+
Object.assign(serverClient, {
|
|
38910
|
+
sql: (query, options2) => driverAdmin.executeSql(query, options2)
|
|
38911
|
+
});
|
|
38912
|
+
logger.info("SQL capability attached to singleton");
|
|
38913
|
+
}
|
|
37020
38914
|
_initRebase(serverClient);
|
|
37021
38915
|
logger.info("Rebase singleton initialized");
|
|
37022
38916
|
if (defaultDriverResult.internals) {
|
|
@@ -37042,13 +38936,15 @@ async function _initializeRebaseBackend(config) {
|
|
|
37042
38936
|
functionsRouter.use("/*", createAdapterAuthMiddleware({
|
|
37043
38937
|
adapter: authAdapter,
|
|
37044
38938
|
driver: defaultDriver,
|
|
37045
|
-
requireAuth: functionsRequireAuth
|
|
38939
|
+
requireAuth: functionsRequireAuth,
|
|
38940
|
+
apiKeyStore
|
|
37046
38941
|
}));
|
|
37047
38942
|
} else {
|
|
37048
38943
|
functionsRouter.use("/*", createAuthMiddleware({
|
|
37049
38944
|
driver: defaultDriver,
|
|
37050
38945
|
requireAuth: functionsRequireAuth,
|
|
37051
|
-
serviceKey
|
|
38946
|
+
serviceKey,
|
|
38947
|
+
apiKeyStore
|
|
37052
38948
|
}));
|
|
37053
38949
|
}
|
|
37054
38950
|
const fnRoutes = createFunctionRoutes2(loadedFunctions);
|
|
@@ -47039,7 +48935,7 @@ class AstSchemaEditor {
|
|
|
47039
48935
|
return "undefined";
|
|
47040
48936
|
}
|
|
47041
48937
|
if (typeof obj === "string") {
|
|
47042
|
-
return
|
|
48938
|
+
return JSON.stringify(obj);
|
|
47043
48939
|
}
|
|
47044
48940
|
if (typeof obj === "number" || typeof obj === "boolean") {
|
|
47045
48941
|
return String(obj);
|
|
@@ -47070,7 +48966,7 @@ ${indent2}]`;
|
|
|
47070
48966
|
const kind = init.getKind();
|
|
47071
48967
|
const isCode = kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.Identifier || kind === SyntaxKind.CallExpression || kind === SyntaxKind.JsxElement;
|
|
47072
48968
|
if (isCode || name2 === "target" || name2 === "callbacks" || name2 === "permissions" || name2 === "securityRules") {
|
|
47073
|
-
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name2) ? name2 :
|
|
48969
|
+
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name2) ? name2 : JSON.stringify(name2);
|
|
47074
48970
|
preservedProps.push(`${keyStr}: ${init.getText()}`);
|
|
47075
48971
|
}
|
|
47076
48972
|
}
|
|
@@ -47080,7 +48976,7 @@ ${indent2}]`;
|
|
|
47080
48976
|
}
|
|
47081
48977
|
if (keys2.length === 0 && preservedProps.length === 0) return "{}";
|
|
47082
48978
|
const props = keys2.map((key) => {
|
|
47083
|
-
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key :
|
|
48979
|
+
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
47084
48980
|
let childAstNode;
|
|
47085
48981
|
if (oldAstNode && typeof record[key] === "object" && record[key] !== null && !Array.isArray(record[key])) {
|
|
47086
48982
|
const oldProp = oldAstNode.getProperty((p) => "getName" in p && typeof p.getName === "function" && (p.getName() === key || p.getName() === `"${key}"` || p.getName() === `'${key}'`));
|
|
@@ -47122,7 +49018,7 @@ ${indent2}}`;
|
|
|
47122
49018
|
}
|
|
47123
49019
|
} else {
|
|
47124
49020
|
propsObj.addPropertyAssignment({
|
|
47125
|
-
name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey :
|
|
49021
|
+
name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),
|
|
47126
49022
|
initializer: newInitializer
|
|
47127
49023
|
});
|
|
47128
49024
|
}
|
|
@@ -48193,7 +50089,7 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48193
50089
|
const mod = await dynamicImport(fileUrl);
|
|
48194
50090
|
const exported = mod.default;
|
|
48195
50091
|
if (!exported) {
|
|
48196
|
-
|
|
50092
|
+
logger.warn(`[functions] ${file}: no default export. Skipping.`);
|
|
48197
50093
|
continue;
|
|
48198
50094
|
}
|
|
48199
50095
|
if (isHonoLike(exported)) {
|
|
@@ -48202,7 +50098,7 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48202
50098
|
name: name2,
|
|
48203
50099
|
app: exported
|
|
48204
50100
|
});
|
|
48205
|
-
|
|
50101
|
+
logger.info(`⚡ Loaded function route: ${name2}`);
|
|
48206
50102
|
continue;
|
|
48207
50103
|
}
|
|
48208
50104
|
if (typeof exported === "function") {
|
|
@@ -48213,20 +50109,20 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48213
50109
|
name: name2,
|
|
48214
50110
|
app: result
|
|
48215
50111
|
});
|
|
48216
|
-
|
|
50112
|
+
logger.info(`⚡ Loaded function route: ${name2}`);
|
|
48217
50113
|
continue;
|
|
48218
50114
|
}
|
|
48219
50115
|
}
|
|
48220
50116
|
const exportType = typeof exported;
|
|
48221
50117
|
const keys2 = exported && typeof exported === "object" ? Object.getOwnPropertyNames(Object.getPrototypeOf(exported)).slice(0, 10).join(", ") : "N/A";
|
|
48222
|
-
|
|
50118
|
+
logger.warn(`[functions] ${file}: default export is not a Hono app or factory. Skipping.
|
|
48223
50119
|
export type: ${exportType}${exported?.constructor?.name ? ` (${exported.constructor.name})` : ""}
|
|
48224
50120
|
prototype methods: ${keys2}
|
|
48225
50121
|
Hint: ensure the function exports a Hono app created with the same hono version as the server.
|
|
48226
50122
|
The loader checks for .fetch() and .routes — any Hono-compatible app will work.`);
|
|
48227
50123
|
} catch (err) {
|
|
48228
50124
|
const message = err instanceof Error ? err.message : String(err);
|
|
48229
|
-
|
|
50125
|
+
logger.error(`[functions] Failed to load ${file}: ${message}`);
|
|
48230
50126
|
}
|
|
48231
50127
|
}
|
|
48232
50128
|
}
|
|
@@ -48275,12 +50171,12 @@ async function loadCronJobsFromDirectory(directory) {
|
|
|
48275
50171
|
const mod = await dynamicImport(fileUrl);
|
|
48276
50172
|
const exported = mod.default;
|
|
48277
50173
|
if (!exported || typeof exported !== "object") {
|
|
48278
|
-
|
|
50174
|
+
logger.warn(`[cron] ${file}: no valid default export. Skipping.`);
|
|
48279
50175
|
continue;
|
|
48280
50176
|
}
|
|
48281
50177
|
const def = exported;
|
|
48282
50178
|
if (typeof def.schedule !== "string" || typeof def.handler !== "function") {
|
|
48283
|
-
|
|
50179
|
+
logger.warn(`[cron] ${file}: default export missing required 'schedule' or 'handler'. Skipping.`);
|
|
48284
50180
|
continue;
|
|
48285
50181
|
}
|
|
48286
50182
|
const id = path$3.basename(file, path$3.extname(file));
|
|
@@ -48296,10 +50192,10 @@ async function loadCronJobsFromDirectory(directory) {
|
|
|
48296
50192
|
id,
|
|
48297
50193
|
definition
|
|
48298
50194
|
});
|
|
48299
|
-
|
|
50195
|
+
logger.info(`⏰ Loaded cron job: ${id} (${definition.schedule})`);
|
|
48300
50196
|
} catch (err) {
|
|
48301
50197
|
const message = err instanceof Error ? err.message : String(err);
|
|
48302
|
-
|
|
50198
|
+
logger.error(`[cron] Failed to load ${file}: ${message}`);
|
|
48303
50199
|
}
|
|
48304
50200
|
}
|
|
48305
50201
|
}
|
|
@@ -48454,12 +50350,12 @@ class CronScheduler {
|
|
|
48454
50350
|
for (const loaded of loadedJobs) {
|
|
48455
50351
|
const validation = validateCronExpression(loaded.definition.schedule);
|
|
48456
50352
|
if (!validation.valid) {
|
|
48457
|
-
|
|
50353
|
+
logger.error(`[cron] Rejecting job "${loaded.id}": invalid schedule "${loaded.definition.schedule}" — ${validation.reason}`);
|
|
48458
50354
|
continue;
|
|
48459
50355
|
}
|
|
48460
50356
|
const existing = this.jobs.get(loaded.id);
|
|
48461
50357
|
if (existing) {
|
|
48462
|
-
|
|
50358
|
+
logger.warn(`[cron] Duplicate cron job id: "${loaded.id}". Overwriting.`);
|
|
48463
50359
|
this.stopJob(loaded.id);
|
|
48464
50360
|
}
|
|
48465
50361
|
const enabled = loaded.definition.enabled !== false;
|
|
@@ -48497,7 +50393,9 @@ class CronScheduler {
|
|
|
48497
50393
|
}
|
|
48498
50394
|
}
|
|
48499
50395
|
}).catch((err) => {
|
|
48500
|
-
|
|
50396
|
+
logger.warn("[cron] Failed to seed job stats from database", {
|
|
50397
|
+
error: err
|
|
50398
|
+
});
|
|
48501
50399
|
});
|
|
48502
50400
|
}
|
|
48503
50401
|
for (const [id, job] of this.jobs) {
|
|
@@ -48505,7 +50403,7 @@ class CronScheduler {
|
|
|
48505
50403
|
this.scheduleNext(id);
|
|
48506
50404
|
}
|
|
48507
50405
|
}
|
|
48508
|
-
|
|
50406
|
+
logger.info(`⏰ Cron scheduler started with ${this.jobs.size} job(s)`);
|
|
48509
50407
|
}
|
|
48510
50408
|
/**
|
|
48511
50409
|
* Stop the scheduler and clear all timers.
|
|
@@ -48579,7 +50477,7 @@ class CronScheduler {
|
|
|
48579
50477
|
const job = this.jobs.get(id);
|
|
48580
50478
|
if (!job) return void 0;
|
|
48581
50479
|
if (job.executing) {
|
|
48582
|
-
|
|
50480
|
+
logger.warn(`[cron] Skipping manual trigger of "${id}" — already executing`);
|
|
48583
50481
|
const logEntry = {
|
|
48584
50482
|
jobId: id,
|
|
48585
50483
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -48623,7 +50521,7 @@ class CronScheduler {
|
|
|
48623
50521
|
const timer = setTimeout(async () => {
|
|
48624
50522
|
if (!job.enabled || !this.started) return;
|
|
48625
50523
|
if (job.executing) {
|
|
48626
|
-
|
|
50524
|
+
logger.warn(`[cron] Skipping scheduled run of "${id}" — still executing from previous run`);
|
|
48627
50525
|
this.scheduleNext(id);
|
|
48628
50526
|
return;
|
|
48629
50527
|
}
|
|
@@ -48637,7 +50535,9 @@ class CronScheduler {
|
|
|
48637
50535
|
}
|
|
48638
50536
|
job.timerId = timer;
|
|
48639
50537
|
} catch (err) {
|
|
48640
|
-
|
|
50538
|
+
logger.error(`[cron] Failed to schedule "${id}"`, {
|
|
50539
|
+
error: err
|
|
50540
|
+
});
|
|
48641
50541
|
job.state = "error";
|
|
48642
50542
|
job.lastError = err instanceof Error ? err.message : String(err);
|
|
48643
50543
|
}
|
|
@@ -48722,13 +50622,15 @@ class CronScheduler {
|
|
|
48722
50622
|
}
|
|
48723
50623
|
if (this.store) {
|
|
48724
50624
|
this.store.insertLog(logEntry).catch((persistErr) => {
|
|
48725
|
-
|
|
50625
|
+
logger.error(`[cron] Failed to persist log for "${job.id}"`, {
|
|
50626
|
+
error: persistErr
|
|
50627
|
+
});
|
|
48726
50628
|
});
|
|
48727
50629
|
}
|
|
48728
50630
|
if (success) {
|
|
48729
|
-
|
|
50631
|
+
logger.info(`✅ [cron] "${job.id}" completed in ${durationMs}ms`);
|
|
48730
50632
|
} else {
|
|
48731
|
-
|
|
50633
|
+
logger.error(`❌ [cron] "${job.id}" failed in ${durationMs}ms: ${error2}`);
|
|
48732
50634
|
}
|
|
48733
50635
|
return logEntry;
|
|
48734
50636
|
}
|
|
@@ -48846,7 +50748,7 @@ const TABLE = "rebase.cron_logs";
|
|
|
48846
50748
|
function createCronStore(driver) {
|
|
48847
50749
|
const admin = driver.admin;
|
|
48848
50750
|
if (!isSQLAdmin(admin)) {
|
|
48849
|
-
|
|
50751
|
+
logger.warn("⚠️ [cron-store] DataDriver does not support SQL admin — cron logs will not be persisted.");
|
|
48850
50752
|
return void 0;
|
|
48851
50753
|
}
|
|
48852
50754
|
const exec = admin.executeSql.bind(admin);
|
|
@@ -48872,10 +50774,12 @@ function createCronStore(driver) {
|
|
|
48872
50774
|
CREATE INDEX IF NOT EXISTS idx_cron_logs_job
|
|
48873
50775
|
ON ${TABLE}(job_id, started_at DESC)
|
|
48874
50776
|
`);
|
|
48875
|
-
|
|
50777
|
+
logger.info("✅ Cron logs table ready");
|
|
48876
50778
|
} catch (err) {
|
|
48877
|
-
|
|
48878
|
-
|
|
50779
|
+
logger.error("❌ Failed to create cron logs table", {
|
|
50780
|
+
error: err
|
|
50781
|
+
});
|
|
50782
|
+
logger.warn("⚠️ Continuing without cron log persistence.");
|
|
48879
50783
|
}
|
|
48880
50784
|
},
|
|
48881
50785
|
async insertLog(entry) {
|
|
@@ -48898,7 +50802,9 @@ function createCronStore(driver) {
|
|
|
48898
50802
|
)
|
|
48899
50803
|
`);
|
|
48900
50804
|
} catch (err) {
|
|
48901
|
-
|
|
50805
|
+
logger.error(`[cron-store] Failed to persist log for "${entry.jobId}"`, {
|
|
50806
|
+
error: err
|
|
50807
|
+
});
|
|
48902
50808
|
}
|
|
48903
50809
|
},
|
|
48904
50810
|
async fetchLogs(jobId, limit = 50) {
|
|
@@ -48912,7 +50818,9 @@ function createCronStore(driver) {
|
|
|
48912
50818
|
`);
|
|
48913
50819
|
return rows.map(rowToLogEntry);
|
|
48914
50820
|
} catch (err) {
|
|
48915
|
-
|
|
50821
|
+
logger.error(`[cron-store] Failed to fetch logs for "${jobId}"`, {
|
|
50822
|
+
error: err
|
|
50823
|
+
});
|
|
48916
50824
|
return [];
|
|
48917
50825
|
}
|
|
48918
50826
|
},
|
|
@@ -48936,7 +50844,9 @@ function createCronStore(driver) {
|
|
|
48936
50844
|
});
|
|
48937
50845
|
}
|
|
48938
50846
|
} catch (err) {
|
|
48939
|
-
|
|
50847
|
+
logger.error("[cron-store] Failed to fetch job stats", {
|
|
50848
|
+
error: err
|
|
50849
|
+
});
|
|
48940
50850
|
}
|
|
48941
50851
|
return stats;
|
|
48942
50852
|
}
|
|
@@ -48967,8 +50877,8 @@ function serveSPA(app, config) {
|
|
|
48967
50877
|
indexFile = "index.html"
|
|
48968
50878
|
} = config;
|
|
48969
50879
|
if (!fs$4.existsSync(frontendPath)) {
|
|
48970
|
-
|
|
48971
|
-
|
|
50880
|
+
logger.warn(`⚠️ Frontend build path does not exist: ${frontendPath}`);
|
|
50881
|
+
logger.warn(" SPA serving is disabled. Build your frontend first.");
|
|
48972
50882
|
return;
|
|
48973
50883
|
}
|
|
48974
50884
|
app.use("/*", serveStatic({
|
|
@@ -48981,13 +50891,13 @@ function serveSPA(app, config) {
|
|
|
48981
50891
|
}
|
|
48982
50892
|
const indexPath = path$3.join(frontendPath, indexFile);
|
|
48983
50893
|
if (!fs$4.existsSync(indexPath)) {
|
|
48984
|
-
|
|
50894
|
+
logger.warn(`⚠️ Index file not found: ${indexPath}`);
|
|
48985
50895
|
return next();
|
|
48986
50896
|
}
|
|
48987
50897
|
const html = fs$4.readFileSync(indexPath, "utf-8");
|
|
48988
50898
|
return c.html(html);
|
|
48989
50899
|
});
|
|
48990
|
-
|
|
50900
|
+
logger.info(`✅ SPA serving enabled from: ${frontendPath}`);
|
|
48991
50901
|
}
|
|
48992
50902
|
const MAX_PORT_ATTEMPTS = 20;
|
|
48993
50903
|
const DEV_PORT_FILENAME = ".rebase-dev-port";
|
|
@@ -48995,6 +50905,19 @@ function listenWithPortRetry(server, startPort, options2) {
|
|
|
48995
50905
|
const host = options2?.host ?? "0.0.0.0";
|
|
48996
50906
|
const maxAttempts = options2?.maxAttempts ?? MAX_PORT_ATTEMPTS;
|
|
48997
50907
|
const portFileDir = options2?.portFileDir;
|
|
50908
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
50909
|
+
if (isProd) {
|
|
50910
|
+
return new Promise((resolve, reject) => {
|
|
50911
|
+
const onError = (err) => {
|
|
50912
|
+
reject(err);
|
|
50913
|
+
};
|
|
50914
|
+
server.once("error", onError);
|
|
50915
|
+
server.listen(startPort, host, () => {
|
|
50916
|
+
server.removeListener("error", onError);
|
|
50917
|
+
resolve(startPort);
|
|
50918
|
+
});
|
|
50919
|
+
});
|
|
50920
|
+
}
|
|
48998
50921
|
let affinityPort = null;
|
|
48999
50922
|
if (portFileDir) {
|
|
49000
50923
|
try {
|
|
@@ -49137,6 +51060,8 @@ const rebaseEnvSchema = objectType({
|
|
|
49137
51060
|
DB_POOL_MAX: stringType().default("20").transform(Number),
|
|
49138
51061
|
DB_POOL_IDLE_TIMEOUT: stringType().default("30000").transform(Number),
|
|
49139
51062
|
DB_POOL_CONNECT_TIMEOUT: stringType().default("10000").transform(Number),
|
|
51063
|
+
DATABASE_DIRECT_URL: stringType().url().optional(),
|
|
51064
|
+
DATABASE_READ_URL: stringType().url().optional(),
|
|
49140
51065
|
FORCE_LOCAL_STORAGE: optionalBoolString,
|
|
49141
51066
|
STORAGE_TYPE: enumType(["local", "s3"]).default("local"),
|
|
49142
51067
|
STORAGE_PATH: stringType().optional(),
|
|
@@ -49213,8 +51138,11 @@ export {
|
|
|
49213
51138
|
RestApiGenerator,
|
|
49214
51139
|
S3StorageController,
|
|
49215
51140
|
SMTPEmailService,
|
|
51141
|
+
TransformCache,
|
|
51142
|
+
TusHandler,
|
|
49216
51143
|
_resetRebaseMock,
|
|
49217
51144
|
_setRebaseMock,
|
|
51145
|
+
apiKeyKeyGenerator,
|
|
49218
51146
|
authJwt,
|
|
49219
51147
|
authRoles,
|
|
49220
51148
|
authUid,
|
|
@@ -49223,6 +51151,9 @@ export {
|
|
|
49223
51151
|
configureLogLevel,
|
|
49224
51152
|
createAdapterAuthMiddleware,
|
|
49225
51153
|
createAdminRoutes,
|
|
51154
|
+
createApiKeyRateLimiter,
|
|
51155
|
+
createApiKeyRoutes,
|
|
51156
|
+
createApiKeyStore,
|
|
49226
51157
|
createAppleProvider,
|
|
49227
51158
|
createAuthMiddleware,
|
|
49228
51159
|
createAuthRoutes,
|
|
@@ -49260,26 +51191,34 @@ export {
|
|
|
49260
51191
|
getWelcomeEmailTemplate,
|
|
49261
51192
|
hashPassword,
|
|
49262
51193
|
hashRefreshToken,
|
|
51194
|
+
httpMethodToOperation,
|
|
49263
51195
|
initializeRebaseBackend,
|
|
51196
|
+
isApiKeyToken,
|
|
49264
51197
|
isAuthAdapter,
|
|
49265
51198
|
isDatabaseAdapter,
|
|
51199
|
+
isOperationAllowed,
|
|
51200
|
+
isTransformableImage,
|
|
49266
51201
|
listenWithPortRetry,
|
|
49267
51202
|
loadCronJobsFromDirectory,
|
|
49268
51203
|
loadEnv,
|
|
49269
51204
|
loadFunctionsFromDirectory,
|
|
49270
51205
|
logger,
|
|
49271
51206
|
optionalAuth,
|
|
51207
|
+
parseTransformOptions,
|
|
49272
51208
|
rebase,
|
|
49273
51209
|
requestLogger,
|
|
49274
51210
|
requireAdmin,
|
|
49275
51211
|
requireAuth,
|
|
49276
51212
|
resetConsole,
|
|
49277
|
-
|
|
51213
|
+
resolveAuthHooks,
|
|
49278
51214
|
serveSPA,
|
|
49279
51215
|
strictAuthLimiter,
|
|
51216
|
+
transformImage,
|
|
51217
|
+
validateApiKey,
|
|
49280
51218
|
validateCronExpression,
|
|
49281
51219
|
validatePasswordStrength,
|
|
49282
51220
|
verifyAccessToken,
|
|
49283
|
-
verifyPassword
|
|
51221
|
+
verifyPassword,
|
|
51222
|
+
z
|
|
49284
51223
|
};
|
|
49285
51224
|
//# sourceMappingURL=index.es.js.map
|