@fuzdev/fuz_app 0.99.0 → 0.100.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.
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
* (filesystem-backed `file:<shard>/<rest>` URLs) either return an
|
|
15
15
|
* `X-Accel-Redirect` header pointing into nginx's internal facts location
|
|
16
16
|
* (production) or stream from disk via the filesystem `FactExternalFetcher`
|
|
17
|
-
* (dev / tests). The runtime mode is selected by the optional
|
|
18
|
-
*
|
|
17
|
+
* (dev / tests). The runtime mode is selected by the optional `x_accel`
|
|
18
|
+
* factory option — a validated `XAccelConfig` (built via
|
|
19
|
+
* `create_x_accel_config` in `server/x_accel.ts`) set in prod, unset in dev.
|
|
20
|
+
* The handle can only be obtained by proving the facts nginx location is
|
|
21
|
+
* `internal;`, so X-Accel serving can't be enabled against a public location.
|
|
19
22
|
*
|
|
20
23
|
* REST, not RPC: binary responses don't fit the JSON-RPC envelope.
|
|
21
24
|
*
|
|
@@ -87,6 +90,7 @@
|
|
|
87
90
|
import type { Logger } from '@fuzdev/fuz_util/log.ts';
|
|
88
91
|
import { type RouteSpec } from '../http/route_spec.js';
|
|
89
92
|
import type { AppDeps } from '../auth/deps.js';
|
|
93
|
+
import type { XAccelConfig } from './x_accel.js';
|
|
90
94
|
export interface CreateServeFactRouteSpecOptions {
|
|
91
95
|
/**
|
|
92
96
|
* App deps reference. Currently unused at handler time (cell + fact
|
|
@@ -100,12 +104,13 @@ export interface CreateServeFactRouteSpecOptions {
|
|
|
100
104
|
facts_dir: string;
|
|
101
105
|
/**
|
|
102
106
|
* When set, external facts return an `X-Accel-Redirect` pointing at
|
|
103
|
-
* `${
|
|
104
|
-
* the bytes. When unset, external facts stream from
|
|
105
|
-
* `<facts_dir>/<shard>/<rest>` directly. Production sets
|
|
106
|
-
*
|
|
107
|
+
* `${x_accel.redirect_prefix}<shard>/<rest>` — nginx's internal facts
|
|
108
|
+
* location serves the bytes. When unset, external facts stream from
|
|
109
|
+
* `<facts_dir>/<shard>/<rest>` directly. Production sets a validated
|
|
110
|
+
* `XAccelConfig` (built via `create_x_accel_config`, which proves the
|
|
111
|
+
* facts location is `internal;`); tests + dev leave it unset.
|
|
107
112
|
*/
|
|
108
|
-
|
|
113
|
+
x_accel?: XAccelConfig;
|
|
109
114
|
log: Logger;
|
|
110
115
|
}
|
|
111
116
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"serve_fact_route.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/serve_fact_route.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwFG;AAMH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AAepD,OAAO,EAAsC,KAAK,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAE1F,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,iBAAiB,CAAC;AAM7C,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,cAAc,CAAC;AA6D/C,MAAM,WAAW,+BAA+B;IAC/C;;;;;;OAMG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC;CACZ;AAgHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iCAAiC,GAC7C,SAAS,+BAA+B,KACtC,SA8CF,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,4BAA4B,GACxC,SAAS,+BAA+B,KACtC,SAgCF,CAAC"}
|
|
@@ -14,8 +14,11 @@
|
|
|
14
14
|
* (filesystem-backed `file:<shard>/<rest>` URLs) either return an
|
|
15
15
|
* `X-Accel-Redirect` header pointing into nginx's internal facts location
|
|
16
16
|
* (production) or stream from disk via the filesystem `FactExternalFetcher`
|
|
17
|
-
* (dev / tests). The runtime mode is selected by the optional
|
|
18
|
-
*
|
|
17
|
+
* (dev / tests). The runtime mode is selected by the optional `x_accel`
|
|
18
|
+
* factory option — a validated `XAccelConfig` (built via
|
|
19
|
+
* `create_x_accel_config` in `server/x_accel.ts`) set in prod, unset in dev.
|
|
20
|
+
* The handle can only be obtained by proving the facts nginx location is
|
|
21
|
+
* `internal;`, so X-Accel serving can't be enabled against a public location.
|
|
19
22
|
*
|
|
20
23
|
* REST, not RPC: binary responses don't fit the JSON-RPC envelope.
|
|
21
24
|
*
|
|
@@ -167,7 +170,7 @@ const params_400_error = {
|
|
|
167
170
|
* fact is genuinely absent or merely unviewable.
|
|
168
171
|
*/
|
|
169
172
|
const serve_fact_bytes = async (c, route, hash, config) => {
|
|
170
|
-
const { facts_dir,
|
|
173
|
+
const { facts_dir, x_accel, log } = config;
|
|
171
174
|
const meta = await query_get_fact_meta({ db: route.db }, hash);
|
|
172
175
|
if (!meta) {
|
|
173
176
|
return c.body(null, 404);
|
|
@@ -198,13 +201,13 @@ const serve_fact_bytes = async (c, route, hash, config) => {
|
|
|
198
201
|
return c.body(null, 404);
|
|
199
202
|
}
|
|
200
203
|
const { shard, rest } = parsed;
|
|
201
|
-
if (
|
|
202
|
-
// Production: hand off to nginx via the internal facts location.
|
|
204
|
+
if (x_accel !== undefined) {
|
|
205
|
+
// Production: hand off to nginx via the validated `internal;` facts location.
|
|
203
206
|
return c.body(null, 200, {
|
|
204
207
|
'Content-Type': content_type,
|
|
205
208
|
'Content-Length': size,
|
|
206
209
|
'Cache-Control': CACHE_CONTROL,
|
|
207
|
-
'X-Accel-Redirect': `${
|
|
210
|
+
'X-Accel-Redirect': `${x_accel.redirect_prefix}${shard}/${rest}`,
|
|
208
211
|
...FACT_SECURITY_HEADERS,
|
|
209
212
|
});
|
|
210
213
|
}
|
|
@@ -252,8 +255,8 @@ const build_public_request_context = async (c, route) => {
|
|
|
252
255
|
* from `c.var.account_id` and enforces visibility per-reference.
|
|
253
256
|
*/
|
|
254
257
|
export const create_serve_cell_fact_route_spec = (options) => {
|
|
255
|
-
const { facts_dir,
|
|
256
|
-
const config = { facts_dir,
|
|
258
|
+
const { facts_dir, x_accel, log } = options;
|
|
259
|
+
const config = { facts_dir, x_accel, log };
|
|
257
260
|
return {
|
|
258
261
|
method: 'GET',
|
|
259
262
|
path: '/api/cells/:cell_id/facts/:hash',
|
|
@@ -310,8 +313,8 @@ export const create_serve_cell_fact_route_spec = (options) => {
|
|
|
310
313
|
* hash to a non-admin.
|
|
311
314
|
*/
|
|
312
315
|
export const create_serve_fact_route_spec = (options) => {
|
|
313
|
-
const { facts_dir,
|
|
314
|
-
const config = { facts_dir,
|
|
316
|
+
const { facts_dir, x_accel, log } = options;
|
|
317
|
+
const config = { facts_dir, x_accel, log };
|
|
315
318
|
return {
|
|
316
319
|
method: 'GET',
|
|
317
320
|
path: '/api/facts/:hash',
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-loud validation of the X-Accel facts nginx location.
|
|
3
|
+
*
|
|
4
|
+
* The `X-Accel-Redirect` serving path's confidentiality depends on the facts
|
|
5
|
+
* nginx location being `internal;` — only the authz'd handler's redirect (an
|
|
6
|
+
* internal subrequest) may reach it. A *public* facts location would serve any
|
|
7
|
+
* fact's bytes to anyone who guesses the `<shard>/<rest>` path, bypassing every
|
|
8
|
+
* cell-visibility check. `XAccelConfig` makes that assertion structural: a
|
|
9
|
+
* consumer can only obtain the redirect prefix by building an `XAccelConfig`,
|
|
10
|
+
* which runs this check and throws **loudly** on a missing or non-`internal;`
|
|
11
|
+
* location — it cannot silently fall closed.
|
|
12
|
+
*
|
|
13
|
+
* This is a best-effort string check (brace-matched `location` blocks), the TS
|
|
14
|
+
* twin of the Rust `fuz_fact_serving` `nginx.rs` — not a real nginx parser, but
|
|
15
|
+
* it catches the security-critical omission. Distinct from
|
|
16
|
+
* `validate_nginx_config` (`server/validate_nginx.ts`), which validates the
|
|
17
|
+
* `/api` Authorization-strip + security headers and takes no facts-location
|
|
18
|
+
* path.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
/** Result of the facts-location check. */
|
|
24
|
+
export interface NginxFactsValidation {
|
|
25
|
+
/** `true` when the facts location exists and is `internal;`. */
|
|
26
|
+
ok: boolean;
|
|
27
|
+
/** Fatal issues — a missing or non-`internal` facts location. */
|
|
28
|
+
errors: Array<string>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Assert the nginx `location` serving the X-Accel facts prefix is `internal;`.
|
|
32
|
+
*
|
|
33
|
+
* `facts_location` is the path the X-Accel redirect prefix points at (e.g.
|
|
34
|
+
* `/_facts/`). Returns a fatal error when no matching `location` block exists,
|
|
35
|
+
* or when the matching block is not marked `internal;` — either is a public
|
|
36
|
+
* facts location that bypasses cell visibility.
|
|
37
|
+
*
|
|
38
|
+
* @param config - the nginx config template string to check
|
|
39
|
+
* @param facts_location - the facts location path the redirect prefix points at
|
|
40
|
+
* @returns `{ok, errors}` — `ok` is `true` only when the location exists and is `internal;`
|
|
41
|
+
*/
|
|
42
|
+
export declare const validate_facts_internal_location: (config: string, facts_location: string) => NginxFactsValidation;
|
|
43
|
+
/**
|
|
44
|
+
* A validated X-Accel redirect configuration — the only handle that enables the
|
|
45
|
+
* `X-Accel-Redirect` serving path in `server/serve_fact_route.ts`.
|
|
46
|
+
*
|
|
47
|
+
* The redirect prefix can be obtained **only** by passing the nginx config
|
|
48
|
+
* through `validate_facts_internal_location` (via `create_x_accel_config`), so
|
|
49
|
+
* X-Accel serving is impossible to enable without proving the facts location is
|
|
50
|
+
* `internal;` at boot — a public facts location would bypass every
|
|
51
|
+
* cell-visibility check. A Zod-branded type: the brand can't be forged without
|
|
52
|
+
* an explicit cast, so the factory is the only ordinary construction path.
|
|
53
|
+
*/
|
|
54
|
+
export declare const XAccelConfig: z.core.$ZodBranded<z.ZodObject<{
|
|
55
|
+
/** The validated redirect prefix the serving path prepends (e.g. `/_facts/`). */
|
|
56
|
+
redirect_prefix: z.ZodString;
|
|
57
|
+
}, z.core.$strict>, "XAccelConfig", "out">;
|
|
58
|
+
export type XAccelConfig = z.infer<typeof XAccelConfig>;
|
|
59
|
+
/**
|
|
60
|
+
* A misconfigured X-Accel facts location — a fail-loud boot error thrown by
|
|
61
|
+
* `create_x_accel_config`.
|
|
62
|
+
*/
|
|
63
|
+
export declare class XAccelConfigError extends Error {
|
|
64
|
+
/** The validator errors that made the location unsafe. */
|
|
65
|
+
readonly errors: Array<string>;
|
|
66
|
+
constructor(errors: Array<string>);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build a validated `XAccelConfig`, asserting the nginx `location` serving
|
|
70
|
+
* `redirect_prefix` is `internal;`.
|
|
71
|
+
*
|
|
72
|
+
* The make-impossible-states gate: serving can only emit `X-Accel-Redirect`
|
|
73
|
+
* into a location proven `internal;` at boot.
|
|
74
|
+
*
|
|
75
|
+
* @param redirect_prefix - the X-Accel redirect prefix (e.g. `/_facts/`)
|
|
76
|
+
* @param nginx_config - the nginx config template string to validate against
|
|
77
|
+
* @returns the validated `XAccelConfig` carrying `redirect_prefix`
|
|
78
|
+
* @throws `XAccelConfigError` when `nginx_config` has no matching `location` for
|
|
79
|
+
* `redirect_prefix`, or the matching block is not marked `internal;` — either
|
|
80
|
+
* is a public facts location that bypasses every cell-visibility check.
|
|
81
|
+
*/
|
|
82
|
+
export declare const create_x_accel_config: (redirect_prefix: string, nginx_config: string) => XAccelConfig;
|
|
83
|
+
//# sourceMappingURL=x_accel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x_accel.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/x_accel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACpC,gEAAgE;IAChE,EAAE,EAAE,OAAO,CAAC;IACZ,iEAAiE;IACjE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAuDD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gCAAgC,GAC5C,QAAQ,MAAM,EACd,gBAAgB,MAAM,KACpB,oBAqBF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,YAAY;IAEvB,iFAAiF;;0CAG5D,CAAC;AACxB,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAExD;;;GAGG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;gBACnB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;CAKjC;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,qBAAqB,GACjC,iBAAiB,MAAM,EACvB,cAAc,MAAM,KAClB,YAMF,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fail-loud validation of the X-Accel facts nginx location.
|
|
3
|
+
*
|
|
4
|
+
* The `X-Accel-Redirect` serving path's confidentiality depends on the facts
|
|
5
|
+
* nginx location being `internal;` — only the authz'd handler's redirect (an
|
|
6
|
+
* internal subrequest) may reach it. A *public* facts location would serve any
|
|
7
|
+
* fact's bytes to anyone who guesses the `<shard>/<rest>` path, bypassing every
|
|
8
|
+
* cell-visibility check. `XAccelConfig` makes that assertion structural: a
|
|
9
|
+
* consumer can only obtain the redirect prefix by building an `XAccelConfig`,
|
|
10
|
+
* which runs this check and throws **loudly** on a missing or non-`internal;`
|
|
11
|
+
* location — it cannot silently fall closed.
|
|
12
|
+
*
|
|
13
|
+
* This is a best-effort string check (brace-matched `location` blocks), the TS
|
|
14
|
+
* twin of the Rust `fuz_fact_serving` `nginx.rs` — not a real nginx parser, but
|
|
15
|
+
* it catches the security-critical omission. Distinct from
|
|
16
|
+
* `validate_nginx_config` (`server/validate_nginx.ts`), which validates the
|
|
17
|
+
* `/api` Authorization-strip + security headers and takes no facts-location
|
|
18
|
+
* path.
|
|
19
|
+
*
|
|
20
|
+
* @module
|
|
21
|
+
*/
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
/** Strip every trailing `/` so `/_facts/` and `/_facts` compare equal. */
|
|
24
|
+
const trim_trailing_slashes = (value) => value.replace(/\/+$/, '');
|
|
25
|
+
/** `true` if a location block body contains an `internal` directive. */
|
|
26
|
+
const is_internal = (content) => content.split(/[;\s]+/).some((token) => token === 'internal');
|
|
27
|
+
/**
|
|
28
|
+
* Extract `{path, content}` for each `location … { … }` block, brace-matched so
|
|
29
|
+
* nested blocks are kept whole. `path` is the last whitespace token before the
|
|
30
|
+
* opening brace (the location path, after any modifier). Best-effort: a stray
|
|
31
|
+
* `location` token in a comment can produce a spurious block, harmless for this
|
|
32
|
+
* check.
|
|
33
|
+
*/
|
|
34
|
+
const find_location_blocks = (config) => {
|
|
35
|
+
const blocks = [];
|
|
36
|
+
let search = 0;
|
|
37
|
+
let kw;
|
|
38
|
+
while ((kw = config.indexOf('location', search)) !== -1) {
|
|
39
|
+
// Require a token boundary before `location` (start or whitespace) so
|
|
40
|
+
// `relocation` etc. don't match.
|
|
41
|
+
const boundary_ok = kw === 0 || /\s/.test(config[kw - 1]);
|
|
42
|
+
const after = kw + 'location'.length;
|
|
43
|
+
const brace = config.indexOf('{', after);
|
|
44
|
+
if (brace === -1)
|
|
45
|
+
break;
|
|
46
|
+
if (!boundary_ok) {
|
|
47
|
+
search = after;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const header = config.slice(after, brace).trim();
|
|
51
|
+
const tokens = header.split(/\s+/).filter((token) => token.length > 0);
|
|
52
|
+
const path = tokens.length > 0 ? tokens[tokens.length - 1] : '';
|
|
53
|
+
// Brace-match the body.
|
|
54
|
+
let depth = 1;
|
|
55
|
+
let i = brace + 1;
|
|
56
|
+
while (i < config.length && depth > 0) {
|
|
57
|
+
const ch = config[i];
|
|
58
|
+
if (ch === '{')
|
|
59
|
+
depth++;
|
|
60
|
+
else if (ch === '}')
|
|
61
|
+
depth--;
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
64
|
+
blocks.push({ path, content: config.slice(brace, i) });
|
|
65
|
+
search = i;
|
|
66
|
+
}
|
|
67
|
+
return blocks;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Assert the nginx `location` serving the X-Accel facts prefix is `internal;`.
|
|
71
|
+
*
|
|
72
|
+
* `facts_location` is the path the X-Accel redirect prefix points at (e.g.
|
|
73
|
+
* `/_facts/`). Returns a fatal error when no matching `location` block exists,
|
|
74
|
+
* or when the matching block is not marked `internal;` — either is a public
|
|
75
|
+
* facts location that bypasses cell visibility.
|
|
76
|
+
*
|
|
77
|
+
* @param config - the nginx config template string to check
|
|
78
|
+
* @param facts_location - the facts location path the redirect prefix points at
|
|
79
|
+
* @returns `{ok, errors}` — `ok` is `true` only when the location exists and is `internal;`
|
|
80
|
+
*/
|
|
81
|
+
export const validate_facts_internal_location = (config, facts_location) => {
|
|
82
|
+
const errors = [];
|
|
83
|
+
const normalized = trim_trailing_slashes(facts_location);
|
|
84
|
+
const blocks = find_location_blocks(config);
|
|
85
|
+
const matching = blocks.filter((block) => trim_trailing_slashes(block.path) === normalized);
|
|
86
|
+
if (matching.length === 0) {
|
|
87
|
+
errors.push(`No nginx \`location ${facts_location}\` block found — the X-Accel facts location must ` +
|
|
88
|
+
`exist and be marked \`internal;\` so only the authz'd handler's redirect can reach it. ` +
|
|
89
|
+
`A public facts location bypasses every cell-visibility check.`);
|
|
90
|
+
}
|
|
91
|
+
else if (!matching.some((block) => is_internal(block.content))) {
|
|
92
|
+
errors.push(`nginx \`location ${facts_location}\` is NOT marked \`internal;\` — a public facts ` +
|
|
93
|
+
`location serves any fact's bytes to anyone who guesses the path, bypassing every ` +
|
|
94
|
+
`cell-visibility check. Add \`internal;\` to the block.`);
|
|
95
|
+
}
|
|
96
|
+
return { ok: errors.length === 0, errors };
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* A validated X-Accel redirect configuration — the only handle that enables the
|
|
100
|
+
* `X-Accel-Redirect` serving path in `server/serve_fact_route.ts`.
|
|
101
|
+
*
|
|
102
|
+
* The redirect prefix can be obtained **only** by passing the nginx config
|
|
103
|
+
* through `validate_facts_internal_location` (via `create_x_accel_config`), so
|
|
104
|
+
* X-Accel serving is impossible to enable without proving the facts location is
|
|
105
|
+
* `internal;` at boot — a public facts location would bypass every
|
|
106
|
+
* cell-visibility check. A Zod-branded type: the brand can't be forged without
|
|
107
|
+
* an explicit cast, so the factory is the only ordinary construction path.
|
|
108
|
+
*/
|
|
109
|
+
export const XAccelConfig = z
|
|
110
|
+
.strictObject({
|
|
111
|
+
/** The validated redirect prefix the serving path prepends (e.g. `/_facts/`). */
|
|
112
|
+
redirect_prefix: z.string(),
|
|
113
|
+
})
|
|
114
|
+
.brand('XAccelConfig');
|
|
115
|
+
/**
|
|
116
|
+
* A misconfigured X-Accel facts location — a fail-loud boot error thrown by
|
|
117
|
+
* `create_x_accel_config`.
|
|
118
|
+
*/
|
|
119
|
+
export class XAccelConfigError extends Error {
|
|
120
|
+
/** The validator errors that made the location unsafe. */
|
|
121
|
+
errors;
|
|
122
|
+
constructor(errors) {
|
|
123
|
+
super(`X-Accel facts location is not safely \`internal;\`: ${errors.join('; ')}`);
|
|
124
|
+
this.name = 'XAccelConfigError';
|
|
125
|
+
this.errors = errors;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Build a validated `XAccelConfig`, asserting the nginx `location` serving
|
|
130
|
+
* `redirect_prefix` is `internal;`.
|
|
131
|
+
*
|
|
132
|
+
* The make-impossible-states gate: serving can only emit `X-Accel-Redirect`
|
|
133
|
+
* into a location proven `internal;` at boot.
|
|
134
|
+
*
|
|
135
|
+
* @param redirect_prefix - the X-Accel redirect prefix (e.g. `/_facts/`)
|
|
136
|
+
* @param nginx_config - the nginx config template string to validate against
|
|
137
|
+
* @returns the validated `XAccelConfig` carrying `redirect_prefix`
|
|
138
|
+
* @throws `XAccelConfigError` when `nginx_config` has no matching `location` for
|
|
139
|
+
* `redirect_prefix`, or the matching block is not marked `internal;` — either
|
|
140
|
+
* is a public facts location that bypasses every cell-visibility check.
|
|
141
|
+
*/
|
|
142
|
+
export const create_x_accel_config = (redirect_prefix, nginx_config) => {
|
|
143
|
+
const validation = validate_facts_internal_location(nginx_config, redirect_prefix);
|
|
144
|
+
if (!validation.ok) {
|
|
145
|
+
throw new XAccelConfigError(validation.errors);
|
|
146
|
+
}
|
|
147
|
+
return XAccelConfig.parse({ redirect_prefix });
|
|
148
|
+
};
|