Files
0pt/src/document-workbench/runtime-config.ts
T
admin e93db5fc1f
CI (SIAX Cloud) / sonar (pull_request) Skipped
CI (SIAX Cloud) / integration (push) Skipped
CI (SIAX Cloud) / sonar (push) Skipped
masterplan-lock / check (pull_request) Successful in 3s
CI (SIAX Cloud) / security (push) Successful in 15s
Security Scan (SIAX Cloud) / security (push) Successful in 15s
Security Scan (SIAX Cloud) / security (pull_request) Successful in 15s
CI (SIAX Cloud) / security (pull_request) Successful in 1m2s
CI (SIAX Cloud) / integration (pull_request) Successful in 1m11s
masterplan-lock / check (push) Successful in 1m38s
CI (SIAX Cloud) / quality (push) Successful in 1m49s
CI (SIAX Cloud) / quality (pull_request) Successful in 1m50s
fix(doc0): call DOC0 as the granted service actor
DOC0 denies an x-actor-id that the service identity has no actor_grant for, so
every upload/structure call from 0pt failed with DOC0_ACCESS_DENIED and became
outcome_unknown. The journal and project authorization keep the verified
end-user actor; only the DOC0 request acts as the service (DOC0_SERVICE_ACTOR_ID).
2026-11-02 16:17:29 +01:00

107 lines
3.6 KiB
TypeScript

/**
* Server-side runtime configuration for the document workbench.
*
* Every integration is optional to BOOT and mandatory to USE: a route that
* needs DOC0/INF0/PAY0 returns a typed "not configured" error instead of
* fabricating success. URLs must be trusted HTTPS endpoints; tokens are never
* logged or returned.
*
* This module is deliberately framework-neutral and free of product imports so
* it can be lifted into a shared package without change.
*/
export interface HttpsEndpoint {
baseUrl: string;
serviceToken: string;
}
export interface DocumentWorkbenchRuntime {
doc0?: HttpsEndpoint & { maxUploadBytes: number; actorId?: string };
inf0?: HttpsEndpoint;
pay0?: HttpsEndpoint;
n0d?: HttpsEndpoint;
st0re?: HttpsEndpoint;
openfga?: {
baseUrl: string;
storeId: string;
modelId: string;
serviceToken: string;
};
}
const DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
function first(...values: Array<string | undefined>): string | undefined {
for (const value of values) {
if (typeof value === 'string' && value.trim()) return value.trim();
}
return undefined;
}
/** Only absolute HTTPS endpoints with no credentials/query/hash are trusted. */
export function isTrustedHttpsUrl(value: string | undefined): value is string {
if (!value) return false;
try {
const url = new URL(value);
return url.protocol === 'https:' && !url.username && !url.password && !url.search && !url.hash;
} catch {
return false;
}
}
function endpoint(
env: NodeJS.ProcessEnv,
urlNames: readonly string[],
tokenNames: readonly string[],
): HttpsEndpoint | undefined {
const baseUrl = first(...urlNames.map((name) => env[name]));
const serviceToken = first(...tokenNames.map((name) => env[name]));
if (!isTrustedHttpsUrl(baseUrl) || !serviceToken) return undefined;
return { baseUrl: baseUrl.replace(/\/+$/, ''), serviceToken };
}
function positiveInt(value: string | undefined, fallback: number): number {
if (!value) return fallback;
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
}
export function readDocumentWorkbenchRuntime(env: NodeJS.ProcessEnv = process.env): DocumentWorkbenchRuntime {
const doc0 = endpoint(env, ['DOC0_URL', 'DOC0_API_URL'], ['DOC0_SERVICE_TOKEN', 'DOC0_SVC_PAT']);
const inf0 = endpoint(env, ['INF0_URL', 'INF0_API_URL'], ['INF0_API_TOKEN', 'INF0_SERVICE_TOKEN']);
const pay0 = endpoint(env, ['PAY0_URL', 'PAY0_API_URL'], ['PAY0_API_TOKEN', 'PAY0_SERVICE_TOKEN']);
const n0d = endpoint(env, ['N0D_URL', 'N0D_API_URL'], ['N0D_API_TOKEN', 'N0D_SERVICE_TOKEN']);
const st0re = endpoint(env, ['ST0RE_URL', 'ST0RE_API_URL'], ['ST0RE_API_TOKEN', 'ST0RE_SERVICE_TOKEN']);
const openfgaBase = first(env.OPENFGA_API_URL);
const openfgaStore = first(env.OPENFGA_STORE_ID);
const openfgaModel = first(env.OPENFGA_MODEL_ID);
const openfgaToken = first(env.OPENFGA_AUTH_TOKEN, env.OPENFGA_AUTH_KEY);
return {
...(doc0
? {
doc0: {
...doc0,
maxUploadBytes: positiveInt(env.OPT_MAX_UPLOAD_BYTES, DEFAULT_MAX_UPLOAD_BYTES),
...(env.DOC0_SERVICE_ACTOR_ID ? { actorId: env.DOC0_SERVICE_ACTOR_ID.trim() } : {}),
},
}
: {}),
...(inf0 ? { inf0 } : {}),
...(pay0 ? { pay0 } : {}),
...(n0d ? { n0d } : {}),
...(st0re ? { st0re } : {}),
...(isTrustedHttpsUrl(openfgaBase) && openfgaStore && openfgaModel && openfgaToken
? {
openfga: {
baseUrl: openfgaBase.replace(/\/+$/, ''),
storeId: openfgaStore,
modelId: openfgaModel,
serviceToken: openfgaToken,
},
}
: {}),
};
}