@rebasepro/server-core 0.2.1 → 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/data/query_builder.d.ts +51 -0
- package/dist/common/src/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 +2340 -370
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2329 -355
- package/dist/index.umd.js.map +1 -1
- package/dist/server-core/src/api/logs-routes.d.ts +37 -0
- package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
- package/dist/server-core/src/api/types.d.ts +6 -1
- package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
- package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
- package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
- package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
- package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
- package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
- package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
- package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
- package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
- package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
- package/dist/server-core/src/auth/index.d.ts +5 -3
- package/dist/server-core/src/auth/interfaces.d.ts +93 -3
- package/dist/server-core/src/auth/jwt.d.ts +3 -1
- package/dist/server-core/src/auth/mfa.d.ts +49 -0
- package/dist/server-core/src/auth/middleware.d.ts +7 -0
- package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
- package/dist/server-core/src/auth/routes.d.ts +3 -3
- package/dist/server-core/src/env.d.ts +6 -0
- package/dist/server-core/src/index.d.ts +1 -0
- package/dist/server-core/src/init.d.ts +3 -3
- package/dist/server-core/src/services/webhook-service.d.ts +29 -0
- package/dist/server-core/src/storage/image-transform.d.ts +48 -0
- package/dist/server-core/src/storage/index.d.ts +3 -0
- package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
- package/dist/types/src/controllers/auth.d.ts +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.d.ts +21 -0
- 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/jest.config.cjs +4 -1
- package/package.json +9 -7
- 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];
|
|
@@ -1165,42 +1148,6 @@ function enumToObjectEntries(enumValues) {
|
|
|
1165
1148
|
});
|
|
1166
1149
|
}
|
|
1167
1150
|
}
|
|
1168
|
-
function getSubcollections(collection) {
|
|
1169
|
-
if (collection.childCollections) {
|
|
1170
|
-
return collection.childCollections() ?? [];
|
|
1171
|
-
}
|
|
1172
|
-
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
|
|
1173
|
-
return collection.subcollections() ?? [];
|
|
1174
|
-
}
|
|
1175
|
-
if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
|
|
1176
|
-
const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
|
|
1177
|
-
return manyRelations.map((r) => {
|
|
1178
|
-
const target = r.target();
|
|
1179
|
-
if (!target) return void 0;
|
|
1180
|
-
const relationKey = r.relationName || target.slug;
|
|
1181
|
-
let customName;
|
|
1182
|
-
if (collection.properties) {
|
|
1183
|
-
const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
|
|
1184
|
-
if (prop && prop[1].name) {
|
|
1185
|
-
customName = prop[1].name;
|
|
1186
|
-
}
|
|
1187
|
-
}
|
|
1188
|
-
const baseOverrides = {
|
|
1189
|
-
slug: relationKey
|
|
1190
|
-
};
|
|
1191
|
-
if (customName) {
|
|
1192
|
-
baseOverrides.name = customName;
|
|
1193
|
-
baseOverrides.singularName = customName;
|
|
1194
|
-
}
|
|
1195
|
-
const targetWithOverrides = {
|
|
1196
|
-
...target,
|
|
1197
|
-
...baseOverrides
|
|
1198
|
-
};
|
|
1199
|
-
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
1200
|
-
}).filter((c) => Boolean(c));
|
|
1201
|
-
}
|
|
1202
|
-
return [];
|
|
1203
|
-
}
|
|
1204
1151
|
function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
1205
1152
|
if (!relation.target) {
|
|
1206
1153
|
throw new Error("Relation is missing a `target` collection.");
|
|
@@ -1232,6 +1179,8 @@ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
|
1232
1179
|
} else {
|
|
1233
1180
|
targetCollection = evaluated;
|
|
1234
1181
|
}
|
|
1182
|
+
} else if (rawTarget && typeof rawTarget === "object") {
|
|
1183
|
+
targetCollection = rawTarget;
|
|
1235
1184
|
}
|
|
1236
1185
|
if (!targetCollection) {
|
|
1237
1186
|
throw new Error("Relation is missing a valid `target` collection.");
|
|
@@ -1288,8 +1237,8 @@ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
|
|
|
1288
1237
|
} catch (e) {
|
|
1289
1238
|
}
|
|
1290
1239
|
if (!foundForeignKey) {
|
|
1291
|
-
const
|
|
1292
|
-
newRelation.foreignKeyOnTarget = generateForeignKeyName(
|
|
1240
|
+
const keyPrefix2 = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
|
|
1241
|
+
newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix2);
|
|
1293
1242
|
}
|
|
1294
1243
|
}
|
|
1295
1244
|
} else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
|
|
@@ -1351,11 +1300,14 @@ function resolveCollectionRelations(collection) {
|
|
|
1351
1300
|
const registeredRelationNames = /* @__PURE__ */ new Set();
|
|
1352
1301
|
if (relCollection.relations) {
|
|
1353
1302
|
relCollection.relations.forEach((relation) => {
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1303
|
+
try {
|
|
1304
|
+
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
1305
|
+
const relationKey = normalizedRelation.relationName;
|
|
1306
|
+
if (relationKey) {
|
|
1307
|
+
relations[relationKey] = normalizedRelation;
|
|
1308
|
+
registeredRelationNames.add(relationKey);
|
|
1309
|
+
}
|
|
1310
|
+
} catch (e) {
|
|
1359
1311
|
}
|
|
1360
1312
|
});
|
|
1361
1313
|
}
|
|
@@ -1403,12 +1355,8 @@ function resolvePropertyRelation({
|
|
|
1403
1355
|
overrides: relProp.overrides
|
|
1404
1356
|
};
|
|
1405
1357
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
1409
|
-
return void 0;
|
|
1410
|
-
}
|
|
1411
|
-
return relation;
|
|
1358
|
+
console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
1359
|
+
return void 0;
|
|
1412
1360
|
}
|
|
1413
1361
|
function getTableName(collection) {
|
|
1414
1362
|
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
@@ -1424,6 +1372,43 @@ function findRelation(resolvedRelations, key) {
|
|
|
1424
1372
|
if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
|
|
1425
1373
|
return void 0;
|
|
1426
1374
|
}
|
|
1375
|
+
function getSubcollections(collection) {
|
|
1376
|
+
if (collection.childCollections) {
|
|
1377
|
+
return collection.childCollections() ?? [];
|
|
1378
|
+
}
|
|
1379
|
+
if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
|
|
1380
|
+
return collection.subcollections() ?? [];
|
|
1381
|
+
}
|
|
1382
|
+
if (getDataSourceCapabilities(collection.driver).supportsRelations) {
|
|
1383
|
+
const resolvedRelations = resolveCollectionRelations(collection);
|
|
1384
|
+
const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
|
|
1385
|
+
return manyRelations.map((r) => {
|
|
1386
|
+
const target = r.target();
|
|
1387
|
+
if (!target) return void 0;
|
|
1388
|
+
const relationKey = r.relationName || target.slug;
|
|
1389
|
+
let customName;
|
|
1390
|
+
if (collection.properties) {
|
|
1391
|
+
const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
|
|
1392
|
+
if (prop && prop[1].name) {
|
|
1393
|
+
customName = prop[1].name;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const baseOverrides = {
|
|
1397
|
+
slug: relationKey
|
|
1398
|
+
};
|
|
1399
|
+
if (customName) {
|
|
1400
|
+
baseOverrides.name = customName;
|
|
1401
|
+
baseOverrides.singularName = customName;
|
|
1402
|
+
}
|
|
1403
|
+
const targetWithOverrides = {
|
|
1404
|
+
...target,
|
|
1405
|
+
...baseOverrides
|
|
1406
|
+
};
|
|
1407
|
+
return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
|
|
1408
|
+
}).filter((c) => Boolean(c));
|
|
1409
|
+
}
|
|
1410
|
+
return [];
|
|
1411
|
+
}
|
|
1427
1412
|
var logic = { exports: {} };
|
|
1428
1413
|
(function(module, exports) {
|
|
1429
1414
|
(function(root, factory) {
|
|
@@ -2331,8 +2316,18 @@ class CollectionRegistry {
|
|
|
2331
2316
|
const mergedRelationsRaw = [...extractedRelations];
|
|
2332
2317
|
for (const manual of manualRelations) {
|
|
2333
2318
|
const name2 = manual.relationName;
|
|
2334
|
-
if (!name2
|
|
2319
|
+
if (!name2) {
|
|
2335
2320
|
mergedRelationsRaw.push(manual);
|
|
2321
|
+
} else {
|
|
2322
|
+
const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name2);
|
|
2323
|
+
if (existingIndex === -1) {
|
|
2324
|
+
mergedRelationsRaw.push(manual);
|
|
2325
|
+
} else {
|
|
2326
|
+
mergedRelationsRaw[existingIndex] = {
|
|
2327
|
+
...manual,
|
|
2328
|
+
...mergedRelationsRaw[existingIndex]
|
|
2329
|
+
};
|
|
2330
|
+
}
|
|
2336
2331
|
}
|
|
2337
2332
|
}
|
|
2338
2333
|
let mergedRelations = mergedRelationsRaw;
|
|
@@ -2552,6 +2547,118 @@ class CollectionRegistry {
|
|
|
2552
2547
|
};
|
|
2553
2548
|
}
|
|
2554
2549
|
}
|
|
2550
|
+
function mapOperator$1(op) {
|
|
2551
|
+
switch (op) {
|
|
2552
|
+
case "==":
|
|
2553
|
+
return "eq";
|
|
2554
|
+
case "!=":
|
|
2555
|
+
return "neq";
|
|
2556
|
+
case ">":
|
|
2557
|
+
return "gt";
|
|
2558
|
+
case ">=":
|
|
2559
|
+
return "gte";
|
|
2560
|
+
case "<":
|
|
2561
|
+
return "lt";
|
|
2562
|
+
case "<=":
|
|
2563
|
+
return "lte";
|
|
2564
|
+
case "array-contains":
|
|
2565
|
+
return "cs";
|
|
2566
|
+
case "array-contains-any":
|
|
2567
|
+
return "csa";
|
|
2568
|
+
case "not-in":
|
|
2569
|
+
return "nin";
|
|
2570
|
+
default:
|
|
2571
|
+
return op;
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
class QueryBuilder {
|
|
2575
|
+
constructor(collection) {
|
|
2576
|
+
this.collection = collection;
|
|
2577
|
+
}
|
|
2578
|
+
params = {
|
|
2579
|
+
where: {}
|
|
2580
|
+
};
|
|
2581
|
+
/**
|
|
2582
|
+
* Add a filter condition to your query.
|
|
2583
|
+
* @example
|
|
2584
|
+
* client.collection('users').where('age', '>=', 18).find()
|
|
2585
|
+
*/
|
|
2586
|
+
where(column, operator, value) {
|
|
2587
|
+
if (!this.params.where) {
|
|
2588
|
+
this.params.where = {};
|
|
2589
|
+
}
|
|
2590
|
+
const mappedOp = mapOperator$1(operator);
|
|
2591
|
+
let formattedValue = value;
|
|
2592
|
+
if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
|
|
2593
|
+
formattedValue = `(${value.join(",")})`;
|
|
2594
|
+
} else if (value === null) {
|
|
2595
|
+
formattedValue = "null";
|
|
2596
|
+
}
|
|
2597
|
+
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
2598
|
+
return this;
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Order the results by a specific column.
|
|
2602
|
+
* @example
|
|
2603
|
+
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
2604
|
+
*/
|
|
2605
|
+
orderBy(column, ascending = "asc") {
|
|
2606
|
+
this.params.orderBy = `${column}:${ascending}`;
|
|
2607
|
+
return this;
|
|
2608
|
+
}
|
|
2609
|
+
/**
|
|
2610
|
+
* Limit the number of results returned.
|
|
2611
|
+
*/
|
|
2612
|
+
limit(count) {
|
|
2613
|
+
this.params.limit = count;
|
|
2614
|
+
return this;
|
|
2615
|
+
}
|
|
2616
|
+
/**
|
|
2617
|
+
* Skip the first N results.
|
|
2618
|
+
*/
|
|
2619
|
+
offset(count) {
|
|
2620
|
+
this.params.offset = count;
|
|
2621
|
+
return this;
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* Set a free-text search string if supported by the backend.
|
|
2625
|
+
*/
|
|
2626
|
+
search(searchString) {
|
|
2627
|
+
this.params.searchString = searchString;
|
|
2628
|
+
return this;
|
|
2629
|
+
}
|
|
2630
|
+
/**
|
|
2631
|
+
* Include related entities in the response.
|
|
2632
|
+
* Relations will be populated with full entity data instead of just IDs.
|
|
2633
|
+
*
|
|
2634
|
+
* @param relations - Relation names to include, or "*" for all.
|
|
2635
|
+
* @example
|
|
2636
|
+
* // Include specific relations
|
|
2637
|
+
* client.data.posts.include("tags", "author").find()
|
|
2638
|
+
*
|
|
2639
|
+
* // Include all relations
|
|
2640
|
+
* client.data.posts.include("*").find()
|
|
2641
|
+
*/
|
|
2642
|
+
include(...relations) {
|
|
2643
|
+
this.params.include = relations;
|
|
2644
|
+
return this;
|
|
2645
|
+
}
|
|
2646
|
+
/**
|
|
2647
|
+
* Execute the find query and return the results.
|
|
2648
|
+
*/
|
|
2649
|
+
async find() {
|
|
2650
|
+
return this.collection.find(this.params);
|
|
2651
|
+
}
|
|
2652
|
+
/**
|
|
2653
|
+
* Listen to realtime updates matching this query.
|
|
2654
|
+
*/
|
|
2655
|
+
listen(onUpdate, onError) {
|
|
2656
|
+
if (!this.collection.listen) {
|
|
2657
|
+
throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
2658
|
+
}
|
|
2659
|
+
return this.collection.listen(this.params, onUpdate, onError);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2555
2662
|
class BackendCollectionRegistry extends CollectionRegistry {
|
|
2556
2663
|
/**
|
|
2557
2664
|
* Get the available relation keys for a given collection path.
|
|
@@ -2731,10 +2838,15 @@ const errorHandler = (err, c) => {
|
|
|
2731
2838
|
const issue = error2.code === "42703" ? "column" : "table";
|
|
2732
2839
|
logMessage = `Database schema mismatch (${issue} missing): ${error2.message}. Did you forget to run migrations ('pnpm db:push' or 'pnpm db:migrate')?`;
|
|
2733
2840
|
}
|
|
2734
|
-
console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
|
|
2735
2841
|
const causePg = error2.cause && typeof error2.cause === "object" ? error2.cause : void 0;
|
|
2736
2842
|
const pgErrorCode = causePg?.code || error2.code;
|
|
2737
|
-
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";
|
|
2738
2850
|
if (!suppressStack) {
|
|
2739
2851
|
console.error(error2.stack || error2);
|
|
2740
2852
|
}
|
|
@@ -2779,7 +2891,7 @@ function codeToStatus(code2) {
|
|
|
2779
2891
|
};
|
|
2780
2892
|
return map2[code2];
|
|
2781
2893
|
}
|
|
2782
|
-
function mapOperator
|
|
2894
|
+
function mapOperator(op) {
|
|
2783
2895
|
switch (op) {
|
|
2784
2896
|
case "eq":
|
|
2785
2897
|
return "==";
|
|
@@ -2815,7 +2927,7 @@ function parseQueryOptions(query) {
|
|
|
2815
2927
|
options2.offset = (page - 1) * limit;
|
|
2816
2928
|
}
|
|
2817
2929
|
options2.where = {};
|
|
2818
|
-
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"];
|
|
2819
2931
|
for (const [key, rawValue] of Object.entries(query)) {
|
|
2820
2932
|
if (reservedQueryKeys.includes(key)) continue;
|
|
2821
2933
|
const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
|
|
@@ -2824,7 +2936,7 @@ function parseQueryOptions(query) {
|
|
|
2824
2936
|
if (parts.length >= 2) {
|
|
2825
2937
|
const op = parts[0];
|
|
2826
2938
|
const val = parts.slice(1).join(".");
|
|
2827
|
-
const rebaseOp = mapOperator
|
|
2939
|
+
const rebaseOp = mapOperator(op);
|
|
2828
2940
|
if (rebaseOp) {
|
|
2829
2941
|
let parsedVal = val;
|
|
2830
2942
|
if (val === "true") parsedVal = true;
|
|
@@ -2887,8 +2999,63 @@ function parseQueryOptions(query) {
|
|
|
2887
2999
|
const fieldsStr = String(query.fields).trim();
|
|
2888
3000
|
options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
2889
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
|
+
}
|
|
2890
3031
|
return options2;
|
|
2891
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
|
+
}
|
|
2892
3059
|
class RestApiGenerator {
|
|
2893
3060
|
collections;
|
|
2894
3061
|
router;
|
|
@@ -2921,6 +3088,19 @@ class RestApiGenerator {
|
|
|
2921
3088
|
this.createSubcollectionRoutes();
|
|
2922
3089
|
return this.router;
|
|
2923
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
|
+
}
|
|
2924
3104
|
/**
|
|
2925
3105
|
* Get the typed RestFetchService from a driver if it exposes one (for include support).
|
|
2926
3106
|
*/
|
|
@@ -2934,6 +3114,7 @@ class RestApiGenerator {
|
|
|
2934
3114
|
const basePath = `/${collection.slug}`;
|
|
2935
3115
|
const resolvedCollection = collection;
|
|
2936
3116
|
this.router.get(`${basePath}/count`, async (c) => {
|
|
3117
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
2937
3118
|
const queryDict = c.req.query();
|
|
2938
3119
|
const queryOptions = parseQueryOptions(queryDict);
|
|
2939
3120
|
const searchString = queryDict.searchString;
|
|
@@ -2944,6 +3125,7 @@ class RestApiGenerator {
|
|
|
2944
3125
|
});
|
|
2945
3126
|
});
|
|
2946
3127
|
this.router.get(basePath, async (c) => {
|
|
3128
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
2947
3129
|
const queryDict = c.req.query();
|
|
2948
3130
|
const queryOptions = parseQueryOptions(queryDict);
|
|
2949
3131
|
const searchString = queryDict.searchString;
|
|
@@ -2958,7 +3140,8 @@ class RestApiGenerator {
|
|
|
2958
3140
|
offset: queryOptions.offset,
|
|
2959
3141
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
2960
3142
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
2961
|
-
searchString
|
|
3143
|
+
searchString,
|
|
3144
|
+
vectorSearch: queryOptions.vectorSearch
|
|
2962
3145
|
}, queryOptions.include);
|
|
2963
3146
|
entities2 = await this.applyAfterReadBatch(collection.slug, entities2, hookCtx);
|
|
2964
3147
|
const total2 = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
@@ -2986,6 +3169,7 @@ class RestApiGenerator {
|
|
|
2986
3169
|
});
|
|
2987
3170
|
});
|
|
2988
3171
|
this.router.get(`${basePath}/:id`, async (c) => {
|
|
3172
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
2989
3173
|
const id = c.req.param("id");
|
|
2990
3174
|
const queryDict = c.req.query();
|
|
2991
3175
|
const queryOptions = parseQueryOptions(queryDict);
|
|
@@ -3016,6 +3200,7 @@ class RestApiGenerator {
|
|
|
3016
3200
|
});
|
|
3017
3201
|
this.router.post(basePath, async (c) => {
|
|
3018
3202
|
try {
|
|
3203
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3019
3204
|
const driver = c.get("driver") || this.driver;
|
|
3020
3205
|
const path2 = collection.slug;
|
|
3021
3206
|
const hookCtx = this.buildHookContext(c, "POST");
|
|
@@ -3044,6 +3229,7 @@ class RestApiGenerator {
|
|
|
3044
3229
|
});
|
|
3045
3230
|
this.router.put(`${basePath}/:id`, async (c) => {
|
|
3046
3231
|
try {
|
|
3232
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3047
3233
|
const id = c.req.param("id");
|
|
3048
3234
|
const driver = c.get("driver") || this.driver;
|
|
3049
3235
|
const hookCtx = this.buildHookContext(c, "PUT");
|
|
@@ -3080,6 +3266,7 @@ class RestApiGenerator {
|
|
|
3080
3266
|
}
|
|
3081
3267
|
});
|
|
3082
3268
|
this.router.delete(`${basePath}/:id`, async (c) => {
|
|
3269
|
+
this.enforceApiKeyPermission(c, collection.slug);
|
|
3083
3270
|
const id = c.req.param("id");
|
|
3084
3271
|
const driver = c.get("driver") || this.driver;
|
|
3085
3272
|
const hookCtx = this.buildHookContext(c, "DELETE");
|
|
@@ -3151,6 +3338,7 @@ class RestApiGenerator {
|
|
|
3151
3338
|
const parsed = parseSubPath(rawPath);
|
|
3152
3339
|
if (!parsed) return next();
|
|
3153
3340
|
const driver = c.get("driver") || this.driver;
|
|
3341
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3154
3342
|
if (parsed.entityId === "count") {
|
|
3155
3343
|
const queryDict = c.req.query();
|
|
3156
3344
|
const queryOptions = parseQueryOptions(queryDict);
|
|
@@ -3198,6 +3386,7 @@ class RestApiGenerator {
|
|
|
3198
3386
|
const parsed = parseSubPath(rawPath);
|
|
3199
3387
|
if (!parsed || parsed.entityId) return next();
|
|
3200
3388
|
const driver = c.get("driver") || this.driver;
|
|
3389
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3201
3390
|
const body = await c.req.json().catch(() => ({}));
|
|
3202
3391
|
const entity = await driver.saveEntity({
|
|
3203
3392
|
path: parsed.collectionPath,
|
|
@@ -3213,6 +3402,7 @@ class RestApiGenerator {
|
|
|
3213
3402
|
const parsed = parseSubPath(rawPath);
|
|
3214
3403
|
if (!parsed || !parsed.entityId) return next();
|
|
3215
3404
|
const driver = c.get("driver") || this.driver;
|
|
3405
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3216
3406
|
const body = await c.req.json().catch(() => ({}));
|
|
3217
3407
|
const entity = await driver.saveEntity({
|
|
3218
3408
|
path: parsed.collectionPath,
|
|
@@ -3229,6 +3419,7 @@ class RestApiGenerator {
|
|
|
3229
3419
|
const parsed = parseSubPath(rawPath);
|
|
3230
3420
|
if (!parsed || !parsed.entityId) return next();
|
|
3231
3421
|
const driver = c.get("driver") || this.driver;
|
|
3422
|
+
this.enforceApiKeyPermission(c, c.req.param("parent"));
|
|
3232
3423
|
const existingEntity = await driver.fetchEntity({
|
|
3233
3424
|
path: parsed.collectionPath,
|
|
3234
3425
|
entityId: parsed.entityId
|
|
@@ -3294,7 +3485,8 @@ class RestApiGenerator {
|
|
|
3294
3485
|
orderBy: queryOptions.orderBy?.[0]?.field,
|
|
3295
3486
|
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
3296
3487
|
startAfter: queryOptions.offset ? String(queryOptions.offset) : void 0,
|
|
3297
|
-
searchString
|
|
3488
|
+
searchString,
|
|
3489
|
+
vectorSearch: queryOptions.vectorSearch
|
|
3298
3490
|
});
|
|
3299
3491
|
return entities.map((entity) => this.flattenEntity(entity));
|
|
3300
3492
|
}
|
|
@@ -4820,7 +5012,7 @@ var cmp_1 = cmp$1;
|
|
|
4820
5012
|
const SemVer$6 = semver$4;
|
|
4821
5013
|
const parse$6 = parse_1;
|
|
4822
5014
|
const { safeRe: re, t } = reExports;
|
|
4823
|
-
const coerce$
|
|
5015
|
+
const coerce$2 = (version2, options2) => {
|
|
4824
5016
|
if (version2 instanceof SemVer$6) {
|
|
4825
5017
|
return version2;
|
|
4826
5018
|
}
|
|
@@ -4855,7 +5047,7 @@ const coerce$1 = (version2, options2) => {
|
|
|
4855
5047
|
const build = options2.includePrerelease && match[6] ? `+${match[6]}` : "";
|
|
4856
5048
|
return parse$6(`${major2}.${minor2}.${patch2}${prerelease2}${build}`, options2);
|
|
4857
5049
|
};
|
|
4858
|
-
var coerce_1 = coerce$
|
|
5050
|
+
var coerce_1 = coerce$2;
|
|
4859
5051
|
const parse$5 = parse_1;
|
|
4860
5052
|
const constants$1 = constants$2;
|
|
4861
5053
|
const SemVer$5 = semver$4;
|
|
@@ -5141,19 +5333,19 @@ function requireRange() {
|
|
|
5141
5333
|
const replaceCaret = (comp, options2) => {
|
|
5142
5334
|
debug2("caret", comp, options2);
|
|
5143
5335
|
const r = options2.loose ? re2[t2.CARETLOOSE] : re2[t2.CARET];
|
|
5144
|
-
const
|
|
5336
|
+
const z2 = options2.includePrerelease ? "-0" : "";
|
|
5145
5337
|
return comp.replace(r, (_, M, m2, p, pr) => {
|
|
5146
5338
|
debug2("caret", comp, _, M, m2, p, pr);
|
|
5147
5339
|
let ret;
|
|
5148
5340
|
if (isX(M)) {
|
|
5149
5341
|
ret = "";
|
|
5150
5342
|
} else if (isX(m2)) {
|
|
5151
|
-
ret = `>=${M}.0.0${
|
|
5343
|
+
ret = `>=${M}.0.0${z2} <${+M + 1}.0.0-0`;
|
|
5152
5344
|
} else if (isX(p)) {
|
|
5153
5345
|
if (M === "0") {
|
|
5154
|
-
ret = `>=${M}.${m2}.0${
|
|
5346
|
+
ret = `>=${M}.${m2}.0${z2} <${M}.${+m2 + 1}.0-0`;
|
|
5155
5347
|
} else {
|
|
5156
|
-
ret = `>=${M}.${m2}.0${
|
|
5348
|
+
ret = `>=${M}.${m2}.0${z2} <${+M + 1}.0.0-0`;
|
|
5157
5349
|
}
|
|
5158
5350
|
} else if (pr) {
|
|
5159
5351
|
debug2("replaceCaret pr", pr);
|
|
@@ -5170,9 +5362,9 @@ function requireRange() {
|
|
|
5170
5362
|
debug2("no pr");
|
|
5171
5363
|
if (M === "0") {
|
|
5172
5364
|
if (m2 === "0") {
|
|
5173
|
-
ret = `>=${M}.${m2}.${p}${
|
|
5365
|
+
ret = `>=${M}.${m2}.${p}${z2} <${M}.${m2}.${+p + 1}-0`;
|
|
5174
5366
|
} else {
|
|
5175
|
-
ret = `>=${M}.${m2}.${p}${
|
|
5367
|
+
ret = `>=${M}.${m2}.${p}${z2} <${M}.${+m2 + 1}.0-0`;
|
|
5176
5368
|
}
|
|
5177
5369
|
} else {
|
|
5178
5370
|
ret = `>=${M}.${m2}.${p} <${+M + 1}.0.0-0`;
|
|
@@ -5829,7 +6021,7 @@ const neq = neq_1;
|
|
|
5829
6021
|
const gte = gte_1;
|
|
5830
6022
|
const lte = lte_1;
|
|
5831
6023
|
const cmp = cmp_1;
|
|
5832
|
-
const coerce = coerce_1;
|
|
6024
|
+
const coerce$1 = coerce_1;
|
|
5833
6025
|
const truncate = truncate_1;
|
|
5834
6026
|
const Comparator = requireComparator();
|
|
5835
6027
|
const Range = requireRange();
|
|
@@ -5868,7 +6060,7 @@ var semver$3 = {
|
|
|
5868
6060
|
gte,
|
|
5869
6061
|
lte,
|
|
5870
6062
|
cmp,
|
|
5871
|
-
coerce,
|
|
6063
|
+
coerce: coerce$1,
|
|
5872
6064
|
truncate,
|
|
5873
6065
|
Comparator,
|
|
5874
6066
|
Range,
|
|
@@ -6749,7 +6941,7 @@ var jsonwebtoken = {
|
|
|
6749
6941
|
NotBeforeError: NotBeforeError_1,
|
|
6750
6942
|
TokenExpiredError: TokenExpiredError_1
|
|
6751
6943
|
};
|
|
6752
|
-
const jwt = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
|
|
6944
|
+
const jwt$1 = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
|
|
6753
6945
|
let jwtConfig = {
|
|
6754
6946
|
secret: "",
|
|
6755
6947
|
accessExpiresIn: "1h",
|
|
@@ -6768,15 +6960,17 @@ function configureJwt(config) {
|
|
|
6768
6960
|
...config
|
|
6769
6961
|
};
|
|
6770
6962
|
}
|
|
6771
|
-
function generateAccessToken(userId, roles) {
|
|
6963
|
+
function generateAccessToken(userId, roles, aal = "aal1", customClaims) {
|
|
6772
6964
|
if (!jwtConfig.secret) {
|
|
6773
6965
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
6774
6966
|
}
|
|
6775
6967
|
const payload = {
|
|
6776
6968
|
userId,
|
|
6777
|
-
roles
|
|
6969
|
+
roles,
|
|
6970
|
+
aal,
|
|
6971
|
+
...customClaims
|
|
6778
6972
|
};
|
|
6779
|
-
return jwt.sign(payload, jwtConfig.secret, {
|
|
6973
|
+
return jwt$1.sign(payload, jwtConfig.secret, {
|
|
6780
6974
|
expiresIn: jwtConfig.accessExpiresIn,
|
|
6781
6975
|
algorithm: "HS256"
|
|
6782
6976
|
});
|
|
@@ -6810,7 +7004,7 @@ function verifyAccessToken(token) {
|
|
|
6810
7004
|
throw new Error("JWT secret not configured. Call configureJwt() first.");
|
|
6811
7005
|
}
|
|
6812
7006
|
try {
|
|
6813
|
-
const decoded = jwt.verify(token, jwtConfig.secret, {
|
|
7007
|
+
const decoded = jwt$1.verify(token, jwtConfig.secret, {
|
|
6814
7008
|
algorithms: ["HS256"]
|
|
6815
7009
|
});
|
|
6816
7010
|
const id = decoded.userId || decoded.uid || decoded.sub;
|
|
@@ -6818,9 +7012,11 @@ function verifyAccessToken(token) {
|
|
|
6818
7012
|
console.error("[JWT] Verification failed: missing id in payload", decoded);
|
|
6819
7013
|
return null;
|
|
6820
7014
|
}
|
|
7015
|
+
const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
|
|
6821
7016
|
return {
|
|
6822
7017
|
userId: id,
|
|
6823
|
-
roles: decoded.roles || []
|
|
7018
|
+
roles: decoded.roles || [],
|
|
7019
|
+
aal
|
|
6824
7020
|
};
|
|
6825
7021
|
} catch (error2) {
|
|
6826
7022
|
console.error("[JWT] Verification failed:", error2, "Token start:", token.substring(0, 15));
|
|
@@ -6860,6 +7056,17 @@ function getRefreshTokenExpiry() {
|
|
|
6860
7056
|
}
|
|
6861
7057
|
return new Date(Date.now() + ms2);
|
|
6862
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" }));
|
|
6863
7070
|
function isRLSScopedDriver(driver) {
|
|
6864
7071
|
return "withAuth" in driver && typeof driver.withAuth === "function";
|
|
6865
7072
|
}
|
|
@@ -6882,6 +7089,81 @@ function safeCompare(a2, b) {
|
|
|
6882
7089
|
return false;
|
|
6883
7090
|
}
|
|
6884
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
|
+
}
|
|
6885
7167
|
const requireAuth = async (c, next) => {
|
|
6886
7168
|
const authHeader = c.req.header("authorization");
|
|
6887
7169
|
const queryToken = c.req.query("token");
|
|
@@ -6988,7 +7270,8 @@ function createAuthMiddleware(options2) {
|
|
|
6988
7270
|
driver,
|
|
6989
7271
|
requireAuth: enforceAuth = true,
|
|
6990
7272
|
validator,
|
|
6991
|
-
serviceKey
|
|
7273
|
+
serviceKey,
|
|
7274
|
+
apiKeyStore
|
|
6992
7275
|
} = options2;
|
|
6993
7276
|
return async (c, next) => {
|
|
6994
7277
|
if (validator) {
|
|
@@ -7063,6 +7346,14 @@ function createAuthMiddleware(options2) {
|
|
|
7063
7346
|
}
|
|
7064
7347
|
}, 500);
|
|
7065
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
|
+
}
|
|
7066
7357
|
} else {
|
|
7067
7358
|
const payload = extractUserFromToken(token);
|
|
7068
7359
|
if (payload) {
|
|
@@ -7123,9 +7414,22 @@ function createAdapterAuthMiddleware(options2) {
|
|
|
7123
7414
|
const {
|
|
7124
7415
|
adapter,
|
|
7125
7416
|
driver,
|
|
7126
|
-
requireAuth: enforceAuth = true
|
|
7417
|
+
requireAuth: enforceAuth = true,
|
|
7418
|
+
apiKeyStore
|
|
7127
7419
|
} = options2;
|
|
7128
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
|
+
}
|
|
7129
7433
|
let authenticatedUser = null;
|
|
7130
7434
|
try {
|
|
7131
7435
|
authenticatedUser = await adapter.verifyRequest(c.req.raw);
|
|
@@ -7221,11 +7525,11 @@ async function verifyPassword(password, storedHash) {
|
|
|
7221
7525
|
const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
|
|
7222
7526
|
return timingSafeEqual$1(derivedKey, storedKey);
|
|
7223
7527
|
}
|
|
7224
|
-
function
|
|
7528
|
+
function resolveAuthHooks(hooks) {
|
|
7225
7529
|
return {
|
|
7226
|
-
hashPassword:
|
|
7227
|
-
verifyPassword:
|
|
7228
|
-
validatePasswordStrength:
|
|
7530
|
+
hashPassword: hooks?.hashPassword ?? hashPassword,
|
|
7531
|
+
verifyPassword: hooks?.verifyPassword ?? verifyPassword,
|
|
7532
|
+
validatePasswordStrength: hooks?.validatePasswordStrength ?? validatePasswordStrength
|
|
7229
7533
|
};
|
|
7230
7534
|
}
|
|
7231
7535
|
function getGreeting(user) {
|
|
@@ -7627,6 +7931,63 @@ const strictAuthLimiter = createRateLimiter({
|
|
|
7627
7931
|
limit: 50,
|
|
7628
7932
|
message: "Too many requests to this sensitive endpoint, please try again later."
|
|
7629
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
|
+
}
|
|
7630
7991
|
var util$3;
|
|
7631
7992
|
(function(util2) {
|
|
7632
7993
|
util2.assertEqual = (_) => {
|
|
@@ -7777,6 +8138,10 @@ const ZodIssueCode = util$3.arrayToEnum([
|
|
|
7777
8138
|
"not_multiple_of",
|
|
7778
8139
|
"not_finite"
|
|
7779
8140
|
]);
|
|
8141
|
+
const quotelessJson = (obj) => {
|
|
8142
|
+
const json = JSON.stringify(obj, null, 2);
|
|
8143
|
+
return json.replace(/"([^"]+)":/g, "$1:");
|
|
8144
|
+
};
|
|
7780
8145
|
class ZodError extends Error {
|
|
7781
8146
|
get errors() {
|
|
7782
8147
|
return this.issues;
|
|
@@ -7972,6 +8337,9 @@ const errorMap = (issue, _ctx) => {
|
|
|
7972
8337
|
return { message };
|
|
7973
8338
|
};
|
|
7974
8339
|
let overrideErrorMap = errorMap;
|
|
8340
|
+
function setErrorMap(map2) {
|
|
8341
|
+
overrideErrorMap = map2;
|
|
8342
|
+
}
|
|
7975
8343
|
function getErrorMap() {
|
|
7976
8344
|
return overrideErrorMap;
|
|
7977
8345
|
}
|
|
@@ -8000,6 +8368,7 @@ const makeIssue = (params) => {
|
|
|
8000
8368
|
message: errorMessage
|
|
8001
8369
|
};
|
|
8002
8370
|
};
|
|
8371
|
+
const EMPTY_PATH = [];
|
|
8003
8372
|
function addIssueToContext(ctx, issueData) {
|
|
8004
8373
|
const overrideMap = getErrorMap();
|
|
8005
8374
|
const issue = makeIssue({
|
|
@@ -10290,6 +10659,113 @@ ZodUnion.create = (types2, params) => {
|
|
|
10290
10659
|
...processCreateParams(params)
|
|
10291
10660
|
});
|
|
10292
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
|
+
}
|
|
10293
10769
|
function mergeValues(a2, b) {
|
|
10294
10770
|
const aType = getParsedType(a2);
|
|
10295
10771
|
const bType = getParsedType(b);
|
|
@@ -10448,6 +10924,59 @@ ZodTuple.create = (schemas, params) => {
|
|
|
10448
10924
|
...processCreateParams(params)
|
|
10449
10925
|
});
|
|
10450
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
|
+
}
|
|
10451
10980
|
class ZodMap extends ZodType {
|
|
10452
10981
|
get keySchema() {
|
|
10453
10982
|
return this._def.keyType;
|
|
@@ -10599,6 +11128,111 @@ ZodSet.create = (valueType, params) => {
|
|
|
10599
11128
|
...processCreateParams(params)
|
|
10600
11129
|
});
|
|
10601
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
|
+
}
|
|
10602
11236
|
class ZodLazy extends ZodType {
|
|
10603
11237
|
get schema() {
|
|
10604
11238
|
return this._def.getter();
|
|
@@ -11056,6 +11690,7 @@ ZodNaN.create = (params) => {
|
|
|
11056
11690
|
...processCreateParams(params)
|
|
11057
11691
|
});
|
|
11058
11692
|
};
|
|
11693
|
+
const BRAND = Symbol("zod_brand");
|
|
11059
11694
|
class ZodBranded extends ZodType {
|
|
11060
11695
|
_parse(input) {
|
|
11061
11696
|
const { ctx } = this._processInputParams(input);
|
|
@@ -11147,6 +11782,36 @@ ZodReadonly.create = (type, params) => {
|
|
|
11147
11782
|
...processCreateParams(params)
|
|
11148
11783
|
});
|
|
11149
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
|
+
};
|
|
11150
11815
|
var ZodFirstPartyTypeKind;
|
|
11151
11816
|
(function(ZodFirstPartyTypeKind2) {
|
|
11152
11817
|
ZodFirstPartyTypeKind2["ZodString"] = "ZodString";
|
|
@@ -11186,17 +11851,251 @@ var ZodFirstPartyTypeKind;
|
|
|
11186
11851
|
ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
|
|
11187
11852
|
ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
|
|
11188
11853
|
})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
|
|
11854
|
+
const instanceOfType = (cls, params = {
|
|
11855
|
+
message: `Input not instance of ${cls.name}`
|
|
11856
|
+
}) => custom((data) => data instanceof cls, params);
|
|
11189
11857
|
const stringType = ZodString.create;
|
|
11190
|
-
|
|
11191
|
-
|
|
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;
|
|
11192
11871
|
const objectType = ZodObject.create;
|
|
11193
|
-
|
|
11194
|
-
|
|
11195
|
-
|
|
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;
|
|
11196
11883
|
const enumType = ZodEnum.create;
|
|
11197
|
-
|
|
11198
|
-
|
|
11199
|
-
|
|
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
|
+
}
|
|
11200
12099
|
function buildAuthResponse(user, roleIds, accessToken, refreshToken) {
|
|
11201
12100
|
return {
|
|
11202
12101
|
user: {
|
|
@@ -11234,9 +12133,9 @@ function createAuthRoutes(config) {
|
|
|
11234
12133
|
emailService,
|
|
11235
12134
|
emailConfig,
|
|
11236
12135
|
allowRegistration = false,
|
|
11237
|
-
|
|
12136
|
+
authHooks
|
|
11238
12137
|
} = config;
|
|
11239
|
-
const ops =
|
|
12138
|
+
const ops = resolveAuthHooks(authHooks);
|
|
11240
12139
|
const registerSchema = objectType({
|
|
11241
12140
|
email: stringType().email("Invalid email address").max(255),
|
|
11242
12141
|
password: stringType().min(1, "Password is required").max(128),
|
|
@@ -11300,7 +12199,19 @@ function createAuthRoutes(config) {
|
|
|
11300
12199
|
async function createSessionAndTokens(userId, userAgent, ipAddress) {
|
|
11301
12200
|
const roles = await authRepo.getUserRoles(userId);
|
|
11302
12201
|
const roleIds = roles.map((r) => r.id);
|
|
11303
|
-
|
|
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);
|
|
11304
12215
|
const refreshToken = generateRefreshToken();
|
|
11305
12216
|
await authRepo.createRefreshToken(userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), userAgent, ipAddress);
|
|
11306
12217
|
return {
|
|
@@ -11335,8 +12246,8 @@ function createAuthRoutes(config) {
|
|
|
11335
12246
|
passwordHash,
|
|
11336
12247
|
displayName: displayName || void 0
|
|
11337
12248
|
};
|
|
11338
|
-
if (
|
|
11339
|
-
createData = await
|
|
12249
|
+
if (authHooks?.beforeUserCreate) {
|
|
12250
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
11340
12251
|
}
|
|
11341
12252
|
const user = await authRepo.createUser(createData);
|
|
11342
12253
|
const existingUsers = await authRepo.listUsers();
|
|
@@ -11355,14 +12266,16 @@ function createAuthRoutes(config) {
|
|
|
11355
12266
|
email: user.email,
|
|
11356
12267
|
displayName: user.displayName
|
|
11357
12268
|
});
|
|
11358
|
-
if (
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
})
|
|
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
|
+
}
|
|
11362
12275
|
}
|
|
11363
|
-
if (
|
|
11364
|
-
|
|
11365
|
-
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);
|
|
11366
12279
|
});
|
|
11367
12280
|
}
|
|
11368
12281
|
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
|
|
@@ -11372,9 +12285,12 @@ function createAuthRoutes(config) {
|
|
|
11372
12285
|
email,
|
|
11373
12286
|
password
|
|
11374
12287
|
} = parseBody2(loginSchema, await c.req.json());
|
|
12288
|
+
if (authHooks?.beforeLogin) {
|
|
12289
|
+
await authHooks.beforeLogin(email, "login");
|
|
12290
|
+
}
|
|
11375
12291
|
let user;
|
|
11376
|
-
if (
|
|
11377
|
-
user = await
|
|
12292
|
+
if (authHooks?.verifyCredentials) {
|
|
12293
|
+
user = await authHooks.verifyCredentials(email, password, authRepo);
|
|
11378
12294
|
if (!user) {
|
|
11379
12295
|
throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
|
|
11380
12296
|
}
|
|
@@ -11396,9 +12312,9 @@ function createAuthRoutes(config) {
|
|
|
11396
12312
|
accessToken,
|
|
11397
12313
|
refreshToken
|
|
11398
12314
|
} = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
|
|
11399
|
-
if (
|
|
11400
|
-
|
|
11401
|
-
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);
|
|
11402
12318
|
});
|
|
11403
12319
|
}
|
|
11404
12320
|
return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken));
|
|
@@ -11437,6 +12353,13 @@ function createAuthRoutes(config) {
|
|
|
11437
12353
|
await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, {
|
|
11438
12354
|
email: externalUser.email
|
|
11439
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
|
+
}
|
|
11440
12363
|
const allUsers = await authRepo.listUsers();
|
|
11441
12364
|
const isFirstUser = allUsers.length === 1 && allUsers[0].id === user.id;
|
|
11442
12365
|
if (isFirstUser) {
|
|
@@ -11522,6 +12445,11 @@ function createAuthRoutes(config) {
|
|
|
11522
12445
|
await authRepo.updatePassword(storedToken.userId, passwordHash);
|
|
11523
12446
|
await authRepo.markPasswordResetTokenUsed(tokenHash);
|
|
11524
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
|
+
}
|
|
11525
12453
|
return c.json({
|
|
11526
12454
|
success: true,
|
|
11527
12455
|
message: "Password has been reset successfully"
|
|
@@ -11625,7 +12553,19 @@ function createAuthRoutes(config) {
|
|
|
11625
12553
|
}
|
|
11626
12554
|
const roles = await authRepo.getUserRoles(storedToken.userId);
|
|
11627
12555
|
const roleIds = roles.map((r) => r.id);
|
|
11628
|
-
|
|
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);
|
|
11629
12569
|
const newRefreshToken = generateRefreshToken();
|
|
11630
12570
|
const userAgent = c.req.header("user-agent") || "unknown";
|
|
11631
12571
|
const ipAddress = c.req.header("x-forwarded-for") || "unknown";
|
|
@@ -11647,6 +12587,18 @@ function createAuthRoutes(config) {
|
|
|
11647
12587
|
const tokenHash = hashRefreshToken(refreshToken);
|
|
11648
12588
|
await authRepo.deleteRefreshToken(tokenHash);
|
|
11649
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
|
+
}
|
|
11650
12602
|
return c.json({
|
|
11651
12603
|
success: true
|
|
11652
12604
|
});
|
|
@@ -11766,6 +12718,270 @@ function createAuthRoutes(config) {
|
|
|
11766
12718
|
enabledProviders
|
|
11767
12719
|
});
|
|
11768
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
|
+
});
|
|
11769
12985
|
return router;
|
|
11770
12986
|
}
|
|
11771
12987
|
function generateSecurePassword() {
|
|
@@ -11797,9 +13013,9 @@ function createAdminRoutes(config) {
|
|
|
11797
13013
|
emailService,
|
|
11798
13014
|
emailConfig,
|
|
11799
13015
|
hooks,
|
|
11800
|
-
|
|
13016
|
+
authHooks
|
|
11801
13017
|
} = config;
|
|
11802
|
-
const ops =
|
|
13018
|
+
const ops = resolveAuthHooks(authHooks);
|
|
11803
13019
|
function buildHookContext(c, method) {
|
|
11804
13020
|
const user = c.get("user");
|
|
11805
13021
|
return {
|
|
@@ -11867,6 +13083,10 @@ function createAdminRoutes(config) {
|
|
|
11867
13083
|
if (!userId) {
|
|
11868
13084
|
throw ApiError.unauthorized("User ID not found in auth context");
|
|
11869
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
|
+
}
|
|
11870
13090
|
await authRepo.setUserRoles(userId, ["admin"]);
|
|
11871
13091
|
if (config.setBootstrapCompleted) {
|
|
11872
13092
|
await config.setBootstrapCompleted();
|
|
@@ -12118,6 +13338,18 @@ function createAdminRoutes(config) {
|
|
|
12118
13338
|
await authRepo.updateUser(userId, updates);
|
|
12119
13339
|
}
|
|
12120
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
|
+
}
|
|
12121
13353
|
await authRepo.setUserRoles(userId, roles);
|
|
12122
13354
|
}
|
|
12123
13355
|
const result = await authRepo.getUserWithRoles(userId);
|
|
@@ -12142,6 +13374,16 @@ function createAdminRoutes(config) {
|
|
|
12142
13374
|
if (!existing) {
|
|
12143
13375
|
throw ApiError.notFound("User not found");
|
|
12144
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
|
+
}
|
|
12145
13387
|
const hookCtx = buildHookContext(c, "DELETE");
|
|
12146
13388
|
if (hooks?.users?.beforeDelete) {
|
|
12147
13389
|
await hooks.users.beforeDelete(userId, hookCtx);
|
|
@@ -12163,8 +13405,7 @@ function createAdminRoutes(config) {
|
|
|
12163
13405
|
id: r.id,
|
|
12164
13406
|
name: r.name,
|
|
12165
13407
|
isAdmin: r.isAdmin,
|
|
12166
|
-
defaultPermissions: r.defaultPermissions
|
|
12167
|
-
config: r.config
|
|
13408
|
+
defaultPermissions: r.defaultPermissions
|
|
12168
13409
|
}));
|
|
12169
13410
|
adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
|
|
12170
13411
|
return c.json({
|
|
@@ -12187,8 +13428,7 @@ function createAdminRoutes(config) {
|
|
|
12187
13428
|
id,
|
|
12188
13429
|
name: name2,
|
|
12189
13430
|
isAdmin,
|
|
12190
|
-
defaultPermissions
|
|
12191
|
-
config: config2
|
|
13431
|
+
defaultPermissions
|
|
12192
13432
|
} = body;
|
|
12193
13433
|
if (!id || !name2) {
|
|
12194
13434
|
throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
|
|
@@ -12201,8 +13441,7 @@ function createAdminRoutes(config) {
|
|
|
12201
13441
|
id,
|
|
12202
13442
|
name: name2,
|
|
12203
13443
|
isAdmin: isAdmin ?? false,
|
|
12204
|
-
defaultPermissions: defaultPermissions ?? null
|
|
12205
|
-
config: config2 ?? null
|
|
13444
|
+
defaultPermissions: defaultPermissions ?? null
|
|
12206
13445
|
});
|
|
12207
13446
|
return c.json({
|
|
12208
13447
|
role
|
|
@@ -12214,8 +13453,7 @@ function createAdminRoutes(config) {
|
|
|
12214
13453
|
const {
|
|
12215
13454
|
name: name2,
|
|
12216
13455
|
isAdmin,
|
|
12217
|
-
defaultPermissions
|
|
12218
|
-
config: config2
|
|
13456
|
+
defaultPermissions
|
|
12219
13457
|
} = body;
|
|
12220
13458
|
const existing = await authRepo.getRoleById(roleId);
|
|
12221
13459
|
if (!existing) {
|
|
@@ -12224,8 +13462,7 @@ function createAdminRoutes(config) {
|
|
|
12224
13462
|
const role = await authRepo.updateRole(roleId, {
|
|
12225
13463
|
name: name2,
|
|
12226
13464
|
isAdmin,
|
|
12227
|
-
defaultPermissions
|
|
12228
|
-
config: config2
|
|
13465
|
+
defaultPermissions
|
|
12229
13466
|
});
|
|
12230
13467
|
return c.json({
|
|
12231
13468
|
role
|
|
@@ -12257,9 +13494,9 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12257
13494
|
oauthProviders = [],
|
|
12258
13495
|
serviceKey,
|
|
12259
13496
|
hooks,
|
|
12260
|
-
|
|
13497
|
+
authHooks
|
|
12261
13498
|
} = config;
|
|
12262
|
-
const resolvedOps =
|
|
13499
|
+
const resolvedOps = resolveAuthHooks(authHooks);
|
|
12263
13500
|
const adapter = {
|
|
12264
13501
|
id: "rebase-builtin",
|
|
12265
13502
|
serviceKey,
|
|
@@ -12333,7 +13570,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12333
13570
|
rawToken: token
|
|
12334
13571
|
};
|
|
12335
13572
|
},
|
|
12336
|
-
userManagement: createUserManagementFromRepo(authRepository, resolvedOps,
|
|
13573
|
+
userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
|
|
12337
13574
|
roleManagement: createRoleManagementFromRepo(authRepository),
|
|
12338
13575
|
createAuthRoutes() {
|
|
12339
13576
|
return createAuthRoutes({
|
|
@@ -12343,7 +13580,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12343
13580
|
allowRegistration,
|
|
12344
13581
|
defaultRole,
|
|
12345
13582
|
oauthProviders,
|
|
12346
|
-
|
|
13583
|
+
authHooks
|
|
12347
13584
|
});
|
|
12348
13585
|
},
|
|
12349
13586
|
createAdminRoutes() {
|
|
@@ -12353,7 +13590,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12353
13590
|
emailConfig,
|
|
12354
13591
|
serviceKey,
|
|
12355
13592
|
hooks,
|
|
12356
|
-
|
|
13593
|
+
authHooks
|
|
12357
13594
|
});
|
|
12358
13595
|
},
|
|
12359
13596
|
async getCapabilities() {
|
|
@@ -12382,7 +13619,7 @@ function createBuiltinAuthAdapter(config) {
|
|
|
12382
13619
|
};
|
|
12383
13620
|
return adapter;
|
|
12384
13621
|
}
|
|
12385
|
-
function createUserManagementFromRepo(repo, resolvedOps,
|
|
13622
|
+
function createUserManagementFromRepo(repo, resolvedOps, authHooks) {
|
|
12386
13623
|
return {
|
|
12387
13624
|
async listUsers(options2) {
|
|
12388
13625
|
const result = await repo.listUsersPaginated({
|
|
@@ -12413,14 +13650,16 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
|
|
|
12413
13650
|
photoUrl: data.photoUrl,
|
|
12414
13651
|
metadata: data.metadata
|
|
12415
13652
|
};
|
|
12416
|
-
if (
|
|
12417
|
-
createData = await
|
|
13653
|
+
if (authHooks?.beforeUserCreate) {
|
|
13654
|
+
createData = await authHooks.beforeUserCreate(createData);
|
|
12418
13655
|
}
|
|
12419
13656
|
const user = await repo.createUser(createData);
|
|
12420
|
-
if (
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
})
|
|
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
|
+
}
|
|
12424
13663
|
}
|
|
12425
13664
|
return toAuthUserData(user);
|
|
12426
13665
|
},
|
|
@@ -12437,7 +13676,15 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
|
|
|
12437
13676
|
return user ? toAuthUserData(user) : null;
|
|
12438
13677
|
},
|
|
12439
13678
|
async deleteUser(id) {
|
|
13679
|
+
if (authHooks?.beforeUserDelete) {
|
|
13680
|
+
await authHooks.beforeUserDelete(id);
|
|
13681
|
+
}
|
|
12440
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
|
+
}
|
|
12441
13688
|
},
|
|
12442
13689
|
async getUserRoles(userId) {
|
|
12443
13690
|
const roles = await repo.getUserRoles(userId);
|
|
@@ -12464,8 +13711,7 @@ function createRoleManagementFromRepo(repo) {
|
|
|
12464
13711
|
name: data.name,
|
|
12465
13712
|
isAdmin: data.isAdmin,
|
|
12466
13713
|
defaultPermissions: data.defaultPermissions,
|
|
12467
|
-
collectionPermissions: data.collectionPermissions
|
|
12468
|
-
config: data.config
|
|
13714
|
+
collectionPermissions: data.collectionPermissions
|
|
12469
13715
|
});
|
|
12470
13716
|
return toAuthRoleData(role);
|
|
12471
13717
|
},
|
|
@@ -12496,8 +13742,7 @@ function toAuthRoleData(role) {
|
|
|
12496
13742
|
name: role.name,
|
|
12497
13743
|
isAdmin: role.isAdmin,
|
|
12498
13744
|
defaultPermissions: role.defaultPermissions,
|
|
12499
|
-
collectionPermissions: role.collectionPermissions
|
|
12500
|
-
config: role.config
|
|
13745
|
+
collectionPermissions: role.collectionPermissions
|
|
12501
13746
|
};
|
|
12502
13747
|
}
|
|
12503
13748
|
function configureLogLevel(logLevel) {
|
|
@@ -12518,20 +13763,20 @@ function configureLogLevel(logLevel) {
|
|
|
12518
13763
|
if (currentLevel < 0) console.error = () => {
|
|
12519
13764
|
};
|
|
12520
13765
|
}
|
|
13766
|
+
let originalConsole;
|
|
12521
13767
|
function resetConsole() {
|
|
12522
|
-
if (!
|
|
12523
|
-
|
|
13768
|
+
if (!originalConsole) {
|
|
13769
|
+
originalConsole = {
|
|
12524
13770
|
log: console.log,
|
|
12525
13771
|
warn: console.warn,
|
|
12526
13772
|
error: console.error,
|
|
12527
13773
|
debug: console.debug
|
|
12528
13774
|
};
|
|
12529
13775
|
}
|
|
12530
|
-
|
|
12531
|
-
console.
|
|
12532
|
-
console.
|
|
12533
|
-
console.
|
|
12534
|
-
console.debug = original.debug;
|
|
13776
|
+
console.log = originalConsole.log;
|
|
13777
|
+
console.warn = originalConsole.warn;
|
|
13778
|
+
console.error = originalConsole.error;
|
|
13779
|
+
console.debug = originalConsole.debug;
|
|
12535
13780
|
}
|
|
12536
13781
|
const GCP_SEVERITY = {
|
|
12537
13782
|
debug: "DEBUG",
|
|
@@ -12773,12 +14018,6 @@ var browser$1 = { exports: {} };
|
|
|
12773
14018
|
exports.Response = globalObject.Response;
|
|
12774
14019
|
})(browser$1, browser$1.exports);
|
|
12775
14020
|
var browserExports = browser$1.exports;
|
|
12776
|
-
const __viteBrowserExternal = {};
|
|
12777
|
-
const __viteBrowserExternal$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
|
|
12778
|
-
__proto__: null,
|
|
12779
|
-
default: __viteBrowserExternal
|
|
12780
|
-
}, Symbol.toStringTag, { value: "Module" }));
|
|
12781
|
-
const require$$11 = /* @__PURE__ */ getAugmentedNamespace(__viteBrowserExternal$1);
|
|
12782
14021
|
const isStream = (stream2) => stream2 !== null && typeof stream2 === "object" && typeof stream2.pipe === "function";
|
|
12783
14022
|
isStream.writable = (stream2) => isStream(stream2) && stream2.writable !== false && typeof stream2._write === "function" && typeof stream2._writableState === "object";
|
|
12784
14023
|
isStream.readable = (stream2) => isStream(stream2) && stream2.readable !== false && typeof stream2._read === "function" && typeof stream2._readableState === "object";
|
|
@@ -13563,16 +14802,16 @@ Object.defineProperty(sha1$1, "__esModule", {
|
|
|
13563
14802
|
value: true
|
|
13564
14803
|
});
|
|
13565
14804
|
sha1$1.default = void 0;
|
|
13566
|
-
function f(s2, x, y2,
|
|
14805
|
+
function f(s2, x, y2, z2) {
|
|
13567
14806
|
switch (s2) {
|
|
13568
14807
|
case 0:
|
|
13569
|
-
return x & y2 ^ ~x &
|
|
14808
|
+
return x & y2 ^ ~x & z2;
|
|
13570
14809
|
case 1:
|
|
13571
|
-
return x ^ y2 ^
|
|
14810
|
+
return x ^ y2 ^ z2;
|
|
13572
14811
|
case 2:
|
|
13573
|
-
return x & y2 ^ x &
|
|
14812
|
+
return x & y2 ^ x & z2 ^ y2 & z2;
|
|
13574
14813
|
case 3:
|
|
13575
|
-
return x ^ y2 ^
|
|
14814
|
+
return x ^ y2 ^ z2;
|
|
13576
14815
|
}
|
|
13577
14816
|
}
|
|
13578
14817
|
function ROTL(x, n) {
|
|
@@ -14621,7 +15860,7 @@ gaxios.Gaxios = void 0;
|
|
|
14621
15860
|
const extend_1 = __importDefault(extend);
|
|
14622
15861
|
const https_1 = require$$1;
|
|
14623
15862
|
const node_fetch_1 = __importDefault(browserExports);
|
|
14624
|
-
const querystring_1$1 = __importDefault(require$$
|
|
15863
|
+
const querystring_1$1 = __importDefault(require$$1$2);
|
|
14625
15864
|
const is_stream_1 = __importDefault(isStream_1);
|
|
14626
15865
|
const url_1 = require$$0$5;
|
|
14627
15866
|
const common_1 = common$1;
|
|
@@ -16299,11 +17538,11 @@ var bignumber = { exports: {} };
|
|
|
16299
17538
|
return n > 0 || n === i ? i : i - 1;
|
|
16300
17539
|
}
|
|
16301
17540
|
function coeffToString(a2) {
|
|
16302
|
-
var s2,
|
|
17541
|
+
var s2, z2, i = 1, j = a2.length, r = a2[0] + "";
|
|
16303
17542
|
for (; i < j; ) {
|
|
16304
17543
|
s2 = a2[i++] + "";
|
|
16305
|
-
|
|
16306
|
-
for (;
|
|
17544
|
+
z2 = LOG_BASE - s2.length;
|
|
17545
|
+
for (; z2--; s2 = "0" + s2) ;
|
|
16307
17546
|
r += s2;
|
|
16308
17547
|
}
|
|
16309
17548
|
for (j = r.length; r.charCodeAt(--j) === 48; ) ;
|
|
@@ -16336,15 +17575,15 @@ var bignumber = { exports: {} };
|
|
|
16336
17575
|
function toExponential(str, e) {
|
|
16337
17576
|
return (str.length > 1 ? str.charAt(0) + "." + str.slice(1) : str) + (e < 0 ? "e" : "e+") + e;
|
|
16338
17577
|
}
|
|
16339
|
-
function toFixedPoint(str, e,
|
|
17578
|
+
function toFixedPoint(str, e, z2) {
|
|
16340
17579
|
var len, zs;
|
|
16341
17580
|
if (e < 0) {
|
|
16342
|
-
for (zs =
|
|
17581
|
+
for (zs = z2 + "."; ++e; zs += z2) ;
|
|
16343
17582
|
str = zs + str;
|
|
16344
17583
|
} else {
|
|
16345
17584
|
len = str.length;
|
|
16346
17585
|
if (++e > len) {
|
|
16347
|
-
for (zs =
|
|
17586
|
+
for (zs = z2, e -= len; --e; zs += z2) ;
|
|
16348
17587
|
str += zs;
|
|
16349
17588
|
} else if (e < len) {
|
|
16350
17589
|
str = str.slice(0, e) + "." + str.slice(e);
|
|
@@ -16763,7 +18002,7 @@ var gcpResidency = {};
|
|
|
16763
18002
|
exports.isGoogleComputeEngine = isGoogleComputeEngine;
|
|
16764
18003
|
exports.detectGCPResidency = detectGCPResidency;
|
|
16765
18004
|
const fs_1 = fs__default;
|
|
16766
|
-
const os_1 = require$$1$
|
|
18005
|
+
const os_1 = require$$1$3;
|
|
16767
18006
|
exports.GCE_LINUX_BIOS_PATHS = {
|
|
16768
18007
|
BIOS_DATE: "/sys/class/dmi/id/bios_date",
|
|
16769
18008
|
BIOS_VENDOR: "/sys/class/dmi/id/bios_vendor"
|
|
@@ -16896,7 +18135,7 @@ Colours.refresh();
|
|
|
16896
18135
|
exports.setBackend = setBackend;
|
|
16897
18136
|
exports.log = log;
|
|
16898
18137
|
const node_events_1 = require$$0$8;
|
|
16899
|
-
const process2 = __importStar2(require$$1$
|
|
18138
|
+
const process2 = __importStar2(require$$1$4);
|
|
16900
18139
|
const util2 = __importStar2(require$$2$1);
|
|
16901
18140
|
const colours_1 = colours;
|
|
16902
18141
|
var LogSeverity;
|
|
@@ -17968,7 +19207,7 @@ loginticket.LoginTicket = LoginTicket;
|
|
|
17968
19207
|
Object.defineProperty(oauth2client, "__esModule", { value: true });
|
|
17969
19208
|
oauth2client.OAuth2Client = oauth2client.ClientAuthentication = oauth2client.CertificateFormat = oauth2client.CodeChallengeMethod = void 0;
|
|
17970
19209
|
const gaxios_1$5 = src$2;
|
|
17971
|
-
const querystring$2 = require$$
|
|
19210
|
+
const querystring$2 = require$$1$2;
|
|
17972
19211
|
const stream$4 = require$$0$4;
|
|
17973
19212
|
const formatEcdsa = ecdsaSigFormatter;
|
|
17974
19213
|
const crypto_1$2 = requireCrypto();
|
|
@@ -19456,7 +20695,7 @@ var refreshclient = {};
|
|
|
19456
20695
|
Object.defineProperty(refreshclient, "__esModule", { value: true });
|
|
19457
20696
|
refreshclient.UserRefreshClient = refreshclient.USER_REFRESH_ACCOUNT_TYPE = void 0;
|
|
19458
20697
|
const oauth2client_1$1 = oauth2client;
|
|
19459
|
-
const querystring_1 = require$$
|
|
20698
|
+
const querystring_1 = require$$1$2;
|
|
19460
20699
|
refreshclient.USER_REFRESH_ACCOUNT_TYPE = "authorized_user";
|
|
19461
20700
|
class UserRefreshClient extends oauth2client_1$1.OAuth2Client {
|
|
19462
20701
|
constructor(optionsOrClientId, clientSecret, refreshToken, eagerRefreshThresholdMillis, forceRefreshOnFailure) {
|
|
@@ -19731,7 +20970,7 @@ var oauth2common = {};
|
|
|
19731
20970
|
Object.defineProperty(oauth2common, "__esModule", { value: true });
|
|
19732
20971
|
oauth2common.OAuthClientAuthHandler = void 0;
|
|
19733
20972
|
oauth2common.getErrorFromOAuthErrorResponse = getErrorFromOAuthErrorResponse;
|
|
19734
|
-
const querystring$1 = require$$
|
|
20973
|
+
const querystring$1 = require$$1$2;
|
|
19735
20974
|
const crypto_1$1 = requireCrypto();
|
|
19736
20975
|
const METHODS_SUPPORTING_REQUEST_BODY = ["PUT", "POST", "PATCH"];
|
|
19737
20976
|
class OAuthClientAuthHandler {
|
|
@@ -19877,7 +21116,7 @@ function getErrorFromOAuthErrorResponse(resp, err) {
|
|
|
19877
21116
|
Object.defineProperty(stscredentials, "__esModule", { value: true });
|
|
19878
21117
|
stscredentials.StsCredentials = void 0;
|
|
19879
21118
|
const gaxios_1$1 = src$2;
|
|
19880
|
-
const querystring = require$$
|
|
21119
|
+
const querystring = require$$1$2;
|
|
19881
21120
|
const transporters_1 = transporters;
|
|
19882
21121
|
const oauth2common_1$1 = oauth2common;
|
|
19883
21122
|
class StsCredentials extends oauth2common_1$1.OAuthClientAuthHandler {
|
|
@@ -21483,7 +22722,7 @@ externalAccountAuthorizedUserClient.ExternalAccountAuthorizedUserClient = Extern
|
|
|
21483
22722
|
const child_process_1 = require$$0$a;
|
|
21484
22723
|
const fs2 = fs__default;
|
|
21485
22724
|
const gcpMetadata2 = src$3;
|
|
21486
|
-
const os2 = require$$1$
|
|
22725
|
+
const os2 = require$$1$3;
|
|
21487
22726
|
const path2 = path__default;
|
|
21488
22727
|
const crypto_12 = requireCrypto();
|
|
21489
22728
|
const transporters_12 = transporters;
|
|
@@ -22847,7 +24086,7 @@ function createMicrosoftProvider(config) {
|
|
|
22847
24086
|
}
|
|
22848
24087
|
function createAppleProvider(config) {
|
|
22849
24088
|
async function generateClientSecret() {
|
|
22850
|
-
return jwt.sign({}, config.privateKey, {
|
|
24089
|
+
return jwt$1.sign({}, config.privateKey, {
|
|
22851
24090
|
algorithm: "ES256",
|
|
22852
24091
|
keyid: config.keyId,
|
|
22853
24092
|
issuer: config.teamId,
|
|
@@ -23327,6 +24566,341 @@ function createSpotifyProvider(config) {
|
|
|
23327
24566
|
}
|
|
23328
24567
|
};
|
|
23329
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
|
+
}
|
|
23330
24904
|
function createCustomAuthAdapter(options2) {
|
|
23331
24905
|
const defaultCapabilities = {
|
|
23332
24906
|
hasBuiltInAuthRoutes: false,
|
|
@@ -23879,6 +25453,409 @@ class S3StorageController {
|
|
|
23879
25453
|
};
|
|
23880
25454
|
}
|
|
23881
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();
|
|
23882
25859
|
function extractWildcardPath(c) {
|
|
23883
25860
|
const routePath = c.req.routePath;
|
|
23884
25861
|
const prefix = routePath.replace("/*", "");
|
|
@@ -23942,6 +25919,7 @@ function createStorageRoutes(config) {
|
|
|
23942
25919
|
throw ApiError.notFound("File not found");
|
|
23943
25920
|
}
|
|
23944
25921
|
const filePath = decodeURIComponent(rawPath);
|
|
25922
|
+
const transformOpts = parseTransformOptions(c.req.query());
|
|
23945
25923
|
if (controller.getType() === "local") {
|
|
23946
25924
|
const localController = controller;
|
|
23947
25925
|
const {
|
|
@@ -23961,8 +25939,19 @@ function createStorageRoutes(config) {
|
|
|
23961
25939
|
} catch {
|
|
23962
25940
|
}
|
|
23963
25941
|
}
|
|
23964
|
-
c.header("Content-Type", contentType);
|
|
23965
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);
|
|
23966
25955
|
return c.body(new Uint8Array(fileContent));
|
|
23967
25956
|
}
|
|
23968
25957
|
const {
|
|
@@ -23973,7 +25962,20 @@ function createStorageRoutes(config) {
|
|
|
23973
25962
|
if (!fileObject) {
|
|
23974
25963
|
throw ApiError.notFound("File not found");
|
|
23975
25964
|
}
|
|
23976
|
-
|
|
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);
|
|
23977
25979
|
c.header("Cache-Control", "public, max-age=3600, immutable");
|
|
23978
25980
|
const buf = await fileObject.arrayBuffer();
|
|
23979
25981
|
return c.body(new Uint8Array(buf));
|
|
@@ -24069,6 +26071,14 @@ function createStorageRoutes(config) {
|
|
|
24069
26071
|
message: "Folder created"
|
|
24070
26072
|
}, 201);
|
|
24071
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")));
|
|
24072
26082
|
return router;
|
|
24073
26083
|
}
|
|
24074
26084
|
const DEFAULT_STORAGE_ID = "(default)";
|
|
@@ -24292,6 +26302,13 @@ function createTransport$1(config) {
|
|
|
24292
26302
|
} catch (e) {
|
|
24293
26303
|
}
|
|
24294
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
|
+
};
|
|
24295
26312
|
if (res.status === 401 && onUnauthorizedHandler) {
|
|
24296
26313
|
const retried = await onUnauthorizedHandler();
|
|
24297
26314
|
if (retried) {
|
|
@@ -24325,7 +26342,7 @@ function createTransport$1(config) {
|
|
|
24325
26342
|
const method = init?.method || "GET";
|
|
24326
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.`;
|
|
24327
26344
|
}
|
|
24328
|
-
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"));
|
|
24329
26346
|
}
|
|
24330
26347
|
return retryBody;
|
|
24331
26348
|
}
|
|
@@ -24336,7 +26353,7 @@ function createTransport$1(config) {
|
|
|
24336
26353
|
const method = init?.method || "GET";
|
|
24337
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.`;
|
|
24338
26355
|
}
|
|
24339
|
-
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"));
|
|
24340
26357
|
}
|
|
24341
26358
|
return body;
|
|
24342
26359
|
}
|
|
@@ -24998,118 +27015,6 @@ function createCron(transport, options2) {
|
|
|
24998
27015
|
toggleJob
|
|
24999
27016
|
};
|
|
25000
27017
|
}
|
|
25001
|
-
function mapOperator(op) {
|
|
25002
|
-
switch (op) {
|
|
25003
|
-
case "==":
|
|
25004
|
-
return "eq";
|
|
25005
|
-
case "!=":
|
|
25006
|
-
return "neq";
|
|
25007
|
-
case ">":
|
|
25008
|
-
return "gt";
|
|
25009
|
-
case ">=":
|
|
25010
|
-
return "gte";
|
|
25011
|
-
case "<":
|
|
25012
|
-
return "lt";
|
|
25013
|
-
case "<=":
|
|
25014
|
-
return "lte";
|
|
25015
|
-
case "array-contains":
|
|
25016
|
-
return "cs";
|
|
25017
|
-
case "array-contains-any":
|
|
25018
|
-
return "csa";
|
|
25019
|
-
case "not-in":
|
|
25020
|
-
return "nin";
|
|
25021
|
-
default:
|
|
25022
|
-
return op;
|
|
25023
|
-
}
|
|
25024
|
-
}
|
|
25025
|
-
class QueryBuilder {
|
|
25026
|
-
constructor(collection) {
|
|
25027
|
-
this.collection = collection;
|
|
25028
|
-
}
|
|
25029
|
-
params = {
|
|
25030
|
-
where: {}
|
|
25031
|
-
};
|
|
25032
|
-
/**
|
|
25033
|
-
* Add a filter condition to your query.
|
|
25034
|
-
* @example
|
|
25035
|
-
* client.collection('users').where('age', '>=', 18).find()
|
|
25036
|
-
*/
|
|
25037
|
-
where(column, operator, value) {
|
|
25038
|
-
if (!this.params.where) {
|
|
25039
|
-
this.params.where = {};
|
|
25040
|
-
}
|
|
25041
|
-
const mappedOp = mapOperator(operator);
|
|
25042
|
-
let formattedValue = value;
|
|
25043
|
-
if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
|
|
25044
|
-
formattedValue = `(${value.join(",")})`;
|
|
25045
|
-
} else if (value === null) {
|
|
25046
|
-
formattedValue = "null";
|
|
25047
|
-
}
|
|
25048
|
-
this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
|
|
25049
|
-
return this;
|
|
25050
|
-
}
|
|
25051
|
-
/**
|
|
25052
|
-
* Order the results by a specific column.
|
|
25053
|
-
* @example
|
|
25054
|
-
* client.collection('users').orderBy('createdAt', 'desc').find()
|
|
25055
|
-
*/
|
|
25056
|
-
orderBy(column, ascending = "asc") {
|
|
25057
|
-
this.params.orderBy = `${column}:${ascending}`;
|
|
25058
|
-
return this;
|
|
25059
|
-
}
|
|
25060
|
-
/**
|
|
25061
|
-
* Limit the number of results returned.
|
|
25062
|
-
*/
|
|
25063
|
-
limit(count) {
|
|
25064
|
-
this.params.limit = count;
|
|
25065
|
-
return this;
|
|
25066
|
-
}
|
|
25067
|
-
/**
|
|
25068
|
-
* Skip the first N results.
|
|
25069
|
-
*/
|
|
25070
|
-
offset(count) {
|
|
25071
|
-
this.params.offset = count;
|
|
25072
|
-
return this;
|
|
25073
|
-
}
|
|
25074
|
-
/**
|
|
25075
|
-
* Set a free-text search string if supported by the backend.
|
|
25076
|
-
*/
|
|
25077
|
-
search(searchString) {
|
|
25078
|
-
this.params.searchString = searchString;
|
|
25079
|
-
return this;
|
|
25080
|
-
}
|
|
25081
|
-
/**
|
|
25082
|
-
* Include related entities in the response.
|
|
25083
|
-
* Relations will be populated with full entity data instead of just IDs.
|
|
25084
|
-
*
|
|
25085
|
-
* @param relations - Relation names to include, or "*" for all.
|
|
25086
|
-
* @example
|
|
25087
|
-
* // Include specific relations
|
|
25088
|
-
* client.data.posts.include("tags", "author").find()
|
|
25089
|
-
*
|
|
25090
|
-
* // Include all relations
|
|
25091
|
-
* client.data.posts.include("*").find()
|
|
25092
|
-
*/
|
|
25093
|
-
include(...relations) {
|
|
25094
|
-
this.params.include = relations;
|
|
25095
|
-
return this;
|
|
25096
|
-
}
|
|
25097
|
-
/**
|
|
25098
|
-
* Execute the find query and return the results.
|
|
25099
|
-
*/
|
|
25100
|
-
async find() {
|
|
25101
|
-
return this.collection.find(this.params);
|
|
25102
|
-
}
|
|
25103
|
-
/**
|
|
25104
|
-
* Listen to realtime updates matching this query.
|
|
25105
|
-
*/
|
|
25106
|
-
listen(onUpdate, onError) {
|
|
25107
|
-
if (!this.collection.listen) {
|
|
25108
|
-
throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
|
|
25109
|
-
}
|
|
25110
|
-
return this.collection.listen(this.params, onUpdate, onError);
|
|
25111
|
-
}
|
|
25112
|
-
}
|
|
25113
27018
|
function rowToEntity(row, slug) {
|
|
25114
27019
|
return {
|
|
25115
27020
|
id: row.id,
|
|
@@ -25704,7 +27609,7 @@ const require$$9 = {
|
|
|
25704
27609
|
const http = require$$0$6;
|
|
25705
27610
|
const https = require$$1;
|
|
25706
27611
|
const urllib$2 = require$$0$5;
|
|
25707
|
-
const zlib = require$$
|
|
27612
|
+
const zlib = require$$3;
|
|
25708
27613
|
const PassThrough$3 = require$$0$4.PassThrough;
|
|
25709
27614
|
const Cookies2 = cookies;
|
|
25710
27615
|
const packageData$7 = require$$9;
|
|
@@ -25939,9 +27844,9 @@ var fetchExports = fetch$1.exports;
|
|
|
25939
27844
|
const util2 = require$$5;
|
|
25940
27845
|
const fs2 = fs__default;
|
|
25941
27846
|
const nmfetch2 = fetchExports;
|
|
25942
|
-
const dns2 = require$$
|
|
27847
|
+
const dns2 = require$$4$1;
|
|
25943
27848
|
const net2 = require$$0$7;
|
|
25944
|
-
const os2 = require$$1$
|
|
27849
|
+
const os2 = require$$1$3;
|
|
25945
27850
|
const DNS_TTL = 5 * 60 * 1e3;
|
|
25946
27851
|
let networkInterfaces;
|
|
25947
27852
|
try {
|
|
@@ -32087,7 +33992,7 @@ const urllib = require$$0$5;
|
|
|
32087
33992
|
const packageData$6 = require$$9;
|
|
32088
33993
|
const MailMessage2 = mailMessage;
|
|
32089
33994
|
const net$1 = require$$0$7;
|
|
32090
|
-
const dns = require$$
|
|
33995
|
+
const dns = require$$4$1;
|
|
32091
33996
|
const crypto$3 = require$$0__default;
|
|
32092
33997
|
class Mail extends EventEmitter$5 {
|
|
32093
33998
|
constructor(transporter, options2, defaults) {
|
|
@@ -32523,7 +34428,7 @@ const packageInfo = require$$9;
|
|
|
32523
34428
|
const EventEmitter$4 = require$$0$9.EventEmitter;
|
|
32524
34429
|
const net = require$$0$7;
|
|
32525
34430
|
const tls = require$$1$1;
|
|
32526
|
-
const os = require$$1$
|
|
34431
|
+
const os = require$$1$3;
|
|
32527
34432
|
const crypto$2 = require$$0__default;
|
|
32528
34433
|
const DataStream2 = dataStream;
|
|
32529
34434
|
const PassThrough = require$$0$4.PassThrough;
|
|
@@ -36711,7 +38616,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36711
38616
|
oauthProviders,
|
|
36712
38617
|
serviceKey,
|
|
36713
38618
|
hooks: config.hooks,
|
|
36714
|
-
|
|
38619
|
+
authHooks: safeAuthConfig.hooks
|
|
36715
38620
|
});
|
|
36716
38621
|
}
|
|
36717
38622
|
logger.info("Authentication initialized");
|
|
@@ -36778,73 +38683,73 @@ async function _initializeRebaseBackend(config) {
|
|
|
36778
38683
|
if (safeAuthConfig.google?.clientId) {
|
|
36779
38684
|
const {
|
|
36780
38685
|
createGoogleProvider: createGoogleProvider2
|
|
36781
|
-
} = await import("./index-
|
|
38686
|
+
} = await import("./index-Cr1D21av.js");
|
|
36782
38687
|
oauthProviders.push(createGoogleProvider2(safeAuthConfig.google));
|
|
36783
38688
|
}
|
|
36784
38689
|
if (safeAuthConfig.linkedin?.clientId && safeAuthConfig.linkedin?.clientSecret) {
|
|
36785
38690
|
const {
|
|
36786
38691
|
createLinkedinProvider: createLinkedinProvider2
|
|
36787
|
-
} = await import("./index-
|
|
38692
|
+
} = await import("./index-Cr1D21av.js");
|
|
36788
38693
|
oauthProviders.push(createLinkedinProvider2(safeAuthConfig.linkedin));
|
|
36789
38694
|
}
|
|
36790
38695
|
if (safeAuthConfig.github?.clientId && safeAuthConfig.github?.clientSecret) {
|
|
36791
38696
|
const {
|
|
36792
38697
|
createGitHubProvider: createGitHubProvider2
|
|
36793
|
-
} = await import("./index-
|
|
38698
|
+
} = await import("./index-Cr1D21av.js");
|
|
36794
38699
|
oauthProviders.push(createGitHubProvider2(safeAuthConfig.github));
|
|
36795
38700
|
}
|
|
36796
38701
|
if (safeAuthConfig.microsoft?.clientId && safeAuthConfig.microsoft?.clientSecret) {
|
|
36797
38702
|
const {
|
|
36798
38703
|
createMicrosoftProvider: createMicrosoftProvider2
|
|
36799
|
-
} = await import("./index-
|
|
38704
|
+
} = await import("./index-Cr1D21av.js");
|
|
36800
38705
|
oauthProviders.push(createMicrosoftProvider2(safeAuthConfig.microsoft));
|
|
36801
38706
|
}
|
|
36802
38707
|
if (safeAuthConfig.apple?.clientId && safeAuthConfig.apple?.teamId && safeAuthConfig.apple?.keyId && safeAuthConfig.apple?.privateKey) {
|
|
36803
38708
|
const {
|
|
36804
38709
|
createAppleProvider: createAppleProvider2
|
|
36805
|
-
} = await import("./index-
|
|
38710
|
+
} = await import("./index-Cr1D21av.js");
|
|
36806
38711
|
oauthProviders.push(createAppleProvider2(safeAuthConfig.apple));
|
|
36807
38712
|
}
|
|
36808
38713
|
if (safeAuthConfig.facebook?.clientId && safeAuthConfig.facebook?.clientSecret) {
|
|
36809
38714
|
const {
|
|
36810
38715
|
createFacebookProvider: createFacebookProvider2
|
|
36811
|
-
} = await import("./index-
|
|
38716
|
+
} = await import("./index-Cr1D21av.js");
|
|
36812
38717
|
oauthProviders.push(createFacebookProvider2(safeAuthConfig.facebook));
|
|
36813
38718
|
}
|
|
36814
38719
|
if (safeAuthConfig.twitter?.clientId && safeAuthConfig.twitter?.clientSecret) {
|
|
36815
38720
|
const {
|
|
36816
38721
|
createTwitterProvider: createTwitterProvider2
|
|
36817
|
-
} = await import("./index-
|
|
38722
|
+
} = await import("./index-Cr1D21av.js");
|
|
36818
38723
|
oauthProviders.push(createTwitterProvider2(safeAuthConfig.twitter));
|
|
36819
38724
|
}
|
|
36820
38725
|
if (safeAuthConfig.discord?.clientId && safeAuthConfig.discord?.clientSecret) {
|
|
36821
38726
|
const {
|
|
36822
38727
|
createDiscordProvider: createDiscordProvider2
|
|
36823
|
-
} = await import("./index-
|
|
38728
|
+
} = await import("./index-Cr1D21av.js");
|
|
36824
38729
|
oauthProviders.push(createDiscordProvider2(safeAuthConfig.discord));
|
|
36825
38730
|
}
|
|
36826
38731
|
if (safeAuthConfig.gitlab?.clientId && safeAuthConfig.gitlab?.clientSecret) {
|
|
36827
38732
|
const {
|
|
36828
38733
|
createGitLabProvider: createGitLabProvider2
|
|
36829
|
-
} = await import("./index-
|
|
38734
|
+
} = await import("./index-Cr1D21av.js");
|
|
36830
38735
|
oauthProviders.push(createGitLabProvider2(safeAuthConfig.gitlab));
|
|
36831
38736
|
}
|
|
36832
38737
|
if (safeAuthConfig.bitbucket?.clientId && safeAuthConfig.bitbucket?.clientSecret) {
|
|
36833
38738
|
const {
|
|
36834
38739
|
createBitbucketProvider: createBitbucketProvider2
|
|
36835
|
-
} = await import("./index-
|
|
38740
|
+
} = await import("./index-Cr1D21av.js");
|
|
36836
38741
|
oauthProviders.push(createBitbucketProvider2(safeAuthConfig.bitbucket));
|
|
36837
38742
|
}
|
|
36838
38743
|
if (safeAuthConfig.slack?.clientId && safeAuthConfig.slack?.clientSecret) {
|
|
36839
38744
|
const {
|
|
36840
38745
|
createSlackProvider: createSlackProvider2
|
|
36841
|
-
} = await import("./index-
|
|
38746
|
+
} = await import("./index-Cr1D21av.js");
|
|
36842
38747
|
oauthProviders.push(createSlackProvider2(safeAuthConfig.slack));
|
|
36843
38748
|
}
|
|
36844
38749
|
if (safeAuthConfig.spotify?.clientId && safeAuthConfig.spotify?.clientSecret) {
|
|
36845
38750
|
const {
|
|
36846
38751
|
createSpotifyProvider: createSpotifyProvider2
|
|
36847
|
-
} = await import("./index-
|
|
38752
|
+
} = await import("./index-Cr1D21av.js");
|
|
36848
38753
|
oauthProviders.push(createSpotifyProvider2(safeAuthConfig.spotify));
|
|
36849
38754
|
}
|
|
36850
38755
|
authAdapter = createBuiltinAuthAdapter({
|
|
@@ -36856,7 +38761,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
36856
38761
|
oauthProviders,
|
|
36857
38762
|
serviceKey,
|
|
36858
38763
|
hooks: config.hooks,
|
|
36859
|
-
|
|
38764
|
+
authHooks: safeAuthConfig.hooks
|
|
36860
38765
|
});
|
|
36861
38766
|
}
|
|
36862
38767
|
if (authAdapter.createAuthRoutes) {
|
|
@@ -36878,6 +38783,21 @@ async function _initializeRebaseBackend(config) {
|
|
|
36878
38783
|
}
|
|
36879
38784
|
}
|
|
36880
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
|
+
}
|
|
36881
38801
|
if (config.collectionsDir) {
|
|
36882
38802
|
if (process.env.NODE_ENV !== "production") {
|
|
36883
38803
|
const {
|
|
@@ -36928,15 +38848,20 @@ async function _initializeRebaseBackend(config) {
|
|
|
36928
38848
|
dataRouter.use("/*", createAdapterAuthMiddleware({
|
|
36929
38849
|
adapter: authAdapter,
|
|
36930
38850
|
driver: defaultDriver,
|
|
36931
|
-
requireAuth: dataRequireAuth
|
|
38851
|
+
requireAuth: dataRequireAuth,
|
|
38852
|
+
apiKeyStore
|
|
36932
38853
|
}));
|
|
36933
38854
|
} else {
|
|
36934
38855
|
dataRouter.use("/*", createAuthMiddleware({
|
|
36935
38856
|
driver: defaultDriver,
|
|
36936
38857
|
requireAuth: dataRequireAuth,
|
|
36937
|
-
serviceKey
|
|
38858
|
+
serviceKey,
|
|
38859
|
+
apiKeyStore
|
|
36938
38860
|
}));
|
|
36939
38861
|
}
|
|
38862
|
+
if (apiKeyStore) {
|
|
38863
|
+
dataRouter.use("/*", createApiKeyRateLimiter());
|
|
38864
|
+
}
|
|
36940
38865
|
if (historyConfigResult && historyConfigResult.historyService) {
|
|
36941
38866
|
const historyRoutes = createHistoryRoutes({
|
|
36942
38867
|
historyService: historyConfigResult.historyService,
|
|
@@ -37030,13 +38955,15 @@ async function _initializeRebaseBackend(config) {
|
|
|
37030
38955
|
functionsRouter.use("/*", createAdapterAuthMiddleware({
|
|
37031
38956
|
adapter: authAdapter,
|
|
37032
38957
|
driver: defaultDriver,
|
|
37033
|
-
requireAuth: functionsRequireAuth
|
|
38958
|
+
requireAuth: functionsRequireAuth,
|
|
38959
|
+
apiKeyStore
|
|
37034
38960
|
}));
|
|
37035
38961
|
} else {
|
|
37036
38962
|
functionsRouter.use("/*", createAuthMiddleware({
|
|
37037
38963
|
driver: defaultDriver,
|
|
37038
38964
|
requireAuth: functionsRequireAuth,
|
|
37039
|
-
serviceKey
|
|
38965
|
+
serviceKey,
|
|
38966
|
+
apiKeyStore
|
|
37040
38967
|
}));
|
|
37041
38968
|
}
|
|
37042
38969
|
const fnRoutes = createFunctionRoutes2(loadedFunctions);
|
|
@@ -47027,7 +48954,7 @@ class AstSchemaEditor {
|
|
|
47027
48954
|
return "undefined";
|
|
47028
48955
|
}
|
|
47029
48956
|
if (typeof obj === "string") {
|
|
47030
|
-
return
|
|
48957
|
+
return JSON.stringify(obj);
|
|
47031
48958
|
}
|
|
47032
48959
|
if (typeof obj === "number" || typeof obj === "boolean") {
|
|
47033
48960
|
return String(obj);
|
|
@@ -47058,7 +48985,7 @@ ${indent2}]`;
|
|
|
47058
48985
|
const kind = init.getKind();
|
|
47059
48986
|
const isCode = kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.Identifier || kind === SyntaxKind.CallExpression || kind === SyntaxKind.JsxElement;
|
|
47060
48987
|
if (isCode || name2 === "target" || name2 === "callbacks" || name2 === "permissions" || name2 === "securityRules") {
|
|
47061
|
-
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);
|
|
47062
48989
|
preservedProps.push(`${keyStr}: ${init.getText()}`);
|
|
47063
48990
|
}
|
|
47064
48991
|
}
|
|
@@ -47068,7 +48995,7 @@ ${indent2}]`;
|
|
|
47068
48995
|
}
|
|
47069
48996
|
if (keys2.length === 0 && preservedProps.length === 0) return "{}";
|
|
47070
48997
|
const props = keys2.map((key) => {
|
|
47071
|
-
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);
|
|
47072
48999
|
let childAstNode;
|
|
47073
49000
|
if (oldAstNode && typeof record[key] === "object" && record[key] !== null && !Array.isArray(record[key])) {
|
|
47074
49001
|
const oldProp = oldAstNode.getProperty((p) => "getName" in p && typeof p.getName === "function" && (p.getName() === key || p.getName() === `"${key}"` || p.getName() === `'${key}'`));
|
|
@@ -47110,7 +49037,7 @@ ${indent2}}`;
|
|
|
47110
49037
|
}
|
|
47111
49038
|
} else {
|
|
47112
49039
|
propsObj.addPropertyAssignment({
|
|
47113
|
-
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),
|
|
47114
49041
|
initializer: newInitializer
|
|
47115
49042
|
});
|
|
47116
49043
|
}
|
|
@@ -48181,7 +50108,7 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48181
50108
|
const mod = await dynamicImport(fileUrl);
|
|
48182
50109
|
const exported = mod.default;
|
|
48183
50110
|
if (!exported) {
|
|
48184
|
-
|
|
50111
|
+
logger.warn(`[functions] ${file}: no default export. Skipping.`);
|
|
48185
50112
|
continue;
|
|
48186
50113
|
}
|
|
48187
50114
|
if (isHonoLike(exported)) {
|
|
@@ -48190,7 +50117,7 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48190
50117
|
name: name2,
|
|
48191
50118
|
app: exported
|
|
48192
50119
|
});
|
|
48193
|
-
|
|
50120
|
+
logger.info(`⚡ Loaded function route: ${name2}`);
|
|
48194
50121
|
continue;
|
|
48195
50122
|
}
|
|
48196
50123
|
if (typeof exported === "function") {
|
|
@@ -48201,20 +50128,20 @@ async function loadFunctionsFromDirectory(directory) {
|
|
|
48201
50128
|
name: name2,
|
|
48202
50129
|
app: result
|
|
48203
50130
|
});
|
|
48204
|
-
|
|
50131
|
+
logger.info(`⚡ Loaded function route: ${name2}`);
|
|
48205
50132
|
continue;
|
|
48206
50133
|
}
|
|
48207
50134
|
}
|
|
48208
50135
|
const exportType = typeof exported;
|
|
48209
50136
|
const keys2 = exported && typeof exported === "object" ? Object.getOwnPropertyNames(Object.getPrototypeOf(exported)).slice(0, 10).join(", ") : "N/A";
|
|
48210
|
-
|
|
50137
|
+
logger.warn(`[functions] ${file}: default export is not a Hono app or factory. Skipping.
|
|
48211
50138
|
export type: ${exportType}${exported?.constructor?.name ? ` (${exported.constructor.name})` : ""}
|
|
48212
50139
|
prototype methods: ${keys2}
|
|
48213
50140
|
Hint: ensure the function exports a Hono app created with the same hono version as the server.
|
|
48214
50141
|
The loader checks for .fetch() and .routes — any Hono-compatible app will work.`);
|
|
48215
50142
|
} catch (err) {
|
|
48216
50143
|
const message = err instanceof Error ? err.message : String(err);
|
|
48217
|
-
|
|
50144
|
+
logger.error(`[functions] Failed to load ${file}: ${message}`);
|
|
48218
50145
|
}
|
|
48219
50146
|
}
|
|
48220
50147
|
}
|
|
@@ -48263,12 +50190,12 @@ async function loadCronJobsFromDirectory(directory) {
|
|
|
48263
50190
|
const mod = await dynamicImport(fileUrl);
|
|
48264
50191
|
const exported = mod.default;
|
|
48265
50192
|
if (!exported || typeof exported !== "object") {
|
|
48266
|
-
|
|
50193
|
+
logger.warn(`[cron] ${file}: no valid default export. Skipping.`);
|
|
48267
50194
|
continue;
|
|
48268
50195
|
}
|
|
48269
50196
|
const def = exported;
|
|
48270
50197
|
if (typeof def.schedule !== "string" || typeof def.handler !== "function") {
|
|
48271
|
-
|
|
50198
|
+
logger.warn(`[cron] ${file}: default export missing required 'schedule' or 'handler'. Skipping.`);
|
|
48272
50199
|
continue;
|
|
48273
50200
|
}
|
|
48274
50201
|
const id = path$3.basename(file, path$3.extname(file));
|
|
@@ -48284,10 +50211,10 @@ async function loadCronJobsFromDirectory(directory) {
|
|
|
48284
50211
|
id,
|
|
48285
50212
|
definition
|
|
48286
50213
|
});
|
|
48287
|
-
|
|
50214
|
+
logger.info(`⏰ Loaded cron job: ${id} (${definition.schedule})`);
|
|
48288
50215
|
} catch (err) {
|
|
48289
50216
|
const message = err instanceof Error ? err.message : String(err);
|
|
48290
|
-
|
|
50217
|
+
logger.error(`[cron] Failed to load ${file}: ${message}`);
|
|
48291
50218
|
}
|
|
48292
50219
|
}
|
|
48293
50220
|
}
|
|
@@ -48442,12 +50369,12 @@ class CronScheduler {
|
|
|
48442
50369
|
for (const loaded of loadedJobs) {
|
|
48443
50370
|
const validation = validateCronExpression(loaded.definition.schedule);
|
|
48444
50371
|
if (!validation.valid) {
|
|
48445
|
-
|
|
50372
|
+
logger.error(`[cron] Rejecting job "${loaded.id}": invalid schedule "${loaded.definition.schedule}" — ${validation.reason}`);
|
|
48446
50373
|
continue;
|
|
48447
50374
|
}
|
|
48448
50375
|
const existing = this.jobs.get(loaded.id);
|
|
48449
50376
|
if (existing) {
|
|
48450
|
-
|
|
50377
|
+
logger.warn(`[cron] Duplicate cron job id: "${loaded.id}". Overwriting.`);
|
|
48451
50378
|
this.stopJob(loaded.id);
|
|
48452
50379
|
}
|
|
48453
50380
|
const enabled = loaded.definition.enabled !== false;
|
|
@@ -48485,7 +50412,9 @@ class CronScheduler {
|
|
|
48485
50412
|
}
|
|
48486
50413
|
}
|
|
48487
50414
|
}).catch((err) => {
|
|
48488
|
-
|
|
50415
|
+
logger.warn("[cron] Failed to seed job stats from database", {
|
|
50416
|
+
error: err
|
|
50417
|
+
});
|
|
48489
50418
|
});
|
|
48490
50419
|
}
|
|
48491
50420
|
for (const [id, job] of this.jobs) {
|
|
@@ -48493,7 +50422,7 @@ class CronScheduler {
|
|
|
48493
50422
|
this.scheduleNext(id);
|
|
48494
50423
|
}
|
|
48495
50424
|
}
|
|
48496
|
-
|
|
50425
|
+
logger.info(`⏰ Cron scheduler started with ${this.jobs.size} job(s)`);
|
|
48497
50426
|
}
|
|
48498
50427
|
/**
|
|
48499
50428
|
* Stop the scheduler and clear all timers.
|
|
@@ -48567,7 +50496,7 @@ class CronScheduler {
|
|
|
48567
50496
|
const job = this.jobs.get(id);
|
|
48568
50497
|
if (!job) return void 0;
|
|
48569
50498
|
if (job.executing) {
|
|
48570
|
-
|
|
50499
|
+
logger.warn(`[cron] Skipping manual trigger of "${id}" — already executing`);
|
|
48571
50500
|
const logEntry = {
|
|
48572
50501
|
jobId: id,
|
|
48573
50502
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -48611,7 +50540,7 @@ class CronScheduler {
|
|
|
48611
50540
|
const timer = setTimeout(async () => {
|
|
48612
50541
|
if (!job.enabled || !this.started) return;
|
|
48613
50542
|
if (job.executing) {
|
|
48614
|
-
|
|
50543
|
+
logger.warn(`[cron] Skipping scheduled run of "${id}" — still executing from previous run`);
|
|
48615
50544
|
this.scheduleNext(id);
|
|
48616
50545
|
return;
|
|
48617
50546
|
}
|
|
@@ -48625,7 +50554,9 @@ class CronScheduler {
|
|
|
48625
50554
|
}
|
|
48626
50555
|
job.timerId = timer;
|
|
48627
50556
|
} catch (err) {
|
|
48628
|
-
|
|
50557
|
+
logger.error(`[cron] Failed to schedule "${id}"`, {
|
|
50558
|
+
error: err
|
|
50559
|
+
});
|
|
48629
50560
|
job.state = "error";
|
|
48630
50561
|
job.lastError = err instanceof Error ? err.message : String(err);
|
|
48631
50562
|
}
|
|
@@ -48710,13 +50641,15 @@ class CronScheduler {
|
|
|
48710
50641
|
}
|
|
48711
50642
|
if (this.store) {
|
|
48712
50643
|
this.store.insertLog(logEntry).catch((persistErr) => {
|
|
48713
|
-
|
|
50644
|
+
logger.error(`[cron] Failed to persist log for "${job.id}"`, {
|
|
50645
|
+
error: persistErr
|
|
50646
|
+
});
|
|
48714
50647
|
});
|
|
48715
50648
|
}
|
|
48716
50649
|
if (success) {
|
|
48717
|
-
|
|
50650
|
+
logger.info(`✅ [cron] "${job.id}" completed in ${durationMs}ms`);
|
|
48718
50651
|
} else {
|
|
48719
|
-
|
|
50652
|
+
logger.error(`❌ [cron] "${job.id}" failed in ${durationMs}ms: ${error2}`);
|
|
48720
50653
|
}
|
|
48721
50654
|
return logEntry;
|
|
48722
50655
|
}
|
|
@@ -48834,7 +50767,7 @@ const TABLE = "rebase.cron_logs";
|
|
|
48834
50767
|
function createCronStore(driver) {
|
|
48835
50768
|
const admin = driver.admin;
|
|
48836
50769
|
if (!isSQLAdmin(admin)) {
|
|
48837
|
-
|
|
50770
|
+
logger.warn("⚠️ [cron-store] DataDriver does not support SQL admin — cron logs will not be persisted.");
|
|
48838
50771
|
return void 0;
|
|
48839
50772
|
}
|
|
48840
50773
|
const exec = admin.executeSql.bind(admin);
|
|
@@ -48860,10 +50793,12 @@ function createCronStore(driver) {
|
|
|
48860
50793
|
CREATE INDEX IF NOT EXISTS idx_cron_logs_job
|
|
48861
50794
|
ON ${TABLE}(job_id, started_at DESC)
|
|
48862
50795
|
`);
|
|
48863
|
-
|
|
50796
|
+
logger.info("✅ Cron logs table ready");
|
|
48864
50797
|
} catch (err) {
|
|
48865
|
-
|
|
48866
|
-
|
|
50798
|
+
logger.error("❌ Failed to create cron logs table", {
|
|
50799
|
+
error: err
|
|
50800
|
+
});
|
|
50801
|
+
logger.warn("⚠️ Continuing without cron log persistence.");
|
|
48867
50802
|
}
|
|
48868
50803
|
},
|
|
48869
50804
|
async insertLog(entry) {
|
|
@@ -48886,7 +50821,9 @@ function createCronStore(driver) {
|
|
|
48886
50821
|
)
|
|
48887
50822
|
`);
|
|
48888
50823
|
} catch (err) {
|
|
48889
|
-
|
|
50824
|
+
logger.error(`[cron-store] Failed to persist log for "${entry.jobId}"`, {
|
|
50825
|
+
error: err
|
|
50826
|
+
});
|
|
48890
50827
|
}
|
|
48891
50828
|
},
|
|
48892
50829
|
async fetchLogs(jobId, limit = 50) {
|
|
@@ -48900,7 +50837,9 @@ function createCronStore(driver) {
|
|
|
48900
50837
|
`);
|
|
48901
50838
|
return rows.map(rowToLogEntry);
|
|
48902
50839
|
} catch (err) {
|
|
48903
|
-
|
|
50840
|
+
logger.error(`[cron-store] Failed to fetch logs for "${jobId}"`, {
|
|
50841
|
+
error: err
|
|
50842
|
+
});
|
|
48904
50843
|
return [];
|
|
48905
50844
|
}
|
|
48906
50845
|
},
|
|
@@ -48924,7 +50863,9 @@ function createCronStore(driver) {
|
|
|
48924
50863
|
});
|
|
48925
50864
|
}
|
|
48926
50865
|
} catch (err) {
|
|
48927
|
-
|
|
50866
|
+
logger.error("[cron-store] Failed to fetch job stats", {
|
|
50867
|
+
error: err
|
|
50868
|
+
});
|
|
48928
50869
|
}
|
|
48929
50870
|
return stats;
|
|
48930
50871
|
}
|
|
@@ -48955,8 +50896,8 @@ function serveSPA(app, config) {
|
|
|
48955
50896
|
indexFile = "index.html"
|
|
48956
50897
|
} = config;
|
|
48957
50898
|
if (!fs$4.existsSync(frontendPath)) {
|
|
48958
|
-
|
|
48959
|
-
|
|
50899
|
+
logger.warn(`⚠️ Frontend build path does not exist: ${frontendPath}`);
|
|
50900
|
+
logger.warn(" SPA serving is disabled. Build your frontend first.");
|
|
48960
50901
|
return;
|
|
48961
50902
|
}
|
|
48962
50903
|
app.use("/*", serveStatic({
|
|
@@ -48969,13 +50910,13 @@ function serveSPA(app, config) {
|
|
|
48969
50910
|
}
|
|
48970
50911
|
const indexPath = path$3.join(frontendPath, indexFile);
|
|
48971
50912
|
if (!fs$4.existsSync(indexPath)) {
|
|
48972
|
-
|
|
50913
|
+
logger.warn(`⚠️ Index file not found: ${indexPath}`);
|
|
48973
50914
|
return next();
|
|
48974
50915
|
}
|
|
48975
50916
|
const html = fs$4.readFileSync(indexPath, "utf-8");
|
|
48976
50917
|
return c.html(html);
|
|
48977
50918
|
});
|
|
48978
|
-
|
|
50919
|
+
logger.info(`✅ SPA serving enabled from: ${frontendPath}`);
|
|
48979
50920
|
}
|
|
48980
50921
|
const MAX_PORT_ATTEMPTS = 20;
|
|
48981
50922
|
const DEV_PORT_FILENAME = ".rebase-dev-port";
|
|
@@ -48983,6 +50924,19 @@ function listenWithPortRetry(server, startPort, options2) {
|
|
|
48983
50924
|
const host = options2?.host ?? "0.0.0.0";
|
|
48984
50925
|
const maxAttempts = options2?.maxAttempts ?? MAX_PORT_ATTEMPTS;
|
|
48985
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
|
+
}
|
|
48986
50940
|
let affinityPort = null;
|
|
48987
50941
|
if (portFileDir) {
|
|
48988
50942
|
try {
|
|
@@ -49125,6 +51079,8 @@ const rebaseEnvSchema = objectType({
|
|
|
49125
51079
|
DB_POOL_MAX: stringType().default("20").transform(Number),
|
|
49126
51080
|
DB_POOL_IDLE_TIMEOUT: stringType().default("30000").transform(Number),
|
|
49127
51081
|
DB_POOL_CONNECT_TIMEOUT: stringType().default("10000").transform(Number),
|
|
51082
|
+
DATABASE_DIRECT_URL: stringType().url().optional(),
|
|
51083
|
+
DATABASE_READ_URL: stringType().url().optional(),
|
|
49128
51084
|
FORCE_LOCAL_STORAGE: optionalBoolString,
|
|
49129
51085
|
STORAGE_TYPE: enumType(["local", "s3"]).default("local"),
|
|
49130
51086
|
STORAGE_PATH: stringType().optional(),
|
|
@@ -49201,8 +51157,11 @@ export {
|
|
|
49201
51157
|
RestApiGenerator,
|
|
49202
51158
|
S3StorageController,
|
|
49203
51159
|
SMTPEmailService,
|
|
51160
|
+
TransformCache,
|
|
51161
|
+
TusHandler,
|
|
49204
51162
|
_resetRebaseMock,
|
|
49205
51163
|
_setRebaseMock,
|
|
51164
|
+
apiKeyKeyGenerator,
|
|
49206
51165
|
authJwt,
|
|
49207
51166
|
authRoles,
|
|
49208
51167
|
authUid,
|
|
@@ -49211,6 +51170,9 @@ export {
|
|
|
49211
51170
|
configureLogLevel,
|
|
49212
51171
|
createAdapterAuthMiddleware,
|
|
49213
51172
|
createAdminRoutes,
|
|
51173
|
+
createApiKeyRateLimiter,
|
|
51174
|
+
createApiKeyRoutes,
|
|
51175
|
+
createApiKeyStore,
|
|
49214
51176
|
createAppleProvider,
|
|
49215
51177
|
createAuthMiddleware,
|
|
49216
51178
|
createAuthRoutes,
|
|
@@ -49248,26 +51210,34 @@ export {
|
|
|
49248
51210
|
getWelcomeEmailTemplate,
|
|
49249
51211
|
hashPassword,
|
|
49250
51212
|
hashRefreshToken,
|
|
51213
|
+
httpMethodToOperation,
|
|
49251
51214
|
initializeRebaseBackend,
|
|
51215
|
+
isApiKeyToken,
|
|
49252
51216
|
isAuthAdapter,
|
|
49253
51217
|
isDatabaseAdapter,
|
|
51218
|
+
isOperationAllowed,
|
|
51219
|
+
isTransformableImage,
|
|
49254
51220
|
listenWithPortRetry,
|
|
49255
51221
|
loadCronJobsFromDirectory,
|
|
49256
51222
|
loadEnv,
|
|
49257
51223
|
loadFunctionsFromDirectory,
|
|
49258
51224
|
logger,
|
|
49259
51225
|
optionalAuth,
|
|
51226
|
+
parseTransformOptions,
|
|
49260
51227
|
rebase,
|
|
49261
51228
|
requestLogger,
|
|
49262
51229
|
requireAdmin,
|
|
49263
51230
|
requireAuth,
|
|
49264
51231
|
resetConsole,
|
|
49265
|
-
|
|
51232
|
+
resolveAuthHooks,
|
|
49266
51233
|
serveSPA,
|
|
49267
51234
|
strictAuthLimiter,
|
|
51235
|
+
transformImage,
|
|
51236
|
+
validateApiKey,
|
|
49268
51237
|
validateCronExpression,
|
|
49269
51238
|
validatePasswordStrength,
|
|
49270
51239
|
verifyAccessToken,
|
|
49271
|
-
verifyPassword
|
|
51240
|
+
verifyPassword,
|
|
51241
|
+
z
|
|
49272
51242
|
};
|
|
49273
51243
|
//# sourceMappingURL=index.es.js.map
|