@rebasepro/server 0.9.1-canary.fd3754b → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/api/errors.d.ts +16 -1
  2. package/dist/api/rest/api-generator.d.ts +13 -1
  3. package/dist/api/rest/write-validation.d.ts +22 -0
  4. package/dist/api/types.d.ts +2 -2
  5. package/dist/auth/admin-users-route.d.ts +3 -3
  6. package/dist/auth/api-keys/api-key-middleware.d.ts +60 -1
  7. package/dist/auth/api-keys/api-key-permission-guard.d.ts +33 -0
  8. package/dist/auth/api-keys/api-key-types.d.ts +21 -8
  9. package/dist/auth/api-keys/index.d.ts +2 -2
  10. package/dist/auth/auth-hooks.d.ts +7 -7
  11. package/dist/auth/collection-callback-warning.d.ts +21 -0
  12. package/dist/auth/index.d.ts +4 -1
  13. package/dist/auth/interfaces.d.ts +28 -28
  14. package/dist/auth/jwt.d.ts +20 -4
  15. package/dist/auth/magic-link-routes.d.ts +2 -2
  16. package/dist/auth/mfa-routes.d.ts +1 -1
  17. package/dist/auth/middleware.d.ts +24 -3
  18. package/dist/auth/rate-limit-store.d.ts +49 -0
  19. package/dist/auth/rate-limiter.d.ts +68 -6
  20. package/dist/auth/reset-password-admin.d.ts +1 -1
  21. package/dist/auth/session-routes.d.ts +2 -2
  22. package/dist/cron/cron-store.d.ts +18 -0
  23. package/dist/env.d.ts +4 -0
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.es.js +2124 -505
  26. package/dist/index.es.js.map +1 -1
  27. package/dist/init/middlewares.d.ts +1 -0
  28. package/dist/init.d.ts +62 -0
  29. package/dist/{jwt-BwIn8xmk.js → jwt-B3zjddCa.js} +177 -9
  30. package/dist/jwt-B3zjddCa.js.map +1 -0
  31. package/dist/{openapi-generator-Z9oYWLf_.js → openapi-generator-DqSIwNLV.js} +8 -2
  32. package/dist/openapi-generator-DqSIwNLV.js.map +1 -0
  33. package/dist/{src-B4OLmNVa.js → src-CsHhSKbi.js} +60 -2
  34. package/dist/src-CsHhSKbi.js.map +1 -0
  35. package/dist/storage/LocalStorageController.d.ts +19 -2
  36. package/dist/storage/routes.d.ts +8 -1
  37. package/dist/storage/tus-handler.d.ts +21 -1
  38. package/dist/storage/types.d.ts +33 -0
  39. package/dist/utils/compression.d.ts +16 -0
  40. package/dist/utils/sql.d.ts +2 -2
  41. package/package.json +12 -11
  42. package/dist/index.umd.js +0 -48745
  43. package/dist/index.umd.js.map +0 -1
  44. package/dist/jwt-BwIn8xmk.js.map +0 -1
  45. package/dist/ms-DnYXB-Wd.js +0 -162
  46. package/dist/ms-DnYXB-Wd.js.map +0 -1
  47. package/dist/openapi-generator-Z9oYWLf_.js.map +0 -1
  48. package/dist/src-B4OLmNVa.js.map +0 -1
  49. package/dist/src-B9tjYmqP.js +0 -24598
  50. package/dist/src-B9tjYmqP.js.map +0 -1
@@ -2,6 +2,7 @@ import { Hono } from "hono";
2
2
  import { HonoEnv } from "../api/types";
3
3
  interface MiddlewareConfig {
4
4
  maxBodySize?: number;
5
+ compression?: boolean;
5
6
  csrf?: {
6
7
  origin: string | string[] | ((origin: string) => boolean);
7
8
  };
package/dist/init.d.ts CHANGED
@@ -5,6 +5,7 @@ import { Server } from "http";
5
5
  import { Hono } from "hono";
6
6
  import { HonoEnv } from "./api/types";
7
7
  import { BackendStorageConfig, StorageController, StorageRegistry } from "./storage";
8
+ import { type DataRateLimitConfig } from "./auth/rate-limiter";
8
9
  import { EmailConfig } from "./email";
9
10
  import type { OAuthProvider } from "./auth/interfaces";
10
11
  import type { AuthHooks } from "./auth/auth-hooks";
@@ -193,6 +194,17 @@ export interface RebaseBackendConfig {
193
194
  server: Server;
194
195
  app: Hono<HonoEnv>;
195
196
  basePath?: string;
197
+ /**
198
+ * Rate limiting for the data API, per caller: an API key by its id, a
199
+ * signed-in user by their uid, anyone else by IP.
200
+ *
201
+ * On by default with loose limits — a floor against a runaway client, not a
202
+ * quota. Counts live in this process's memory unless you pass a `store`, so
203
+ * N replicas enforce N times the limit between them; set real quotas at a
204
+ * proxy or supply a shared store if that matters. `{ enabled: false }` for
205
+ * a deployment whose edge already does this.
206
+ */
207
+ rateLimit?: DataRateLimitConfig;
196
208
  /**
197
209
  * How much of Rebase to run.
198
210
  *
@@ -275,6 +287,27 @@ export interface RebaseBackendConfig {
275
287
  * external storage) that the backend does not proxy.
276
288
  */
277
289
  storageSources?: import("@rebasepro/types").StorageSourceDefinition[];
290
+ /**
291
+ * Per-object access control for storage — the analogue of a collection's
292
+ * security rules, and the thing `requireAuth` / `publicRead` cannot
293
+ * express because they are global switches.
294
+ *
295
+ * Called after authentication on every storage route with the key, bucket,
296
+ * operation (`read` / `write` / `delete` / `list`) and resolved user.
297
+ * Return false to deny with a 403; throwing denies too.
298
+ *
299
+ * ```ts
300
+ * storageAuthorize: async ({ key, user, operation }) => {
301
+ * if (!user) return false;
302
+ * const [ownerId] = key.split("/");
303
+ * return ownerId === user.uid || operation === "read";
304
+ * }
305
+ * ```
306
+ *
307
+ * Without it, any authenticated caller may read any key they can name, so
308
+ * multi-tenant apps should treat this as required rather than optional.
309
+ */
310
+ storageAuthorize?: import("./storage/types").StorageAuthorize;
278
311
  /**
279
312
  * Entity history / audit-log configuration.
280
313
  *
@@ -299,6 +332,20 @@ export interface RebaseBackendConfig {
299
332
  * `maxFileSize` property (default: 50MB), which takes precedence over this.
300
333
  */
301
334
  maxBodySize?: number;
335
+ /**
336
+ * Response compression for API routes. **Enabled by default.**
337
+ *
338
+ * Compresses responses with gzip/deflate, negotiated from the request's
339
+ * `Accept-Encoding`. Bodies that are already compressed (images, video),
340
+ * streamed (`text/event-stream`), or explicitly marked
341
+ * `Cache-Control: no-transform` are left untouched, so this is safe to
342
+ * leave on — a large JSON list response typically drops by ~20x.
343
+ *
344
+ * Set to `false` when something in front of the app already compresses
345
+ * (nginx, Cloudflare, or another reverse proxy / load balancer), to avoid
346
+ * paying for it twice.
347
+ */
348
+ compression?: boolean;
302
349
  /**
303
350
  * CSRF protection configuration. **Opt-in** — disabled by default.
304
351
  *
@@ -357,6 +404,21 @@ export interface RebaseBackendInstance {
357
404
  storageController?: StorageController;
358
405
  collectionRegistry: BackendCollectionRegistry;
359
406
  cronScheduler?: import("./cron").CronScheduler;
407
+ /**
408
+ * Attach collection callbacks AFTER initialization.
409
+ *
410
+ * Use this instead of mutating `collectionRegistry.get(slug).callbacks`.
411
+ * Every registry normalizes its collections through `{ ...c }`, so the
412
+ * backend registry and each driver's registry hold **separate copies** of
413
+ * the same collection — assigning callbacks to one is invisible to the
414
+ * driver that actually invokes them, and the hooks silently never fire.
415
+ * This writes to all of them.
416
+ *
417
+ * (Assignment, not `Object.defineProperty`: the driver resolves callbacks
418
+ * with a spread, which copies only enumerable properties, and
419
+ * defineProperty defaults `enumerable` to false.)
420
+ */
421
+ setCollectionCallbacks(slug: string, callbacks: import("@rebasepro/types").CollectionCallbacks): void;
360
422
  /**
361
423
  * Deep health check that verifies database connectivity.
362
424
  * Returns latency and component status.
@@ -1,9 +1,44 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import process from "process";
3
- __createRequire(import.meta.url);
4
- import { a as __require, i as __exportAll, n as __commonJSMin, s as __toESM, t as require_ms } from "./ms-DnYXB-Wd.js";
3
+ const require = __createRequire(import.meta.url);
5
4
  import { t as logger } from "./logger-BYU66ENZ.js";
6
5
  import { createHash, randomBytes } from "crypto";
6
+ //#region \0rolldown/runtime.js
7
+ var __create = Object.create;
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __getProtoOf = Object.getPrototypeOf;
12
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
13
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
14
+ var __exportAll = (all, no_symbols) => {
15
+ let target = {};
16
+ for (var name in all) __defProp(target, name, {
17
+ get: all[name],
18
+ enumerable: true
19
+ });
20
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
21
+ return target;
22
+ };
23
+ var __copyProps = (to, from, except, desc) => {
24
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
25
+ key = keys[i];
26
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
27
+ get: ((k) => from[k]).bind(null, key),
28
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
+ });
30
+ }
31
+ return to;
32
+ };
33
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
34
+ value: mod,
35
+ enumerable: true
36
+ }) : target, mod));
37
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, { get: (a, b) => (typeof require !== "undefined" ? require : a)[b] }) : x)(function(x) {
38
+ if (typeof require !== "undefined") return require.apply(this, arguments);
39
+ throw Error("Calling `require` for \"" + x + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
40
+ });
41
+ //#endregion
7
42
  //#region ../../node_modules/.pnpm/safe-buffer@5.2.1/node_modules/safe-buffer/index.js
8
43
  var require_safe_buffer = /* @__PURE__ */ __commonJSMin(((exports, module) => {
9
44
  /*! safe-buffer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */
@@ -674,6 +709,124 @@ var require_TokenExpiredError = /* @__PURE__ */ __commonJSMin(((exports, module)
674
709
  module.exports = TokenExpiredError;
675
710
  }));
676
711
  //#endregion
712
+ //#region ../../node_modules/.pnpm/ms@2.1.3/node_modules/ms/index.js
713
+ var require_ms = /* @__PURE__ */ __commonJSMin(((exports, module) => {
714
+ /**
715
+ * Helpers.
716
+ */
717
+ var s = 1e3;
718
+ var m = s * 60;
719
+ var h = m * 60;
720
+ var d = h * 24;
721
+ var w = d * 7;
722
+ var y = d * 365.25;
723
+ /**
724
+ * Parse or format the given `val`.
725
+ *
726
+ * Options:
727
+ *
728
+ * - `long` verbose formatting [false]
729
+ *
730
+ * @param {String|Number} val
731
+ * @param {Object} [options]
732
+ * @throws {Error} throw an error if val is not a non-empty string or a number
733
+ * @return {String|Number}
734
+ * @api public
735
+ */
736
+ module.exports = function(val, options) {
737
+ options = options || {};
738
+ var type = typeof val;
739
+ if (type === "string" && val.length > 0) return parse(val);
740
+ else if (type === "number" && isFinite(val)) return options.long ? fmtLong(val) : fmtShort(val);
741
+ throw new Error("val is not a non-empty string or a valid number. val=" + JSON.stringify(val));
742
+ };
743
+ /**
744
+ * Parse the given `str` and return milliseconds.
745
+ *
746
+ * @param {String} str
747
+ * @return {Number}
748
+ * @api private
749
+ */
750
+ function parse(str) {
751
+ str = String(str);
752
+ if (str.length > 100) return;
753
+ var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(str);
754
+ if (!match) return;
755
+ var n = parseFloat(match[1]);
756
+ switch ((match[2] || "ms").toLowerCase()) {
757
+ case "years":
758
+ case "year":
759
+ case "yrs":
760
+ case "yr":
761
+ case "y": return n * y;
762
+ case "weeks":
763
+ case "week":
764
+ case "w": return n * w;
765
+ case "days":
766
+ case "day":
767
+ case "d": return n * d;
768
+ case "hours":
769
+ case "hour":
770
+ case "hrs":
771
+ case "hr":
772
+ case "h": return n * h;
773
+ case "minutes":
774
+ case "minute":
775
+ case "mins":
776
+ case "min":
777
+ case "m": return n * m;
778
+ case "seconds":
779
+ case "second":
780
+ case "secs":
781
+ case "sec":
782
+ case "s": return n * s;
783
+ case "milliseconds":
784
+ case "millisecond":
785
+ case "msecs":
786
+ case "msec":
787
+ case "ms": return n;
788
+ default: return;
789
+ }
790
+ }
791
+ /**
792
+ * Short format for `ms`.
793
+ *
794
+ * @param {Number} ms
795
+ * @return {String}
796
+ * @api private
797
+ */
798
+ function fmtShort(ms) {
799
+ var msAbs = Math.abs(ms);
800
+ if (msAbs >= d) return Math.round(ms / d) + "d";
801
+ if (msAbs >= h) return Math.round(ms / h) + "h";
802
+ if (msAbs >= m) return Math.round(ms / m) + "m";
803
+ if (msAbs >= s) return Math.round(ms / s) + "s";
804
+ return ms + "ms";
805
+ }
806
+ /**
807
+ * Long format for `ms`.
808
+ *
809
+ * @param {Number} ms
810
+ * @return {String}
811
+ * @api private
812
+ */
813
+ function fmtLong(ms) {
814
+ var msAbs = Math.abs(ms);
815
+ if (msAbs >= d) return plural(ms, msAbs, d, "day");
816
+ if (msAbs >= h) return plural(ms, msAbs, h, "hour");
817
+ if (msAbs >= m) return plural(ms, msAbs, m, "minute");
818
+ if (msAbs >= s) return plural(ms, msAbs, s, "second");
819
+ return ms + " ms";
820
+ }
821
+ /**
822
+ * Pluralization helper.
823
+ */
824
+ function plural(ms, msAbs, n, name) {
825
+ var isPlural = msAbs >= n * 1.5;
826
+ return Math.round(ms / n) + " " + name + (isPlural ? "s" : "");
827
+ }
828
+ }));
829
+ //#endregion
677
830
  //#region ../../node_modules/.pnpm/jsonwebtoken@9.0.3/node_modules/jsonwebtoken/lib/timespan.js
678
831
  var require_timespan = /* @__PURE__ */ __commonJSMin(((exports, module) => {
679
832
  var ms = require_ms();
@@ -4065,10 +4218,10 @@ function configureJwt(config) {
4065
4218
  /**
4066
4219
  * Generate an access token (short-lived, 1 hour by default)
4067
4220
  */
4068
- function generateAccessToken(userId, roles, aal = "aal1", customClaims) {
4221
+ function generateAccessToken(uid, roles, aal = "aal1", customClaims) {
4069
4222
  if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4070
4223
  const payload = {
4071
- userId,
4224
+ uid,
4072
4225
  roles,
4073
4226
  aal,
4074
4227
  ...customClaims
@@ -4100,20 +4253,35 @@ function getAccessTokenExpiry() {
4100
4253
  return Date.now() + getAccessTokenExpiryMs();
4101
4254
  }
4102
4255
  /**
4103
- * Verify and decode an access token
4256
+ * Verify and decode an access token.
4257
+ *
4258
+ * Every token this server issues is signed with the same secret, so what a
4259
+ * token *is* comes from its claims, not from its signature. A download token
4260
+ * ({@link generateDownloadToken}) is therefore a validly-signed string that
4261
+ * must never authenticate anybody: it is scoped to one file path and handed out
4262
+ * in URLs, which is a far weaker thing to hold than a session.
4263
+ *
4264
+ * Today it is rejected below for want of an id — but only by luck, since
4265
+ * nothing stops a future download token from carrying one. So the purpose is
4266
+ * checked explicitly: a token minted for reading a file is not a token for
4267
+ * being a user.
4104
4268
  */
4105
4269
  function verifyAccessToken(token) {
4106
4270
  if (!jwtConfig.secret) throw new Error("JWT secret not configured. Call configureJwt() first.");
4107
4271
  try {
4108
4272
  const decoded = import_jsonwebtoken.default.verify(token, jwtConfig.secret, { algorithms: ["HS256"] });
4109
- const id = decoded.userId || decoded.uid || decoded.sub;
4273
+ if (decoded.purpose) {
4274
+ logger.error("[JWT] Verification failed: a purpose-scoped token is not an access token", { purpose: decoded.purpose });
4275
+ return null;
4276
+ }
4277
+ const id = decoded.uid || decoded.userId || decoded.sub;
4110
4278
  if (!id) {
4111
4279
  logger.error("[JWT] Verification failed: missing id in payload", { detail: decoded });
4112
4280
  return null;
4113
4281
  }
4114
4282
  const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
4115
4283
  return {
4116
- userId: id,
4284
+ uid: id,
4117
4285
  roles: decoded.roles || [],
4118
4286
  aal
4119
4287
  };
@@ -4195,6 +4363,6 @@ function verifyDownloadToken(token) {
4195
4363
  }
4196
4364
  }
4197
4365
  //#endregion
4198
- export { getAccessTokenExpiry as a, jwt_exports as c, require_jsonwebtoken as d, generateRefreshToken as i, verifyAccessToken as l, generateAccessToken as n, getRefreshTokenExpiry as o, generateDownloadToken as r, hashRefreshToken as s, configureJwt as t, verifyDownloadToken as u };
4366
+ export { getAccessTokenExpiry as a, jwt_exports as c, require_jsonwebtoken as d, __commonJSMin as f, __toESM as h, generateRefreshToken as i, verifyAccessToken as l, __require as m, generateAccessToken as n, getRefreshTokenExpiry as o, __exportAll as p, generateDownloadToken as r, hashRefreshToken as s, configureJwt as t, verifyDownloadToken as u };
4199
4367
 
4200
- //# sourceMappingURL=jwt-BwIn8xmk.js.map
4368
+ //# sourceMappingURL=jwt-B3zjddCa.js.map