@fuzdev/fuz_app 0.105.0 → 0.106.1

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 (72) hide show
  1. package/dist/actions/action_rpc.d.ts.map +1 -1
  2. package/dist/actions/action_rpc.js +2 -1
  3. package/dist/actions/perform_action.d.ts +8 -0
  4. package/dist/actions/perform_action.d.ts.map +1 -1
  5. package/dist/actions/perform_action.js +30 -12
  6. package/dist/actions/register_action_ws.d.ts.map +1 -1
  7. package/dist/actions/register_action_ws.js +27 -3
  8. package/dist/auth/account_action_specs.d.ts +28 -5
  9. package/dist/auth/account_action_specs.d.ts.map +1 -1
  10. package/dist/auth/account_action_specs.js +19 -6
  11. package/dist/auth/account_actions.d.ts.map +1 -1
  12. package/dist/auth/account_actions.js +15 -2
  13. package/dist/auth/account_schema.d.ts +13 -0
  14. package/dist/auth/account_schema.d.ts.map +1 -1
  15. package/dist/auth/account_schema.js +8 -1
  16. package/dist/auth/api_token_queries.d.ts +2 -1
  17. package/dist/auth/api_token_queries.d.ts.map +1 -1
  18. package/dist/auth/api_token_queries.js +21 -5
  19. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  20. package/dist/auth/audit_log_routes.js +9 -1
  21. package/dist/auth/bearer_auth.d.ts.map +1 -1
  22. package/dist/auth/bearer_auth.js +6 -1
  23. package/dist/auth/daemon_token_middleware.d.ts.map +1 -1
  24. package/dist/auth/daemon_token_middleware.js +5 -1
  25. package/dist/auth/migrations.d.ts.map +1 -1
  26. package/dist/auth/migrations.js +33 -0
  27. package/dist/auth/request_context.d.ts +18 -0
  28. package/dist/auth/request_context.d.ts.map +1 -1
  29. package/dist/auth/request_context.js +27 -1
  30. package/dist/auth/role_grant_offer_action_specs.d.ts +12 -0
  31. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  32. package/dist/auth/role_grant_offer_action_specs.js +12 -1
  33. package/dist/auth/self_service_role_action_specs.d.ts +7 -0
  34. package/dist/auth/self_service_role_action_specs.d.ts.map +1 -1
  35. package/dist/auth/self_service_role_action_specs.js +7 -1
  36. package/dist/auth/token_scope.d.ts +155 -0
  37. package/dist/auth/token_scope.d.ts.map +1 -0
  38. package/dist/auth/token_scope.js +190 -0
  39. package/dist/db/CLAUDE.md +1 -1
  40. package/dist/hono_context.d.ts +23 -0
  41. package/dist/hono_context.d.ts.map +1 -1
  42. package/dist/hono_context.js +14 -0
  43. package/dist/http/error_schemas.d.ts +12 -0
  44. package/dist/http/error_schemas.d.ts.map +1 -1
  45. package/dist/http/error_schemas.js +12 -0
  46. package/dist/server/serve_fact_route.d.ts.map +1 -1
  47. package/dist/server/serve_fact_route.js +9 -1
  48. package/dist/server/x_accel.d.ts +2 -4
  49. package/dist/server/x_accel.d.ts.map +1 -1
  50. package/dist/server/x_accel.js +2 -4
  51. package/dist/testing/admin_integration.js +2 -2
  52. package/dist/testing/app_server.d.ts.map +1 -1
  53. package/dist/testing/app_server.js +2 -1
  54. package/dist/testing/audit_completeness.js +1 -1
  55. package/dist/testing/cross_backend/conformance_case.d.ts +23 -0
  56. package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/conformance_case.js +21 -0
  58. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  59. package/dist/testing/cross_backend/conformance_table.js +58 -5
  60. package/dist/testing/cross_backend/expected_schema.json +1 -0
  61. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  62. package/dist/testing/cross_backend/setup.js +5 -1
  63. package/dist/testing/integration.js +3 -3
  64. package/dist/ui/account_sessions_state.svelte.d.ts +3 -3
  65. package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
  66. package/package.json +2 -2
  67. package/dist/server/fact_write.d.ts +0 -32
  68. package/dist/server/fact_write.d.ts.map +0 -1
  69. package/dist/server/fact_write.js +0 -57
  70. package/dist/server/validate_nginx.d.ts +0 -34
  71. package/dist/server/validate_nginx.d.ts.map +0 -1
  72. package/dist/server/validate_nginx.js +0 -169
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_app",
3
- "version": "0.105.0",
3
+ "version": "0.106.1",
4
4
  "description": "fullstack app library",
5
5
  "glyph": "🗝",
6
6
  "logo": "logo.svg",
@@ -33,7 +33,7 @@
33
33
  "peerDependencies": {
34
34
  "@electric-sql/pglite": ">=0.4",
35
35
  "@fuzdev/blake3_wasm": ">=0.1.0",
36
- "@fuzdev/fuz_util": ">=0.65.2",
36
+ "@fuzdev/fuz_util": ">=0.68.0",
37
37
  "@hono/node-server": ">=1",
38
38
  "@hono/node-ws": ">=1",
39
39
  "@node-rs/argon2": ">=2",
@@ -1,32 +0,0 @@
1
- /**
2
- * Shared helper for routing fact bytes between embedded (PG `bytes`
3
- * column) and external (filesystem + `put_ref`) storage tiers based
4
- * on size.
5
- *
6
- * External writes go through atomic temp+rename so the `facts` row
7
- * never references a partial file; idempotence comes from POSIX
8
- * `rename` overwrite + `INSERT ... ON CONFLICT DO NOTHING` in the
9
- * fact-store queries layer.
10
- *
11
- * @module
12
- */
13
- import { type FactHash } from '@fuzdev/fuz_util/hash_schemas.ts';
14
- import type { FactStore } from '@fuzdev/fuz_util/fact_store.ts';
15
- export interface WriteFactOptions {
16
- content_type: string;
17
- }
18
- /**
19
- * Write `bytes` as a fact, choosing embedded (PG) vs external (disk +
20
- * `put_ref`) based on `embedded_threshold`. Returns the canonical
21
- * `blake3:` hash either way.
22
- *
23
- * @param fact_store - the `FactStore` (typically `PgFactStore`)
24
- * @param embedded_threshold - bytes ≤ threshold → embedded; > threshold → disk
25
- * @param facts_dir - root of the sharded facts directory tree on disk
26
- * @param bytes - the raw fact bytes
27
- * @param options - content type for the fact metadata
28
- * @returns the fact's `blake3:<hex64>` hash
29
- * @mutates `fact_store`, `facts_dir` filesystem
30
- */
31
- export declare const write_fact: (fact_store: FactStore, embedded_threshold: number, facts_dir: string, bytes: Uint8Array, options: WriteFactOptions) => Promise<FactHash>;
32
- //# sourceMappingURL=fact_write.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fact_write.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/fact_write.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AACnF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAGhE,MAAM,WAAW,gBAAgB;IAChC,YAAY,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,UAAU,GACtB,YAAY,SAAS,EACrB,oBAAoB,MAAM,EAC1B,WAAW,MAAM,EACjB,OAAO,UAAU,EACjB,SAAS,gBAAgB,KACvB,OAAO,CAAC,QAAQ,CA4BlB,CAAC"}
@@ -1,57 +0,0 @@
1
- /**
2
- * Shared helper for routing fact bytes between embedded (PG `bytes`
3
- * column) and external (filesystem + `put_ref`) storage tiers based
4
- * on size.
5
- *
6
- * External writes go through atomic temp+rename so the `facts` row
7
- * never references a partial file; idempotence comes from POSIX
8
- * `rename` overwrite + `INSERT ... ON CONFLICT DO NOTHING` in the
9
- * fact-store queries layer.
10
- *
11
- * @module
12
- */
13
- import { randomBytes } from 'node:crypto';
14
- import { writeFile, rename, mkdir, unlink } from 'node:fs/promises';
15
- import { join } from 'node:path';
16
- import { fact_hash_bytes } from '@fuzdev/fuz_util/fact_hash.ts';
17
- import { FACT_HASH_PREFIX } from '@fuzdev/fuz_util/hash_schemas.ts';
18
- import { mint_file_fact_url } from '../db/file_fact_url.js';
19
- /**
20
- * Write `bytes` as a fact, choosing embedded (PG) vs external (disk +
21
- * `put_ref`) based on `embedded_threshold`. Returns the canonical
22
- * `blake3:` hash either way.
23
- *
24
- * @param fact_store - the `FactStore` (typically `PgFactStore`)
25
- * @param embedded_threshold - bytes ≤ threshold → embedded; > threshold → disk
26
- * @param facts_dir - root of the sharded facts directory tree on disk
27
- * @param bytes - the raw fact bytes
28
- * @param options - content type for the fact metadata
29
- * @returns the fact's `blake3:<hex64>` hash
30
- * @mutates `fact_store`, `facts_dir` filesystem
31
- */
32
- export const write_fact = async (fact_store, embedded_threshold, facts_dir, bytes, options) => {
33
- if (bytes.length <= embedded_threshold) {
34
- return fact_store.put(bytes, options);
35
- }
36
- const hash = fact_hash_bytes(bytes);
37
- const hex = hash.slice(FACT_HASH_PREFIX.length);
38
- const shard = hex.slice(0, 2);
39
- const rest = hex.slice(2);
40
- const shard_dir = join(facts_dir, shard);
41
- const tmp_dir = join(facts_dir, '.tmp');
42
- const tmp_path = join(tmp_dir, `${randomBytes(16).toString('hex')}.tmp`);
43
- const final_path = join(shard_dir, rest);
44
- await Promise.all([mkdir(shard_dir, { recursive: true }), mkdir(tmp_dir, { recursive: true })]);
45
- let renamed = false;
46
- try {
47
- await writeFile(tmp_path, bytes);
48
- await rename(tmp_path, final_path);
49
- renamed = true;
50
- }
51
- finally {
52
- if (!renamed) {
53
- await unlink(tmp_path).catch(() => undefined);
54
- }
55
- }
56
- return fact_store.put_ref(mint_file_fact_url(shard, rest), bytes.length, options);
57
- };
@@ -1,34 +0,0 @@
1
- /**
2
- * String-based nginx config validator for fuz_app deploy configs.
3
- *
4
- * Checks consumer `NGINX_CONFIG` template strings for required security
5
- * properties. This is pattern matching on template strings, not a real
6
- * nginx parser — it catches common security omissions but won't catch
7
- * all possible misconfigurations.
8
- *
9
- * @module
10
- */
11
- /**
12
- * Result of validating an nginx config template string.
13
- */
14
- export interface NginxValidationResult {
15
- /** True when no errors were detected. Warnings do not affect this flag. */
16
- ok: boolean;
17
- /** Non-fatal issues — missing optional headers, weakened defaults, etc. */
18
- warnings: Array<string>;
19
- /** Fatal issues — missing `/api` block, missing required security headers, etc. */
20
- errors: Array<string>;
21
- }
22
- /**
23
- * Validate an nginx config template string for security properties.
24
- *
25
- * Checks for required security headers, Authorization stripping in `/api`
26
- * blocks, and the nginx `add_header` inheritance gotcha. Designed for
27
- * fuz_app consumer deploy configs (zap.ts `NGINX_CONFIG` constants).
28
- *
29
- * Limitations: string pattern matching, not a real nginx parser. Catches
30
- * common omissions in fuz_app deploy configs but won't catch all possible
31
- * misconfigurations.
32
- */
33
- export declare const validate_nginx_config: (config: string) => NginxValidationResult;
34
- //# sourceMappingURL=validate_nginx.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate_nginx.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/server/validate_nginx.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACrC,2EAA2E;IAC3E,EAAE,EAAE,OAAO,CAAC;IACZ,2EAA2E;IAC3E,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACxB,mFAAmF;IACnF,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACtB;AAgGD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,KAAG,qBA+FtD,CAAC"}
@@ -1,169 +0,0 @@
1
- /**
2
- * String-based nginx config validator for fuz_app deploy configs.
3
- *
4
- * Checks consumer `NGINX_CONFIG` template strings for required security
5
- * properties. This is pattern matching on template strings, not a real
6
- * nginx parser — it catches common security omissions but won't catch
7
- * all possible misconfigurations.
8
- *
9
- * @module
10
- */
11
- /**
12
- * Extract location blocks from an nginx config string.
13
- *
14
- * Finds `location [modifier] <path> {` directives (modifier may be `=`, `~`,
15
- * `~*`, `^~`, or absent) and returns the full block content including nested
16
- * braces.
17
- */
18
- const extract_location_blocks = (config) => {
19
- const blocks = [];
20
- const location_regex = /location\s+(=|~\*?|\^~)?\s*(\S+)\s*\{/g;
21
- let match;
22
- while ((match = location_regex.exec(config)) !== null) {
23
- const modifier = match[1] ?? '';
24
- const path = match[2];
25
- const open_brace_index = match.index + match[0].length - 1;
26
- let depth = 1;
27
- let block_end = open_brace_index + 1;
28
- for (let i = open_brace_index + 1; i < config.length; i++) {
29
- if (config[i] === '{')
30
- depth++;
31
- else if (config[i] === '}') {
32
- depth--;
33
- if (depth === 0) {
34
- block_end = i + 1;
35
- break;
36
- }
37
- }
38
- }
39
- blocks.push({ modifier, path, content: config.slice(match.index, block_end) });
40
- }
41
- return blocks;
42
- };
43
- /**
44
- * Canonical `/api` URIs used to probe regex location patterns.
45
- *
46
- * Two probes cover the common regex shapes:
47
- * - `/api` catches `^/api$`, `^/api(/|$)`, `^/(admin|api)`, etc.
48
- * - `/api/` catches `^/api/` (which requires the trailing slash).
49
- *
50
- * Only consulted from the regex branch of `location_matches_api` — non-regex
51
- * blocks compare `block.path` literally.
52
- */
53
- const API_TEST_URIS = ['/api', '/api/'];
54
- /**
55
- * Does a location block route `/api` traffic?
56
- *
57
- * The matching strategy is deliberately asymmetric across modifier types:
58
- *
59
- * - **Regex (`~`, `~*`)**: compiles `block.path` as a regex and tests it
60
- * against `API_TEST_URIS`. `~*` gets the `i` flag. Any match flags the
61
- * block as `/api`-handling. Invalid regex returns `false` (nginx would
62
- * reject it too). URI-probing is needed because regex patterns don't
63
- * admit a reliable substring check — `^/(admin|api)` has no `/api` prefix
64
- * textually but routes `/api` traffic.
65
- * - **Prefix (no modifier, `^~`) and exact (`=`)**: literal check —
66
- * `block.path === '/api'` or `block.path.startsWith('/api/')`. We do NOT
67
- * probe with `API_TEST_URIS` here because that would produce false
68
- * positives on overly broad prefixes: a catch-all `location /` technically
69
- * routes `/api` requests, but nginx would prefer a more specific `/api`
70
- * block when one exists — and we want the separate "No /api block found"
71
- * error when one doesn't.
72
- *
73
- * Known blind spot: a regex matching only a sub-path that isn't in
74
- * `API_TEST_URIS` (e.g. `^/api/v99$`, or `^/api/.+` which requires content
75
- * after the slash) won't be flagged. Acceptable for fuz_app deploy configs,
76
- * which route all `/api` traffic through a single broad block.
77
- */
78
- const location_matches_api = (block) => {
79
- if (block.modifier === '~' || block.modifier === '~*') {
80
- try {
81
- const re = new RegExp(block.path, block.modifier === '~*' ? 'i' : '');
82
- return API_TEST_URIS.some((uri) => re.test(uri));
83
- }
84
- catch {
85
- return false;
86
- }
87
- }
88
- return block.path === '/api' || block.path.startsWith('/api/');
89
- };
90
- /**
91
- * Validate an nginx config template string for security properties.
92
- *
93
- * Checks for required security headers, Authorization stripping in `/api`
94
- * blocks, and the nginx `add_header` inheritance gotcha. Designed for
95
- * fuz_app consumer deploy configs (zap.ts `NGINX_CONFIG` constants).
96
- *
97
- * Limitations: string pattern matching, not a real nginx parser. Catches
98
- * common omissions in fuz_app deploy configs but won't catch all possible
99
- * misconfigurations.
100
- */
101
- export const validate_nginx_config = (config) => {
102
- const errors = [];
103
- const warnings = [];
104
- const all_blocks = extract_location_blocks(config);
105
- // 1. proxy_set_header Authorization "" in /api location blocks
106
- const api_blocks = all_blocks.filter(location_matches_api);
107
- if (api_blocks.length === 0) {
108
- errors.push('No /api location block found — config must have an /api location block ' +
109
- 'with Authorization header stripping. If you intentionally route /api ' +
110
- 'through a different structure, skip this validator.');
111
- }
112
- else {
113
- const has_auth_strip = api_blocks.some((block) => block.content.includes('proxy_set_header Authorization ""') ||
114
- block.content.includes("proxy_set_header Authorization ''"));
115
- if (!has_auth_strip) {
116
- errors.push('Missing `proxy_set_header Authorization ""` in /api location block — ' +
117
- 'required for v1 cookie-only external auth posture');
118
- }
119
- }
120
- // 2. Strict-Transport-Security (error if missing)
121
- if (!config.includes('Strict-Transport-Security')) {
122
- errors.push('Missing Strict-Transport-Security header');
123
- }
124
- // 3. X-Content-Type-Options "nosniff" (warning if missing)
125
- if (!config.includes('X-Content-Type-Options')) {
126
- warnings.push('Missing X-Content-Type-Options "nosniff" header');
127
- }
128
- // 4. X-Frame-Options (warning if missing)
129
- if (!config.includes('X-Frame-Options')) {
130
- warnings.push('Missing X-Frame-Options header');
131
- }
132
- // 5. Referrer-Policy (warning if missing)
133
- if (!config.includes('Referrer-Policy')) {
134
- warnings.push('Missing Referrer-Policy header');
135
- }
136
- // 6. server_tokens off (warning if missing)
137
- if (!config.includes('server_tokens off')) {
138
- warnings.push('Missing server_tokens off — nginx version may be disclosed');
139
- }
140
- // 7. limit_req (warning if missing — may be in a separate rate_limit.conf)
141
- if (!config.includes('limit_req')) {
142
- warnings.push('Missing limit_req — may be in a separate rate_limit.conf. ' +
143
- 'Consider adding nginx-level rate limiting');
144
- }
145
- // 8. X-Forwarded-For: prefer $remote_addr over $proxy_add_x_forwarded_for
146
- if (config.includes('$proxy_add_x_forwarded_for')) {
147
- warnings.push('Using $proxy_add_x_forwarded_for — prefer $remote_addr for single-proxy setups ' +
148
- 'to avoid client-injected XFF headers');
149
- }
150
- // 9. Child location blocks with add_header must repeat security headers
151
- for (const block of all_blocks) {
152
- if (block.content.includes('add_header') &&
153
- !block.content.includes('Strict-Transport-Security')) {
154
- // Only flag child locations that add their own response headers
155
- // (Cache-Control, Content-Disposition, etc.) — these override
156
- // inherited headers from the parent server block
157
- if (block.content.includes('Cache-Control') ||
158
- block.content.includes('Content-Disposition')) {
159
- warnings.push(`Location ${block.path} has add_header but is missing Strict-Transport-Security — ` +
160
- 'nginx add_header in child blocks replaces (not extends) inherited headers');
161
- }
162
- }
163
- }
164
- return {
165
- ok: errors.length === 0,
166
- warnings,
167
- errors
168
- };
169
- };