@rebasepro/server-core 0.2.3 → 0.2.4
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 +12 -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 +2166 -208
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2155 -193
- 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 +2 -24
- package/dist/types/src/controllers/client.d.ts +0 -3
- package/dist/types/src/controllers/collection_registry.d.ts +1 -1
- package/dist/types/src/controllers/data_driver.d.ts +18 -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 +2 -4
- 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 +2 -2
- package/dist/types/src/types/translations.d.ts +28 -12
- package/dist/types/src/types/user_management_delegate.d.ts +6 -4
- package/dist/types/src/users/roles.d.ts +0 -8
- 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 +39 -14
- 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 +31 -19
- 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 +37 -7
- 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 +74 -7
- 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 -4
- 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/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") {
|
|
@@ -2855,10 +2838,15 @@ const errorHandler = (err, c) => {
|
|
|
2855
2838
|
const issue = error2.code === "42703" ? "column" : "table";
|
|
2856
2839
|
logMessage = `Database schema mismatch (${issue} missing): ${error2.message}. Did you forget to run migrations ('pnpm db:push' or 'pnpm db:migrate')?`;
|
|
2857
2840
|
}
|
|
2858
|
-
console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2859
2841
|
const causePg = error2.cause && typeof error2.cause === "object" ? error2.cause : void 0;
|
|
2860
2842
|
const pgErrorCode = causePg?.code || error2.code;
|
|
2861
|
-
const
|
|
2843
|
+
const isDbSchemaMismatch = pgErrorCode === "42703" || pgErrorCode === "42P01";
|
|
2844
|
+
if (isDbSchemaMismatch) {
|
|
2845
|
+
console.warn(`⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2846
|
+
} else {
|
|
2847
|
+
console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2848
|
+
}
|
|
2849
|
+
const suppressStack = isDbSchemaMismatch || statusCode < 500 && code2 === "BAD_REQUEST";
|
|
2862
2850
|
if (!suppressStack) {
|
|
2863
2851
|
console.error(error2.stack || error2);
|
|
2864
2852
|
}
|
|
@@ -2939,7 +2927,7 @@ function parseQueryOptions(query) {
|
|
|
2939
2927
|
options2.offset = (page - 1) * limit;
|
|
2940
2928
|
}
|
|
2941
2929
|
options2.where = {};
|
|
2942
|
-
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString"];
|
|
2930
|
+
const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
|
|
2943
2931
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
2944
2932
|
if (reservedQueryKeys.includes(key)) continue;
|
|
2945
2933
|
const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
|
|
@@ -3011,8 +2999,63 @@ function parseQueryOptions(query) {
|
|
|
3011
2999
|
const fieldsStr = String(query.fields).trim();
|
|
3012
3000
|
options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
3013
3001
|
}
|
|
3002
|
+
if (query.vector_search && query.vector) {
|
|
3003
|
+
const vectorStr = String(query.vector);
|
|
3004
|
+
let queryVector;
|
|
3005
|
+
try {
|
|
3006
|
+
queryVector = JSON.parse(vectorStr);
|
|
3007
|
+
if (!Array.isArray(queryVector) || !queryVector.every((v) => typeof v === "number")) {
|
|
3008
|
+
throw new Error("Expected array of numbers");
|
|
3009
|
+
}
|
|
3010
|
+
} catch {
|
|
3011
|
+
throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
|
|
3012
|
+
}
|
|
3013
|
+
const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
|
|
3014
|
+
if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
|
|
3015
|
+
throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
|
|
3016
|
+
}
|
|
3017
|
+
const vectorSearch = {
|
|
3018
|
+
property: String(query.vector_search),
|
|
3019
|
+
vector: queryVector,
|
|
3020
|
+
distance: distanceParam
|
|
3021
|
+
};
|
|
3022
|
+
if (query.vector_threshold) {
|
|
3023
|
+
const threshold = parseFloat(String(query.vector_threshold));
|
|
3024
|
+
if (isNaN(threshold)) {
|
|
3025
|
+
throw new Error("Invalid vector_threshold. Expected a number.");
|
|
3026
|
+
}
|
|
3027
|
+
vectorSearch.threshold = threshold;
|
|
3028
|
+
}
|
|
3029
|
+
options2.vectorSearch = vectorSearch;
|
|
3030
|
+
}
|
|
3014
3031
|
return options2;
|
|
3015
3032
|
}
|
|
3033
|
+
function httpMethodToOperation(method) {
|
|
3034
|
+
const upper = method.toUpperCase();
|
|
3035
|
+
switch (upper) {
|
|
3036
|
+
case "GET":
|
|
3037
|
+
case "HEAD":
|
|
3038
|
+
case "OPTIONS":
|
|
3039
|
+
return "read";
|
|
3040
|
+
case "POST":
|
|
3041
|
+
case "PUT":
|
|
3042
|
+
case "PATCH":
|
|
3043
|
+
return "write";
|
|
3044
|
+
case "DELETE":
|
|
3045
|
+
return "delete";
|
|
3046
|
+
default:
|
|
3047
|
+
return "read";
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
function isOperationAllowed(permissions, collection, operation) {
|
|
3051
|
+
for (const perm of permissions) {
|
|
3052
|
+
const collectionMatch = perm.collection === "*" || perm.collection === collection;
|
|
3053
|
+
if (collectionMatch && perm.operations.includes(operation)) {
|
|
3054
|
+
return true;
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
return false;
|
|
3058
|
+
}
|
|
3016
3059
|
class RestApiGenerator {
|
|
3017
3060
|
collections;
|
|
3018
3061
|
router;
|
|
@@ -3045,6 +3088,19 @@ class RestApiGenerator {
|
|
|
3045
3088
|
this.createSubcollectionRoutes();
|
|
3046
3089
|
return this.router;
|
|
3047
3090
|
}
|
|
3091
|
+
/**
|
|
3092
|
+
* Check API key permissions for a collection operation.
|
|
3093
|
+
* Throws 403 if the key doesn't have the required permission.
|
|
3094
|
+
* No-ops if the request is not authenticated via an API key.
|
|
3095
|
+
*/
|
|
3096
|
+
enforceApiKeyPermission(c, collectionSlug) {
|
|
3097
|
+
const apiKey = c.get("apiKey");
|
|
3098
|
+
if (!apiKey) return;
|
|
3099
|
+
const operation = httpMethodToOperation(c.req.method);
|
|
3100
|
+
if (!isOperationAllowed(apiKey.permissions, collectionSlug, operation)) {
|
|
3101
|
+
throw ApiError.forbidden(`API key does not have "${operation}" permission for collection "${collectionSlug}"`, "API_KEY_FORBIDDEN");
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3048
3104
|
/**
|
|
3049
3105
|
* Get the typed RestFetchService from a driver if it exposes one (for include support).
|
|
3050
3106
|
*/
|
|
@@ -3058,6 +3114,7 @@ class RestApiGenerator {
|
|
|
3058
3114
|
const basePath = `/${collection.slug}`;
|
|
3059
3115
|
const resolvedCollection = collection;
|
|
3060
3116
|
this.router.get(`${basePath}/count`, async (c) => {
|
|
3117
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3061
3118
|
const queryDict = c.req.query();
|
|
3062
3119
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3063
3120
|
const searchString = queryDict.searchString;
|
|
@@ -3068,6 +3125,7 @@ class RestApiGenerator {
|
|
|
3068
3125
|
});
|
|
3069
3126
|
});
|
|
3070
3127
|
this.router.get(basePath, async (c) => {
|
|
3128
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3071
3129
|
const queryDict = c.req.query();
|
|
3072
3130
|
const queryOptions = parseQueryOptions(queryDict);
|
|
3073
3131
|
const searchString = queryDict.searchString;
|
|
@@ -3082,7 +3140,8 @@ class RestApiGenerator {
|
|
|
3082
3140
|
offset: queryOptions.offset,
|
|
3083
3141
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3084
3142
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3085
|
-
searchString
|
|
3143
|
+
searchString,
|
|
3144
|
+
vectorSearch: queryOptions.vectorSearch
|
|
3086
3145
|
}, queryOptions.include);
|
|
3087
3146
|
entities2 = await this.applyAfterReadBatch(collection.slug, entities2, hookCtx);
|
|
3088
3147
|
const total2 = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
@@ -3110,6 +3169,7 @@ class RestApiGenerator {
|
|
|
3110
3169
|
});
|
|
3111
3170
|
});
|
|
3112
3171
|
this.router.get(`${basePath}/:id`, async (c) => {
|
|
3172
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3113
3173
|
const id = c.req.param("id");
|
|
3114
3174
|
const queryDict = c.req.query();
|
|
3115
3175
|
const queryOptions = parseQueryOptions(queryDict);
|
|
@@ -3140,6 +3200,7 @@ class RestApiGenerator {
|
|
|
3140
3200
|
});
|
|
3141
3201
|
this.router.post(basePath, async (c) => {
|
|
3142
3202
|
try {
|
|
3203
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3143
3204
|
const driver = c.get("driver") || this.driver;
|
|
3144
3205
|
const path2 = collection.slug;
|
|
3145
3206
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
@@ -3168,6 +3229,7 @@ class RestApiGenerator {
|
|
|
3168
3229
|
});
|
|
3169
3230
|
this.router.put(`${basePath}/:id`, async (c) => {
|
|
3170
3231
|
try {
|
|
3232
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3171
3233
|
const id = c.req.param("id");
|
|
3172
3234
|
const driver = c.get("driver") || this.driver;
|
|
3173
3235
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
@@ -3204,6 +3266,7 @@ class RestApiGenerator {
|
|
|
3204
3266
|
}
|
|
3205
3267
|
});
|
|
3206
3268
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
3269
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3207
3270
|
const id = c.req.param("id");
|
|
3208
3271
|
const driver = c.get("driver") || this.driver;
|
|
3209
3272
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
@@ -3275,6 +3338,7 @@ class RestApiGenerator {
|
|
|
3275
3338
|
const parsed = parseSubPath(rawPath);
|
|
3276
3339
|
if (!parsed) return next();
|
|
3277
3340
|
const driver = c.get("driver") || this.driver;
|
|
3341
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3278
3342
|
if (parsed.entityId === "count") {
|
|
3279
3343
|
const queryDict = c.req.query();
|
|
3280
3344
|
const queryOptions = parseQueryOptions(queryDict);
|
|
@@ -3322,6 +3386,7 @@ class RestApiGenerator {
|
|
|
3322
3386
|
const parsed = parseSubPath(rawPath);
|
|
3323
3387
|
if (!parsed || parsed.entityId) return next();
|
|
3324
3388
|
const driver = c.get("driver") || this.driver;
|
|
3389
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3325
3390
|
const body = await c.req.json().catch(() => ({}));
|
|
3326
3391
|
const entity = await driver.saveEntity({
|
|
3327
3392
|
path: parsed.collectionPath,
|
|
@@ -3337,6 +3402,7 @@ class RestApiGenerator {
|
|
|
3337
3402
|
const parsed = parseSubPath(rawPath);
|
|
3338
3403
|
if (!parsed || !parsed.entityId) return next();
|
|
3339
3404
|
const driver = c.get("driver") || this.driver;
|
|
3405
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3340
3406
|
const body = await c.req.json().catch(() => ({}));
|
|
3341
3407
|
const entity = await driver.saveEntity({
|
|
3342
3408
|
path: parsed.collectionPath,
|
|
@@ -3353,6 +3419,7 @@ class RestApiGenerator {
|
|
|
3353
3419
|
const parsed = parseSubPath(rawPath);
|
|
3354
3420
|
if (!parsed || !parsed.entityId) return next();
|
|
3355
3421
|
const driver = c.get("driver") || this.driver;
|
|
3422
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3356
3423
|
const existingEntity = await driver.fetchEntity({
|
|
3357
3424
|
path: parsed.collectionPath,
|
|
3358
3425
|
entityId: parsed.entityId
|
|
@@ -3418,7 +3485,8 @@ class RestApiGenerator {
|
|
|
3418
3485
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3419
3486
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3420
3487
|
startAfter: queryOptions.offset ? String(queryOptions.offset) : void 0,
|
|
3421
|
-
searchString
|
|
3488
|
+
searchString,
|
|
3489
|
+
vectorSearch: queryOptions.vectorSearch
|
|
3422
3490
|
});
|
|
3423
3491
|
return entities.map((entity) => this.flattenEntity(entity));
|
|
3424
3492
|
}
|
|
@@ -4944,7 +5012,7 @@ var cmp_1 = cmp$1;
|
|
|
4944
5012
|
const SemVer$6 = semver$4;
|
|
4945
5013
|
const parse$6 = parse_1;
|
|
4946
5014
|
const { safeRe: re, t } = reExports;
|
|
4947
|
-
const coerce$
|
|
5015
|
+
const coerce$2 = (version2, options2) => {
|
|
4948
5016
|
if (version2 instanceof SemVer$6) {
|
|
4949
5017
|
return version2;
|
|
4950
5018
|
}
|
|
@@ -4979,7 +5047,7 @@ const coerce$1 = (version2, options2) => {
|
|
|
4979
5047
|
const build = options2.includePrerelease && match[6] ? `+${match[6]}` : "";
|
|
4980
5048
|
return parse$6(`${major2}.${minor2}.${patch2}${prerelease2}${build}`, options2);
|
|
4981
5049
|
};
|
|
4982
|
-
var coerce_1 = coerce$
|
|
5050
|
+
var coerce_1 = coerce$2;
|
|
4983
5051
|
const parse$5 = parse_1;
|
|
4984
5052
|
const constants$1 = constants$2;
|
|
4985
5053
|
const SemVer$5 = semver$4;
|
|
@@ -5265,19 +5333,19 @@ function requireRange() {
|
|
|
5265
5333
|
const replaceCaret = (comp, options2) => {
|
|
5266
5334
|
debug2("caret", comp, options2);
|
|
5267
5335
|
const r = options2.loose ? re2[t2.CARETLOOSE] : re2[t2.CARET];
|
|
5268
|
-
const
|
|
5336
|
+
const z2 = options2.includePrerelease ? "-0" : "";
|
|
5269
5337
|
return comp.replace(r, (_, M, m2, p, pr) => {
|
|
5270
5338
|
debug2("caret", comp, _, M, m2, p, pr);
|
|
5271
5339
|
let ret;
|
|
5272
5340
|
if (isX(M)) {
|
|
5273
5341
|
ret = "";
|
|
5274
5342
|
} else if (isX(m2)) {
|
|
5275
|
-
ret = `>=${M}.0.0${
|
|
5343
|
+
ret = `>=${M}.0.0${z2} <${+M + 1}.0.0-0`;
|
|
5276
5344
|
} else if (isX(p)) {
|
|
5277
5345
|
if (M === "0") {
|
|
5278
|
-
ret = `>=${M}.${m2}.0${
|
|
5346
|
+
ret = `>=${M}.${m2}.0${z2} <${M}.${+m2 + 1}.0-0`;
|
|
5279
5347
|
} else {
|
|
5280
|
-
ret = `>=${M}.${m2}.0${
|
|
5348
|
+
ret = `>=${M}.${m2}.0${z2} <${+M + 1}.0.0-0`;
|
|
5281
5349
|
}
|
|
5282
5350
|
} else if (pr) {
|
|
5283
5351
|
debug2("replaceCaret pr", pr);
|
|
@@ -5294,9 +5362,9 @@ function requireRange() {
|
|
|
5294
5362
|
debug2("no pr");
|
|
5295
5363
|
if (M === "0") {
|
|
5296
5364
|
if (m2 === "0") {
|
|
5297
|
-
ret = `>=${M}.${m2}.${p}${
|
|
5365
|
+
ret = `>=${M}.${m2}.${p}${z2} <${M}.${m2}.${+p + 1}-0`;
|
|
5298
5366
|
} else {
|
|
5299
|
-
ret = `>=${M}.${m2}.${p}${
|
|
5367
|
+
ret = `>=${M}.${m2}.${p}${z2} <${M}.${+m2 + 1}.0-0`;
|
|
5300
5368
|
}
|
|
5301
5369
|
} else {
|
|
5302
5370
|
ret = `>=${M}.${m2}.${p} <${+M + 1}.0.0-0`;
|
|
@@ -5953,7 +6021,7 @@ const neq = neq_1;
|
|
|
5953
6021
|
const gte = gte_1;
|
|
5954
6022
|
const lte = lte_1;
|
|
5955
6023
|
const cmp = cmp_1;
|
|
5956
|
-
const coerce = coerce_1;
|
|
6024
|
+
const coerce$1 = coerce_1;
|
|
5957
6025
|
const truncate = truncate_1;
|
|
5958
6026
|
const Comparator = requireComparator();
|
|
5959
6027
|
const Range = requireRange();
|
|
@@ -5992,7 +6060,7 @@ var semver$3 = {
|
|
|
5992
6060
|
gte,
|
|
5993
6061
|
lte,
|
|
5994
6062
|
cmp,
|
|
5995
|
-
coerce,
|
|
6063
|
+
coerce: coerce$1,
|
|
5996
6064
|
truncate,
|
|
5997
6065
|
Comparator,
|
|
5998
6066
|
Range,
|
|
@@ -6873,7 +6941,7 @@ var jsonwebtoken = {
|
|
|
6873
6941
|
NotBeforeError: NotBeforeError_1,
|
|
6874
6942
|
TokenExpiredError: TokenExpiredError_1
|
|
6875
6943
|
};
|
|
6876
|
-
const jwt = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
|
|
6944
|
+
const jwt$1 = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
|
|
6877
6945
|
let jwtConfig = {
|
|
6878
6946
|
secret: "",
|
|
6879
6947
|
accessExpiresIn: "1h",
|
|
@@ -6892,15 +6960,17 @@ function configureJwt(config) {
|
|
|
6892
6960
|
...config
|
|
6893
6961
|
};
|
|
6894
6962
|
}
|
|
6895
|
-
function generateAccessToken(userId, roles) {
|
|
6963
|
+
function generateAccessToken(userId, roles, aal = "aal1", customClaims) {
|
|
6896
6964
|
if (!jwtConfig.secret) {
|
|
6897
6965
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
6898
6966
|
}
|
|
6899
6967
|
const payload = {
|
|
6900
6968
|
userId,
|
|
6901
|
-
roles
|
|
6969
|
+
roles,
|
|
6970
|
+
aal,
|
|
6971
|
+
...customClaims
|
|
6902
6972
|
};
|
|
6903
|
-
return jwt.sign(payload, jwtConfig.secret, {
|
|
6973
|
+
return jwt$1.sign(payload, jwtConfig.secret, {
|
|
6904
6974
|
expiresIn: jwtConfig.accessExpiresIn,
|
|
6905
6975
|
algorithm: "HS256"
|
|
6906
6976
|
});
|
|
@@ -6934,7 +7004,7 @@ function verifyAccessToken(token) {
|
|
|
6934
7004
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
6935
7005
|
}
|
|
6936
7006
|
try {
|
|
6937
|
-
const decoded = jwt.verify(token, jwtConfig.secret, {
|
|
7007
|
+
const decoded = jwt$1.verify(token, jwtConfig.secret, {
|
|
6938
7008
|
algorithms: ["HS256"]
|
|
6939
7009
|
});
|
|
6940
7010
|
const id = decoded.userId || decoded.uid || decoded.sub;
|
|
@@ -6942,9 +7012,11 @@ function verifyAccessToken(token) {
|
|
|
6942
7012
|
console.error("[JWT] Verification failed: missing id in payload", decoded);
|
|
6943
7013
|
return null;
|
|
6944
7014
|
}
|
|
7015
|
+
const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
|
|
6945
7016
|
return {
|
|
6946
7017
|
userId: id,
|
|
6947
|
-
roles: decoded.roles || []
|
|
7018
|
+
roles: decoded.roles || [],
|
|
7019
|
+
aal
|
|
6948
7020
|
};
|
|
6949
7021
|
} catch (error2) {
|
|
6950
7022
|
console.error("[JWT] Verification failed:", error2, "Token start:", token.substring(0, 15));
|
|
@@ -6984,6 +7056,17 @@ function getRefreshTokenExpiry() {
|
|
|
6984
7056
|
}
|
|
6985
7057
|
return new Date(Date.now() + ms2);
|
|
6986
7058
|
}
|
|
7059
|
+
const jwt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
7060
|
+
__proto__: null,
|
|
7061
|
+
configureJwt,
|
|
7062
|
+
generateAccessToken,
|
|
7063
|
+
generateRefreshToken,
|
|
7064
|
+
getAccessTokenExpiry,
|
|
7065
|
+
getAccessTokenExpiryMs,
|
|
7066
|
+
getRefreshTokenExpiry,
|
|
7067
|
+
hashRefreshToken,
|
|
7068
|
+
verifyAccessToken
|
|
7069
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
6987
7070
|
function isRLSScopedDriver(driver) {
|
|
6988
7071
|
return "withAuth" in driver && typeof driver.withAuth === "function";
|
|
6989
7072
|
}
|
|
@@ -7006,6 +7089,81 @@ function safeCompare(a2, b) {
|
|
|
7006
7089
|
return false;
|
|
7007
7090
|
}
|
|
7008
7091
|
}
|
|
7092
|
+
function isApiKeyToken(token) {
|
|
7093
|
+
return token.startsWith("rk_");
|
|
7094
|
+
}
|
|
7095
|
+
function hashToken$2(token) {
|
|
7096
|
+
return createHash("sha256").update(token).digest("hex");
|
|
7097
|
+
}
|
|
7098
|
+
async function validateApiKey(c, token, options2) {
|
|
7099
|
+
const {
|
|
7100
|
+
store,
|
|
7101
|
+
driver
|
|
7102
|
+
} = options2;
|
|
7103
|
+
const hash = hashToken$2(token);
|
|
7104
|
+
const apiKey = await store.findByKeyHash(hash);
|
|
7105
|
+
if (!apiKey) {
|
|
7106
|
+
return c.json({
|
|
7107
|
+
error: {
|
|
7108
|
+
message: "Invalid API key",
|
|
7109
|
+
code: "UNAUTHORIZED"
|
|
7110
|
+
}
|
|
7111
|
+
}, 401);
|
|
7112
|
+
}
|
|
7113
|
+
if (apiKey.revoked_at) {
|
|
7114
|
+
return c.json({
|
|
7115
|
+
error: {
|
|
7116
|
+
message: "API key has been revoked",
|
|
7117
|
+
code: "UNAUTHORIZED"
|
|
7118
|
+
}
|
|
7119
|
+
}, 401);
|
|
7120
|
+
}
|
|
7121
|
+
if (apiKey.expires_at && new Date(apiKey.expires_at) < /* @__PURE__ */ new Date()) {
|
|
7122
|
+
return c.json({
|
|
7123
|
+
error: {
|
|
7124
|
+
message: "API key has expired",
|
|
7125
|
+
code: "UNAUTHORIZED"
|
|
7126
|
+
}
|
|
7127
|
+
}, 401);
|
|
7128
|
+
}
|
|
7129
|
+
const userId = `api-key:${apiKey.id}`;
|
|
7130
|
+
c.set("user", {
|
|
7131
|
+
userId,
|
|
7132
|
+
roles: []
|
|
7133
|
+
});
|
|
7134
|
+
const masked = {
|
|
7135
|
+
id: apiKey.id,
|
|
7136
|
+
name: apiKey.name,
|
|
7137
|
+
key_prefix: apiKey.key_prefix,
|
|
7138
|
+
permissions: apiKey.permissions,
|
|
7139
|
+
rate_limit: apiKey.rate_limit,
|
|
7140
|
+
created_by: apiKey.created_by,
|
|
7141
|
+
created_at: apiKey.created_at,
|
|
7142
|
+
updated_at: apiKey.updated_at,
|
|
7143
|
+
last_used_at: apiKey.last_used_at,
|
|
7144
|
+
expires_at: apiKey.expires_at,
|
|
7145
|
+
revoked_at: apiKey.revoked_at
|
|
7146
|
+
};
|
|
7147
|
+
c.set("apiKey", masked);
|
|
7148
|
+
try {
|
|
7149
|
+
const scopedDriver = await scopeDataDriver(driver, {
|
|
7150
|
+
uid: userId,
|
|
7151
|
+
roles: ["service"]
|
|
7152
|
+
});
|
|
7153
|
+
c.set("driver", scopedDriver);
|
|
7154
|
+
} catch (error2) {
|
|
7155
|
+
console.error("[AUTH] RLS scoping failed for API key:", error2);
|
|
7156
|
+
return c.json({
|
|
7157
|
+
error: {
|
|
7158
|
+
message: "Internal authentication error",
|
|
7159
|
+
code: "INTERNAL_ERROR"
|
|
7160
|
+
}
|
|
7161
|
+
}, 500);
|
|
7162
|
+
}
|
|
7163
|
+
store.updateLastUsed(apiKey.id).catch(() => {
|
|
7164
|
+
});
|
|
7165
|
+
return true;
|
|
7166
|
+
}
|
|
7009
7167
|
const requireAuth = async (c, next) => {
|
|
7010
7168
|
const authHeader = c.req.header("authorization");
|
|
7011
7169
|
const queryToken = c.req.query("token");
|
|
@@ -7112,7 +7270,8 @@ function createAuthMiddleware(options2) {
|
|
|
7112
7270
|
driver,
|
|
7113
7271
|
requireAuth: enforceAuth = true,
|
|
7114
7272
|
validator,
|
|
7115
|
-
serviceKey
|
|
7273
|
+
serviceKey,
|
|
7274
|
+
apiKeyStore
|
|
7116
7275
|
} = options2;
|
|
7117
7276
|
return async (c, next) => {
|
|
7118
7277
|
if (validator) {
|
|
@@ -7187,6 +7346,14 @@ function createAuthMiddleware(options2) {
|
|
|
7187
7346
|
}
|
|
7188
7347
|
}, 500);
|
|
7189
7348
|
}
|
|
7349
|
+
} else if (apiKeyStore && isApiKeyToken(token)) {
|
|
7350
|
+
const result = await validateApiKey(c, token, {
|
|
7351
|
+
store: apiKeyStore,
|
|
7352
|
+
driver
|
|
7353
|
+
});
|
|
7354
|
+
if (result !== true) {
|
|
7355
|
+
return result;
|
|
7356
|
+
}
|
|
7190
7357
|
} else {
|
|
7191
7358
|
const payload = extractUserFromToken(token);
|
|
7192
7359
|
if (payload) {
|
|
@@ -7247,9 +7414,22 @@ function createAdapterAuthMiddleware(options2) {
|
|
|
7247
7414
|
const {
|
|
7248
7415
|
adapter,
|
|
7249
7416
|
driver,
|
|
7250
|
-
requireAuth: enforceAuth = true
|
|
7417
|
+
requireAuth: enforceAuth = true,
|
|
7418
|
+
apiKeyStore
|
|
7251
7419
|
} = options2;
|
|
7252
7420
|
return async (c, next) => {
|
|
7421
|
+
if (apiKeyStore) {
|
|
7422
|
+
const authHeader = c.req.header("authorization") || "";
|
|
7423
|
+
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
|
|
7424
|
+
if (token.startsWith("rk_")) {
|
|
7425
|
+
const result = await validateApiKey(c, token, {
|
|
7426
|
+
store: apiKeyStore,
|
|
7427
|
+
driver
|
|
7428
|
+
});
|
|
7429
|
+
if (result === true) return next();
|
|
7430
|
+
return result;
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7253
7433
|
let authenticatedUser = null;
|
|
7254
7434
|
try {
|
|
7255
7435
|
authenticatedUser = await adapter.verifyRequest(c.req.raw);
|
|
@@ -7345,11 +7525,11 @@ async function verifyPassword(password, storedHash) {
|
|
|
7345
7525
|
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
|
|
7346
7526
|
return timingSafeEqual$1(derivedKey, storedKey);
|
|
7347
7527
|
}
|
|
7348
|
-
function
|
|
7528
|
+
function resolveAuthHooks(hooks) {
|
|
7349
7529
|
return {
|
|
7350
|
-
hashPassword:
|
|
7351
|
-
verifyPassword:
|
|
7352
|
-
validatePasswordStrength:
|
|
7530
|
+
hashPassword: hooks?.hashPassword ?? hashPassword,
|
|
7531
|
+
verifyPassword: hooks?.verifyPassword ?? verifyPassword,
|
|
7532
|
+
validatePasswordStrength: hooks?.validatePasswordStrength ?? validatePasswordStrength
|
|
7353
7533
|
};
|
|
7354
7534
|
}
|
|
7355
7535
|
function getGreeting(user) {
|
|
@@ -7751,6 +7931,63 @@ const strictAuthLimiter = createRateLimiter({
|
|
|
7751
7931
|
limit: 50,
|
|
7752
7932
|
message: "Too many requests to this sensitive endpoint, please try again later."
|
|
7753
7933
|
});
|
|
7934
|
+
function apiKeyKeyGenerator(c) {
|
|
7935
|
+
const apiKey = c.get("apiKey");
|
|
7936
|
+
if (apiKey) {
|
|
7937
|
+
return `api-key:${apiKey.id}`;
|
|
7938
|
+
}
|
|
7939
|
+
return defaultKeyGenerator(c);
|
|
7940
|
+
}
|
|
7941
|
+
function createApiKeyRateLimiter(defaultLimit = 1e3, windowMs = 15 * 60 * 1e3) {
|
|
7942
|
+
const store = /* @__PURE__ */ new Map();
|
|
7943
|
+
const cleanupInterval = setInterval(() => {
|
|
7944
|
+
const now = Date.now();
|
|
7945
|
+
for (const [key, entry] of store.entries()) {
|
|
7946
|
+
entry.timestamps = entry.timestamps.filter((t2) => now - t2 < windowMs);
|
|
7947
|
+
if (entry.timestamps.length === 0) {
|
|
7948
|
+
store.delete(key);
|
|
7949
|
+
}
|
|
7950
|
+
}
|
|
7951
|
+
}, windowMs);
|
|
7952
|
+
if (cleanupInterval.unref) {
|
|
7953
|
+
cleanupInterval.unref();
|
|
7954
|
+
}
|
|
7955
|
+
return async (c, next) => {
|
|
7956
|
+
const apiKey = c.get("apiKey");
|
|
7957
|
+
if (!apiKey) {
|
|
7958
|
+
return next();
|
|
7959
|
+
}
|
|
7960
|
+
const limit = apiKey.rate_limit ?? defaultLimit;
|
|
7961
|
+
const key = `api-key:${apiKey.id}`;
|
|
7962
|
+
const now = Date.now();
|
|
7963
|
+
let entry = store.get(key);
|
|
7964
|
+
if (!entry) {
|
|
7965
|
+
entry = {
|
|
7966
|
+
timestamps: []
|
|
7967
|
+
};
|
|
7968
|
+
store.set(key, entry);
|
|
7969
|
+
}
|
|
7970
|
+
entry.timestamps = entry.timestamps.filter((t2) => now - t2 < windowMs);
|
|
7971
|
+
if (entry.timestamps.length >= limit) {
|
|
7972
|
+
const retryAfterMs = entry.timestamps[0] + windowMs - now;
|
|
7973
|
+
const retryAfterSec = Math.ceil(retryAfterMs / 1e3);
|
|
7974
|
+
c.header("Retry-After", String(retryAfterSec));
|
|
7975
|
+
c.header("X-RateLimit-Limit", String(limit));
|
|
7976
|
+
c.header("X-RateLimit-Remaining", "0");
|
|
7977
|
+
c.header("X-RateLimit-Reset", String(Math.ceil((now + retryAfterMs) / 1e3)));
|
|
7978
|
+
return c.json({
|
|
7979
|
+
error: {
|
|
7980
|
+
message: "API key rate limit exceeded, please try again later.",
|
|
7981
|
+
code: "RATE_LIMITED"
|
|
7982
|
+
}
|
|
7983
|
+
}, 429);
|
|
7984
|
+
}
|
|
7985
|
+
entry.timestamps.push(now);
|
|
7986
|
+
c.header("X-RateLimit-Limit", String(limit));
|
|
7987
|
+
c.header("X-RateLimit-Remaining", String(limit - entry.timestamps.length));
|
|
7988
|
+
return next();
|
|
7989
|
+
};
|
|
7990
|
+
}
|
|
7754
7991
|
var util$3;
|
|
7755
7992
|
(function(util2) {
|
|
7756
7993
|
util2.assertEqual = (_) => {
|
|
@@ -7901,6 +8138,10 @@ const ZodIssueCode = util$3.arrayToEnum([
|
|
|
7901
8138
|
"not_multiple_of",
|
|
7902
8139
|
"not_finite"
|
|
7903
8140
|
]);
|
|
8141
|
+
const quotelessJson = (obj) => {
|
|
8142
|
+
const json = JSON.stringify(obj, null, 2);
|
|
8143
|
+
return json.replace(/"([^"]+)":/g, "$1:");
|
|
8144
|
+
};
|
|
7904
8145
|
class ZodError extends Error {
|
|
7905
8146
|
get errors() {
|
|
7906
8147
|
return this.issues;
|
|
@@ -8096,6 +8337,9 @@ const errorMap = (issue, _ctx) => {
|
|
|
8096
8337
|
return { message };
|
|
8097
8338
|
};
|
|
8098
8339
|
let overrideErrorMap = errorMap;
|
|
8340
|
+
function setErrorMap(map2) {
|
|
8341
|
+
overrideErrorMap = map2;
|
|
8342
|
+
}
|
|
8099
8343
|
function getErrorMap() {
|
|
8100
8344
|
return overrideErrorMap;
|
|
8101
8345
|
}
|
|
@@ -8124,6 +8368,7 @@ const makeIssue = (params) => {
|
|
|
8124
8368
|
message: errorMessage
|
|
8125
8369
|
};
|
|
8126
8370
|
};
|
|
8371
|
+
const EMPTY_PATH = [];
|
|
8127
8372
|
function addIssueToContext(ctx, issueData) {
|
|
8128
8373
|
const overrideMap = getErrorMap();
|
|
8129
8374
|
const issue = makeIssue({
|
|
@@ -10414,6 +10659,113 @@ ZodUnion.create = (types2, params) => {
|
|
|
10414
10659
|
...processCreateParams(params)
|
|
10415
10660
|
});
|
|
10416
10661
|
};
|
|
10662
|
+
const getDiscriminator = (type) => {
|
|
10663
|
+
if (type instanceof ZodLazy) {
|
|
10664
|
+
return getDiscriminator(type.schema);
|
|
10665
|
+
} else if (type instanceof ZodEffects) {
|
|
10666
|
+
return getDiscriminator(type.innerType());
|
|
10667
|
+
} else if (type instanceof ZodLiteral) {
|
|
10668
|
+
return [type.value];
|
|
10669
|
+
} else if (type instanceof ZodEnum) {
|
|
10670
|
+
return type.options;
|
|
10671
|
+
} else if (type instanceof ZodNativeEnum) {
|
|
10672
|
+
return util$3.objectValues(type.enum);
|
|
10673
|
+
} else if (type instanceof ZodDefault) {
|
|
10674
|
+
return getDiscriminator(type._def.innerType);
|
|
10675
|
+
} else if (type instanceof ZodUndefined) {
|
|
10676
|
+
return [void 0];
|
|
10677
|
+
} else if (type instanceof ZodNull) {
|
|
10678
|
+
return [null];
|
|
10679
|
+
} else if (type instanceof ZodOptional) {
|
|
10680
|
+
return [void 0, ...getDiscriminator(type.unwrap())];
|
|
10681
|
+
} else if (type instanceof ZodNullable) {
|
|
10682
|
+
return [null, ...getDiscriminator(type.unwrap())];
|
|
10683
|
+
} else if (type instanceof ZodBranded) {
|
|
10684
|
+
return getDiscriminator(type.unwrap());
|
|
10685
|
+
} else if (type instanceof ZodReadonly) {
|
|
10686
|
+
return getDiscriminator(type.unwrap());
|
|
10687
|
+
} else if (type instanceof ZodCatch) {
|
|
10688
|
+
return getDiscriminator(type._def.innerType);
|
|
10689
|
+
} else {
|
|
10690
|
+
return [];
|
|
10691
|
+
}
|
|
10692
|
+
};
|
|
10693
|
+
class ZodDiscriminatedUnion extends ZodType {
|
|
10694
|
+
_parse(input) {
|
|
10695
|
+
const { ctx } = this._processInputParams(input);
|
|
10696
|
+
if (ctx.parsedType !== ZodParsedType.object) {
|
|
10697
|
+
addIssueToContext(ctx, {
|
|
10698
|
+
code: ZodIssueCode.invalid_type,
|
|
10699
|
+
expected: ZodParsedType.object,
|
|
10700
|
+
received: ctx.parsedType
|
|
10701
|
+
});
|
|
10702
|
+
return INVALID;
|
|
10703
|
+
}
|
|
10704
|
+
const discriminator = this.discriminator;
|
|
10705
|
+
const discriminatorValue = ctx.data[discriminator];
|
|
10706
|
+
const option = this.optionsMap.get(discriminatorValue);
|
|
10707
|
+
if (!option) {
|
|
10708
|
+
addIssueToContext(ctx, {
|
|
10709
|
+
code: ZodIssueCode.invalid_union_discriminator,
|
|
10710
|
+
options: Array.from(this.optionsMap.keys()),
|
|
10711
|
+
path: [discriminator]
|
|
10712
|
+
});
|
|
10713
|
+
return INVALID;
|
|
10714
|
+
}
|
|
10715
|
+
if (ctx.common.async) {
|
|
10716
|
+
return option._parseAsync({
|
|
10717
|
+
data: ctx.data,
|
|
10718
|
+
path: ctx.path,
|
|
10719
|
+
parent: ctx
|
|
10720
|
+
});
|
|
10721
|
+
} else {
|
|
10722
|
+
return option._parseSync({
|
|
10723
|
+
data: ctx.data,
|
|
10724
|
+
path: ctx.path,
|
|
10725
|
+
parent: ctx
|
|
10726
|
+
});
|
|
10727
|
+
}
|
|
10728
|
+
}
|
|
10729
|
+
get discriminator() {
|
|
10730
|
+
return this._def.discriminator;
|
|
10731
|
+
}
|
|
10732
|
+
get options() {
|
|
10733
|
+
return this._def.options;
|
|
10734
|
+
}
|
|
10735
|
+
get optionsMap() {
|
|
10736
|
+
return this._def.optionsMap;
|
|
10737
|
+
}
|
|
10738
|
+
/**
|
|
10739
|
+
* The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor.
|
|
10740
|
+
* However, it only allows a union of objects, all of which need to share a discriminator property. This property must
|
|
10741
|
+
* have a different value for each object in the union.
|
|
10742
|
+
* @param discriminator the name of the discriminator property
|
|
10743
|
+
* @param types an array of object schemas
|
|
10744
|
+
* @param params
|
|
10745
|
+
*/
|
|
10746
|
+
static create(discriminator, options2, params) {
|
|
10747
|
+
const optionsMap = /* @__PURE__ */ new Map();
|
|
10748
|
+
for (const type of options2) {
|
|
10749
|
+
const discriminatorValues = getDiscriminator(type.shape[discriminator]);
|
|
10750
|
+
if (!discriminatorValues.length) {
|
|
10751
|
+
throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`);
|
|
10752
|
+
}
|
|
10753
|
+
for (const value of discriminatorValues) {
|
|
10754
|
+
if (optionsMap.has(value)) {
|
|
10755
|
+
throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);
|
|
10756
|
+
}
|
|
10757
|
+
optionsMap.set(value, type);
|
|
10758
|
+
}
|
|
10759
|
+
}
|
|
10760
|
+
return new ZodDiscriminatedUnion({
|
|
10761
|
+
typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,
|
|
10762
|
+
discriminator,
|
|
10763
|
+
options: options2,
|
|
10764
|
+
optionsMap,
|
|
10765
|
+
...processCreateParams(params)
|
|
10766
|
+
});
|
|
10767
|
+
}
|
|
10768
|
+
}
|
|
10417
10769
|
function mergeValues(a2, b) {
|
|
10418
10770
|
const aType = getParsedType(a2);
|
|
10419
10771
|
const bType = getParsedType(b);
|
|
@@ -10572,6 +10924,59 @@ ZodTuple.create = (schemas, params) => {
|
|
|
10572
10924
|
...processCreateParams(params)
|
|
10573
10925
|
});
|
|
10574
10926
|
};
|
|
10927
|
+
class ZodRecord extends ZodType {
|
|
10928
|
+
get keySchema() {
|
|
10929
|
+
return this._def.keyType;
|
|
10930
|
+
}
|
|
10931
|
+
get valueSchema() {
|
|
10932
|
+
return this._def.valueType;
|
|
10933
|
+
}
|
|
10934
|
+
_parse(input) {
|
|
10935
|
+
const { status, ctx } = this._processInputParams(input);
|
|
10936
|
+
if (ctx.parsedType !== ZodParsedType.object) {
|
|
10937
|
+
addIssueToContext(ctx, {
|
|
10938
|
+
code: ZodIssueCode.invalid_type,
|
|
10939
|
+
expected: ZodParsedType.object,
|
|
10940
|
+
received: ctx.parsedType
|
|
10941
|
+
});
|
|
10942
|
+
return INVALID;
|
|
10943
|
+
}
|
|
10944
|
+
const pairs = [];
|
|
10945
|
+
const keyType = this._def.keyType;
|
|
10946
|
+
const valueType = this._def.valueType;
|
|
10947
|
+
for (const key in ctx.data) {
|
|
10948
|
+
pairs.push({
|
|
10949
|
+
key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)),
|
|
10950
|
+
value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)),
|
|
10951
|
+
alwaysSet: key in ctx.data
|
|
10952
|
+
});
|
|
10953
|
+
}
|
|
10954
|
+
if (ctx.common.async) {
|
|
10955
|
+
return ParseStatus.mergeObjectAsync(status, pairs);
|
|
10956
|
+
} else {
|
|
10957
|
+
return ParseStatus.mergeObjectSync(status, pairs);
|
|
10958
|
+
}
|
|
10959
|
+
}
|
|
10960
|
+
get element() {
|
|
10961
|
+
return this._def.valueType;
|
|
10962
|
+
}
|
|
10963
|
+
static create(first, second, third) {
|
|
10964
|
+
if (second instanceof ZodType) {
|
|
10965
|
+
return new ZodRecord({
|
|
10966
|
+
keyType: first,
|
|
10967
|
+
valueType: second,
|
|
10968
|
+
typeName: ZodFirstPartyTypeKind.ZodRecord,
|
|
10969
|
+
...processCreateParams(third)
|
|
10970
|
+
});
|
|
10971
|
+
}
|
|
10972
|
+
return new ZodRecord({
|
|
10973
|
+
keyType: ZodString.create(),
|
|
10974
|
+
valueType: first,
|
|
10975
|
+
typeName: ZodFirstPartyTypeKind.ZodRecord,
|
|
10976
|
+
...processCreateParams(second)
|
|
10977
|
+
});
|
|
10978
|
+
}
|
|
10979
|
+
}
|
|
10575
10980
|
class ZodMap extends ZodType {
|
|
10576
10981
|
get keySchema() {
|
|
10577
10982
|
return this._def.keyType;
|
|
@@ -10723,6 +11128,111 @@ ZodSet.create = (valueType, params) => {
|
|
|
10723
11128
|
...processCreateParams(params)
|
|
10724
11129
|
});
|
|
10725
11130
|
};
|
|
11131
|
+
class ZodFunction extends ZodType {
|
|
11132
|
+
constructor() {
|
|
11133
|
+
super(...arguments);
|
|
11134
|
+
this.validate = this.implement;
|
|
11135
|
+
}
|
|
11136
|
+
_parse(input) {
|
|
11137
|
+
const { ctx } = this._processInputParams(input);
|
|
11138
|
+
if (ctx.parsedType !== ZodParsedType.function) {
|
|
11139
|
+
addIssueToContext(ctx, {
|
|
11140
|
+
code: ZodIssueCode.invalid_type,
|
|
11141
|
+
expected: ZodParsedType.function,
|
|
11142
|
+
received: ctx.parsedType
|
|
11143
|
+
});
|
|
11144
|
+
return INVALID;
|
|
11145
|
+
}
|
|
11146
|
+
function makeArgsIssue(args, error2) {
|
|
11147
|
+
return makeIssue({
|
|
11148
|
+
data: args,
|
|
11149
|
+
path: ctx.path,
|
|
11150
|
+
errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), errorMap].filter((x) => !!x),
|
|
11151
|
+
issueData: {
|
|
11152
|
+
code: ZodIssueCode.invalid_arguments,
|
|
11153
|
+
argumentsError: error2
|
|
11154
|
+
}
|
|
11155
|
+
});
|
|
11156
|
+
}
|
|
11157
|
+
function makeReturnsIssue(returns, error2) {
|
|
11158
|
+
return makeIssue({
|
|
11159
|
+
data: returns,
|
|
11160
|
+
path: ctx.path,
|
|
11161
|
+
errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), errorMap].filter((x) => !!x),
|
|
11162
|
+
issueData: {
|
|
11163
|
+
code: ZodIssueCode.invalid_return_type,
|
|
11164
|
+
returnTypeError: error2
|
|
11165
|
+
}
|
|
11166
|
+
});
|
|
11167
|
+
}
|
|
11168
|
+
const params = { errorMap: ctx.common.contextualErrorMap };
|
|
11169
|
+
const fn = ctx.data;
|
|
11170
|
+
if (this._def.returns instanceof ZodPromise) {
|
|
11171
|
+
const me = this;
|
|
11172
|
+
return OK(async function(...args) {
|
|
11173
|
+
const error2 = new ZodError([]);
|
|
11174
|
+
const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => {
|
|
11175
|
+
error2.addIssue(makeArgsIssue(args, e));
|
|
11176
|
+
throw error2;
|
|
11177
|
+
});
|
|
11178
|
+
const result = await Reflect.apply(fn, this, parsedArgs);
|
|
11179
|
+
const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e) => {
|
|
11180
|
+
error2.addIssue(makeReturnsIssue(result, e));
|
|
11181
|
+
throw error2;
|
|
11182
|
+
});
|
|
11183
|
+
return parsedReturns;
|
|
11184
|
+
});
|
|
11185
|
+
} else {
|
|
11186
|
+
const me = this;
|
|
11187
|
+
return OK(function(...args) {
|
|
11188
|
+
const parsedArgs = me._def.args.safeParse(args, params);
|
|
11189
|
+
if (!parsedArgs.success) {
|
|
11190
|
+
throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);
|
|
11191
|
+
}
|
|
11192
|
+
const result = Reflect.apply(fn, this, parsedArgs.data);
|
|
11193
|
+
const parsedReturns = me._def.returns.safeParse(result, params);
|
|
11194
|
+
if (!parsedReturns.success) {
|
|
11195
|
+
throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);
|
|
11196
|
+
}
|
|
11197
|
+
return parsedReturns.data;
|
|
11198
|
+
});
|
|
11199
|
+
}
|
|
11200
|
+
}
|
|
11201
|
+
parameters() {
|
|
11202
|
+
return this._def.args;
|
|
11203
|
+
}
|
|
11204
|
+
returnType() {
|
|
11205
|
+
return this._def.returns;
|
|
11206
|
+
}
|
|
11207
|
+
args(...items) {
|
|
11208
|
+
return new ZodFunction({
|
|
11209
|
+
...this._def,
|
|
11210
|
+
args: ZodTuple.create(items).rest(ZodUnknown.create())
|
|
11211
|
+
});
|
|
11212
|
+
}
|
|
11213
|
+
returns(returnType) {
|
|
11214
|
+
return new ZodFunction({
|
|
11215
|
+
...this._def,
|
|
11216
|
+
returns: returnType
|
|
11217
|
+
});
|
|
11218
|
+
}
|
|
11219
|
+
implement(func) {
|
|
11220
|
+
const validatedFunc = this.parse(func);
|
|
11221
|
+
return validatedFunc;
|
|
11222
|
+
}
|
|
11223
|
+
strictImplement(func) {
|
|
11224
|
+
const validatedFunc = this.parse(func);
|
|
11225
|
+
return validatedFunc;
|
|
11226
|
+
}
|
|
11227
|
+
static create(args, returns, params) {
|
|
11228
|
+
return new ZodFunction({
|
|
11229
|
+
args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()),
|
|
11230
|
+
returns: returns || ZodUnknown.create(),
|
|
11231
|
+
typeName: ZodFirstPartyTypeKind.ZodFunction,
|
|
11232
|
+
...processCreateParams(params)
|
|
11233
|
+
});
|
|
11234
|
+
}
|
|
11235
|
+
}
|
|
10726
11236
|
class ZodLazy extends ZodType {
|
|
10727
11237
|
get schema() {
|
|
10728
11238
|
return this._def.getter();
|
|
@@ -11180,6 +11690,7 @@ ZodNaN.create = (params) => {
|
|
|
11180
11690
|
...processCreateParams(params)
|
|
11181
11691
|
});
|
|
11182
11692
|
};
|
|
11693
|
+
const BRAND = Symbol("zod_brand");
|
|
11183
11694
|
class ZodBranded extends ZodType {
|
|
11184
11695
|
_parse(input) {
|
|
11185
11696
|
const { ctx } = this._processInputParams(input);
|
|
@@ -11271,6 +11782,36 @@ ZodReadonly.create = (type, params) => {
|
|
|
11271
11782
|
...processCreateParams(params)
|
|
11272
11783
|
});
|
|
11273
11784
|
};
|
|
11785
|
+
function cleanParams(params, data) {
|
|
11786
|
+
const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
|
|
11787
|
+
const p2 = typeof p === "string" ? { message: p } : p;
|
|
11788
|
+
return p2;
|
|
11789
|
+
}
|
|
11790
|
+
function custom(check, _params = {}, fatal) {
|
|
11791
|
+
if (check)
|
|
11792
|
+
return ZodAny.create().superRefine((data, ctx) => {
|
|
11793
|
+
const r = check(data);
|
|
11794
|
+
if (r instanceof Promise) {
|
|
11795
|
+
return r.then((r2) => {
|
|
11796
|
+
if (!r2) {
|
|
11797
|
+
const params = cleanParams(_params, data);
|
|
11798
|
+
const _fatal = params.fatal ?? fatal ?? true;
|
|
11799
|
+
ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
|
|
11800
|
+
}
|
|
11801
|
+
});
|
|
11802
|
+
}
|
|
11803
|
+
if (!r) {
|
|
11804
|
+
const params = cleanParams(_params, data);
|
|
11805
|
+
const _fatal = params.fatal ?? fatal ?? true;
|
|
11806
|
+
ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
|
|
11807
|
+
}
|
|
11808
|
+
return;
|
|
11809
|
+
});
|
|
11810
|
+
return ZodAny.create();
|
|
11811
|
+
}
|
|
11812
|
+
const late = {
|
|
11813
|
+
object: ZodObject.lazycreate
|
|
11814
|
+
};
|
|
11274
11815
|
var ZodFirstPartyTypeKind;
|
|
11275
11816
|
(function(ZodFirstPartyTypeKind2) {
|
|
11276
11817
|
ZodFirstPartyTypeKind2["ZodString"] = "ZodString";
|
|
@@ -11310,17 +11851,251 @@ var ZodFirstPartyTypeKind;
|
|
|
11310
11851
|
ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
|
|
11311
11852
|
ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
|
|
11312
11853
|
})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
11854
|
+
const instanceOfType = (cls, params = {
|
|
11855
|
+
message: `Input not instance of ${cls.name}`
|
|
11856
|
+
}) => custom((data) => data instanceof cls, params);
|
|
11313
11857
|
const stringType = ZodString.create;
|
|
11314
|
-
|
|
11315
|
-
|
|
11858
|
+
const numberType = ZodNumber.create;
|
|
11859
|
+
const nanType = ZodNaN.create;
|
|
11860
|
+
const bigIntType = ZodBigInt.create;
|
|
11861
|
+
const booleanType = ZodBoolean.create;
|
|
11862
|
+
const dateType = ZodDate.create;
|
|
11863
|
+
const symbolType = ZodSymbol.create;
|
|
11864
|
+
const undefinedType = ZodUndefined.create;
|
|
11865
|
+
const nullType = ZodNull.create;
|
|
11866
|
+
const anyType = ZodAny.create;
|
|
11867
|
+
const unknownType = ZodUnknown.create;
|
|
11868
|
+
const neverType = ZodNever.create;
|
|
11869
|
+
const voidType = ZodVoid.create;
|
|
11870
|
+
const arrayType = ZodArray.create;
|
|
11316
11871
|
const objectType = ZodObject.create;
|
|
11317
|
-
|
|
11318
|
-
|
|
11319
|
-
|
|
11872
|
+
const strictObjectType = ZodObject.strictCreate;
|
|
11873
|
+
const unionType = ZodUnion.create;
|
|
11874
|
+
const discriminatedUnionType = ZodDiscriminatedUnion.create;
|
|
11875
|
+
const intersectionType = ZodIntersection.create;
|
|
11876
|
+
const tupleType = ZodTuple.create;
|
|
11877
|
+
const recordType = ZodRecord.create;
|
|
11878
|
+
const mapType = ZodMap.create;
|
|
11879
|
+
const setType = ZodSet.create;
|
|
11880
|
+
const functionType = ZodFunction.create;
|
|
11881
|
+
const lazyType = ZodLazy.create;
|
|
11882
|
+
const literalType = ZodLiteral.create;
|
|
11320
11883
|
const enumType = ZodEnum.create;
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11884
|
+
const nativeEnumType = ZodNativeEnum.create;
|
|
11885
|
+
const promiseType = ZodPromise.create;
|
|
11886
|
+
const effectsType = ZodEffects.create;
|
|
11887
|
+
const optionalType = ZodOptional.create;
|
|
11888
|
+
const nullableType = ZodNullable.create;
|
|
11889
|
+
const preprocessType = ZodEffects.createWithPreprocess;
|
|
11890
|
+
const pipelineType = ZodPipeline.create;
|
|
11891
|
+
const ostring = () => stringType().optional();
|
|
11892
|
+
const onumber = () => numberType().optional();
|
|
11893
|
+
const oboolean = () => booleanType().optional();
|
|
11894
|
+
const coerce = {
|
|
11895
|
+
string: (arg) => ZodString.create({ ...arg, coerce: true }),
|
|
11896
|
+
number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
|
|
11897
|
+
boolean: (arg) => ZodBoolean.create({
|
|
11898
|
+
...arg,
|
|
11899
|
+
coerce: true
|
|
11900
|
+
}),
|
|
11901
|
+
bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
|
|
11902
|
+
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
11903
|
+
};
|
|
11904
|
+
const NEVER = INVALID;
|
|
11905
|
+
const z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
11906
|
+
__proto__: null,
|
|
11907
|
+
BRAND,
|
|
11908
|
+
DIRTY,
|
|
11909
|
+
EMPTY_PATH,
|
|
11910
|
+
INVALID,
|
|
11911
|
+
NEVER,
|
|
11912
|
+
OK,
|
|
11913
|
+
ParseStatus,
|
|
11914
|
+
Schema: ZodType,
|
|
11915
|
+
ZodAny,
|
|
11916
|
+
ZodArray,
|
|
11917
|
+
ZodBigInt,
|
|
11918
|
+
ZodBoolean,
|
|
11919
|
+
ZodBranded,
|
|
11920
|
+
ZodCatch,
|
|
11921
|
+
ZodDate,
|
|
11922
|
+
ZodDefault,
|
|
11923
|
+
ZodDiscriminatedUnion,
|
|
11924
|
+
ZodEffects,
|
|
11925
|
+
ZodEnum,
|
|
11926
|
+
ZodError,
|
|
11927
|
+
get ZodFirstPartyTypeKind() {
|
|
11928
|
+
return ZodFirstPartyTypeKind;
|
|
11929
|
+
},
|
|
11930
|
+
ZodFunction,
|
|
11931
|
+
ZodIntersection,
|
|
11932
|
+
ZodIssueCode,
|
|
11933
|
+
ZodLazy,
|
|
11934
|
+
ZodLiteral,
|
|
11935
|
+
ZodMap,
|
|
11936
|
+
ZodNaN,
|
|
11937
|
+
ZodNativeEnum,
|
|
11938
|
+
ZodNever,
|
|
11939
|
+
ZodNull,
|
|
11940
|
+
ZodNullable,
|
|
11941
|
+
ZodNumber,
|
|
11942
|
+
ZodObject,
|
|
11943
|
+
ZodOptional,
|
|
11944
|
+
ZodParsedType,
|
|
11945
|
+
ZodPipeline,
|
|
11946
|
+
ZodPromise,
|
|
11947
|
+
ZodReadonly,
|
|
11948
|
+
ZodRecord,
|
|
11949
|
+
ZodSchema: ZodType,
|
|
11950
|
+
ZodSet,
|
|
11951
|
+
ZodString,
|
|
11952
|
+
ZodSymbol,
|
|
11953
|
+
ZodTransformer: ZodEffects,
|
|
11954
|
+
ZodTuple,
|
|
11955
|
+
ZodType,
|
|
11956
|
+
ZodUndefined,
|
|
11957
|
+
ZodUnion,
|
|
11958
|
+
ZodUnknown,
|
|
11959
|
+
ZodVoid,
|
|
11960
|
+
addIssueToContext,
|
|
11961
|
+
any: anyType,
|
|
11962
|
+
array: arrayType,
|
|
11963
|
+
bigint: bigIntType,
|
|
11964
|
+
boolean: booleanType,
|
|
11965
|
+
coerce,
|
|
11966
|
+
custom,
|
|
11967
|
+
date: dateType,
|
|
11968
|
+
datetimeRegex,
|
|
11969
|
+
defaultErrorMap: errorMap,
|
|
11970
|
+
discriminatedUnion: discriminatedUnionType,
|
|
11971
|
+
effect: effectsType,
|
|
11972
|
+
enum: enumType,
|
|
11973
|
+
function: functionType,
|
|
11974
|
+
getErrorMap,
|
|
11975
|
+
getParsedType,
|
|
11976
|
+
instanceof: instanceOfType,
|
|
11977
|
+
intersection: intersectionType,
|
|
11978
|
+
isAborted,
|
|
11979
|
+
isAsync,
|
|
11980
|
+
isDirty,
|
|
11981
|
+
isValid,
|
|
11982
|
+
late,
|
|
11983
|
+
lazy: lazyType,
|
|
11984
|
+
literal: literalType,
|
|
11985
|
+
makeIssue,
|
|
11986
|
+
map: mapType,
|
|
11987
|
+
nan: nanType,
|
|
11988
|
+
nativeEnum: nativeEnumType,
|
|
11989
|
+
never: neverType,
|
|
11990
|
+
null: nullType,
|
|
11991
|
+
nullable: nullableType,
|
|
11992
|
+
number: numberType,
|
|
11993
|
+
object: objectType,
|
|
11994
|
+
get objectUtil() {
|
|
11995
|
+
return objectUtil;
|
|
11996
|
+
},
|
|
11997
|
+
oboolean,
|
|
11998
|
+
onumber,
|
|
11999
|
+
optional: optionalType,
|
|
12000
|
+
ostring,
|
|
12001
|
+
pipeline: pipelineType,
|
|
12002
|
+
preprocess: preprocessType,
|
|
12003
|
+
promise: promiseType,
|
|
12004
|
+
quotelessJson,
|
|
12005
|
+
record: recordType,
|
|
12006
|
+
set: setType,
|
|
12007
|
+
setErrorMap,
|
|
12008
|
+
strictObject: strictObjectType,
|
|
12009
|
+
string: stringType,
|
|
12010
|
+
symbol: symbolType,
|
|
12011
|
+
transformer: effectsType,
|
|
12012
|
+
tuple: tupleType,
|
|
12013
|
+
undefined: undefinedType,
|
|
12014
|
+
union: unionType,
|
|
12015
|
+
unknown: unknownType,
|
|
12016
|
+
get util() {
|
|
12017
|
+
return util$3;
|
|
12018
|
+
},
|
|
12019
|
+
void: voidType
|
|
12020
|
+
}, Symbol.toStringTag, { value: "Module" }));
|
|
12021
|
+
const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
12022
|
+
function base32Encode(buffer) {
|
|
12023
|
+
let bits = 0;
|
|
12024
|
+
let value = 0;
|
|
12025
|
+
let output = "";
|
|
12026
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
12027
|
+
value = value << 8 | buffer[i];
|
|
12028
|
+
bits += 8;
|
|
12029
|
+
while (bits >= 5) {
|
|
12030
|
+
output += BASE32_ALPHABET[value >>> bits - 5 & 31];
|
|
12031
|
+
bits -= 5;
|
|
12032
|
+
}
|
|
12033
|
+
}
|
|
12034
|
+
if (bits > 0) {
|
|
12035
|
+
output += BASE32_ALPHABET[value << 5 - bits & 31];
|
|
12036
|
+
}
|
|
12037
|
+
return output;
|
|
12038
|
+
}
|
|
12039
|
+
function base32Decode(encoded) {
|
|
12040
|
+
const cleanInput = encoded.replace(/=+$/, "").toUpperCase();
|
|
12041
|
+
const bytes = [];
|
|
12042
|
+
let bits = 0;
|
|
12043
|
+
let value = 0;
|
|
12044
|
+
for (let i = 0; i < cleanInput.length; i++) {
|
|
12045
|
+
const index = BASE32_ALPHABET.indexOf(cleanInput[i]);
|
|
12046
|
+
if (index === -1) continue;
|
|
12047
|
+
value = value << 5 | index;
|
|
12048
|
+
bits += 5;
|
|
12049
|
+
if (bits >= 8) {
|
|
12050
|
+
bytes.push(value >>> bits - 8 & 255);
|
|
12051
|
+
bits -= 8;
|
|
12052
|
+
}
|
|
12053
|
+
}
|
|
12054
|
+
return Buffer.from(bytes);
|
|
12055
|
+
}
|
|
12056
|
+
function generateHotp(secret, counter) {
|
|
12057
|
+
const hmac = createHmac("sha1", secret);
|
|
12058
|
+
const counterBuffer = Buffer.alloc(8);
|
|
12059
|
+
counterBuffer.writeBigInt64BE(counter);
|
|
12060
|
+
hmac.update(counterBuffer);
|
|
12061
|
+
const hash = hmac.digest();
|
|
12062
|
+
const offset = hash[hash.length - 1] & 15;
|
|
12063
|
+
const code2 = ((hash[offset] & 127) << 24 | hash[offset + 1] << 16 | hash[offset + 2] << 8 | hash[offset + 3]) % 1e6;
|
|
12064
|
+
return code2.toString().padStart(6, "0");
|
|
12065
|
+
}
|
|
12066
|
+
function verifyTotp(secret, token, window2 = 1) {
|
|
12067
|
+
const timeStep = 30;
|
|
12068
|
+
const counter = BigInt(Math.floor(Date.now() / 1e3 / timeStep));
|
|
12069
|
+
for (let i = -window2; i <= window2; i++) {
|
|
12070
|
+
if (generateHotp(secret, counter + BigInt(i)) === token) {
|
|
12071
|
+
return true;
|
|
12072
|
+
}
|
|
12073
|
+
}
|
|
12074
|
+
return false;
|
|
12075
|
+
}
|
|
12076
|
+
function generateTotpSecret(issuer, accountName) {
|
|
12077
|
+
const secretBuffer = randomBytes(20);
|
|
12078
|
+
const secret = base32Encode(secretBuffer);
|
|
12079
|
+
const encodedIssuer = encodeURIComponent(issuer);
|
|
12080
|
+
const encodedAccount = encodeURIComponent(accountName);
|
|
12081
|
+
const uri = `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
|
|
12082
|
+
return {
|
|
12083
|
+
secret,
|
|
12084
|
+
uri
|
|
12085
|
+
};
|
|
12086
|
+
}
|
|
12087
|
+
function generateRecoveryCodes(count = 10) {
|
|
12088
|
+
return Array.from({
|
|
12089
|
+
length: count
|
|
12090
|
+
}, () => {
|
|
12091
|
+
const raw = randomBytes(5).toString("hex").toUpperCase();
|
|
12092
|
+
const parts = raw.match(/.{1,5}/g);
|
|
12093
|
+
return parts ? parts.join("-") : raw;
|
|
12094
|
+
});
|
|
12095
|
+
}
|
|
12096
|
+
function hashRecoveryCode(code2) {
|
|
12097
|
+
return createHash("sha256").update(code2.replace(/-/g, "").toUpperCase()).digest("hex");
|
|
12098
|
+
}
|
|
11324
12099
|
function buildAuthResponse(user, roleIds, accessToken, refreshToken) {
|
|
11325
12100
|
return {
|
|
11326
12101
|
user: {
|
|
@@ -11358,9 +12133,9 @@ function createAuthRoutes(config) {
|
|
|
11358
12133
|
emailService,
|
|
11359
12134
|
emailConfig,
|
|
11360
12135
|
allowRegistration = false,
|
|
11361
|
-
|
|
12136
|
+
authHooks
|
|
11362
12137
|
} = config;
|
|
11363
|
-
const ops =
|
|
12138
|
+
const ops = resolveAuthHooks(authHooks);
|
|
11364
12139
|
const registerSchema = objectType({
|
|
11365
12140
|
email: stringType().email("Invalid email address").max(255),
|
|
11366
12141
|
password: stringType().min(1, "Password is required").max(128),
|
|
@@ -11424,7 +12199,19 @@ function createAuthRoutes(config) {
|
|
|
11424
12199
|
async function createSessionAndTokens(userId, userAgent, ipAddress) {
|
|
11425
12200
|
const roles = await authRepo.getUserRoles(userId);
|
|
11426
12201
|
const roleIds = roles.map((r) => r.id);
|
|
11427
|
-
|
|
12202
|
+
let customClaims;
|
|
12203
|
+
if (authHooks?.customizeAccessToken) {
|
|
12204
|
+
const user = await authRepo.getUserById(userId);
|
|
12205
|
+
if (user) {
|
|
12206
|
+
const defaultClaims = {
|
|
12207
|
+
userId,
|
|
12208
|
+
roles: roleIds,
|
|
12209
|
+
aal: "aal1"
|
|
12210
|
+
};
|
|
12211
|
+
customClaims = await authHooks.customizeAccessToken(defaultClaims, user);
|
|
12212
|
+
}
|
|
12213
|
+
}
|
|
12214
|
+
const accessToken = generateAccessToken(userId, roleIds, "aal1", customClaims);
|
|
11428
12215
|
const refreshToken = generateRefreshToken();
|
|
11429
12216
|
await authRepo.createRefreshToken(userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), userAgent, ipAddress);
|
|
11430
12217
|
return {
|
|
@@ -11459,8 +12246,8 @@ function createAuthRoutes(config) {
|
|
|
11459
12246
|
passwordHash,
|
|
11460
12247
|
displayName: displayName || void 0
|
|
11461
12248
|
};
|
|
11462
|
-
if (
|
|
11463
|
-
createData = await
|
|
12249
|
+
if (authHooks?.beforeUserCreate) {
|
|
12250
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
11464
12251
|
}
|
|
11465
12252
|
const user = await authRepo.createUser(createData);
|
|
11466
12253
|
const existingUsers = await authRepo.listUsers();
|
|
@@ -11479,14 +12266,16 @@ function createAuthRoutes(config) {
|
|
|
11479
12266
|
email: user.email,
|
|
11480
12267
|
displayName: user.displayName
|
|
11481
12268
|
});
|
|
11482
|
-
if (
|
|
11483
|
-
|
|
11484
|
-
|
|
11485
|
-
})
|
|
12269
|
+
if (authHooks?.afterUserCreate) {
|
|
12270
|
+
try {
|
|
12271
|
+
await authHooks.afterUserCreate(user);
|
|
12272
|
+
} catch (err) {
|
|
12273
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
12274
|
+
}
|
|
11486
12275
|
}
|
|
11487
|
-
if (
|
|
11488
|
-
|
|
11489
|
-
console.error("[
|
|
12276
|
+
if (authHooks?.onAuthenticated) {
|
|
12277
|
+
authHooks.onAuthenticated(user, "register").catch((err) => {
|
|
12278
|
+
console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
|
|
11490
12279
|
});
|
|
11491
12280
|
}
|
|
11492
12281
|
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
|
|
@@ -11496,9 +12285,12 @@ function createAuthRoutes(config) {
|
|
|
11496
12285
|
email,
|
|
11497
12286
|
password
|
|
11498
12287
|
} = parseBody2(loginSchema, await c.req.json());
|
|
12288
|
+
if (authHooks?.beforeLogin) {
|
|
12289
|
+
await authHooks.beforeLogin(email, "login");
|
|
12290
|
+
}
|
|
11499
12291
|
let user;
|
|
11500
|
-
if (
|
|
11501
|
-
user = await
|
|
12292
|
+
if (authHooks?.verifyCredentials) {
|
|
12293
|
+
user = await authHooks.verifyCredentials(email, password, authRepo);
|
|
11502
12294
|
if (!user) {
|
|
11503
12295
|
throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
|
|
11504
12296
|
}
|
|
@@ -11520,9 +12312,9 @@ function createAuthRoutes(config) {
|
|
|
11520
12312
|
accessToken,
|
|
11521
12313
|
refreshToken
|
|
11522
12314
|
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
11523
|
-
if (
|
|
11524
|
-
|
|
11525
|
-
console.error("[
|
|
12315
|
+
if (authHooks?.onAuthenticated) {
|
|
12316
|
+
authHooks.onAuthenticated(user, "login").catch((err) => {
|
|
12317
|
+
console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
|
|
11526
12318
|
});
|
|
11527
12319
|
}
|
|
11528
12320
|
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken));
|
|
@@ -11561,6 +12353,13 @@ function createAuthRoutes(config) {
|
|
|
11561
12353
|
await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, {
|
|
11562
12354
|
email: externalUser.email
|
|
11563
12355
|
});
|
|
12356
|
+
if (authHooks?.afterUserCreate) {
|
|
12357
|
+
try {
|
|
12358
|
+
await authHooks.afterUserCreate(user);
|
|
12359
|
+
} catch (err) {
|
|
12360
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
12361
|
+
}
|
|
12362
|
+
}
|
|
11564
12363
|
const allUsers = await authRepo.listUsers();
|
|
11565
12364
|
const isFirstUser = allUsers.length === 1 && allUsers[0].id === user.id;
|
|
11566
12365
|
if (isFirstUser) {
|
|
@@ -11646,6 +12445,11 @@ function createAuthRoutes(config) {
|
|
|
11646
12445
|
await authRepo.updatePassword(storedToken.userId, passwordHash);
|
|
11647
12446
|
await authRepo.markPasswordResetTokenUsed(tokenHash);
|
|
11648
12447
|
await authRepo.deleteAllRefreshTokensForUser(storedToken.userId);
|
|
12448
|
+
if (authHooks?.onPasswordReset) {
|
|
12449
|
+
authHooks.onPasswordReset(storedToken.userId).catch((err) => {
|
|
12450
|
+
console.error("[AuthHooks] onPasswordReset error:", err instanceof Error ? err.message : err);
|
|
12451
|
+
});
|
|
12452
|
+
}
|
|
11649
12453
|
return c.json({
|
|
11650
12454
|
success: true,
|
|
11651
12455
|
message: "Password has been reset successfully"
|
|
@@ -11749,7 +12553,19 @@ function createAuthRoutes(config) {
|
|
|
11749
12553
|
}
|
|
11750
12554
|
const roles = await authRepo.getUserRoles(storedToken.userId);
|
|
11751
12555
|
const roleIds = roles.map((r) => r.id);
|
|
11752
|
-
|
|
12556
|
+
let customClaims;
|
|
12557
|
+
if (authHooks?.customizeAccessToken) {
|
|
12558
|
+
const user = await authRepo.getUserById(storedToken.userId);
|
|
12559
|
+
if (user) {
|
|
12560
|
+
const defaultClaims = {
|
|
12561
|
+
userId: storedToken.userId,
|
|
12562
|
+
roles: roleIds,
|
|
12563
|
+
aal: "aal1"
|
|
12564
|
+
};
|
|
12565
|
+
customClaims = await authHooks.customizeAccessToken(defaultClaims, user);
|
|
12566
|
+
}
|
|
12567
|
+
}
|
|
12568
|
+
const newAccessToken = generateAccessToken(storedToken.userId, roleIds, "aal1", customClaims);
|
|
11753
12569
|
const newRefreshToken = generateRefreshToken();
|
|
11754
12570
|
const userAgent = c.req.header("user-agent") || "unknown";
|
|
11755
12571
|
const ipAddress = c.req.header("x-forwarded-for") || "unknown";
|
|
@@ -11771,6 +12587,18 @@ function createAuthRoutes(config) {
|
|
|
11771
12587
|
const tokenHash = hashRefreshToken(refreshToken);
|
|
11772
12588
|
await authRepo.deleteRefreshToken(tokenHash);
|
|
11773
12589
|
}
|
|
12590
|
+
const authHeader = c.req.header("authorization");
|
|
12591
|
+
if (authHooks?.afterLogout && authHeader?.startsWith("Bearer ")) {
|
|
12592
|
+
const {
|
|
12593
|
+
verifyAccessToken: verifyAccessToken2
|
|
12594
|
+
} = await Promise.resolve().then(() => jwt);
|
|
12595
|
+
const payload = verifyAccessToken2(authHeader.substring(7));
|
|
12596
|
+
if (payload) {
|
|
12597
|
+
authHooks.afterLogout(payload.userId).catch((err) => {
|
|
12598
|
+
console.error("[AuthHooks] afterLogout error:", err instanceof Error ? err.message : err);
|
|
12599
|
+
});
|
|
12600
|
+
}
|
|
12601
|
+
}
|
|
11774
12602
|
return c.json({
|
|
11775
12603
|
success: true
|
|
11776
12604
|
});
|
|
@@ -11890,6 +12718,270 @@ function createAuthRoutes(config) {
|
|
|
11890
12718
|
enabledProviders
|
|
11891
12719
|
});
|
|
11892
12720
|
});
|
|
12721
|
+
router.post("/anonymous", strictAuthLimiter, async (c) => {
|
|
12722
|
+
const anonId = randomBytes(16).toString("hex");
|
|
12723
|
+
const anonEmail = `anon_${anonId.slice(0, 8)}@anonymous.local`;
|
|
12724
|
+
let createData = {
|
|
12725
|
+
email: anonEmail,
|
|
12726
|
+
emailVerified: false,
|
|
12727
|
+
isAnonymous: true
|
|
12728
|
+
};
|
|
12729
|
+
if (authHooks?.beforeUserCreate) {
|
|
12730
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
12731
|
+
}
|
|
12732
|
+
const user = await authRepo.createUser(createData);
|
|
12733
|
+
if (config.defaultRole) {
|
|
12734
|
+
await authRepo.assignDefaultRole(user.id, config.defaultRole);
|
|
12735
|
+
}
|
|
12736
|
+
const {
|
|
12737
|
+
roleIds,
|
|
12738
|
+
accessToken,
|
|
12739
|
+
refreshToken
|
|
12740
|
+
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
12741
|
+
if (authHooks?.afterUserCreate) {
|
|
12742
|
+
authHooks.afterUserCreate(user).catch((err) => {
|
|
12743
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
12744
|
+
});
|
|
12745
|
+
}
|
|
12746
|
+
if (authHooks?.onAuthenticated) {
|
|
12747
|
+
authHooks.onAuthenticated(user, "anonymous").catch((err) => {
|
|
12748
|
+
console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
|
|
12749
|
+
});
|
|
12750
|
+
}
|
|
12751
|
+
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
|
|
12752
|
+
});
|
|
12753
|
+
router.post("/anonymous/link", requireAuth, async (c) => {
|
|
12754
|
+
const userCtx = c.get("user");
|
|
12755
|
+
if (!userCtx) {
|
|
12756
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12757
|
+
}
|
|
12758
|
+
const user = await authRepo.getUserById(userCtx.userId);
|
|
12759
|
+
if (!user?.isAnonymous) {
|
|
12760
|
+
throw ApiError.badRequest("User is not anonymous", "NOT_ANONYMOUS");
|
|
12761
|
+
}
|
|
12762
|
+
const linkSchema = objectType({
|
|
12763
|
+
email: stringType().email("Invalid email address").max(255),
|
|
12764
|
+
password: stringType().min(1, "Password is required").max(128)
|
|
12765
|
+
});
|
|
12766
|
+
const {
|
|
12767
|
+
email,
|
|
12768
|
+
password
|
|
12769
|
+
} = parseBody2(linkSchema, await c.req.json());
|
|
12770
|
+
const passwordValidation = ops.validatePasswordStrength(password);
|
|
12771
|
+
if (!passwordValidation.valid) {
|
|
12772
|
+
throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
|
|
12773
|
+
}
|
|
12774
|
+
const existingUser = await authRepo.getUserByEmail(email.toLowerCase());
|
|
12775
|
+
if (existingUser) {
|
|
12776
|
+
throw ApiError.conflict("Email already registered", "EMAIL_EXISTS");
|
|
12777
|
+
}
|
|
12778
|
+
const passwordHash = await ops.hashPassword(password);
|
|
12779
|
+
const updatedUser = await authRepo.updateUser(user.id, {
|
|
12780
|
+
email: email.toLowerCase(),
|
|
12781
|
+
passwordHash,
|
|
12782
|
+
isAnonymous: false
|
|
12783
|
+
});
|
|
12784
|
+
if (!updatedUser) {
|
|
12785
|
+
throw ApiError.notFound("User not found");
|
|
12786
|
+
}
|
|
12787
|
+
const {
|
|
12788
|
+
roleIds,
|
|
12789
|
+
accessToken,
|
|
12790
|
+
refreshToken
|
|
12791
|
+
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
12792
|
+
return c.json(buildAuthResponse(updatedUser, roleIds, accessToken, refreshToken));
|
|
12793
|
+
});
|
|
12794
|
+
router.post("/mfa/enroll", requireAuth, async (c) => {
|
|
12795
|
+
const userCtx = c.get("user");
|
|
12796
|
+
if (!userCtx) {
|
|
12797
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12798
|
+
}
|
|
12799
|
+
const body = await c.req.json().catch(() => ({}));
|
|
12800
|
+
const friendlyName = typeof body.friendlyName === "string" ? body.friendlyName : void 0;
|
|
12801
|
+
const issuer = typeof body.issuer === "string" ? body.issuer : emailConfig?.appName || "Rebase";
|
|
12802
|
+
const user = await authRepo.getUserById(userCtx.userId);
|
|
12803
|
+
if (!user) {
|
|
12804
|
+
throw ApiError.notFound("User not found");
|
|
12805
|
+
}
|
|
12806
|
+
const {
|
|
12807
|
+
secret,
|
|
12808
|
+
uri
|
|
12809
|
+
} = generateTotpSecret(issuer, user.email);
|
|
12810
|
+
const factor = await authRepo.createMfaFactor(
|
|
12811
|
+
user.id,
|
|
12812
|
+
"totp",
|
|
12813
|
+
secret,
|
|
12814
|
+
// In production, encrypt this before storage
|
|
12815
|
+
friendlyName
|
|
12816
|
+
);
|
|
12817
|
+
const codes = generateRecoveryCodes(10);
|
|
12818
|
+
const codeHashes = codes.map(hashRecoveryCode);
|
|
12819
|
+
await authRepo.createRecoveryCodes(user.id, codeHashes);
|
|
12820
|
+
return c.json({
|
|
12821
|
+
factor: {
|
|
12822
|
+
id: factor.id,
|
|
12823
|
+
factorType: factor.factorType,
|
|
12824
|
+
friendlyName: factor.friendlyName
|
|
12825
|
+
},
|
|
12826
|
+
totp: {
|
|
12827
|
+
secret,
|
|
12828
|
+
uri,
|
|
12829
|
+
qrUri: uri
|
|
12830
|
+
// Client can use a QR library to render this
|
|
12831
|
+
},
|
|
12832
|
+
recoveryCodes: codes
|
|
12833
|
+
}, 201);
|
|
12834
|
+
});
|
|
12835
|
+
router.post("/mfa/verify", requireAuth, async (c) => {
|
|
12836
|
+
const userCtx = c.get("user");
|
|
12837
|
+
if (!userCtx) {
|
|
12838
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12839
|
+
}
|
|
12840
|
+
const verifySchema = objectType({
|
|
12841
|
+
factorId: stringType().min(1, "Factor ID is required"),
|
|
12842
|
+
code: stringType().length(6, "Code must be 6 digits")
|
|
12843
|
+
});
|
|
12844
|
+
const {
|
|
12845
|
+
factorId,
|
|
12846
|
+
code: code2
|
|
12847
|
+
} = parseBody2(verifySchema, await c.req.json());
|
|
12848
|
+
const factor = await authRepo.getMfaFactorById(factorId);
|
|
12849
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
12850
|
+
throw ApiError.notFound("MFA factor not found");
|
|
12851
|
+
}
|
|
12852
|
+
if (factor.verified) {
|
|
12853
|
+
throw ApiError.badRequest("Factor is already verified", "ALREADY_VERIFIED");
|
|
12854
|
+
}
|
|
12855
|
+
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
12856
|
+
const isValid2 = verifyTotp(secretBuffer, code2);
|
|
12857
|
+
if (!isValid2) {
|
|
12858
|
+
throw ApiError.unauthorized("Invalid TOTP code", "INVALID_CODE");
|
|
12859
|
+
}
|
|
12860
|
+
await authRepo.verifyMfaFactor(factorId);
|
|
12861
|
+
return c.json({
|
|
12862
|
+
success: true,
|
|
12863
|
+
message: "MFA factor verified and enrolled"
|
|
12864
|
+
});
|
|
12865
|
+
});
|
|
12866
|
+
router.post("/mfa/challenge", requireAuth, async (c) => {
|
|
12867
|
+
const userCtx = c.get("user");
|
|
12868
|
+
if (!userCtx) {
|
|
12869
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12870
|
+
}
|
|
12871
|
+
const challengeSchema = objectType({
|
|
12872
|
+
factorId: stringType().min(1, "Factor ID is required")
|
|
12873
|
+
});
|
|
12874
|
+
const {
|
|
12875
|
+
factorId
|
|
12876
|
+
} = parseBody2(challengeSchema, await c.req.json());
|
|
12877
|
+
const factor = await authRepo.getMfaFactorById(factorId);
|
|
12878
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
12879
|
+
throw ApiError.notFound("MFA factor not found");
|
|
12880
|
+
}
|
|
12881
|
+
if (!factor.verified) {
|
|
12882
|
+
throw ApiError.badRequest("MFA factor is not yet verified", "FACTOR_NOT_VERIFIED");
|
|
12883
|
+
}
|
|
12884
|
+
const ipAddress = c.req.header("x-forwarded-for") || "unknown";
|
|
12885
|
+
const challenge = await authRepo.createMfaChallenge(factorId, ipAddress);
|
|
12886
|
+
return c.json({
|
|
12887
|
+
challengeId: challenge.id,
|
|
12888
|
+
factorId: challenge.factorId,
|
|
12889
|
+
expiresAt: new Date(Date.now() + 5 * 60 * 1e3).toISOString()
|
|
12890
|
+
});
|
|
12891
|
+
});
|
|
12892
|
+
router.post("/mfa/challenge/verify", requireAuth, async (c) => {
|
|
12893
|
+
const userCtx = c.get("user");
|
|
12894
|
+
if (!userCtx) {
|
|
12895
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12896
|
+
}
|
|
12897
|
+
const challengeVerifySchema = objectType({
|
|
12898
|
+
challengeId: stringType().min(1, "Challenge ID is required"),
|
|
12899
|
+
code: stringType().min(1, "Code is required")
|
|
12900
|
+
});
|
|
12901
|
+
const {
|
|
12902
|
+
challengeId,
|
|
12903
|
+
code: code2
|
|
12904
|
+
} = parseBody2(challengeVerifySchema, await c.req.json());
|
|
12905
|
+
const challenge = await authRepo.getMfaChallengeById(challengeId);
|
|
12906
|
+
if (!challenge) {
|
|
12907
|
+
throw ApiError.badRequest("Invalid or expired challenge", "INVALID_CHALLENGE");
|
|
12908
|
+
}
|
|
12909
|
+
const factor = await authRepo.getMfaFactorById(challenge.factorId);
|
|
12910
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
12911
|
+
throw ApiError.notFound("MFA factor not found");
|
|
12912
|
+
}
|
|
12913
|
+
const isRecoveryCode = code2.length > 6;
|
|
12914
|
+
let isValid2 = false;
|
|
12915
|
+
if (isRecoveryCode) {
|
|
12916
|
+
const codeHash = hashRecoveryCode(code2);
|
|
12917
|
+
isValid2 = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
|
|
12918
|
+
} else {
|
|
12919
|
+
const secretBuffer = base32Decode(factor.secretEncrypted);
|
|
12920
|
+
isValid2 = verifyTotp(secretBuffer, code2);
|
|
12921
|
+
}
|
|
12922
|
+
if (!isValid2) {
|
|
12923
|
+
throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
|
|
12924
|
+
}
|
|
12925
|
+
await authRepo.verifyMfaChallenge(challengeId);
|
|
12926
|
+
const roles = await authRepo.getUserRoles(userCtx.userId);
|
|
12927
|
+
const roleIds = roles.map((r) => r.id);
|
|
12928
|
+
const accessToken = generateAccessToken(userCtx.userId, roleIds, "aal2");
|
|
12929
|
+
const refreshToken = generateRefreshToken();
|
|
12930
|
+
await authRepo.createRefreshToken(userCtx.userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
12931
|
+
if (authHooks?.onMfaVerified) {
|
|
12932
|
+
authHooks.onMfaVerified(userCtx.userId, factor.id).catch((err) => {
|
|
12933
|
+
console.error("[AuthHooks] onMfaVerified error:", err instanceof Error ? err.message : err);
|
|
12934
|
+
});
|
|
12935
|
+
}
|
|
12936
|
+
return c.json({
|
|
12937
|
+
tokens: {
|
|
12938
|
+
accessToken,
|
|
12939
|
+
refreshToken,
|
|
12940
|
+
accessTokenExpiresAt: getAccessTokenExpiry()
|
|
12941
|
+
}
|
|
12942
|
+
});
|
|
12943
|
+
});
|
|
12944
|
+
router.get("/mfa/factors", requireAuth, async (c) => {
|
|
12945
|
+
const userCtx = c.get("user");
|
|
12946
|
+
if (!userCtx) {
|
|
12947
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12948
|
+
}
|
|
12949
|
+
const factors = await authRepo.getMfaFactors(userCtx.userId);
|
|
12950
|
+
return c.json({
|
|
12951
|
+
factors: factors.map((f2) => ({
|
|
12952
|
+
id: f2.id,
|
|
12953
|
+
factorType: f2.factorType,
|
|
12954
|
+
friendlyName: f2.friendlyName,
|
|
12955
|
+
verified: f2.verified,
|
|
12956
|
+
createdAt: f2.createdAt
|
|
12957
|
+
}))
|
|
12958
|
+
});
|
|
12959
|
+
});
|
|
12960
|
+
router.delete("/mfa/unenroll", requireAuth, async (c) => {
|
|
12961
|
+
const userCtx = c.get("user");
|
|
12962
|
+
if (!userCtx) {
|
|
12963
|
+
throw ApiError.unauthorized("Not authenticated");
|
|
12964
|
+
}
|
|
12965
|
+
const unenrollSchema = objectType({
|
|
12966
|
+
factorId: stringType().min(1, "Factor ID is required")
|
|
12967
|
+
});
|
|
12968
|
+
const {
|
|
12969
|
+
factorId
|
|
12970
|
+
} = parseBody2(unenrollSchema, await c.req.json());
|
|
12971
|
+
const factor = await authRepo.getMfaFactorById(factorId);
|
|
12972
|
+
if (!factor || factor.userId !== userCtx.userId) {
|
|
12973
|
+
throw ApiError.notFound("MFA factor not found");
|
|
12974
|
+
}
|
|
12975
|
+
await authRepo.deleteMfaFactor(factorId, userCtx.userId);
|
|
12976
|
+
const hasFactors = await authRepo.hasVerifiedMfaFactors(userCtx.userId);
|
|
12977
|
+
if (!hasFactors) {
|
|
12978
|
+
await authRepo.deleteAllRecoveryCodes(userCtx.userId);
|
|
12979
|
+
}
|
|
12980
|
+
return c.json({
|
|
12981
|
+
success: true,
|
|
12982
|
+
message: "MFA factor removed"
|
|
12983
|
+
});
|
|
12984
|
+
});
|
|
11893
12985
|
return router;
|
|
11894
12986
|
}
|
|
11895
12987
|
function generateSecurePassword() {
|
|
@@ -11921,9 +13013,9 @@ function createAdminRoutes(config) {
|
|
|
11921
13013
|
emailService,
|
|
11922
13014
|
emailConfig,
|
|
11923
13015
|
hooks,
|
|
11924
|
-
|
|
13016
|
+
authHooks
|
|
11925
13017
|
} = config;
|
|
11926
|
-
const ops =
|
|
13018
|
+
const ops = resolveAuthHooks(authHooks);
|
|
11927
13019
|
function buildHookContext(c, method) {
|
|
11928
13020
|
const user = c.get("user");
|
|
11929
13021
|
return {
|
|
@@ -11991,6 +13083,10 @@ function createAdminRoutes(config) {
|
|
|
11991
13083
|
if (!userId) {
|
|
11992
13084
|
throw ApiError.unauthorized("User ID not found in auth context");
|
|
11993
13085
|
}
|
|
13086
|
+
const caller = await authRepo.getUserById(userId);
|
|
13087
|
+
if (!caller) {
|
|
13088
|
+
throw ApiError.notFound("Authenticated user does not exist in the database. Please sign out and sign in/register again.", "USER_NOT_FOUND");
|
|
13089
|
+
}
|
|
11994
13090
|
await authRepo.setUserRoles(userId, ["admin"]);
|
|
11995
13091
|
if (config.setBootstrapCompleted) {
|
|
11996
13092
|
await config.setBootstrapCompleted();
|
|
@@ -12242,6 +13338,18 @@ function createAdminRoutes(config) {
|
|
|
12242
13338
|
await authRepo.updateUser(userId, updates);
|
|
12243
13339
|
}
|
|
12244
13340
|
if (roles !== void 0 && Array.isArray(roles)) {
|
|
13341
|
+
const currentRoles = await authRepo.getUserRoleIds(userId);
|
|
13342
|
+
const wasAdmin = currentRoles.includes("admin");
|
|
13343
|
+
const willBeAdmin = roles.includes("admin");
|
|
13344
|
+
if (wasAdmin && !willBeAdmin) {
|
|
13345
|
+
const adminUsers = await authRepo.listUsersPaginated({
|
|
13346
|
+
roleId: "admin",
|
|
13347
|
+
limit: 1
|
|
13348
|
+
});
|
|
13349
|
+
if (adminUsers.total <= 1) {
|
|
13350
|
+
throw ApiError.forbidden("Cannot demote the last administrator", "LAST_ADMIN");
|
|
13351
|
+
}
|
|
13352
|
+
}
|
|
12245
13353
|
await authRepo.setUserRoles(userId, roles);
|
|
12246
13354
|
}
|
|
12247
13355
|
const result = await authRepo.getUserWithRoles(userId);
|
|
@@ -12266,6 +13374,16 @@ function createAdminRoutes(config) {
|
|
|
12266
13374
|
if (!existing) {
|
|
12267
13375
|
throw ApiError.notFound("User not found");
|
|
12268
13376
|
}
|
|
13377
|
+
const roles = await authRepo.getUserRoleIds(userId);
|
|
13378
|
+
if (roles.includes("admin")) {
|
|
13379
|
+
const adminUsers = await authRepo.listUsersPaginated({
|
|
13380
|
+
roleId: "admin",
|
|
13381
|
+
limit: 1
|
|
13382
|
+
});
|
|
13383
|
+
if (adminUsers.total <= 1) {
|
|
13384
|
+
throw ApiError.forbidden("Cannot delete the last administrator", "LAST_ADMIN");
|
|
13385
|
+
}
|
|
13386
|
+
}
|
|
12269
13387
|
const hookCtx = buildHookContext(c, "DELETE");
|
|
12270
13388
|
if (hooks?.users?.beforeDelete) {
|
|
12271
13389
|
await hooks.users.beforeDelete(userId, hookCtx);
|
|
@@ -12287,8 +13405,7 @@ function createAdminRoutes(config) {
|
|
|
12287
13405
|
id: r.id,
|
|
12288
13406
|
name: r.name,
|
|
12289
13407
|
isAdmin: r.isAdmin,
|
|
12290
|
-
defaultPermissions: r.defaultPermissions
|
|
12291
|
-
config: r.config
|
|
13408
|
+
defaultPermissions: r.defaultPermissions
|
|
12292
13409
|
}));
|
|
12293
13410
|
adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
|
|
12294
13411
|
return c.json({
|
|
@@ -12311,8 +13428,7 @@ function createAdminRoutes(config) {
|
|
|
12311
13428
|
id,
|
|
12312
13429
|
name: name2,
|
|
12313
13430
|
isAdmin,
|
|
12314
|
-
defaultPermissions
|
|
12315
|
-
config: config2
|
|
13431
|
+
defaultPermissions
|
|
12316
13432
|
} = body;
|
|
12317
13433
|
if (!id || !name2) {
|
|
12318
13434
|
throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
|
|
@@ -12325,8 +13441,7 @@ function createAdminRoutes(config) {
|
|
|
12325
13441
|
id,
|
|
12326
13442
|
name: name2,
|
|
12327
13443
|
isAdmin: isAdmin ?? false,
|
|
12328
|
-
defaultPermissions: defaultPermissions ?? null
|
|
12329
|
-
config: config2 ?? null
|
|
13444
|
+
defaultPermissions: defaultPermissions ?? null
|
|
12330
13445
|
});
|
|
12331
13446
|
return c.json({
|
|
12332
13447
|
role
|
|
@@ -12338,8 +13453,7 @@ function createAdminRoutes(config) {
|
|
|
12338
13453
|
const {
|
|
12339
13454
|
name: name2,
|
|
12340
13455
|
isAdmin,
|
|
12341
|
-
defaultPermissions
|
|
12342
|
-
config: config2
|
|
13456
|
+
defaultPermissions
|
|
12343
13457
|
} = body;
|
|
12344
13458
|
const existing = await authRepo.getRoleById(roleId);
|
|
12345
13459
|
if (!existing) {
|
|
@@ -12348,8 +13462,7 @@ function createAdminRoutes(config) {
|
|
|
12348
13462
|
const role = await authRepo.updateRole(roleId, {
|
|
12349
13463
|
name: name2,
|
|
12350
13464
|
isAdmin,
|
|
12351
|
-
defaultPermissions
|
|
12352
|
-
config: config2
|
|
13465
|
+
defaultPermissions
|
|
12353
13466
|
});
|
|
12354
13467
|
return c.json({
|
|
12355
13468
|
role
|
|
@@ -12381,9 +13494,9 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12381
13494
|
oauthProviders = [],
|
|
12382
13495
|
serviceKey,
|
|
12383
13496
|
hooks,
|
|
12384
|
-
|
|
13497
|
+
authHooks
|
|
12385
13498
|
} = config;
|
|
12386
|
-
const resolvedOps =
|
|
13499
|
+
const resolvedOps = resolveAuthHooks(authHooks);
|
|
12387
13500
|
const adapter = {
|
|
12388
13501
|
id: "rebase-builtin",
|
|
12389
13502
|
serviceKey,
|
|
@@ -12457,7 +13570,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12457
13570
|
rawToken: token
|
|
12458
13571
|
};
|
|
12459
13572
|
},
|
|
12460
|
-
userManagement: createUserManagementFromRepo(authRepository, resolvedOps,
|
|
13573
|
+
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
|
|
12461
13574
|
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
12462
13575
|
createAuthRoutes() {
|
|
12463
13576
|
return createAuthRoutes({
|
|
@@ -12467,7 +13580,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12467
13580
|
allowRegistration,
|
|
12468
13581
|
defaultRole,
|
|
12469
13582
|
oauthProviders,
|
|
12470
|
-
|
|
13583
|
+
authHooks
|
|
12471
13584
|
});
|
|
12472
13585
|
},
|
|
12473
13586
|
createAdminRoutes() {
|
|
@@ -12477,7 +13590,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12477
13590
|
emailConfig,
|
|
12478
13591
|
serviceKey,
|
|
12479
13592
|
hooks,
|
|
12480
|
-
|
|
13593
|
+
authHooks
|
|
12481
13594
|
});
|
|
12482
13595
|
},
|
|
12483
13596
|
async getCapabilities() {
|
|
@@ -12506,7 +13619,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12506
13619
|
};
|
|
12507
13620
|
return adapter;
|
|
12508
13621
|
}
|
|
12509
|
-
function createUserManagementFromRepo(repo, resolvedOps,
|
|
13622
|
+
function createUserManagementFromRepo(repo, resolvedOps, authHooks) {
|
|
12510
13623
|
return {
|
|
12511
13624
|
async listUsers(options2) {
|
|
12512
13625
|
const result = await repo.listUsersPaginated({
|
|
@@ -12537,14 +13650,16 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
|
|
|
12537
13650
|
photoUrl: data.photoUrl,
|
|
12538
13651
|
metadata: data.metadata
|
|
12539
13652
|
};
|
|
12540
|
-
if (
|
|
12541
|
-
createData = await
|
|
13653
|
+
if (authHooks?.beforeUserCreate) {
|
|
13654
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
12542
13655
|
}
|
|
12543
13656
|
const user = await repo.createUser(createData);
|
|
12544
|
-
if (
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
})
|
|
13657
|
+
if (authHooks?.afterUserCreate) {
|
|
13658
|
+
try {
|
|
13659
|
+
await authHooks.afterUserCreate(user);
|
|
13660
|
+
} catch (err) {
|
|
13661
|
+
console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
|
|
13662
|
+
}
|
|
12548
13663
|
}
|
|
12549
13664
|
return toAuthUserData(user);
|
|
12550
13665
|
},
|
|
@@ -12561,7 +13676,15 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
|
|
|
12561
13676
|
return user ? toAuthUserData(user) : null;
|
|
12562
13677
|
},
|
|
12563
13678
|
async deleteUser(id) {
|
|
13679
|
+
if (authHooks?.beforeUserDelete) {
|
|
13680
|
+
await authHooks.beforeUserDelete(id);
|
|
13681
|
+
}
|
|
12564
13682
|
await repo.deleteUser(id);
|
|
13683
|
+
if (authHooks?.afterUserDelete) {
|
|
13684
|
+
authHooks.afterUserDelete(id).catch((err) => {
|
|
13685
|
+
console.error("[AuthHooks] afterUserDelete error:", err instanceof Error ? err.message : err);
|
|
13686
|
+
});
|
|
13687
|
+
}
|
|
12565
13688
|
},
|
|
12566
13689
|
async getUserRoles(userId) {
|
|
12567
13690
|
const roles = await repo.getUserRoles(userId);
|
|
@@ -12588,8 +13711,7 @@ function createRoleManagementFromRepo(repo) {
|
|
|
12588
13711
|
name: data.name,
|
|
12589
13712
|
isAdmin: data.isAdmin,
|
|
12590
13713
|
defaultPermissions: data.defaultPermissions,
|
|
12591
|
-
collectionPermissions: data.collectionPermissions
|
|
12592
|
-
config: data.config
|
|
13714
|
+
collectionPermissions: data.collectionPermissions
|
|
12593
13715
|
});
|
|
12594
13716
|
return toAuthRoleData(role);
|
|
12595
13717
|
},
|
|
@@ -12620,8 +13742,7 @@ function toAuthRoleData(role) {
|
|
|
12620
13742
|
name: role.name,
|
|
12621
13743
|
isAdmin: role.isAdmin,
|
|
12622
13744
|
defaultPermissions: role.defaultPermissions,
|
|
12623
|
-
collectionPermissions: role.collectionPermissions
|
|
12624
|
-
config: role.config
|
|
13745
|
+
collectionPermissions: role.collectionPermissions
|
|
12625
13746
|
};
|
|
12626
13747
|
}
|
|
12627
13748
|
function configureLogLevel(logLevel) {
|
|
@@ -12642,20 +13763,20 @@ function configureLogLevel(logLevel) {
|
|
|
12642
13763
|
if (currentLevel < 0) console.error = () => {
|
|
12643
13764
|
};
|
|
12644
13765
|
}
|
|
13766
|
+
let originalConsole;
|
|
12645
13767
|
function resetConsole() {
|
|
12646
|
-
if (!
|
|
12647
|
-
|
|
13768
|
+
if (!originalConsole) {
|
|
13769
|
+
originalConsole = {
|
|
12648
13770
|
log: console.log,
|
|
12649
13771
|
warn: console.warn,
|
|
12650
13772
|
error: console.error,
|
|
12651
13773
|
debug: console.debug
|
|
12652
13774
|
};
|
|
12653
13775
|
}
|
|
12654
|
-
|
|
12655
|
-
console.
|
|
12656
|
-
console.
|
|
12657
|
-
console.
|
|
12658
|
-
console.debug = original.debug;
|
|
13776
|
+
console.log = originalConsole.log;
|
|
13777
|
+
console.warn = originalConsole.warn;
|
|
13778
|
+
console.error = originalConsole.error;
|
|
13779
|
+
console.debug = originalConsole.debug;
|
|
12659
13780
|
}
|
|
12660
13781
|
const GCP_SEVERITY = {
|
|
12661
13782
|
debug: "DEBUG",
|
|
@@ -12897,12 +14018,6 @@ var browser$1 = { exports: {} };
|
|
|
12897
14018
|
exports.Response = globalObject.Response;
|
|
12898
14019
|
})(browser$1, browser$1.exports);
|
|
12899
14020
|
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
14021
|
const isStream = (stream2) => stream2 !== null && typeof stream2 === "object" && typeof stream2.pipe === "function";
|
|
12907
14022
|
isStream.writable = (stream2) => isStream(stream2) && stream2.writable !== false && typeof stream2._write === "function" && typeof stream2._writableState === "object";
|
|
12908
14023
|
isStream.readable = (stream2) => isStream(stream2) && stream2.readable !== false && typeof stream2._read === "function" && typeof stream2._readableState === "object";
|
|
@@ -13687,16 +14802,16 @@ Object.defineProperty(sha1$1, "__esModule", {
|
|
|
13687
14802
|
value: true
|
|
13688
14803
|
});
|
|
13689
14804
|
sha1$1.default = void 0;
|
|
13690
|
-
function f(s2, x, y2,
|
|
14805
|
+
function f(s2, x, y2, z2) {
|
|
13691
14806
|
switch (s2) {
|
|
13692
14807
|
case 0:
|
|
13693
|
-
return x & y2 ^ ~x &
|
|
14808
|
+
return x & y2 ^ ~x & z2;
|
|
13694
14809
|
case 1:
|
|
13695
|
-
return x ^ y2 ^
|
|
14810
|
+
return x ^ y2 ^ z2;
|
|
13696
14811
|
case 2:
|
|
13697
|
-
return x & y2 ^ x &
|
|
14812
|
+
return x & y2 ^ x & z2 ^ y2 & z2;
|
|
13698
14813
|
case 3:
|
|
13699
|
-
return x ^ y2 ^
|
|
14814
|
+
return x ^ y2 ^ z2;
|
|
13700
14815
|
}
|
|
13701
14816
|
}
|
|
13702
14817
|
function ROTL(x, n) {
|
|
@@ -14745,7 +15860,7 @@ gaxios.Gaxios = void 0;
|
|
|
14745
15860
|
const extend_1 = __importDefault(extend);
|
|
14746
15861
|
const https_1 = require$$1;
|
|
14747
15862
|
const node_fetch_1 = __importDefault(browserExports);
|
|
14748
|
-
const querystring_1$1 = __importDefault(require$$
|
|
15863
|
+
const querystring_1$1 = __importDefault(require$$1$2);
|
|
14749
15864
|
const is_stream_1 = __importDefault(isStream_1);
|
|
14750
15865
|
const url_1 = require$$0$5;
|
|
14751
15866
|
const common_1 = common$1;
|
|
@@ -16423,11 +17538,11 @@ var bignumber = { exports: {} };
|
|
|
16423
17538
|
return n > 0 || n === i ? i : i - 1;
|
|
16424
17539
|
}
|
|
16425
17540
|
function coeffToString(a2) {
|
|
16426
|
-
var s2,
|
|
17541
|
+
var s2, z2, i = 1, j = a2.length, r = a2[0] + "";
|
|
16427
17542
|
for (; i < j; ) {
|
|
16428
17543
|
s2 = a2[i++] + "";
|
|
16429
|
-
|
|
16430
|
-
for (;
|
|
17544
|
+
z2 = LOG_BASE - s2.length;
|
|
17545
|
+
for (; z2--; s2 = "0" + s2) ;
|
|
16431
17546
|
r += s2;
|
|
16432
17547
|
}
|
|
16433
17548
|
for (j = r.length; r.charCodeAt(--j) === 48; ) ;
|
|
@@ -16460,15 +17575,15 @@ var bignumber = { exports: {} };
|
|
|
16460
17575
|
function toExponential(str, e) {
|
|
16461
17576
|
return (str.length > 1 ? str.charAt(0) + "." + str.slice(1) : str) + (e < 0 ? "e" : "e+") + e;
|
|
16462
17577
|
}
|
|
16463
|
-
function toFixedPoint(str, e,
|
|
17578
|
+
function toFixedPoint(str, e, z2) {
|
|
16464
17579
|
var len, zs;
|
|
16465
17580
|
if (e < 0) {
|
|
16466
|
-
for (zs =
|
|
17581
|
+
for (zs = z2 + "."; ++e; zs += z2) ;
|
|
16467
17582
|
str = zs + str;
|
|
16468
17583
|
} else {
|
|
16469
17584
|
len = str.length;
|
|
16470
17585
|
if (++e > len) {
|
|
16471
|
-
for (zs =
|
|
17586
|
+
for (zs = z2, e -= len; --e; zs += z2) ;
|
|
16472
17587
|
str += zs;
|
|
16473
17588
|
} else if (e < len) {
|
|
16474
17589
|
str = str.slice(0, e) + "." + str.slice(e);
|
|
@@ -16887,7 +18002,7 @@ var gcpResidency = {};
|
|
|
16887
18002
|
exports.isGoogleComputeEngine = isGoogleComputeEngine;
|
|
16888
18003
|
exports.detectGCPResidency = detectGCPResidency;
|
|
16889
18004
|
const fs_1 = fs__default;
|
|
16890
|
-
const os_1 = require$$1$
|
|
18005
|
+
const os_1 = require$$1$3;
|
|
16891
18006
|
exports.GCE_LINUX_BIOS_PATHS = {
|
|
16892
18007
|
BIOS_DATE: "/sys/class/dmi/id/bios_date",
|
|
16893
18008
|
BIOS_VENDOR: "/sys/class/dmi/id/bios_vendor"
|
|
@@ -17020,7 +18135,7 @@ Colours.refresh();
|
|
|
17020
18135
|
exports.setBackend = setBackend;
|
|
17021
18136
|
exports.log = log;
|
|
17022
18137
|
const node_events_1 = require$$0$8;
|
|
17023
|
-
const process2 = __importStar2(require$$1$
|
|
18138
|
+
const process2 = __importStar2(require$$1$4);
|
|
17024
18139
|
const util2 = __importStar2(require$$2$1);
|
|
17025
18140
|
const colours_1 = colours;
|
|
17026
18141
|
var LogSeverity;
|
|
@@ -18092,7 +19207,7 @@ loginticket.LoginTicket = LoginTicket;
|
|
|
18092
19207
|
Object.defineProperty(oauth2client, "__esModule", { value: true });
|
|
18093
19208
|
oauth2client.OAuth2Client = oauth2client.ClientAuthentication = oauth2client.CertificateFormat = oauth2client.CodeChallengeMethod = void 0;
|
|
18094
19209
|
const gaxios_1$5 = src$2;
|
|
18095
|
-
const querystring$2 = require$$
|
|
19210
|
+
const querystring$2 = require$$1$2;
|
|
18096
19211
|
const stream$4 = require$$0$4;
|
|
18097
19212
|
const formatEcdsa = ecdsaSigFormatter;
|
|
18098
19213
|
const crypto_1$2 = requireCrypto();
|
|
@@ -19580,7 +20695,7 @@ var refreshclient = {};
|
|
|
19580
20695
|
Object.defineProperty(refreshclient, "__esModule", { value: true });
|
|
19581
20696
|
refreshclient.UserRefreshClient = refreshclient.USER_REFRESH_ACCOUNT_TYPE = void 0;
|
|
19582
20697
|
const oauth2client_1$1 = oauth2client;
|
|
19583
|
-
const querystring_1 = require$$
|
|
20698
|
+
const querystring_1 = require$$1$2;
|
|
19584
20699
|
refreshclient.USER_REFRESH_ACCOUNT_TYPE = "authorized_user";
|
|
19585
20700
|
class UserRefreshClient extends oauth2client_1$1.OAuth2Client {
|
|
19586
20701
|
constructor(optionsOrClientId, clientSecret, refreshToken, eagerRefreshThresholdMillis, forceRefreshOnFailure) {
|
|
@@ -19855,7 +20970,7 @@ var oauth2common = {};
|
|
|
19855
20970
|
Object.defineProperty(oauth2common, "__esModule", { value: true });
|
|
19856
20971
|
oauth2common.OAuthClientAuthHandler = void 0;
|
|
19857
20972
|
oauth2common.getErrorFromOAuthErrorResponse = getErrorFromOAuthErrorResponse;
|
|
19858
|
-
const querystring$1 = require$$
|
|
20973
|
+
const querystring$1 = require$$1$2;
|
|
19859
20974
|
const crypto_1$1 = requireCrypto();
|
|
19860
20975
|
const METHODS_SUPPORTING_REQUEST_BODY = ["PUT", "POST", "PATCH"];
|
|
19861
20976
|
class OAuthClientAuthHandler {
|
|
@@ -20001,7 +21116,7 @@ function getErrorFromOAuthErrorResponse(resp, err) {
|
|
|
20001
21116
|
Object.defineProperty(stscredentials, "__esModule", { value: true });
|
|
20002
21117
|
stscredentials.StsCredentials = void 0;
|
|
20003
21118
|
const gaxios_1$1 = src$2;
|
|
20004
|
-
const querystring = require$$
|
|
21119
|
+
const querystring = require$$1$2;
|
|
20005
21120
|
const transporters_1 = transporters;
|
|
20006
21121
|
const oauth2common_1$1 = oauth2common;
|
|
20007
21122
|
class StsCredentials extends oauth2common_1$1.OAuthClientAuthHandler {
|
|
@@ -21607,7 +22722,7 @@ externalAccountAuthorizedUserClient.ExternalAccountAuthorizedUserClient = Extern
|
|
|
21607
22722
|
const child_process_1 = require$$0$a;
|
|
21608
22723
|
const fs2 = fs__default;
|
|
21609
22724
|
const gcpMetadata2 = src$3;
|
|
21610
|
-
const os2 = require$$1$
|
|
22725
|
+
const os2 = require$$1$3;
|
|
21611
22726
|
const path2 = path__default;
|
|
21612
22727
|
const crypto_12 = requireCrypto();
|
|
21613
22728
|
const transporters_12 = transporters;
|
|
@@ -22971,7 +24086,7 @@ function createMicrosoftProvider(config) {
|
|
|
22971
24086
|
}
|
|
22972
24087
|
function createAppleProvider(config) {
|
|
22973
24088
|
async function generateClientSecret() {
|
|
22974
|
-
return jwt.sign({}, config.privateKey, {
|
|
24089
|
+
return jwt$1.sign({}, config.privateKey, {
|
|
22975
24090
|
algorithm: "ES256",
|
|
22976
24091
|
keyid: config.keyId,
|
|
22977
24092
|
issuer: config.teamId,
|
|
@@ -23451,6 +24566,341 @@ function createSpotifyProvider(config) {
|
|
|
23451
24566
|
}
|
|
23452
24567
|
};
|
|
23453
24568
|
}
|
|
24569
|
+
const TABLE$1 = "rebase.api_keys";
|
|
24570
|
+
function generateApiKey() {
|
|
24571
|
+
const random = randomBytes(16).toString("hex");
|
|
24572
|
+
return `rk_live_${random}`;
|
|
24573
|
+
}
|
|
24574
|
+
function hashKey(plaintext) {
|
|
24575
|
+
return createHash("sha256").update(plaintext).digest("hex");
|
|
24576
|
+
}
|
|
24577
|
+
function keyPrefix(plaintext) {
|
|
24578
|
+
return plaintext.substring(0, 12);
|
|
24579
|
+
}
|
|
24580
|
+
function toMasked(row) {
|
|
24581
|
+
return {
|
|
24582
|
+
id: row.id,
|
|
24583
|
+
name: row.name,
|
|
24584
|
+
key_prefix: row.key_prefix,
|
|
24585
|
+
permissions: row.permissions,
|
|
24586
|
+
rate_limit: row.rate_limit,
|
|
24587
|
+
created_by: row.created_by,
|
|
24588
|
+
created_at: row.created_at,
|
|
24589
|
+
updated_at: row.updated_at,
|
|
24590
|
+
last_used_at: row.last_used_at,
|
|
24591
|
+
expires_at: row.expires_at,
|
|
24592
|
+
revoked_at: row.revoked_at
|
|
24593
|
+
};
|
|
24594
|
+
}
|
|
24595
|
+
function rowToApiKey(row) {
|
|
24596
|
+
const permissions = typeof row.permissions === "string" ? JSON.parse(row.permissions) : row.permissions ?? [];
|
|
24597
|
+
return {
|
|
24598
|
+
id: row.id,
|
|
24599
|
+
name: row.name,
|
|
24600
|
+
key_prefix: row.key_prefix,
|
|
24601
|
+
key_hash: row.key_hash,
|
|
24602
|
+
permissions,
|
|
24603
|
+
rate_limit: row.rate_limit !== null && row.rate_limit !== void 0 ? Number(row.rate_limit) : null,
|
|
24604
|
+
created_by: row.created_by,
|
|
24605
|
+
created_at: new Date(row.created_at).toISOString(),
|
|
24606
|
+
updated_at: new Date(row.updated_at).toISOString(),
|
|
24607
|
+
last_used_at: row.last_used_at ? new Date(row.last_used_at).toISOString() : null,
|
|
24608
|
+
expires_at: row.expires_at ? new Date(row.expires_at).toISOString() : null,
|
|
24609
|
+
revoked_at: row.revoked_at ? new Date(row.revoked_at).toISOString() : null
|
|
24610
|
+
};
|
|
24611
|
+
}
|
|
24612
|
+
function esc(value) {
|
|
24613
|
+
return value.replace(/'/g, "''");
|
|
24614
|
+
}
|
|
24615
|
+
function createApiKeyStore(driver) {
|
|
24616
|
+
const admin = driver.admin;
|
|
24617
|
+
if (!isSQLAdmin(admin)) {
|
|
24618
|
+
logger.warn("⚠️ [api-key-store] DataDriver does not support SQL admin — API keys will not be available.");
|
|
24619
|
+
return void 0;
|
|
24620
|
+
}
|
|
24621
|
+
const exec = admin.executeSql.bind(admin);
|
|
24622
|
+
return {
|
|
24623
|
+
// ── Schema bootstrap ────────────────────────────────────────
|
|
24624
|
+
async ensureTable() {
|
|
24625
|
+
try {
|
|
24626
|
+
await exec("CREATE SCHEMA IF NOT EXISTS rebase");
|
|
24627
|
+
await exec(`
|
|
24628
|
+
CREATE TABLE IF NOT EXISTS ${TABLE$1} (
|
|
24629
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
24630
|
+
name TEXT NOT NULL,
|
|
24631
|
+
key_prefix TEXT NOT NULL,
|
|
24632
|
+
key_hash TEXT NOT NULL UNIQUE,
|
|
24633
|
+
permissions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
24634
|
+
rate_limit INTEGER,
|
|
24635
|
+
created_by TEXT NOT NULL,
|
|
24636
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
24637
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
24638
|
+
last_used_at TIMESTAMPTZ,
|
|
24639
|
+
expires_at TIMESTAMPTZ,
|
|
24640
|
+
revoked_at TIMESTAMPTZ
|
|
24641
|
+
)
|
|
24642
|
+
`);
|
|
24643
|
+
await exec(`
|
|
24644
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_hash
|
|
24645
|
+
ON ${TABLE$1}(key_hash)
|
|
24646
|
+
`);
|
|
24647
|
+
await exec(`
|
|
24648
|
+
CREATE INDEX IF NOT EXISTS idx_api_keys_prefix
|
|
24649
|
+
ON ${TABLE$1}(key_prefix)
|
|
24650
|
+
`);
|
|
24651
|
+
logger.info("✅ API keys table ready");
|
|
24652
|
+
} catch (err) {
|
|
24653
|
+
logger.error("❌ Failed to create API keys table", {
|
|
24654
|
+
error: err
|
|
24655
|
+
});
|
|
24656
|
+
logger.warn("⚠️ Continuing without API keys support.");
|
|
24657
|
+
}
|
|
24658
|
+
},
|
|
24659
|
+
// ── Create ──────────────────────────────────────────────────
|
|
24660
|
+
async createApiKey(request, createdBy) {
|
|
24661
|
+
const plaintext = generateApiKey();
|
|
24662
|
+
const hash = hashKey(plaintext);
|
|
24663
|
+
const prefix = keyPrefix(plaintext);
|
|
24664
|
+
const permissionsJson = JSON.stringify(request.permissions);
|
|
24665
|
+
const rateLimitSql = request.rate_limit !== null && request.rate_limit !== void 0 ? String(request.rate_limit) : "NULL";
|
|
24666
|
+
const expiresAtSql = request.expires_at ? `'${esc(request.expires_at)}'` : "NULL";
|
|
24667
|
+
const rows = await exec(`
|
|
24668
|
+
INSERT INTO ${TABLE$1} (name, key_prefix, key_hash, permissions, rate_limit, created_by, expires_at)
|
|
24669
|
+
VALUES (
|
|
24670
|
+
'${esc(request.name)}',
|
|
24671
|
+
'${esc(prefix)}',
|
|
24672
|
+
'${esc(hash)}',
|
|
24673
|
+
'${esc(permissionsJson)}'::jsonb,
|
|
24674
|
+
${rateLimitSql},
|
|
24675
|
+
'${esc(createdBy)}',
|
|
24676
|
+
${expiresAtSql}
|
|
24677
|
+
)
|
|
24678
|
+
RETURNING *
|
|
24679
|
+
`);
|
|
24680
|
+
const apiKey = rowToApiKey(rows[0]);
|
|
24681
|
+
return {
|
|
24682
|
+
...toMasked(apiKey),
|
|
24683
|
+
key: plaintext
|
|
24684
|
+
};
|
|
24685
|
+
},
|
|
24686
|
+
// ── Lookup by hash ──────────────────────────────────────────
|
|
24687
|
+
async findByKeyHash(hash) {
|
|
24688
|
+
const rows = await exec(`
|
|
24689
|
+
SELECT * FROM ${TABLE$1}
|
|
24690
|
+
WHERE key_hash = '${esc(hash)}'
|
|
24691
|
+
LIMIT 1
|
|
24692
|
+
`);
|
|
24693
|
+
if (rows.length === 0) return null;
|
|
24694
|
+
return rowToApiKey(rows[0]);
|
|
24695
|
+
},
|
|
24696
|
+
// ── List all (masked) ───────────────────────────────────────
|
|
24697
|
+
async listApiKeys() {
|
|
24698
|
+
const rows = await exec(`
|
|
24699
|
+
SELECT * FROM ${TABLE$1}
|
|
24700
|
+
ORDER BY created_at DESC
|
|
24701
|
+
`);
|
|
24702
|
+
return rows.map((r) => toMasked(rowToApiKey(r)));
|
|
24703
|
+
},
|
|
24704
|
+
// ── Get by ID (masked) ──────────────────────────────────────
|
|
24705
|
+
async getApiKeyById(id) {
|
|
24706
|
+
const rows = await exec(`
|
|
24707
|
+
SELECT * FROM ${TABLE$1}
|
|
24708
|
+
WHERE id = '${esc(id)}'
|
|
24709
|
+
LIMIT 1
|
|
24710
|
+
`);
|
|
24711
|
+
if (rows.length === 0) return null;
|
|
24712
|
+
return toMasked(rowToApiKey(rows[0]));
|
|
24713
|
+
},
|
|
24714
|
+
// ── Update ──────────────────────────────────────────────────
|
|
24715
|
+
async updateApiKey(id, updates) {
|
|
24716
|
+
const setClauses = [];
|
|
24717
|
+
if (updates.name !== void 0) {
|
|
24718
|
+
setClauses.push(`name = '${esc(updates.name)}'`);
|
|
24719
|
+
}
|
|
24720
|
+
if (updates.permissions !== void 0) {
|
|
24721
|
+
setClauses.push(`permissions = '${esc(JSON.stringify(updates.permissions))}'::jsonb`);
|
|
24722
|
+
}
|
|
24723
|
+
if (updates.rate_limit !== void 0) {
|
|
24724
|
+
setClauses.push(updates.rate_limit !== null ? `rate_limit = ${updates.rate_limit}` : "rate_limit = NULL");
|
|
24725
|
+
}
|
|
24726
|
+
if (updates.expires_at !== void 0) {
|
|
24727
|
+
setClauses.push(updates.expires_at !== null ? `expires_at = '${esc(updates.expires_at)}'` : "expires_at = NULL");
|
|
24728
|
+
}
|
|
24729
|
+
if (setClauses.length === 0) {
|
|
24730
|
+
return this.getApiKeyById(id);
|
|
24731
|
+
}
|
|
24732
|
+
setClauses.push("updated_at = NOW()");
|
|
24733
|
+
const rows = await exec(`
|
|
24734
|
+
UPDATE ${TABLE$1}
|
|
24735
|
+
SET ${setClauses.join(", ")}
|
|
24736
|
+
WHERE id = '${esc(id)}'
|
|
24737
|
+
RETURNING *
|
|
24738
|
+
`);
|
|
24739
|
+
if (rows.length === 0) return null;
|
|
24740
|
+
return toMasked(rowToApiKey(rows[0]));
|
|
24741
|
+
},
|
|
24742
|
+
// ── Revoke (soft-delete) ────────────────────────────────────
|
|
24743
|
+
async revokeApiKey(id) {
|
|
24744
|
+
const rows = await exec(`
|
|
24745
|
+
UPDATE ${TABLE$1}
|
|
24746
|
+
SET revoked_at = NOW(), updated_at = NOW()
|
|
24747
|
+
WHERE id = '${esc(id)}' AND revoked_at IS NULL
|
|
24748
|
+
RETURNING id
|
|
24749
|
+
`);
|
|
24750
|
+
return rows.length > 0;
|
|
24751
|
+
},
|
|
24752
|
+
// ── Touch last_used_at ──────────────────────────────────────
|
|
24753
|
+
async updateLastUsed(id) {
|
|
24754
|
+
try {
|
|
24755
|
+
await exec(`
|
|
24756
|
+
UPDATE ${TABLE$1}
|
|
24757
|
+
SET last_used_at = NOW()
|
|
24758
|
+
WHERE id = '${esc(id)}'
|
|
24759
|
+
`);
|
|
24760
|
+
} catch (err) {
|
|
24761
|
+
logger.error("[api-key-store] Failed to update last_used_at", {
|
|
24762
|
+
error: err
|
|
24763
|
+
});
|
|
24764
|
+
}
|
|
24765
|
+
}
|
|
24766
|
+
};
|
|
24767
|
+
}
|
|
24768
|
+
function validatePermissions(permissions) {
|
|
24769
|
+
if (!Array.isArray(permissions)) return false;
|
|
24770
|
+
for (const perm of permissions) {
|
|
24771
|
+
if (typeof perm !== "object" || perm === null) return false;
|
|
24772
|
+
if (typeof perm.collection !== "string" || perm.collection.length === 0) return false;
|
|
24773
|
+
if (!Array.isArray(perm.operations) || perm.operations.length === 0) return false;
|
|
24774
|
+
const validOps = /* @__PURE__ */ new Set(["read", "write", "delete"]);
|
|
24775
|
+
for (const op of perm.operations) {
|
|
24776
|
+
if (!validOps.has(op)) return false;
|
|
24777
|
+
}
|
|
24778
|
+
}
|
|
24779
|
+
return true;
|
|
24780
|
+
}
|
|
24781
|
+
function createApiKeyRoutes(options2) {
|
|
24782
|
+
const {
|
|
24783
|
+
store,
|
|
24784
|
+
serviceKey
|
|
24785
|
+
} = options2;
|
|
24786
|
+
const router = new Hono();
|
|
24787
|
+
router.onError(errorHandler);
|
|
24788
|
+
router.use("/*", createRequireAuth({
|
|
24789
|
+
serviceKey
|
|
24790
|
+
}));
|
|
24791
|
+
router.use("/*", requireAdmin);
|
|
24792
|
+
router.get("/", async (c) => {
|
|
24793
|
+
const keys2 = await store.listApiKeys();
|
|
24794
|
+
return c.json({
|
|
24795
|
+
keys: keys2
|
|
24796
|
+
});
|
|
24797
|
+
});
|
|
24798
|
+
router.post("/", async (c) => {
|
|
24799
|
+
const body = await c.req.json();
|
|
24800
|
+
const {
|
|
24801
|
+
name: name2,
|
|
24802
|
+
permissions,
|
|
24803
|
+
rate_limit,
|
|
24804
|
+
expires_at
|
|
24805
|
+
} = body;
|
|
24806
|
+
if (!name2 || typeof name2 !== "string" || name2.trim().length === 0) {
|
|
24807
|
+
throw ApiError.badRequest("Name is required", "INVALID_INPUT");
|
|
24808
|
+
}
|
|
24809
|
+
if (!validatePermissions(permissions)) {
|
|
24810
|
+
throw ApiError.badRequest("Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }", "INVALID_INPUT");
|
|
24811
|
+
}
|
|
24812
|
+
if (rate_limit !== void 0 && rate_limit !== null) {
|
|
24813
|
+
if (typeof rate_limit !== "number" || rate_limit < 1 || !Number.isInteger(rate_limit)) {
|
|
24814
|
+
throw ApiError.badRequest("rate_limit must be a positive integer or null", "INVALID_INPUT");
|
|
24815
|
+
}
|
|
24816
|
+
}
|
|
24817
|
+
if (expires_at !== void 0 && expires_at !== null) {
|
|
24818
|
+
const parsed = new Date(expires_at);
|
|
24819
|
+
if (isNaN(parsed.getTime())) {
|
|
24820
|
+
throw ApiError.badRequest("expires_at must be a valid ISO-8601 date", "INVALID_INPUT");
|
|
24821
|
+
}
|
|
24822
|
+
if (parsed <= /* @__PURE__ */ new Date()) {
|
|
24823
|
+
throw ApiError.badRequest("expires_at must be in the future", "INVALID_INPUT");
|
|
24824
|
+
}
|
|
24825
|
+
}
|
|
24826
|
+
const user = c.get("user");
|
|
24827
|
+
const createdBy = user && typeof user === "object" && "userId" in user ? user.userId : "unknown";
|
|
24828
|
+
const request = {
|
|
24829
|
+
name: name2.trim(),
|
|
24830
|
+
permissions,
|
|
24831
|
+
rate_limit: rate_limit ?? null,
|
|
24832
|
+
expires_at: expires_at ?? null
|
|
24833
|
+
};
|
|
24834
|
+
const keyWithSecret = await store.createApiKey(request, createdBy);
|
|
24835
|
+
return c.json({
|
|
24836
|
+
key: keyWithSecret
|
|
24837
|
+
}, 201);
|
|
24838
|
+
});
|
|
24839
|
+
router.get("/:id", async (c) => {
|
|
24840
|
+
const id = c.req.param("id");
|
|
24841
|
+
const key = await store.getApiKeyById(id);
|
|
24842
|
+
if (!key) {
|
|
24843
|
+
throw ApiError.notFound("API key not found");
|
|
24844
|
+
}
|
|
24845
|
+
return c.json({
|
|
24846
|
+
key
|
|
24847
|
+
});
|
|
24848
|
+
});
|
|
24849
|
+
router.put("/:id", async (c) => {
|
|
24850
|
+
const id = c.req.param("id");
|
|
24851
|
+
const body = await c.req.json();
|
|
24852
|
+
const {
|
|
24853
|
+
name: name2,
|
|
24854
|
+
permissions,
|
|
24855
|
+
rate_limit,
|
|
24856
|
+
expires_at
|
|
24857
|
+
} = body;
|
|
24858
|
+
if (name2 !== void 0) {
|
|
24859
|
+
if (typeof name2 !== "string" || name2.trim().length === 0) {
|
|
24860
|
+
throw ApiError.badRequest("Name must be a non-empty string", "INVALID_INPUT");
|
|
24861
|
+
}
|
|
24862
|
+
}
|
|
24863
|
+
if (permissions !== void 0) {
|
|
24864
|
+
if (!validatePermissions(permissions)) {
|
|
24865
|
+
throw ApiError.badRequest("Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }", "INVALID_INPUT");
|
|
24866
|
+
}
|
|
24867
|
+
}
|
|
24868
|
+
if (rate_limit !== void 0 && rate_limit !== null) {
|
|
24869
|
+
if (typeof rate_limit !== "number" || rate_limit < 1 || !Number.isInteger(rate_limit)) {
|
|
24870
|
+
throw ApiError.badRequest("rate_limit must be a positive integer or null", "INVALID_INPUT");
|
|
24871
|
+
}
|
|
24872
|
+
}
|
|
24873
|
+
if (expires_at !== void 0 && expires_at !== null) {
|
|
24874
|
+
const parsed = new Date(expires_at);
|
|
24875
|
+
if (isNaN(parsed.getTime())) {
|
|
24876
|
+
throw ApiError.badRequest("expires_at must be a valid ISO-8601 date", "INVALID_INPUT");
|
|
24877
|
+
}
|
|
24878
|
+
}
|
|
24879
|
+
const updates = {};
|
|
24880
|
+
if (name2 !== void 0) updates.name = name2.trim();
|
|
24881
|
+
if (permissions !== void 0) updates.permissions = permissions;
|
|
24882
|
+
if (rate_limit !== void 0) updates.rate_limit = rate_limit;
|
|
24883
|
+
if (expires_at !== void 0) updates.expires_at = expires_at;
|
|
24884
|
+
const key = await store.updateApiKey(id, updates);
|
|
24885
|
+
if (!key) {
|
|
24886
|
+
throw ApiError.notFound("API key not found");
|
|
24887
|
+
}
|
|
24888
|
+
return c.json({
|
|
24889
|
+
key
|
|
24890
|
+
});
|
|
24891
|
+
});
|
|
24892
|
+
router.delete("/:id", async (c) => {
|
|
24893
|
+
const id = c.req.param("id");
|
|
24894
|
+
const revoked = await store.revokeApiKey(id);
|
|
24895
|
+
if (!revoked) {
|
|
24896
|
+
throw ApiError.notFound("API key not found or already revoked");
|
|
24897
|
+
}
|
|
24898
|
+
return c.json({
|
|
24899
|
+
success: true
|
|
24900
|
+
});
|
|
24901
|
+
});
|
|
24902
|
+
return router;
|
|
24903
|
+
}
|
|
23454
24904
|
function createCustomAuthAdapter(options2) {
|
|
23455
24905
|
const defaultCapabilities = {
|
|
23456
24906
|
hasBuiltInAuthRoutes: false,
|
|
@@ -24003,6 +25453,409 @@ class S3StorageController {
|
|
|
24003
25453
|
};
|
|
24004
25454
|
}
|
|
24005
25455
|
}
|
|
25456
|
+
let sharpFactory;
|
|
25457
|
+
async function getSharp() {
|
|
25458
|
+
if (!sharpFactory) {
|
|
25459
|
+
try {
|
|
25460
|
+
const mod = await import("sharp");
|
|
25461
|
+
sharpFactory = mod.default;
|
|
25462
|
+
} catch (err) {
|
|
25463
|
+
throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
|
|
25464
|
+
}
|
|
25465
|
+
}
|
|
25466
|
+
if (!sharpFactory) {
|
|
25467
|
+
throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
|
|
25468
|
+
}
|
|
25469
|
+
return sharpFactory;
|
|
25470
|
+
}
|
|
25471
|
+
const MAX_DIMENSION = 4096;
|
|
25472
|
+
const MAX_QUALITY = 100;
|
|
25473
|
+
const MIN_QUALITY = 1;
|
|
25474
|
+
const VALID_FORMATS = /* @__PURE__ */ new Set(["webp", "avif", "jpeg", "png"]);
|
|
25475
|
+
const VALID_FITS = /* @__PURE__ */ new Set(["cover", "contain", "fill", "inside", "outside"]);
|
|
25476
|
+
function parseTransformOptions(query) {
|
|
25477
|
+
const opts = {};
|
|
25478
|
+
let hasTransform = false;
|
|
25479
|
+
if (query.width) {
|
|
25480
|
+
const w2 = parseInt(query.width, 10);
|
|
25481
|
+
if (!Number.isNaN(w2) && w2 > 0) {
|
|
25482
|
+
opts.width = Math.min(w2, MAX_DIMENSION);
|
|
25483
|
+
hasTransform = true;
|
|
25484
|
+
}
|
|
25485
|
+
}
|
|
25486
|
+
if (query.height) {
|
|
25487
|
+
const h2 = parseInt(query.height, 10);
|
|
25488
|
+
if (!Number.isNaN(h2) && h2 > 0) {
|
|
25489
|
+
opts.height = Math.min(h2, MAX_DIMENSION);
|
|
25490
|
+
hasTransform = true;
|
|
25491
|
+
}
|
|
25492
|
+
}
|
|
25493
|
+
if (query.quality) {
|
|
25494
|
+
const q = parseInt(query.quality, 10);
|
|
25495
|
+
if (!Number.isNaN(q)) {
|
|
25496
|
+
opts.quality = Math.min(Math.max(q, MIN_QUALITY), MAX_QUALITY);
|
|
25497
|
+
hasTransform = true;
|
|
25498
|
+
}
|
|
25499
|
+
}
|
|
25500
|
+
if (query.format && VALID_FORMATS.has(query.format)) {
|
|
25501
|
+
opts.format = query.format;
|
|
25502
|
+
hasTransform = true;
|
|
25503
|
+
}
|
|
25504
|
+
if (query.fit && VALID_FITS.has(query.fit)) {
|
|
25505
|
+
opts.fit = query.fit;
|
|
25506
|
+
hasTransform = true;
|
|
25507
|
+
}
|
|
25508
|
+
return hasTransform ? opts : null;
|
|
25509
|
+
}
|
|
25510
|
+
const FORMAT_CONTENT_TYPES = {
|
|
25511
|
+
webp: "image/webp",
|
|
25512
|
+
avif: "image/avif",
|
|
25513
|
+
jpeg: "image/jpeg",
|
|
25514
|
+
png: "image/png"
|
|
25515
|
+
};
|
|
25516
|
+
function isTransformableImage(contentType) {
|
|
25517
|
+
return contentType.startsWith("image/") && !contentType.includes("svg") && !contentType.includes("gif");
|
|
25518
|
+
}
|
|
25519
|
+
async function transformImage(buffer, options2) {
|
|
25520
|
+
const sharp = await getSharp();
|
|
25521
|
+
let pipeline = sharp(buffer);
|
|
25522
|
+
if (options2.width || options2.height) {
|
|
25523
|
+
pipeline = pipeline.resize({
|
|
25524
|
+
width: options2.width,
|
|
25525
|
+
height: options2.height,
|
|
25526
|
+
fit: options2.fit || "cover",
|
|
25527
|
+
withoutEnlargement: true
|
|
25528
|
+
});
|
|
25529
|
+
}
|
|
25530
|
+
const format = options2.format || "webp";
|
|
25531
|
+
const quality = options2.quality || 80;
|
|
25532
|
+
switch (format) {
|
|
25533
|
+
case "webp":
|
|
25534
|
+
pipeline = pipeline.webp({
|
|
25535
|
+
quality
|
|
25536
|
+
});
|
|
25537
|
+
break;
|
|
25538
|
+
case "avif":
|
|
25539
|
+
pipeline = pipeline.avif({
|
|
25540
|
+
quality
|
|
25541
|
+
});
|
|
25542
|
+
break;
|
|
25543
|
+
case "jpeg":
|
|
25544
|
+
pipeline = pipeline.jpeg({
|
|
25545
|
+
quality
|
|
25546
|
+
});
|
|
25547
|
+
break;
|
|
25548
|
+
case "png":
|
|
25549
|
+
pipeline = pipeline.png({
|
|
25550
|
+
quality
|
|
25551
|
+
});
|
|
25552
|
+
break;
|
|
25553
|
+
}
|
|
25554
|
+
const data = await pipeline.toBuffer();
|
|
25555
|
+
return {
|
|
25556
|
+
data,
|
|
25557
|
+
contentType: FORMAT_CONTENT_TYPES[format]
|
|
25558
|
+
};
|
|
25559
|
+
}
|
|
25560
|
+
class TransformCache {
|
|
25561
|
+
cache = /* @__PURE__ */ new Map();
|
|
25562
|
+
maxEntries;
|
|
25563
|
+
maxAgeMs;
|
|
25564
|
+
constructor(maxEntries = 500, maxAgeMs = 36e5) {
|
|
25565
|
+
this.maxEntries = maxEntries;
|
|
25566
|
+
this.maxAgeMs = maxAgeMs;
|
|
25567
|
+
}
|
|
25568
|
+
/** Build a deterministic cache key from file key + transform options. */
|
|
25569
|
+
buildKey(fileKey, options2) {
|
|
25570
|
+
return `${fileKey}::${JSON.stringify(options2)}`;
|
|
25571
|
+
}
|
|
25572
|
+
get(cacheKey) {
|
|
25573
|
+
const entry = this.cache.get(cacheKey);
|
|
25574
|
+
if (!entry) return null;
|
|
25575
|
+
if (Date.now() - entry.timestamp > this.maxAgeMs) {
|
|
25576
|
+
this.cache.delete(cacheKey);
|
|
25577
|
+
return null;
|
|
25578
|
+
}
|
|
25579
|
+
this.cache.delete(cacheKey);
|
|
25580
|
+
this.cache.set(cacheKey, entry);
|
|
25581
|
+
return {
|
|
25582
|
+
data: entry.data,
|
|
25583
|
+
contentType: entry.contentType
|
|
25584
|
+
};
|
|
25585
|
+
}
|
|
25586
|
+
set(cacheKey, data, contentType) {
|
|
25587
|
+
if (this.cache.size >= this.maxEntries) {
|
|
25588
|
+
const oldest = this.cache.keys().next().value;
|
|
25589
|
+
if (oldest !== void 0) this.cache.delete(oldest);
|
|
25590
|
+
}
|
|
25591
|
+
this.cache.set(cacheKey, {
|
|
25592
|
+
data,
|
|
25593
|
+
contentType,
|
|
25594
|
+
timestamp: Date.now()
|
|
25595
|
+
});
|
|
25596
|
+
}
|
|
25597
|
+
}
|
|
25598
|
+
const MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024;
|
|
25599
|
+
const UPLOAD_EXPIRY_MS = 24 * 60 * 60 * 1e3;
|
|
25600
|
+
class TusHandler {
|
|
25601
|
+
constructor(storageBaseDir, storageController) {
|
|
25602
|
+
this.storageController = storageController;
|
|
25603
|
+
this.tusDir = join$1(storageBaseDir, ".tus-uploads");
|
|
25604
|
+
}
|
|
25605
|
+
uploads = /* @__PURE__ */ new Map();
|
|
25606
|
+
tusDir;
|
|
25607
|
+
cleanupTimer;
|
|
25608
|
+
/** Ensure the temp directory exists. */
|
|
25609
|
+
async ensureDir() {
|
|
25610
|
+
if (!existsSync(this.tusDir)) {
|
|
25611
|
+
await mkdir$1(this.tusDir, {
|
|
25612
|
+
recursive: true
|
|
25613
|
+
});
|
|
25614
|
+
}
|
|
25615
|
+
}
|
|
25616
|
+
/** Start periodic cleanup of stale uploads. */
|
|
25617
|
+
startCleanup() {
|
|
25618
|
+
if (this.cleanupTimer) return;
|
|
25619
|
+
this.cleanupTimer = setInterval(() => {
|
|
25620
|
+
void this.cleanupStale();
|
|
25621
|
+
}, 6e4);
|
|
25622
|
+
}
|
|
25623
|
+
/** Remove uploads that have been idle for longer than UPLOAD_EXPIRY_MS. */
|
|
25624
|
+
async cleanupStale() {
|
|
25625
|
+
const now = Date.now();
|
|
25626
|
+
for (const [id, upload] of this.uploads) {
|
|
25627
|
+
if (now - upload.createdAt > UPLOAD_EXPIRY_MS && !upload.completed) {
|
|
25628
|
+
try {
|
|
25629
|
+
await unlink$1(upload.filePath);
|
|
25630
|
+
} catch {
|
|
25631
|
+
}
|
|
25632
|
+
this.uploads.delete(id);
|
|
25633
|
+
}
|
|
25634
|
+
}
|
|
25635
|
+
}
|
|
25636
|
+
// -----------------------------------------------------------------------
|
|
25637
|
+
// TUS Metadata Parsing
|
|
25638
|
+
// -----------------------------------------------------------------------
|
|
25639
|
+
/**
|
|
25640
|
+
* Parse the `Upload-Metadata` header.
|
|
25641
|
+
*
|
|
25642
|
+
* Format: `key base64value,key2 base64value2`
|
|
25643
|
+
*/
|
|
25644
|
+
parseMetadata(header) {
|
|
25645
|
+
const metadata = {};
|
|
25646
|
+
if (!header) return metadata;
|
|
25647
|
+
for (const pair of header.split(",")) {
|
|
25648
|
+
const trimmed = pair.trim();
|
|
25649
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
25650
|
+
if (spaceIdx === -1) {
|
|
25651
|
+
metadata[trimmed] = "";
|
|
25652
|
+
} else {
|
|
25653
|
+
const key = trimmed.substring(0, spaceIdx);
|
|
25654
|
+
const value = Buffer.from(trimmed.substring(spaceIdx + 1), "base64").toString("utf-8");
|
|
25655
|
+
metadata[key] = value;
|
|
25656
|
+
}
|
|
25657
|
+
}
|
|
25658
|
+
return metadata;
|
|
25659
|
+
}
|
|
25660
|
+
// -----------------------------------------------------------------------
|
|
25661
|
+
// Protocol Endpoints
|
|
25662
|
+
// -----------------------------------------------------------------------
|
|
25663
|
+
/** `OPTIONS /tus` — TUS capability discovery. */
|
|
25664
|
+
options() {
|
|
25665
|
+
return new Response(null, {
|
|
25666
|
+
status: 204,
|
|
25667
|
+
headers: {
|
|
25668
|
+
"Tus-Resumable": "1.0.0",
|
|
25669
|
+
"Tus-Version": "1.0.0",
|
|
25670
|
+
"Tus-Extension": "creation,termination",
|
|
25671
|
+
"Tus-Max-Size": String(MAX_UPLOAD_SIZE)
|
|
25672
|
+
}
|
|
25673
|
+
});
|
|
25674
|
+
}
|
|
25675
|
+
/** `POST /tus` — Create a new upload. */
|
|
25676
|
+
async create(c) {
|
|
25677
|
+
await this.ensureDir();
|
|
25678
|
+
const uploadLengthHeader = c.req.header("Upload-Length");
|
|
25679
|
+
if (!uploadLengthHeader) {
|
|
25680
|
+
return c.json({
|
|
25681
|
+
error: "Upload-Length header is required"
|
|
25682
|
+
}, 400);
|
|
25683
|
+
}
|
|
25684
|
+
const uploadLength = parseInt(uploadLengthHeader, 10);
|
|
25685
|
+
if (Number.isNaN(uploadLength) || uploadLength <= 0) {
|
|
25686
|
+
return c.json({
|
|
25687
|
+
error: "Invalid Upload-Length"
|
|
25688
|
+
}, 400);
|
|
25689
|
+
}
|
|
25690
|
+
if (uploadLength > MAX_UPLOAD_SIZE) {
|
|
25691
|
+
return c.json({
|
|
25692
|
+
error: `Upload-Length exceeds maximum of ${MAX_UPLOAD_SIZE} bytes`
|
|
25693
|
+
}, 413);
|
|
25694
|
+
}
|
|
25695
|
+
const metadata = this.parseMetadata(c.req.header("Upload-Metadata") || "");
|
|
25696
|
+
const id = randomUUID$1();
|
|
25697
|
+
const filePath = join$1(this.tusDir, id);
|
|
25698
|
+
await writeFile$1(filePath, Buffer.alloc(0));
|
|
25699
|
+
const upload = {
|
|
25700
|
+
id,
|
|
25701
|
+
size: uploadLength,
|
|
25702
|
+
offset: 0,
|
|
25703
|
+
metadata,
|
|
25704
|
+
createdAt: Date.now(),
|
|
25705
|
+
filePath,
|
|
25706
|
+
bucket: metadata.bucket || void 0,
|
|
25707
|
+
key: metadata.key || metadata.filename || void 0,
|
|
25708
|
+
completed: false
|
|
25709
|
+
};
|
|
25710
|
+
this.uploads.set(id, upload);
|
|
25711
|
+
const reqUrl = new URL(c.req.url);
|
|
25712
|
+
const location = `${reqUrl.origin}${reqUrl.pathname}/${id}`;
|
|
25713
|
+
return new Response(null, {
|
|
25714
|
+
status: 201,
|
|
25715
|
+
headers: {
|
|
25716
|
+
Location: location,
|
|
25717
|
+
"Tus-Resumable": "1.0.0",
|
|
25718
|
+
"Upload-Offset": "0"
|
|
25719
|
+
}
|
|
25720
|
+
});
|
|
25721
|
+
}
|
|
25722
|
+
/** `HEAD /tus/:id` — Query upload progress. */
|
|
25723
|
+
head(c, id) {
|
|
25724
|
+
const upload = this.uploads.get(id);
|
|
25725
|
+
if (!upload) {
|
|
25726
|
+
return c.json({
|
|
25727
|
+
error: "Upload not found"
|
|
25728
|
+
}, 404);
|
|
25729
|
+
}
|
|
25730
|
+
return new Response(null, {
|
|
25731
|
+
status: 200,
|
|
25732
|
+
headers: {
|
|
25733
|
+
"Tus-Resumable": "1.0.0",
|
|
25734
|
+
"Upload-Offset": String(upload.offset),
|
|
25735
|
+
"Upload-Length": String(upload.size),
|
|
25736
|
+
"Cache-Control": "no-store"
|
|
25737
|
+
}
|
|
25738
|
+
});
|
|
25739
|
+
}
|
|
25740
|
+
/** `PATCH /tus/:id` — Append data to an upload. */
|
|
25741
|
+
async patch(c, id) {
|
|
25742
|
+
const upload = this.uploads.get(id);
|
|
25743
|
+
if (!upload) {
|
|
25744
|
+
return c.json({
|
|
25745
|
+
error: "Upload not found"
|
|
25746
|
+
}, 404);
|
|
25747
|
+
}
|
|
25748
|
+
if (upload.completed) {
|
|
25749
|
+
return c.json({
|
|
25750
|
+
error: "Upload already completed"
|
|
25751
|
+
}, 400);
|
|
25752
|
+
}
|
|
25753
|
+
const offsetHeader = c.req.header("Upload-Offset");
|
|
25754
|
+
if (!offsetHeader) {
|
|
25755
|
+
return c.json({
|
|
25756
|
+
error: "Upload-Offset header is required"
|
|
25757
|
+
}, 400);
|
|
25758
|
+
}
|
|
25759
|
+
const offset = parseInt(offsetHeader, 10);
|
|
25760
|
+
if (offset !== upload.offset) {
|
|
25761
|
+
return c.json({
|
|
25762
|
+
error: "Offset mismatch"
|
|
25763
|
+
}, 409);
|
|
25764
|
+
}
|
|
25765
|
+
const contentType = c.req.header("Content-Type");
|
|
25766
|
+
if (contentType !== "application/offset+octet-stream") {
|
|
25767
|
+
return c.json({
|
|
25768
|
+
error: "Content-Type must be application/offset+octet-stream"
|
|
25769
|
+
}, 415);
|
|
25770
|
+
}
|
|
25771
|
+
const body = await c.req.arrayBuffer();
|
|
25772
|
+
const chunk = Buffer.from(body);
|
|
25773
|
+
if (upload.offset + chunk.length > upload.size) {
|
|
25774
|
+
return c.json({
|
|
25775
|
+
error: "Chunk exceeds declared Upload-Length"
|
|
25776
|
+
}, 413);
|
|
25777
|
+
}
|
|
25778
|
+
const fh = await open(upload.filePath, "a");
|
|
25779
|
+
try {
|
|
25780
|
+
await fh.write(chunk);
|
|
25781
|
+
} finally {
|
|
25782
|
+
await fh.close();
|
|
25783
|
+
}
|
|
25784
|
+
upload.offset += chunk.length;
|
|
25785
|
+
if (upload.offset >= upload.size) {
|
|
25786
|
+
await this.finalize(upload);
|
|
25787
|
+
}
|
|
25788
|
+
return new Response(null, {
|
|
25789
|
+
status: 204,
|
|
25790
|
+
headers: {
|
|
25791
|
+
"Tus-Resumable": "1.0.0",
|
|
25792
|
+
"Upload-Offset": String(upload.offset)
|
|
25793
|
+
}
|
|
25794
|
+
});
|
|
25795
|
+
}
|
|
25796
|
+
/** `DELETE /tus/:id` — Cancel and remove an upload. */
|
|
25797
|
+
async delete(c, id) {
|
|
25798
|
+
const upload = this.uploads.get(id);
|
|
25799
|
+
if (!upload) {
|
|
25800
|
+
return c.json({
|
|
25801
|
+
error: "Upload not found"
|
|
25802
|
+
}, 404);
|
|
25803
|
+
}
|
|
25804
|
+
try {
|
|
25805
|
+
await unlink$1(upload.filePath);
|
|
25806
|
+
} catch {
|
|
25807
|
+
}
|
|
25808
|
+
this.uploads.delete(id);
|
|
25809
|
+
return new Response(null, {
|
|
25810
|
+
status: 204,
|
|
25811
|
+
headers: {
|
|
25812
|
+
"Tus-Resumable": "1.0.0"
|
|
25813
|
+
}
|
|
25814
|
+
});
|
|
25815
|
+
}
|
|
25816
|
+
// -----------------------------------------------------------------------
|
|
25817
|
+
// Finalization
|
|
25818
|
+
// -----------------------------------------------------------------------
|
|
25819
|
+
/**
|
|
25820
|
+
* Move a completed upload into the storage controller.
|
|
25821
|
+
*/
|
|
25822
|
+
async finalize(upload) {
|
|
25823
|
+
upload.completed = true;
|
|
25824
|
+
if (!this.storageController) {
|
|
25825
|
+
logger.warn("[TUS] Upload completed but no StorageController configured. Temp file remains:", {
|
|
25826
|
+
filePath: upload.filePath
|
|
25827
|
+
});
|
|
25828
|
+
return;
|
|
25829
|
+
}
|
|
25830
|
+
try {
|
|
25831
|
+
const {
|
|
25832
|
+
readFile: readFile2
|
|
25833
|
+
} = await import("fs/promises");
|
|
25834
|
+
const data = await readFile2(upload.filePath);
|
|
25835
|
+
const fileName = upload.key || upload.metadata.filename || upload.id;
|
|
25836
|
+
const mimeType = upload.metadata.contentType || upload.metadata.filetype || "application/octet-stream";
|
|
25837
|
+
const file = new File([data], fileName, {
|
|
25838
|
+
type: mimeType
|
|
25839
|
+
});
|
|
25840
|
+
await this.storageController.putObject({
|
|
25841
|
+
file,
|
|
25842
|
+
key: fileName,
|
|
25843
|
+
bucket: upload.bucket
|
|
25844
|
+
});
|
|
25845
|
+
try {
|
|
25846
|
+
await unlink$1(upload.filePath);
|
|
25847
|
+
} catch {
|
|
25848
|
+
}
|
|
25849
|
+
this.uploads.delete(upload.id);
|
|
25850
|
+
logger.info(`[TUS] Upload ${upload.id} finalized → ${fileName}`);
|
|
25851
|
+
} catch (err) {
|
|
25852
|
+
logger.error(`[TUS] Failed to finalize upload ${upload.id}`, {
|
|
25853
|
+
error: err
|
|
25854
|
+
});
|
|
25855
|
+
}
|
|
25856
|
+
}
|
|
25857
|
+
}
|
|
25858
|
+
const transformCache = new TransformCache();
|
|
24006
25859
|
function extractWildcardPath(c) {
|
|
24007
25860
|
const routePath = c.req.routePath;
|
|
24008
25861
|
const prefix = routePath.replace("/*", "");
|
|
@@ -24066,6 +25919,7 @@ function createStorageRoutes(config) {
|
|
|
24066
25919
|
throw ApiError.notFound("File not found");
|
|
24067
25920
|
}
|
|
24068
25921
|
const filePath = decodeURIComponent(rawPath);
|
|
25922
|
+
const transformOpts = parseTransformOptions(c.req.query());
|
|
24069
25923
|
if (controller.getType() === "local") {
|
|
24070
25924
|
const localController = controller;
|
|
24071
25925
|
const {
|
|
@@ -24085,8 +25939,19 @@ function createStorageRoutes(config) {
|
|
|
24085
25939
|
} catch {
|
|
24086
25940
|
}
|
|
24087
25941
|
}
|
|
24088
|
-
c.header("Content-Type", contentType);
|
|
24089
25942
|
const fileContent = fs$4.readFileSync(absolutePath);
|
|
25943
|
+
if (transformOpts && isTransformableImage(contentType)) {
|
|
25944
|
+
const cacheKey = transformCache.buildKey(filePath, transformOpts);
|
|
25945
|
+
let cached = transformCache.get(cacheKey);
|
|
25946
|
+
if (!cached) {
|
|
25947
|
+
cached = await transformImage(Buffer.from(fileContent), transformOpts);
|
|
25948
|
+
transformCache.set(cacheKey, cached.data, cached.contentType);
|
|
25949
|
+
}
|
|
25950
|
+
c.header("Content-Type", cached.contentType);
|
|
25951
|
+
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
25952
|
+
return c.body(new Uint8Array(cached.data));
|
|
25953
|
+
}
|
|
25954
|
+
c.header("Content-Type", contentType);
|
|
24090
25955
|
return c.body(new Uint8Array(fileContent));
|
|
24091
25956
|
}
|
|
24092
25957
|
const {
|
|
@@ -24097,7 +25962,20 @@ function createStorageRoutes(config) {
|
|
|
24097
25962
|
if (!fileObject) {
|
|
24098
25963
|
throw ApiError.notFound("File not found");
|
|
24099
25964
|
}
|
|
24100
|
-
|
|
25965
|
+
const remoteContentType = fileObject.type || "application/octet-stream";
|
|
25966
|
+
if (transformOpts && isTransformableImage(remoteContentType)) {
|
|
25967
|
+
const cacheKey = transformCache.buildKey(filePath, transformOpts);
|
|
25968
|
+
let cached = transformCache.get(cacheKey);
|
|
25969
|
+
if (!cached) {
|
|
25970
|
+
const buf2 = Buffer.from(await fileObject.arrayBuffer());
|
|
25971
|
+
cached = await transformImage(buf2, transformOpts);
|
|
25972
|
+
transformCache.set(cacheKey, cached.data, cached.contentType);
|
|
25973
|
+
}
|
|
25974
|
+
c.header("Content-Type", cached.contentType);
|
|
25975
|
+
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
25976
|
+
return c.body(new Uint8Array(cached.data));
|
|
25977
|
+
}
|
|
25978
|
+
c.header("Content-Type", remoteContentType);
|
|
24101
25979
|
c.header("Cache-Control", "public, max-age=3600, immutable");
|
|
24102
25980
|
const buf = await fileObject.arrayBuffer();
|
|
24103
25981
|
return c.body(new Uint8Array(buf));
|
|
@@ -24193,6 +26071,14 @@ function createStorageRoutes(config) {
|
|
|
24193
26071
|
message: "Folder created"
|
|
24194
26072
|
}, 201);
|
|
24195
26073
|
});
|
|
26074
|
+
const tusBaseDir = controller.getType() === "local" ? controller.getBasePath() : process.env.STORAGE_PATH || "./uploads";
|
|
26075
|
+
const tusHandler = new TusHandler(tusBaseDir, controller);
|
|
26076
|
+
tusHandler.startCleanup();
|
|
26077
|
+
router.options("/tus", (_c2) => tusHandler.options());
|
|
26078
|
+
router.post("/tus", writeAuthMiddleware, async (c) => tusHandler.create(c));
|
|
26079
|
+
router.get("/tus/:id", readAuthMiddleware, (c) => tusHandler.head(c, c.req.param("id")));
|
|
26080
|
+
router.patch("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.patch(c, c.req.param("id")));
|
|
26081
|
+
router.delete("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.delete(c, c.req.param("id")));
|
|
24196
26082
|
return router;
|
|
24197
26083
|
}
|
|
24198
26084
|
const DEFAULT_STORAGE_ID = "(default)";
|
|
@@ -24416,6 +26302,13 @@ function createTransport$1(config) {
|
|
|
24416
26302
|
} catch (e) {
|
|
24417
26303
|
}
|
|
24418
26304
|
}
|
|
26305
|
+
const getErrorField = (obj, field) => {
|
|
26306
|
+
const err = obj?.error;
|
|
26307
|
+
if (err && typeof err === "object" && err !== null && field in err) {
|
|
26308
|
+
return err[field];
|
|
26309
|
+
}
|
|
26310
|
+
return obj?.[field];
|
|
26311
|
+
};
|
|
24419
26312
|
if (res.status === 401 && onUnauthorizedHandler) {
|
|
24420
26313
|
const retried = await onUnauthorizedHandler();
|
|
24421
26314
|
if (retried) {
|
|
@@ -24449,7 +26342,7 @@ function createTransport$1(config) {
|
|
|
24449
26342
|
const method = init?.method || "GET";
|
|
24450
26343
|
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
26344
|
}
|
|
24452
|
-
throw new RebaseApiError(retryRes.status, retryBody
|
|
26345
|
+
throw new RebaseApiError(retryRes.status, String(getErrorField(retryBody, "message") || fallbackMessage || `Request failed with status ${retryRes.status}`), getErrorField(retryBody, "code"), getErrorField(retryBody, "details"));
|
|
24453
26346
|
}
|
|
24454
26347
|
return retryBody;
|
|
24455
26348
|
}
|
|
@@ -24460,7 +26353,7 @@ function createTransport$1(config) {
|
|
|
24460
26353
|
const method = init?.method || "GET";
|
|
24461
26354
|
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
26355
|
}
|
|
24463
|
-
throw new RebaseApiError(res.status, body
|
|
26356
|
+
throw new RebaseApiError(res.status, String(getErrorField(body, "message") || fallbackMessage || `Request failed with status ${res.status}`), getErrorField(body, "code"), getErrorField(body, "details"));
|
|
24464
26357
|
}
|
|
24465
26358
|
return body;
|
|
24466
26359
|
}
|
|
@@ -25716,7 +27609,7 @@ const require$$9 = {
|
|
|
25716
27609
|
const http = require$$0$6;
|
|
25717
27610
|
const https = require$$1;
|
|
25718
27611
|
const urllib$2 = require$$0$5;
|
|
25719
|
-
const zlib = require$$
|
|
27612
|
+
const zlib = require$$3;
|
|
25720
27613
|
const PassThrough$3 = require$$0$4.PassThrough;
|
|
25721
27614
|
const Cookies2 = cookies;
|
|
25722
27615
|
const packageData$7 = require$$9;
|
|
@@ -25951,9 +27844,9 @@ var fetchExports = fetch$1.exports;
|
|
|
25951
27844
|
const util2 = require$$5;
|
|
25952
27845
|
const fs2 = fs__default;
|
|
25953
27846
|
const nmfetch2 = fetchExports;
|
|
25954
|
-
const dns2 = require$$
|
|
27847
|
+
const dns2 = require$$4$1;
|
|
25955
27848
|
const net2 = require$$0$7;
|
|
25956
|
-
const os2 = require$$1$
|
|
27849
|
+
const os2 = require$$1$3;
|
|
25957
27850
|
const DNS_TTL = 5 * 60 * 1e3;
|
|
25958
27851
|
let networkInterfaces;
|
|
25959
27852
|
try {
|
|
@@ -32099,7 +33992,7 @@ const urllib = require$$0$5;
|
|
|
32099
33992
|
const packageData$6 = require$$9;
|
|
32100
33993
|
const MailMessage2 = mailMessage;
|
|
32101
33994
|
const net$1 = require$$0$7;
|
|
32102
|
-
const dns = require$$
|
|
33995
|
+
const dns = require$$4$1;
|
|
32103
33996
|
const crypto$3 = require$$0__default;
|
|
32104
33997
|
class Mail extends EventEmitter$5 {
|
|
32105
33998
|
constructor(transporter, options2, defaults) {
|
|
@@ -32535,7 +34428,7 @@ const packageInfo = require$$9;
|
|
|
32535
34428
|
const EventEmitter$4 = require$$0$9.EventEmitter;
|
|
32536
34429
|
const net = require$$0$7;
|
|
32537
34430
|
const tls = require$$1$1;
|
|
32538
|
-
const os = require$$1$
|
|
34431
|
+
const os = require$$1$3;
|
|
32539
34432
|
const crypto$2 = require$$0__default;
|
|
32540
34433
|
const DataStream2 = dataStream;
|
|
32541
34434
|
const PassThrough = require$$0$4.PassThrough;
|
|
@@ -36723,7 +38616,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36723
38616
|
oauthProviders,
|
|
36724
38617
|
serviceKey,
|
|
36725
38618
|
hooks: config.hooks,
|
|
36726
|
-
|
|
38619
|
+
authHooks: safeAuthConfig.hooks
|
|
36727
38620
|
});
|
|
36728
38621
|
}
|
|
36729
38622
|
logger.info("Authentication initialized");
|
|
@@ -36790,73 +38683,73 @@ async function _initializeRebaseBackend(config) {
|
|
|
36790
38683
|
if (safeAuthConfig.google?.clientId) {
|
|
36791
38684
|
const {
|
|
36792
38685
|
createGoogleProvider: createGoogleProvider2
|
|
36793
|
-
} = await import("./index-
|
|
38686
|
+
} = await import("./index-Cr1D21av.js");
|
|
36794
38687
|
oauthProviders.push(createGoogleProvider2(safeAuthConfig.google));
|
|
36795
38688
|
}
|
|
36796
38689
|
if (safeAuthConfig.linkedin?.clientId && safeAuthConfig.linkedin?.clientSecret) {
|
|
36797
38690
|
const {
|
|
36798
38691
|
createLinkedinProvider: createLinkedinProvider2
|
|
36799
|
-
} = await import("./index-
|
|
38692
|
+
} = await import("./index-Cr1D21av.js");
|
|
36800
38693
|
oauthProviders.push(createLinkedinProvider2(safeAuthConfig.linkedin));
|
|
36801
38694
|
}
|
|
36802
38695
|
if (safeAuthConfig.github?.clientId && safeAuthConfig.github?.clientSecret) {
|
|
36803
38696
|
const {
|
|
36804
38697
|
createGitHubProvider: createGitHubProvider2
|
|
36805
|
-
} = await import("./index-
|
|
38698
|
+
} = await import("./index-Cr1D21av.js");
|
|
36806
38699
|
oauthProviders.push(createGitHubProvider2(safeAuthConfig.github));
|
|
36807
38700
|
}
|
|
36808
38701
|
if (safeAuthConfig.microsoft?.clientId && safeAuthConfig.microsoft?.clientSecret) {
|
|
36809
38702
|
const {
|
|
36810
38703
|
createMicrosoftProvider: createMicrosoftProvider2
|
|
36811
|
-
} = await import("./index-
|
|
38704
|
+
} = await import("./index-Cr1D21av.js");
|
|
36812
38705
|
oauthProviders.push(createMicrosoftProvider2(safeAuthConfig.microsoft));
|
|
36813
38706
|
}
|
|
36814
38707
|
if (safeAuthConfig.apple?.clientId && safeAuthConfig.apple?.teamId && safeAuthConfig.apple?.keyId && safeAuthConfig.apple?.privateKey) {
|
|
36815
38708
|
const {
|
|
36816
38709
|
createAppleProvider: createAppleProvider2
|
|
36817
|
-
} = await import("./index-
|
|
38710
|
+
} = await import("./index-Cr1D21av.js");
|
|
36818
38711
|
oauthProviders.push(createAppleProvider2(safeAuthConfig.apple));
|
|
36819
38712
|
}
|
|
36820
38713
|
if (safeAuthConfig.facebook?.clientId && safeAuthConfig.facebook?.clientSecret) {
|
|
36821
38714
|
const {
|
|
36822
38715
|
createFacebookProvider: createFacebookProvider2
|
|
36823
|
-
} = await import("./index-
|
|
38716
|
+
} = await import("./index-Cr1D21av.js");
|
|
36824
38717
|
oauthProviders.push(createFacebookProvider2(safeAuthConfig.facebook));
|
|
36825
38718
|
}
|
|
36826
38719
|
if (safeAuthConfig.twitter?.clientId && safeAuthConfig.twitter?.clientSecret) {
|
|
36827
38720
|
const {
|
|
36828
38721
|
createTwitterProvider: createTwitterProvider2
|
|
36829
|
-
} = await import("./index-
|
|
38722
|
+
} = await import("./index-Cr1D21av.js");
|
|
36830
38723
|
oauthProviders.push(createTwitterProvider2(safeAuthConfig.twitter));
|
|
36831
38724
|
}
|
|
36832
38725
|
if (safeAuthConfig.discord?.clientId && safeAuthConfig.discord?.clientSecret) {
|
|
36833
38726
|
const {
|
|
36834
38727
|
createDiscordProvider: createDiscordProvider2
|
|
36835
|
-
} = await import("./index-
|
|
38728
|
+
} = await import("./index-Cr1D21av.js");
|
|
36836
38729
|
oauthProviders.push(createDiscordProvider2(safeAuthConfig.discord));
|
|
36837
38730
|
}
|
|
36838
38731
|
if (safeAuthConfig.gitlab?.clientId && safeAuthConfig.gitlab?.clientSecret) {
|
|
36839
38732
|
const {
|
|
36840
38733
|
createGitLabProvider: createGitLabProvider2
|
|
36841
|
-
} = await import("./index-
|
|
38734
|
+
} = await import("./index-Cr1D21av.js");
|
|
36842
38735
|
oauthProviders.push(createGitLabProvider2(safeAuthConfig.gitlab));
|
|
36843
38736
|
}
|
|
36844
38737
|
if (safeAuthConfig.bitbucket?.clientId && safeAuthConfig.bitbucket?.clientSecret) {
|
|
36845
38738
|
const {
|
|
36846
38739
|
createBitbucketProvider: createBitbucketProvider2
|
|
36847
|
-
} = await import("./index-
|
|
38740
|
+
} = await import("./index-Cr1D21av.js");
|
|
36848
38741
|
oauthProviders.push(createBitbucketProvider2(safeAuthConfig.bitbucket));
|
|
36849
38742
|
}
|
|
36850
38743
|
if (safeAuthConfig.slack?.clientId && safeAuthConfig.slack?.clientSecret) {
|
|
36851
38744
|
const {
|
|
36852
38745
|
createSlackProvider: createSlackProvider2
|
|
36853
|
-
} = await import("./index-
|
|
38746
|
+
} = await import("./index-Cr1D21av.js");
|
|
36854
38747
|
oauthProviders.push(createSlackProvider2(safeAuthConfig.slack));
|
|
36855
38748
|
}
|
|
36856
38749
|
if (safeAuthConfig.spotify?.clientId && safeAuthConfig.spotify?.clientSecret) {
|
|
36857
38750
|
const {
|
|
36858
38751
|
createSpotifyProvider: createSpotifyProvider2
|
|
36859
|
-
} = await import("./index-
|
|
38752
|
+
} = await import("./index-Cr1D21av.js");
|
|
36860
38753
|
oauthProviders.push(createSpotifyProvider2(safeAuthConfig.spotify));
|
|
36861
38754
|
}
|
|
36862
38755
|
authAdapter = createBuiltinAuthAdapter({
|
|
@@ -36868,7 +38761,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36868
38761
|
oauthProviders,
|
|
36869
38762
|
serviceKey,
|
|
36870
38763
|
hooks: config.hooks,
|
|
36871
|
-
|
|
38764
|
+
authHooks: safeAuthConfig.hooks
|
|
36872
38765
|
});
|
|
36873
38766
|
}
|
|
36874
38767
|
if (authAdapter.createAuthRoutes) {
|
|
@@ -36890,6 +38783,21 @@ async function _initializeRebaseBackend(config) {
|
|
|
36890
38783
|
}
|
|
36891
38784
|
}
|
|
36892
38785
|
}
|
|
38786
|
+
let apiKeyStore;
|
|
38787
|
+
const apiKeyStoreResult = createApiKeyStore(defaultDriver);
|
|
38788
|
+
if (apiKeyStoreResult) {
|
|
38789
|
+
apiKeyStore = apiKeyStoreResult;
|
|
38790
|
+
await apiKeyStore.ensureTable();
|
|
38791
|
+
logger.info("Service API Keys initialized");
|
|
38792
|
+
const apiKeyRoutes = createApiKeyRoutes({
|
|
38793
|
+
store: apiKeyStore,
|
|
38794
|
+
serviceKey
|
|
38795
|
+
});
|
|
38796
|
+
config.app.route(`${basePath}/admin/api-keys`, apiKeyRoutes);
|
|
38797
|
+
logger.info("API key admin routes mounted", {
|
|
38798
|
+
path: `${basePath}/admin/api-keys`
|
|
38799
|
+
});
|
|
38800
|
+
}
|
|
36893
38801
|
if (config.collectionsDir) {
|
|
36894
38802
|
if (process.env.NODE_ENV !== "production") {
|
|
36895
38803
|
const {
|
|
@@ -36940,15 +38848,20 @@ async function _initializeRebaseBackend(config) {
|
|
|
36940
38848
|
dataRouter.use("/*", createAdapterAuthMiddleware({
|
|
36941
38849
|
adapter: authAdapter,
|
|
36942
38850
|
driver: defaultDriver,
|
|
36943
|
-
requireAuth: dataRequireAuth
|
|
38851
|
+
requireAuth: dataRequireAuth,
|
|
38852
|
+
apiKeyStore
|
|
36944
38853
|
}));
|
|
36945
38854
|
} else {
|
|
36946
38855
|
dataRouter.use("/*", createAuthMiddleware({
|
|
36947
38856
|
driver: defaultDriver,
|
|
36948
38857
|
requireAuth: dataRequireAuth,
|
|
36949
|
-
serviceKey
|
|
38858
|
+
serviceKey,
|
|
38859
|
+
apiKeyStore
|
|
36950
38860
|
}));
|
|
36951
38861
|
}
|
|
38862
|
+
if (apiKeyStore) {
|
|
38863
|
+
dataRouter.use("/*", createApiKeyRateLimiter());
|
|
38864
|
+
}
|
|
36952
38865
|
if (historyConfigResult && historyConfigResult.historyService) {
|
|
36953
38866
|
const historyRoutes = createHistoryRoutes({
|
|
36954
38867
|
historyService: historyConfigResult.historyService,
|
|
@@ -37042,13 +38955,15 @@ async function _initializeRebaseBackend(config) {
|
|
|
37042
38955
|
functionsRouter.use("/*", createAdapterAuthMiddleware({
|
|
37043
38956
|
adapter: authAdapter,
|
|
37044
38957
|
driver: defaultDriver,
|
|
37045
|
-
requireAuth: functionsRequireAuth
|
|
38958
|
+
requireAuth: functionsRequireAuth,
|
|
38959
|
+
apiKeyStore
|
|
37046
38960
|
}));
|
|
37047
38961
|
} else {
|
|
37048
38962
|
functionsRouter.use("/*", createAuthMiddleware({
|
|
37049
38963
|
driver: defaultDriver,
|
|
37050
38964
|
requireAuth: functionsRequireAuth,
|
|
37051
|
-
serviceKey
|
|
38965
|
+
serviceKey,
|
|
38966
|
+
apiKeyStore
|
|
37052
38967
|
}));
|
|
37053
38968
|
}
|
|
37054
38969
|
const fnRoutes = createFunctionRoutes2(loadedFunctions);
|
|
@@ -47039,7 +48954,7 @@ class AstSchemaEditor {
|
|
|
47039
48954
|
return "undefined";
|
|
47040
48955
|
}
|
|
47041
48956
|
if (typeof obj === "string") {
|
|
47042
|
-
return
|
|
48957
|
+
return JSON.stringify(obj);
|
|
47043
48958
|
}
|
|
47044
48959
|
if (typeof obj === "number" || typeof obj === "boolean") {
|
|
47045
48960
|
return String(obj);
|
|
@@ -47070,7 +48985,7 @@ ${indent2}]`;
|
|
|
47070
48985
|
const kind = init.getKind();
|
|
47071
48986
|
const isCode = kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.Identifier || kind === SyntaxKind.CallExpression || kind === SyntaxKind.JsxElement;
|
|
47072
48987
|
if (isCode || name2 === "target" || name2 === "callbacks" || name2 === "permissions" || name2 === "securityRules") {
|
|
47073
|
-
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name2) ? name2 :
|
|
48988
|
+
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name2) ? name2 : JSON.stringify(name2);
|
|
47074
48989
|
preservedProps.push(`${keyStr}: ${init.getText()}`);
|
|
47075
48990
|
}
|
|
47076
48991
|
}
|
|
@@ -47080,7 +48995,7 @@ ${indent2}]`;
|
|
|
47080
48995
|
}
|
|
47081
48996
|
if (keys2.length === 0 && preservedProps.length === 0) return "{}";
|
|
47082
48997
|
const props = keys2.map((key) => {
|
|
47083
|
-
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key :
|
|
48998
|
+
const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
|
|
47084
48999
|
let childAstNode;
|
|
47085
49000
|
if (oldAstNode && typeof record[key] === "object" && record[key] !== null && !Array.isArray(record[key])) {
|
|
47086
49001
|
const oldProp = oldAstNode.getProperty((p) => "getName" in p && typeof p.getName === "function" && (p.getName() === key || p.getName() === `"${key}"` || p.getName() === `'${key}'`));
|
|
@@ -47122,7 +49037,7 @@ ${indent2}}`;
|
|
|
47122
49037
|
}
|
|
47123
49038
|
} else {
|
|
47124
49039
|
propsObj.addPropertyAssignment({
|
|
47125
|
-
name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey :
|
|
49040
|
+
name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),
|
|
47126
49041
|
initializer: newInitializer
|
|
47127
49042
|
});
|
|
47128
49043
|
}
|
|
@@ -48193,7 +50108,7 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48193
50108
|
const mod = await dynamicImport(fileUrl);
|
|
48194
50109
|
const exported = mod.default;
|
|
48195
50110
|
if (!exported) {
|
|
48196
|
-
|
|
50111
|
+
logger.warn(`[functions] ${file}: no default export. Skipping.`);
|
|
48197
50112
|
continue;
|
|
48198
50113
|
}
|
|
48199
50114
|
if (isHonoLike(exported)) {
|
|
@@ -48202,7 +50117,7 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48202
50117
|
name: name2,
|
|
48203
50118
|
app: exported
|
|
48204
50119
|
});
|
|
48205
|
-
|
|
50120
|
+
logger.info(`⚡ Loaded function route: ${name2}`);
|
|
48206
50121
|
continue;
|
|
48207
50122
|
}
|
|
48208
50123
|
if (typeof exported === "function") {
|
|
@@ -48213,20 +50128,20 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48213
50128
|
name: name2,
|
|
48214
50129
|
app: result
|
|
48215
50130
|
});
|
|
48216
|
-
|
|
50131
|
+
logger.info(`⚡ Loaded function route: ${name2}`);
|
|
48217
50132
|
continue;
|
|
48218
50133
|
}
|
|
48219
50134
|
}
|
|
48220
50135
|
const exportType = typeof exported;
|
|
48221
50136
|
const keys2 = exported && typeof exported === "object" ? Object.getOwnPropertyNames(Object.getPrototypeOf(exported)).slice(0, 10).join(", ") : "N/A";
|
|
48222
|
-
|
|
50137
|
+
logger.warn(`[functions] ${file}: default export is not a Hono app or factory. Skipping.
|
|
48223
50138
|
export type: ${exportType}${exported?.constructor?.name ? ` (${exported.constructor.name})` : ""}
|
|
48224
50139
|
prototype methods: ${keys2}
|
|
48225
50140
|
Hint: ensure the function exports a Hono app created with the same hono version as the server.
|
|
48226
50141
|
The loader checks for .fetch() and .routes — any Hono-compatible app will work.`);
|
|
48227
50142
|
} catch (err) {
|
|
48228
50143
|
const message = err instanceof Error ? err.message : String(err);
|
|
48229
|
-
|
|
50144
|
+
logger.error(`[functions] Failed to load ${file}: ${message}`);
|
|
48230
50145
|
}
|
|
48231
50146
|
}
|
|
48232
50147
|
}
|
|
@@ -48275,12 +50190,12 @@ async function loadCronJobsFromDirectory(directory) {
|
|
|
48275
50190
|
const mod = await dynamicImport(fileUrl);
|
|
48276
50191
|
const exported = mod.default;
|
|
48277
50192
|
if (!exported || typeof exported !== "object") {
|
|
48278
|
-
|
|
50193
|
+
logger.warn(`[cron] ${file}: no valid default export. Skipping.`);
|
|
48279
50194
|
continue;
|
|
48280
50195
|
}
|
|
48281
50196
|
const def = exported;
|
|
48282
50197
|
if (typeof def.schedule !== "string" || typeof def.handler !== "function") {
|
|
48283
|
-
|
|
50198
|
+
logger.warn(`[cron] ${file}: default export missing required 'schedule' or 'handler'. Skipping.`);
|
|
48284
50199
|
continue;
|
|
48285
50200
|
}
|
|
48286
50201
|
const id = path$3.basename(file, path$3.extname(file));
|
|
@@ -48296,10 +50211,10 @@ async function loadCronJobsFromDirectory(directory) {
|
|
|
48296
50211
|
id,
|
|
48297
50212
|
definition
|
|
48298
50213
|
});
|
|
48299
|
-
|
|
50214
|
+
logger.info(`⏰ Loaded cron job: ${id} (${definition.schedule})`);
|
|
48300
50215
|
} catch (err) {
|
|
48301
50216
|
const message = err instanceof Error ? err.message : String(err);
|
|
48302
|
-
|
|
50217
|
+
logger.error(`[cron] Failed to load ${file}: ${message}`);
|
|
48303
50218
|
}
|
|
48304
50219
|
}
|
|
48305
50220
|
}
|
|
@@ -48454,12 +50369,12 @@ class CronScheduler {
|
|
|
48454
50369
|
for (const loaded of loadedJobs) {
|
|
48455
50370
|
const validation = validateCronExpression(loaded.definition.schedule);
|
|
48456
50371
|
if (!validation.valid) {
|
|
48457
|
-
|
|
50372
|
+
logger.error(`[cron] Rejecting job "${loaded.id}": invalid schedule "${loaded.definition.schedule}" — ${validation.reason}`);
|
|
48458
50373
|
continue;
|
|
48459
50374
|
}
|
|
48460
50375
|
const existing = this.jobs.get(loaded.id);
|
|
48461
50376
|
if (existing) {
|
|
48462
|
-
|
|
50377
|
+
logger.warn(`[cron] Duplicate cron job id: "${loaded.id}". Overwriting.`);
|
|
48463
50378
|
this.stopJob(loaded.id);
|
|
48464
50379
|
}
|
|
48465
50380
|
const enabled = loaded.definition.enabled !== false;
|
|
@@ -48497,7 +50412,9 @@ class CronScheduler {
|
|
|
48497
50412
|
}
|
|
48498
50413
|
}
|
|
48499
50414
|
}).catch((err) => {
|
|
48500
|
-
|
|
50415
|
+
logger.warn("[cron] Failed to seed job stats from database", {
|
|
50416
|
+
error: err
|
|
50417
|
+
});
|
|
48501
50418
|
});
|
|
48502
50419
|
}
|
|
48503
50420
|
for (const [id, job] of this.jobs) {
|
|
@@ -48505,7 +50422,7 @@ class CronScheduler {
|
|
|
48505
50422
|
this.scheduleNext(id);
|
|
48506
50423
|
}
|
|
48507
50424
|
}
|
|
48508
|
-
|
|
50425
|
+
logger.info(`⏰ Cron scheduler started with ${this.jobs.size} job(s)`);
|
|
48509
50426
|
}
|
|
48510
50427
|
/**
|
|
48511
50428
|
* Stop the scheduler and clear all timers.
|
|
@@ -48579,7 +50496,7 @@ class CronScheduler {
|
|
|
48579
50496
|
const job = this.jobs.get(id);
|
|
48580
50497
|
if (!job) return void 0;
|
|
48581
50498
|
if (job.executing) {
|
|
48582
|
-
|
|
50499
|
+
logger.warn(`[cron] Skipping manual trigger of "${id}" — already executing`);
|
|
48583
50500
|
const logEntry = {
|
|
48584
50501
|
jobId: id,
|
|
48585
50502
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -48623,7 +50540,7 @@ class CronScheduler {
|
|
|
48623
50540
|
const timer = setTimeout(async () => {
|
|
48624
50541
|
if (!job.enabled || !this.started) return;
|
|
48625
50542
|
if (job.executing) {
|
|
48626
|
-
|
|
50543
|
+
logger.warn(`[cron] Skipping scheduled run of "${id}" — still executing from previous run`);
|
|
48627
50544
|
this.scheduleNext(id);
|
|
48628
50545
|
return;
|
|
48629
50546
|
}
|
|
@@ -48637,7 +50554,9 @@ class CronScheduler {
|
|
|
48637
50554
|
}
|
|
48638
50555
|
job.timerId = timer;
|
|
48639
50556
|
} catch (err) {
|
|
48640
|
-
|
|
50557
|
+
logger.error(`[cron] Failed to schedule "${id}"`, {
|
|
50558
|
+
error: err
|
|
50559
|
+
});
|
|
48641
50560
|
job.state = "error";
|
|
48642
50561
|
job.lastError = err instanceof Error ? err.message : String(err);
|
|
48643
50562
|
}
|
|
@@ -48722,13 +50641,15 @@ class CronScheduler {
|
|
|
48722
50641
|
}
|
|
48723
50642
|
if (this.store) {
|
|
48724
50643
|
this.store.insertLog(logEntry).catch((persistErr) => {
|
|
48725
|
-
|
|
50644
|
+
logger.error(`[cron] Failed to persist log for "${job.id}"`, {
|
|
50645
|
+
error: persistErr
|
|
50646
|
+
});
|
|
48726
50647
|
});
|
|
48727
50648
|
}
|
|
48728
50649
|
if (success) {
|
|
48729
|
-
|
|
50650
|
+
logger.info(`✅ [cron] "${job.id}" completed in ${durationMs}ms`);
|
|
48730
50651
|
} else {
|
|
48731
|
-
|
|
50652
|
+
logger.error(`❌ [cron] "${job.id}" failed in ${durationMs}ms: ${error2}`);
|
|
48732
50653
|
}
|
|
48733
50654
|
return logEntry;
|
|
48734
50655
|
}
|
|
@@ -48846,7 +50767,7 @@ const TABLE = "rebase.cron_logs";
|
|
|
48846
50767
|
function createCronStore(driver) {
|
|
48847
50768
|
const admin = driver.admin;
|
|
48848
50769
|
if (!isSQLAdmin(admin)) {
|
|
48849
|
-
|
|
50770
|
+
logger.warn("⚠️ [cron-store] DataDriver does not support SQL admin — cron logs will not be persisted.");
|
|
48850
50771
|
return void 0;
|
|
48851
50772
|
}
|
|
48852
50773
|
const exec = admin.executeSql.bind(admin);
|
|
@@ -48872,10 +50793,12 @@ function createCronStore(driver) {
|
|
|
48872
50793
|
CREATE INDEX IF NOT EXISTS idx_cron_logs_job
|
|
48873
50794
|
ON ${TABLE}(job_id, started_at DESC)
|
|
48874
50795
|
`);
|
|
48875
|
-
|
|
50796
|
+
logger.info("✅ Cron logs table ready");
|
|
48876
50797
|
} catch (err) {
|
|
48877
|
-
|
|
48878
|
-
|
|
50798
|
+
logger.error("❌ Failed to create cron logs table", {
|
|
50799
|
+
error: err
|
|
50800
|
+
});
|
|
50801
|
+
logger.warn("⚠️ Continuing without cron log persistence.");
|
|
48879
50802
|
}
|
|
48880
50803
|
},
|
|
48881
50804
|
async insertLog(entry) {
|
|
@@ -48898,7 +50821,9 @@ function createCronStore(driver) {
|
|
|
48898
50821
|
)
|
|
48899
50822
|
`);
|
|
48900
50823
|
} catch (err) {
|
|
48901
|
-
|
|
50824
|
+
logger.error(`[cron-store] Failed to persist log for "${entry.jobId}"`, {
|
|
50825
|
+
error: err
|
|
50826
|
+
});
|
|
48902
50827
|
}
|
|
48903
50828
|
},
|
|
48904
50829
|
async fetchLogs(jobId, limit = 50) {
|
|
@@ -48912,7 +50837,9 @@ function createCronStore(driver) {
|
|
|
48912
50837
|
`);
|
|
48913
50838
|
return rows.map(rowToLogEntry);
|
|
48914
50839
|
} catch (err) {
|
|
48915
|
-
|
|
50840
|
+
logger.error(`[cron-store] Failed to fetch logs for "${jobId}"`, {
|
|
50841
|
+
error: err
|
|
50842
|
+
});
|
|
48916
50843
|
return [];
|
|
48917
50844
|
}
|
|
48918
50845
|
},
|
|
@@ -48936,7 +50863,9 @@ function createCronStore(driver) {
|
|
|
48936
50863
|
});
|
|
48937
50864
|
}
|
|
48938
50865
|
} catch (err) {
|
|
48939
|
-
|
|
50866
|
+
logger.error("[cron-store] Failed to fetch job stats", {
|
|
50867
|
+
error: err
|
|
50868
|
+
});
|
|
48940
50869
|
}
|
|
48941
50870
|
return stats;
|
|
48942
50871
|
}
|
|
@@ -48967,8 +50896,8 @@ function serveSPA(app, config) {
|
|
|
48967
50896
|
indexFile = "index.html"
|
|
48968
50897
|
} = config;
|
|
48969
50898
|
if (!fs$4.existsSync(frontendPath)) {
|
|
48970
|
-
|
|
48971
|
-
|
|
50899
|
+
logger.warn(`⚠️ Frontend build path does not exist: ${frontendPath}`);
|
|
50900
|
+
logger.warn(" SPA serving is disabled. Build your frontend first.");
|
|
48972
50901
|
return;
|
|
48973
50902
|
}
|
|
48974
50903
|
app.use("/*", serveStatic({
|
|
@@ -48981,13 +50910,13 @@ function serveSPA(app, config) {
|
|
|
48981
50910
|
}
|
|
48982
50911
|
const indexPath = path$3.join(frontendPath, indexFile);
|
|
48983
50912
|
if (!fs$4.existsSync(indexPath)) {
|
|
48984
|
-
|
|
50913
|
+
logger.warn(`⚠️ Index file not found: ${indexPath}`);
|
|
48985
50914
|
return next();
|
|
48986
50915
|
}
|
|
48987
50916
|
const html = fs$4.readFileSync(indexPath, "utf-8");
|
|
48988
50917
|
return c.html(html);
|
|
48989
50918
|
});
|
|
48990
|
-
|
|
50919
|
+
logger.info(`✅ SPA serving enabled from: ${frontendPath}`);
|
|
48991
50920
|
}
|
|
48992
50921
|
const MAX_PORT_ATTEMPTS = 20;
|
|
48993
50922
|
const DEV_PORT_FILENAME = ".rebase-dev-port";
|
|
@@ -48995,6 +50924,19 @@ function listenWithPortRetry(server, startPort, options2) {
|
|
|
48995
50924
|
const host = options2?.host ?? "0.0.0.0";
|
|
48996
50925
|
const maxAttempts = options2?.maxAttempts ?? MAX_PORT_ATTEMPTS;
|
|
48997
50926
|
const portFileDir = options2?.portFileDir;
|
|
50927
|
+
const isProd = process.env.NODE_ENV === "production";
|
|
50928
|
+
if (isProd) {
|
|
50929
|
+
return new Promise((resolve, reject) => {
|
|
50930
|
+
const onError = (err) => {
|
|
50931
|
+
reject(err);
|
|
50932
|
+
};
|
|
50933
|
+
server.once("error", onError);
|
|
50934
|
+
server.listen(startPort, host, () => {
|
|
50935
|
+
server.removeListener("error", onError);
|
|
50936
|
+
resolve(startPort);
|
|
50937
|
+
});
|
|
50938
|
+
});
|
|
50939
|
+
}
|
|
48998
50940
|
let affinityPort = null;
|
|
48999
50941
|
if (portFileDir) {
|
|
49000
50942
|
try {
|
|
@@ -49137,6 +51079,8 @@ const rebaseEnvSchema = objectType({
|
|
|
49137
51079
|
DB_POOL_MAX: stringType().default("20").transform(Number),
|
|
49138
51080
|
DB_POOL_IDLE_TIMEOUT: stringType().default("30000").transform(Number),
|
|
49139
51081
|
DB_POOL_CONNECT_TIMEOUT: stringType().default("10000").transform(Number),
|
|
51082
|
+
DATABASE_DIRECT_URL: stringType().url().optional(),
|
|
51083
|
+
DATABASE_READ_URL: stringType().url().optional(),
|
|
49140
51084
|
FORCE_LOCAL_STORAGE: optionalBoolString,
|
|
49141
51085
|
STORAGE_TYPE: enumType(["local", "s3"]).default("local"),
|
|
49142
51086
|
STORAGE_PATH: stringType().optional(),
|
|
@@ -49213,8 +51157,11 @@ export {
|
|
|
49213
51157
|
RestApiGenerator,
|
|
49214
51158
|
S3StorageController,
|
|
49215
51159
|
SMTPEmailService,
|
|
51160
|
+
TransformCache,
|
|
51161
|
+
TusHandler,
|
|
49216
51162
|
_resetRebaseMock,
|
|
49217
51163
|
_setRebaseMock,
|
|
51164
|
+
apiKeyKeyGenerator,
|
|
49218
51165
|
authJwt,
|
|
49219
51166
|
authRoles,
|
|
49220
51167
|
authUid,
|
|
@@ -49223,6 +51170,9 @@ export {
|
|
|
49223
51170
|
configureLogLevel,
|
|
49224
51171
|
createAdapterAuthMiddleware,
|
|
49225
51172
|
createAdminRoutes,
|
|
51173
|
+
createApiKeyRateLimiter,
|
|
51174
|
+
createApiKeyRoutes,
|
|
51175
|
+
createApiKeyStore,
|
|
49226
51176
|
createAppleProvider,
|
|
49227
51177
|
createAuthMiddleware,
|
|
49228
51178
|
createAuthRoutes,
|
|
@@ -49260,26 +51210,34 @@ export {
|
|
|
49260
51210
|
getWelcomeEmailTemplate,
|
|
49261
51211
|
hashPassword,
|
|
49262
51212
|
hashRefreshToken,
|
|
51213
|
+
httpMethodToOperation,
|
|
49263
51214
|
initializeRebaseBackend,
|
|
51215
|
+
isApiKeyToken,
|
|
49264
51216
|
isAuthAdapter,
|
|
49265
51217
|
isDatabaseAdapter,
|
|
51218
|
+
isOperationAllowed,
|
|
51219
|
+
isTransformableImage,
|
|
49266
51220
|
listenWithPortRetry,
|
|
49267
51221
|
loadCronJobsFromDirectory,
|
|
49268
51222
|
loadEnv,
|
|
49269
51223
|
loadFunctionsFromDirectory,
|
|
49270
51224
|
logger,
|
|
49271
51225
|
optionalAuth,
|
|
51226
|
+
parseTransformOptions,
|
|
49272
51227
|
rebase,
|
|
49273
51228
|
requestLogger,
|
|
49274
51229
|
requireAdmin,
|
|
49275
51230
|
requireAuth,
|
|
49276
51231
|
resetConsole,
|
|
49277
|
-
|
|
51232
|
+
resolveAuthHooks,
|
|
49278
51233
|
serveSPA,
|
|
49279
51234
|
strictAuthLimiter,
|
|
51235
|
+
transformImage,
|
|
51236
|
+
validateApiKey,
|
|
49280
51237
|
validateCronExpression,
|
|
49281
51238
|
validatePasswordStrength,
|
|
49282
51239
|
verifyAccessToken,
|
|
49283
|
-
verifyPassword
|
|
51240
|
+
verifyPassword,
|
|
51241
|
+
z
|
|
49284
51242
|
};
|
|
49285
51243
|
//# sourceMappingURL=index.es.js.map
|