@objectstack/runtime 11.0.0 → 11.2.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 +3 -3
- package/dist/index.cjs +42 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -6
- package/dist/index.d.ts +17 -6
- package/dist/index.js +43 -2
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
package/dist/index.d.cts
CHANGED
|
@@ -918,12 +918,16 @@ interface HttpDispatcherOptions {
|
|
|
918
918
|
scopeManager?: EnvironmentScopeManager;
|
|
919
919
|
}
|
|
920
920
|
/**
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
921
|
+
* The HTTP dispatch engine — translates an inbound (method, path, body, ctx)
|
|
922
|
+
* request into a kernel response. Used directly by the framework's HTTP adapters
|
|
923
|
+
* (express / fastify / nextjs / nestjs / nuxt / sveltekit / hono) and plugin-msw,
|
|
924
|
+
* which need a *callable* dispatcher.
|
|
925
|
+
*
|
|
926
|
+
* NOTE: `createDispatcherPlugin()` is a different thing — a kernel plugin that
|
|
927
|
+
* registers routes on a kernel-hosted HTTP server. It is NOT a drop-in for
|
|
928
|
+
* adapters. Retiring this public class behind a `createHttpDispatcher()` factory
|
|
929
|
+
* is tracked in #2380 (a deliberate adapter-API change, not yet done) — so this
|
|
930
|
+
* is intentionally NOT marked `@deprecated` while no working replacement exists.
|
|
927
931
|
*/
|
|
928
932
|
declare class HttpDispatcher {
|
|
929
933
|
private kernel;
|
|
@@ -1120,6 +1124,13 @@ declare class HttpDispatcher {
|
|
|
1120
1124
|
* response object that callers should surface directly — no further
|
|
1121
1125
|
* dispatch happens.
|
|
1122
1126
|
*/
|
|
1127
|
+
/**
|
|
1128
|
+
* ADR-0069 — returns a 403 response when the resolved session is blocked by
|
|
1129
|
+
* an auth-policy gate (expired password / required MFA) on a non-allow-listed
|
|
1130
|
+
* path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
|
|
1131
|
+
* (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
|
|
1132
|
+
*/
|
|
1133
|
+
private enforceAuthGate;
|
|
1123
1134
|
private enforceProjectMembership;
|
|
1124
1135
|
/**
|
|
1125
1136
|
* Generates the discovery JSON response for the API root.
|
package/dist/index.d.ts
CHANGED
|
@@ -918,12 +918,16 @@ interface HttpDispatcherOptions {
|
|
|
918
918
|
scopeManager?: EnvironmentScopeManager;
|
|
919
919
|
}
|
|
920
920
|
/**
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
921
|
+
* The HTTP dispatch engine — translates an inbound (method, path, body, ctx)
|
|
922
|
+
* request into a kernel response. Used directly by the framework's HTTP adapters
|
|
923
|
+
* (express / fastify / nextjs / nestjs / nuxt / sveltekit / hono) and plugin-msw,
|
|
924
|
+
* which need a *callable* dispatcher.
|
|
925
|
+
*
|
|
926
|
+
* NOTE: `createDispatcherPlugin()` is a different thing — a kernel plugin that
|
|
927
|
+
* registers routes on a kernel-hosted HTTP server. It is NOT a drop-in for
|
|
928
|
+
* adapters. Retiring this public class behind a `createHttpDispatcher()` factory
|
|
929
|
+
* is tracked in #2380 (a deliberate adapter-API change, not yet done) — so this
|
|
930
|
+
* is intentionally NOT marked `@deprecated` while no working replacement exists.
|
|
927
931
|
*/
|
|
928
932
|
declare class HttpDispatcher {
|
|
929
933
|
private kernel;
|
|
@@ -1120,6 +1124,13 @@ declare class HttpDispatcher {
|
|
|
1120
1124
|
* response object that callers should surface directly — no further
|
|
1121
1125
|
* dispatch happens.
|
|
1122
1126
|
*/
|
|
1127
|
+
/**
|
|
1128
|
+
* ADR-0069 — returns a 403 response when the resolved session is blocked by
|
|
1129
|
+
* an auth-policy gate (expired password / required MFA) on a non-allow-listed
|
|
1130
|
+
* path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
|
|
1131
|
+
* (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
|
|
1132
|
+
*/
|
|
1133
|
+
private enforceAuthGate;
|
|
1123
1134
|
private enforceProjectMembership;
|
|
1124
1135
|
/**
|
|
1125
1136
|
* Generates the discovery JSON response for the API root.
|
package/dist/index.js
CHANGED
|
@@ -1556,7 +1556,7 @@ async function createStandaloneStack(config) {
|
|
|
1556
1556
|
const environmentId = cfg.environmentId ?? process.env.OS_ENVIRONMENT_ID ?? "proj_local";
|
|
1557
1557
|
const artifactPathInput = cfg.artifactPath ?? process.env.OS_ARTIFACT_PATH ?? resolvePath2(cwd, "dist/objectstack.json");
|
|
1558
1558
|
const artifactPath = isHttpUrl(artifactPathInput) ? artifactPathInput : artifactPathInput.startsWith("/") ? artifactPathInput : resolvePath2(cwd, artifactPathInput);
|
|
1559
|
-
const dbUrl = cfg.databaseUrl ?? readEnvWithDeprecation("OS_DATABASE_URL", "DATABASE_URL")?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${resolvePath2(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
1559
|
+
const dbUrl = cfg.databaseUrl ?? readEnvWithDeprecation("OS_DATABASE_URL", "DATABASE_URL", { silent: true })?.trim() ?? process.env.TURSO_DATABASE_URL?.trim() ?? (process.env.OS_HOME?.trim() ? `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}` : cfg.projectRoot ? `file:${resolvePath2(cfg.projectRoot, ".objectstack/data/standalone.db")}` : `file:${resolvePath2(resolveObjectStackHome(), "data/standalone.db")}`);
|
|
1560
1560
|
const explicitDriver = cfg.databaseDriver ?? process.env.OS_DATABASE_DRIVER?.trim();
|
|
1561
1561
|
const dbDriver = explicitDriver ?? detectDriverFromUrl(dbUrl);
|
|
1562
1562
|
let driverPlugin;
|
|
@@ -1975,7 +1975,7 @@ function safeGet(ctx, name) {
|
|
|
1975
1975
|
|
|
1976
1976
|
// src/http-dispatcher.ts
|
|
1977
1977
|
init_package_state_store();
|
|
1978
|
-
import { getEnv, resolveLocale } from "@objectstack/core";
|
|
1978
|
+
import { getEnv, resolveLocale, evaluateAuthGate, isAuthGateAllowlisted } from "@objectstack/core";
|
|
1979
1979
|
import { CoreServiceName } from "@objectstack/spec/system";
|
|
1980
1980
|
import { pluralToSingular, PLURAL_TO_SINGULAR } from "@objectstack/spec/shared";
|
|
1981
1981
|
|
|
@@ -2844,6 +2844,43 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
2844
2844
|
* response object that callers should surface directly — no further
|
|
2845
2845
|
* dispatch happens.
|
|
2846
2846
|
*/
|
|
2847
|
+
/**
|
|
2848
|
+
* ADR-0069 — returns a 403 response when the resolved session is blocked by
|
|
2849
|
+
* an auth-policy gate (expired password / required MFA) on a non-allow-listed
|
|
2850
|
+
* path, else null. Mirrors the REST `enforceAuth` seam so REST + dispatcher
|
|
2851
|
+
* (MCP, GraphQL) enforce consistently. Fails open on any lookup error.
|
|
2852
|
+
*/
|
|
2853
|
+
async enforceAuthGate(context, cleanPath) {
|
|
2854
|
+
try {
|
|
2855
|
+
if (isAuthGateAllowlisted(cleanPath)) return null;
|
|
2856
|
+
const authService = await this.resolveService("auth", context.environmentId);
|
|
2857
|
+
if (!authService || typeof authService.isAuthGateActive !== "function" || !authService.isAuthGateActive()) {
|
|
2858
|
+
return null;
|
|
2859
|
+
}
|
|
2860
|
+
let api = authService.api;
|
|
2861
|
+
if (!api && typeof authService.getApi === "function") api = await authService.getApi();
|
|
2862
|
+
if (!api?.getSession) return null;
|
|
2863
|
+
const raw = context?.request?.headers;
|
|
2864
|
+
let headers;
|
|
2865
|
+
if (raw && typeof raw.get === "function") {
|
|
2866
|
+
headers = raw;
|
|
2867
|
+
} else if (raw && typeof raw === "object") {
|
|
2868
|
+
headers = new globalThis.Headers();
|
|
2869
|
+
for (const k of Object.keys(raw)) {
|
|
2870
|
+
const v = raw[k];
|
|
2871
|
+
if (v != null) headers.set(String(k), Array.isArray(v) ? v.join(",") : String(v));
|
|
2872
|
+
}
|
|
2873
|
+
} else {
|
|
2874
|
+
return null;
|
|
2875
|
+
}
|
|
2876
|
+
const session = await api.getSession({ headers }).catch(() => void 0);
|
|
2877
|
+
const gate = evaluateAuthGate(session?.user, cleanPath);
|
|
2878
|
+
if (!gate) return null;
|
|
2879
|
+
return this.error(gate.message, 403, { code: gate.code });
|
|
2880
|
+
} catch {
|
|
2881
|
+
return null;
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2847
2884
|
async enforceProjectMembership(context, path) {
|
|
2848
2885
|
if (!this.enforceMembership) return null;
|
|
2849
2886
|
const skipPaths = ["/auth", "/cloud", "/health", "/ready", "/discovery"];
|
|
@@ -4763,6 +4800,10 @@ var _HttpDispatcher = class _HttpDispatcher {
|
|
|
4763
4800
|
});
|
|
4764
4801
|
} catch {
|
|
4765
4802
|
}
|
|
4803
|
+
const authGated = await this.enforceAuthGate(context, cleanPath);
|
|
4804
|
+
if (authGated) {
|
|
4805
|
+
return { handled: true, response: authGated };
|
|
4806
|
+
}
|
|
4766
4807
|
const forbidden = await this.enforceProjectMembership(context, cleanPath);
|
|
4767
4808
|
if (forbidden) {
|
|
4768
4809
|
return { handled: true, response: forbidden };
|