@rebasepro/server-core 0.2.3 → 0.2.5

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