@objectstack/runtime 11.0.0 → 11.1.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.
package/README.md CHANGED
@@ -134,7 +134,7 @@ new AppPlugin(appConfig)
134
134
 
135
135
  #### IHttpServer
136
136
 
137
- Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (Express, Fastify, Hono, etc.) without tight coupling.
137
+ Abstract interface for HTTP server capabilities. Allows plugins to work with any HTTP framework (e.g. Hono) without tight coupling.
138
138
 
139
139
  ```typescript
140
140
  import { IHttpServer, IHttpRequest, IHttpResponse } from '@objectstack/runtime';
@@ -144,7 +144,7 @@ class MyHttpServerPlugin implements Plugin {
144
144
  name = 'http-server';
145
145
 
146
146
  async init(ctx: PluginContext) {
147
- const server: IHttpServer = createMyServer(); // Express, Hono, etc.
147
+ const server: IHttpServer = createMyServer(); // Hono, or any framework via IHttpServer
148
148
  ctx.registerService('http-server', server);
149
149
  }
150
150
  }
@@ -599,7 +599,7 @@ createDispatcherPlugin({
599
599
 
600
600
  Token-bucket `RateLimiter` with pluggable `RateLimitStore` (in-memory default,
601
601
  Redis-friendly contract). Curated `DEFAULT_RATE_LIMITS` for auth / write / read
602
- buckets. Fastify / Hono / Express recipes in
602
+ buckets. A Hono recipe in
603
603
  [`docs/HARDENING.md`](../../docs/HARDENING.md#rate-limiting).
604
604
 
605
605
  ```ts
package/dist/index.cjs CHANGED
@@ -1575,7 +1575,7 @@ async function createStandaloneStack(config) {
1575
1575
  const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
1576
1576
  const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? (0, import_node_path3.resolve)(cwd, "dist/objectstack.json");
1577
1577
  const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : (0, import_node_path3.resolve)(cwd, artifactPathInput);
1578
- const dbUrl = cfg.databaseUrl ?? (0, import_types2.readEnvWithDeprecation)("OS_DATABASE_URL", "DATABASE_URL")?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${(0, import_node_path3.resolve)(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${(0, import_node_path3.resolve)(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${(0, import_node_path3.resolve)(resolveObjectStackHome(), "data/standalone.db")}`);
1578
+ const dbUrl = cfg.databaseUrl ?? (0, import_types2.readEnvWithDeprecation)("OS_DATABASE_URL", "DATABASE_URL", { silent: true })?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${(0, import_node_path3.resolve)(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${(0, import_node_path3.resolve)(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${(0, import_node_path3.resolve)(resolveObjectStackHome(), "data/standalone.db")}`);
1579
1579
  const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
1580
1580
  const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
1581
1581
  let driverPlugin;
@@ -2909,6 +2909,43 @@ var _HttpDispatcher = class _HttpDispatcher {
2909
2909
  * response object that callers should surface directly — no further
2910
2910
  * dispatch happens.
2911
2911
  */
2912
+ /**
2913
+ * ADR-0069 — returns a 403 response when the resolved session is blocked by
2914
+ * an auth-policy gate (expired password / required MFA) on a non-allow-listed
2915
+ * path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
2916
+ * (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
2917
+ */
2918
+ async enforceAuthGate(context, cleanPath) {
2919
+ try {
2920
+ if ((0, import_core4.isAuthGateAllowlisted)(cleanPath)) return null;
2921
+ const authService = await this.resolveService("auth", context.environmentId);
2922
+ if (!authService || typeof authService.isAuthGateActive !== "function" || !authService.isAuthGateActive()) {
2923
+ return null;
2924
+ }
2925
+ let api = authService.api;
2926
+ if (!api && typeof authService.getApi === "function") api = await authService.getApi();
2927
+ if (!api?.getSession) return null;
2928
+ const raw = context?.request?.headers;
2929
+ let headers;
2930
+ if (raw && typeof raw.get === "function") {
2931
+ headers = raw;
2932
+ } else if (raw && typeof raw === "object") {
2933
+ headers = new globalThis.Headers();
2934
+ for (const k of Object.keys(raw)) {
2935
+ const v = raw[k];
2936
+ if (v != null) headers.set(String(k), Array.isArray(v) ? v.join(",") : String(v));
2937
+ }
2938
+ } else {
2939
+ return null;
2940
+ }
2941
+ const session = await api.getSession({ headers }).catch(() => void 0);
2942
+ const gate = (0, import_core4.evaluateAuthGate)(session?.user, cleanPath);
2943
+ if (!gate) return null;
2944
+ return this.error(gate.message, 403, { code: gate.code });
2945
+ } catch {
2946
+ return null;
2947
+ }
2948
+ }
2912
2949
  async enforceProjectMembership(context, path) {
2913
2950
  if (!this.enforceMembership) return null;
2914
2951
  const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
@@ -4828,6 +4865,10 @@ var _HttpDispatcher = class _HttpDispatcher {
4828
4865
  });
4829
4866
  } catch {
4830
4867
  }
4868
+ const authGated = await this.enforceAuthGate(context, cleanPath);
4869
+ if (authGated) {
4870
+ return { handled: true, response: authGated };
4871
+ }
4831
4872
  const forbidden = await this.enforceProjectMembership(context, cleanPath);
4832
4873
  if (forbidden) {
4833
4874
  return { handled: true, response: forbidden };