@fuzdev/fuz_app 0.131.0 → 0.132.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/dist/actions/action_dispatcher.js +0 -1
- package/dist/actions/action_event_data.d.ts +2 -2
- package/dist/actions/action_event_types.d.ts +2 -2
- package/dist/actions/action_rpc.js +0 -1
- package/dist/actions/action_spec.d.ts +19 -19
- package/dist/actions/perform_action.js +0 -1
- package/dist/actions/register_action_ws.js +0 -1
- package/dist/actions/transports_http.d.ts +7 -0
- package/dist/actions/transports_http.d.ts.map +1 -1
- package/dist/actions/transports_http.js +18 -1
- package/dist/auth/CLAUDE.md +1 -1
- package/dist/auth/account_routes.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -5
- package/dist/auth/cell_action_specs.d.ts +3 -3
- package/dist/auth/credential_type_schema.d.ts +1 -1
- package/dist/auth/middleware.d.ts +13 -10
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +14 -10
- package/dist/cli/daemon.js +0 -1
- package/dist/cli/logger.js +0 -1
- package/dist/env/load.js +0 -1
- package/dist/hono_context.d.ts +1 -1
- package/dist/http/CLAUDE.md +2 -1
- package/dist/http/auth_shape.d.ts +6 -6
- package/dist/http/error_schemas.d.ts +12 -2
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +10 -1
- package/dist/http/origin.d.ts +10 -6
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +10 -6
- package/dist/http/route_spec.d.ts +19 -12
- package/dist/http/route_spec.d.ts.map +1 -1
- package/dist/http/route_spec.js +33 -6
- package/dist/http/schema_helpers.d.ts +3 -1
- package/dist/http/schema_helpers.d.ts.map +1 -1
- package/dist/http/schema_helpers.js +3 -2
- package/dist/http/simple_request.d.ts +63 -0
- package/dist/http/simple_request.d.ts.map +1 -0
- package/dist/http/simple_request.js +84 -0
- package/dist/http/surface.js +0 -1
- package/dist/realtime/sse.js +0 -1
- package/dist/server/app_server.d.ts +2 -2
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +5 -2
- package/dist/testing/CLAUDE.md +16 -0
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +2 -1
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +14 -15
- package/dist/testing/audit_completeness.js +1 -1
- package/dist/testing/cross_backend/action_manifest.d.ts +10 -10
- package/dist/testing/cross_backend/simple_request.d.ts +11 -0
- package/dist/testing/cross_backend/simple_request.d.ts.map +1 -0
- package/dist/testing/cross_backend/simple_request.js +142 -0
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -5
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +40 -11
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_helpers.js +19 -7
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +3 -0
- package/dist/testing/transports/ws_client.js +0 -1
- package/dist/ui/auth_state.svelte.d.ts.map +1 -1
- package/dist/ui/auth_state.svelte.js +5 -1
- package/package.json +2 -2
|
@@ -91,13 +91,17 @@ export const describe_adversarial_auth = (options) => {
|
|
|
91
91
|
const spec_lookup = build_route_spec_lookup(route_specs);
|
|
92
92
|
const test_path = (route) => resolve_valid_path(route.path, spec_lookup.get(`${route.method} ${route.path}`)?.params);
|
|
93
93
|
const apps = create_auth_test_apps(route_specs, roles);
|
|
94
|
+
// every route's request carries the JSON content type a real client sends, so
|
|
95
|
+
// the per-route content-type check (415) never answers ahead of the auth gate
|
|
96
|
+
const probe_init = (route) => ({
|
|
97
|
+
method: route.method,
|
|
98
|
+
headers: { 'content-type': 'application/json' }
|
|
99
|
+
});
|
|
94
100
|
describe('adversarial HTTP auth enforcement', () => {
|
|
95
101
|
describe('unauthenticated → 401', () => {
|
|
96
102
|
for (const route of protected_routes) {
|
|
97
103
|
test(`${route.method} ${route.path}`, async () => {
|
|
98
|
-
const res = await apps.public.request(test_path(route),
|
|
99
|
-
method: route.method
|
|
100
|
-
});
|
|
104
|
+
const res = await apps.public.request(test_path(route), probe_init(route));
|
|
101
105
|
assert.strictEqual(res.status, 401, `${route.method} ${route.path}`);
|
|
102
106
|
const body = await res.json();
|
|
103
107
|
assert.strictEqual(body.error, ERROR_AUTHENTICATION_REQUIRED);
|
|
@@ -126,9 +130,7 @@ export const describe_adversarial_auth = (options) => {
|
|
|
126
130
|
const app = apps.by_role.get(wrong_role);
|
|
127
131
|
if (!app)
|
|
128
132
|
throw new Error(`No test app for role '${wrong_role}'`);
|
|
129
|
-
const res = await app.request(test_path(route),
|
|
130
|
-
method: route.method
|
|
131
|
-
});
|
|
133
|
+
const res = await app.request(test_path(route), probe_init(route));
|
|
132
134
|
assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
|
|
133
135
|
const body = await res.json();
|
|
134
136
|
assert.strictEqual(body.error, ERROR_INSUFFICIENT_PERMISSIONS);
|
|
@@ -141,9 +143,7 @@ export const describe_adversarial_auth = (options) => {
|
|
|
141
143
|
describe('authenticated without role → 403', () => {
|
|
142
144
|
for (const route of role_only_routes) {
|
|
143
145
|
test(`${route.method} ${route.path}`, async () => {
|
|
144
|
-
const res = await apps.authed.request(test_path(route),
|
|
145
|
-
method: route.method
|
|
146
|
-
});
|
|
146
|
+
const res = await apps.authed.request(test_path(route), probe_init(route));
|
|
147
147
|
assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
|
|
148
148
|
const body = await res.json();
|
|
149
149
|
assert.strictEqual(body.error, ERROR_INSUFFICIENT_PERMISSIONS);
|
|
@@ -175,9 +175,9 @@ export const describe_adversarial_auth = (options) => {
|
|
|
175
175
|
continue;
|
|
176
176
|
}
|
|
177
177
|
test(`${route.method} ${route.path} (${refused})`, async () => {
|
|
178
|
-
const res = await apps.by_credential_type
|
|
179
|
-
|
|
180
|
-
|
|
178
|
+
const res = await apps.by_credential_type
|
|
179
|
+
.get(refused)
|
|
180
|
+
.request(test_path(route), probe_init(route));
|
|
181
181
|
assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
|
|
182
182
|
const body = await res.json();
|
|
183
183
|
assert.strictEqual(body.error, ERROR_CREDENTIAL_TYPE_REQUIRED);
|
|
@@ -190,12 +190,11 @@ export const describe_adversarial_auth = (options) => {
|
|
|
190
190
|
describe('correct auth passes guard', () => {
|
|
191
191
|
for (const route of protected_routes) {
|
|
192
192
|
test(`${route.method} ${route.path}`, async () => {
|
|
193
|
-
const res = await select_auth_app(apps, route.auth).request(test_path(route),
|
|
194
|
-
method: route.method
|
|
195
|
-
});
|
|
193
|
+
const res = await select_auth_app(apps, route.auth).request(test_path(route), probe_init(route));
|
|
196
194
|
// handler may error (500) or return 404 (stub deps) — that's fine, we only verify auth passed
|
|
197
195
|
assert.notStrictEqual(res.status, 401, 'should not be 401 (auth rejected)');
|
|
198
196
|
assert.notStrictEqual(res.status, 403, 'should not be 403 (role rejected)');
|
|
197
|
+
assert.notStrictEqual(res.status, 415, 'should not be 415 (content type refused)');
|
|
199
198
|
// handler-level 404 (resource not found with stub deps) is fine —
|
|
200
199
|
// only reject router-level 404 (route not registered)
|
|
201
200
|
if (res.status === 404) {
|
|
@@ -155,7 +155,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
155
155
|
assert.ok(logout_route, 'Expected POST /logout route');
|
|
156
156
|
const res = await fixture.transport(logout_route.path, {
|
|
157
157
|
method: 'POST',
|
|
158
|
-
headers: fixture.create_session_headers()
|
|
158
|
+
headers: fixture.create_session_headers({ 'content-type': 'application/json' })
|
|
159
159
|
});
|
|
160
160
|
assert.strictEqual(res.status, 200);
|
|
161
161
|
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
@@ -49,15 +49,15 @@ export declare const ActionManifestEntry: z.ZodObject<{
|
|
|
49
49
|
side_effects: z.ZodBoolean;
|
|
50
50
|
/** The `account` auth axis. */
|
|
51
51
|
account: z.ZodEnum<{
|
|
52
|
-
optional: "optional";
|
|
53
|
-
none: "none";
|
|
54
52
|
required: "required";
|
|
53
|
+
none: "none";
|
|
54
|
+
optional: "optional";
|
|
55
55
|
}>;
|
|
56
56
|
/** The `actor` auth axis. */
|
|
57
57
|
actor: z.ZodEnum<{
|
|
58
|
-
optional: "optional";
|
|
59
|
-
none: "none";
|
|
60
58
|
required: "required";
|
|
59
|
+
none: "none";
|
|
60
|
+
optional: "optional";
|
|
61
61
|
}>;
|
|
62
62
|
/** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
|
|
63
63
|
roles: z.ZodArray<z.ZodString>;
|
|
@@ -65,9 +65,9 @@ export declare const ActionManifestEntry: z.ZodObject<{
|
|
|
65
65
|
credential_types: z.ZodArray<z.ZodString>;
|
|
66
66
|
/** Per-action rate-limit class, or `null` when the action declares none. */
|
|
67
67
|
rate_limit: z.ZodNullable<z.ZodEnum<{
|
|
68
|
+
both: "both";
|
|
68
69
|
account: "account";
|
|
69
70
|
ip: "ip";
|
|
70
|
-
both: "both";
|
|
71
71
|
}>>;
|
|
72
72
|
}, z.core.$strict>;
|
|
73
73
|
export type ActionManifestEntry = z.infer<typeof ActionManifestEntry>;
|
|
@@ -80,15 +80,15 @@ export declare const ActionManifest: z.ZodObject<{
|
|
|
80
80
|
side_effects: z.ZodBoolean;
|
|
81
81
|
/** The `account` auth axis. */
|
|
82
82
|
account: z.ZodEnum<{
|
|
83
|
-
optional: "optional";
|
|
84
|
-
none: "none";
|
|
85
83
|
required: "required";
|
|
84
|
+
none: "none";
|
|
85
|
+
optional: "optional";
|
|
86
86
|
}>;
|
|
87
87
|
/** The `actor` auth axis. */
|
|
88
88
|
actor: z.ZodEnum<{
|
|
89
|
-
optional: "optional";
|
|
90
|
-
none: "none";
|
|
91
89
|
required: "required";
|
|
90
|
+
none: "none";
|
|
91
|
+
optional: "optional";
|
|
92
92
|
}>;
|
|
93
93
|
/** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
|
|
94
94
|
roles: z.ZodArray<z.ZodString>;
|
|
@@ -96,9 +96,9 @@ export declare const ActionManifest: z.ZodObject<{
|
|
|
96
96
|
credential_types: z.ZodArray<z.ZodString>;
|
|
97
97
|
/** Per-action rate-limit class, or `null` when the action declares none. */
|
|
98
98
|
rate_limit: z.ZodNullable<z.ZodEnum<{
|
|
99
|
+
both: "both";
|
|
99
100
|
account: "account";
|
|
100
101
|
ip: "ip";
|
|
101
|
-
both: "both";
|
|
102
102
|
}>>;
|
|
103
103
|
}, z.core.$strict>>;
|
|
104
104
|
}, z.core.$strict>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { RpcPathCrossSuiteOptions } from './setup.js';
|
|
3
|
+
/** Options for the simple-request parity suite. */
|
|
4
|
+
export interface SimpleRequestCrossTestOptions extends RpcPathCrossSuiteOptions {
|
|
5
|
+
/** REST login route path. Default `/api/account/login` (the spine convention). */
|
|
6
|
+
readonly login_path?: string;
|
|
7
|
+
/** REST logout route path. Default `/api/account/logout` (the spine convention). */
|
|
8
|
+
readonly logout_path?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare const describe_simple_request_cross_tests: (options: SimpleRequestCrossTestOptions) => void;
|
|
11
|
+
//# sourceMappingURL=simple_request.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple_request.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/simple_request.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,KAAK,EAAE,wBAAwB,EAAe,MAAM,YAAY,CAAC;AAGxE,mDAAmD;AACnD,MAAM,WAAW,6BAA8B,SAAQ,wBAAwB;IAC9E,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,oFAAoF;IACpF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAaD,eAAO,MAAM,mCAAmC,GAC/C,SAAS,6BAA6B,KACpC,IAuHF,CAAC"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Cross-backend parity suite for refusing CORS simple requests.
|
|
4
|
+
*
|
|
5
|
+
* A `POST` with no `Content-Type`, or a `text/plain` / form / multipart one,
|
|
6
|
+
* is what a browser sends cross-origin without a preflight — carrying the
|
|
7
|
+
* session cookie when the page is same-site, which any other `localhost`
|
|
8
|
+
* port is. Every spine's JSON routes require `application/json` and refuse
|
|
9
|
+
* anything else with a flat `415` `{error: 'unsupported_content_type'}`
|
|
10
|
+
* before reading a credential or the body: the TS spine per route
|
|
11
|
+
* (`apply_route_specs`, every `POST` that isn't `raw_body`), the Rust spine
|
|
12
|
+
* in each JSON handler. The cases carry an allowed `Origin` and, where it
|
|
13
|
+
* matters, the keeper's session — the exact shape of a same-site page's
|
|
14
|
+
* forged request, which the origin allowlist alone admits when it's a
|
|
15
|
+
* wildcard — and a refused request must leave that session untouched.
|
|
16
|
+
*
|
|
17
|
+
* Runs both legs via the shared `setup_test` protocol: in-process
|
|
18
|
+
* (`auth/simple_request_parity.db.test.ts`) and cross-process
|
|
19
|
+
* (`cross_backend/simple_request.cross.test.ts`). Every spine has the gate,
|
|
20
|
+
* so the suite is ungated.
|
|
21
|
+
*
|
|
22
|
+
* @module
|
|
23
|
+
*/
|
|
24
|
+
import { describe, test, assert } from 'vitest';
|
|
25
|
+
import { account_verify_action_spec } from '../../auth/account_action_specs.js';
|
|
26
|
+
import { ERROR_UNSUPPORTED_CONTENT_TYPE } from '../../http/error_schemas.js';
|
|
27
|
+
import { SPINE_RPC_PATH } from './spine_surface_constants.js';
|
|
28
|
+
/** Build the JSON-RPC envelope body for a nullary `account_verify` call. */
|
|
29
|
+
const verify_envelope = (id) => JSON.stringify({ jsonrpc: '2.0', method: account_verify_action_spec.method, id });
|
|
30
|
+
/** Assert a flat `415` content-type refusal. */
|
|
31
|
+
const assert_refused = async (res, label) => {
|
|
32
|
+
assert.strictEqual(res.status, 415, `${label} must be refused with 415`);
|
|
33
|
+
const body = (await res.json().catch(() => undefined));
|
|
34
|
+
assert.strictEqual(body?.error, ERROR_UNSUPPORTED_CONTENT_TYPE, label);
|
|
35
|
+
};
|
|
36
|
+
export const describe_simple_request_cross_tests = (options) => {
|
|
37
|
+
const { setup_test } = options;
|
|
38
|
+
const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
|
|
39
|
+
const login_path = options.login_path ?? '/api/account/login';
|
|
40
|
+
const logout_path = options.logout_path ?? '/api/account/logout';
|
|
41
|
+
/** POST a JSON `account_verify` with the fixture's session — 200 while it's valid. */
|
|
42
|
+
const verify_session = async (fixture, id) => {
|
|
43
|
+
const res = await fixture.transport(rpc_path, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
|
|
46
|
+
body: verify_envelope(id)
|
|
47
|
+
});
|
|
48
|
+
return res.status;
|
|
49
|
+
};
|
|
50
|
+
describe('CORS simple-request refusal parity', () => {
|
|
51
|
+
test('a CORS-safelisted RPC POST with a session → 415, in any spelling', async () => {
|
|
52
|
+
const fixture = await setup_test();
|
|
53
|
+
for (const content_type of [
|
|
54
|
+
'text/plain;charset=UTF-8',
|
|
55
|
+
'Text/Plain',
|
|
56
|
+
'application/x-www-form-urlencoded',
|
|
57
|
+
'multipart/form-data; boundary=x'
|
|
58
|
+
]) {
|
|
59
|
+
const res = await fixture.transport(rpc_path, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { ...fixture.create_session_headers(), 'content-type': content_type },
|
|
62
|
+
body: verify_envelope('simple-safelisted')
|
|
63
|
+
});
|
|
64
|
+
await assert_refused(res, `a ${content_type} RPC POST`);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
test('RPC POST with no Content-Type → 415', async () => {
|
|
68
|
+
const fixture = await setup_test();
|
|
69
|
+
// a byte body, so `fetch` adds no `Content-Type` of its own (a string
|
|
70
|
+
// body would get `text/plain;charset=UTF-8`)
|
|
71
|
+
const res = await fixture.transport(rpc_path, {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: fixture.create_session_headers(),
|
|
74
|
+
body: new TextEncoder().encode(verify_envelope('simple-untyped'))
|
|
75
|
+
});
|
|
76
|
+
await assert_refused(res, 'an untyped RPC POST');
|
|
77
|
+
});
|
|
78
|
+
test('a JSON route refuses any type but application/json', async () => {
|
|
79
|
+
const fixture = await setup_test();
|
|
80
|
+
for (const content_type of ['application/octet-stream', 'application/vnd.api+json', '']) {
|
|
81
|
+
const res = await fixture.transport(rpc_path, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { ...fixture.create_session_headers(), 'content-type': content_type },
|
|
84
|
+
body: verify_envelope('simple-not-json')
|
|
85
|
+
});
|
|
86
|
+
await assert_refused(res, `an RPC POST typed '${content_type}'`);
|
|
87
|
+
const login = await fixture.fresh_transport()(login_path, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'content-type': content_type },
|
|
90
|
+
body: JSON.stringify({ username: 'keeper', password: 'whatever' })
|
|
91
|
+
});
|
|
92
|
+
await assert_refused(login, `a login typed '${content_type}'`);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
test('form-encoded login → 415', async () => {
|
|
96
|
+
const fixture = await setup_test();
|
|
97
|
+
const res = await fixture.fresh_transport()(login_path, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
|
100
|
+
body: 'username=keeper&password=whatever'
|
|
101
|
+
});
|
|
102
|
+
await assert_refused(res, 'a form-encoded login');
|
|
103
|
+
});
|
|
104
|
+
test('a forged text/plain logout with a session → 415, and the session survives', async () => {
|
|
105
|
+
const fixture = await setup_test();
|
|
106
|
+
const res = await fixture.transport(logout_path, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: { ...fixture.create_session_headers(), 'content-type': 'text/plain' }
|
|
109
|
+
});
|
|
110
|
+
await assert_refused(res, 'a text/plain logout');
|
|
111
|
+
assert.strictEqual(await verify_session(fixture, 'after-forged-logout'), 200, 'the refused logout must not end the session');
|
|
112
|
+
});
|
|
113
|
+
test('an untyped logout with no session → 415, before the auth check', async () => {
|
|
114
|
+
const fixture = await setup_test();
|
|
115
|
+
const res = await fixture.fresh_transport()(logout_path, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: { 'content-type': 'application/octet-stream' }
|
|
118
|
+
});
|
|
119
|
+
await assert_refused(res, 'an octet-stream logout');
|
|
120
|
+
});
|
|
121
|
+
test('JSON logout with no body and no session → 401, not 415', async () => {
|
|
122
|
+
const fixture = await setup_test();
|
|
123
|
+
const res = await fixture.fresh_transport()(logout_path, {
|
|
124
|
+
method: 'POST',
|
|
125
|
+
headers: { 'content-type': 'application/json' }
|
|
126
|
+
});
|
|
127
|
+
assert.strictEqual(res.status, 401, 'a bodyless JSON logout must reach the auth check');
|
|
128
|
+
});
|
|
129
|
+
test('application/json with a charset → dispatched', async () => {
|
|
130
|
+
const fixture = await setup_test();
|
|
131
|
+
const res = await fixture.transport(rpc_path, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: {
|
|
134
|
+
...fixture.create_session_headers(),
|
|
135
|
+
'content-type': 'application/json; charset=utf-8'
|
|
136
|
+
},
|
|
137
|
+
body: verify_envelope('json-charset')
|
|
138
|
+
});
|
|
139
|
+
assert.strictEqual(res.status, 200, 'a JSON RPC POST must be dispatched');
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
};
|
|
@@ -427,21 +427,21 @@ export declare const testing_action_manifest_action_spec: {
|
|
|
427
427
|
method: z.ZodString;
|
|
428
428
|
side_effects: z.ZodBoolean;
|
|
429
429
|
account: z.ZodEnum<{
|
|
430
|
-
optional: "optional";
|
|
431
|
-
none: "none";
|
|
432
430
|
required: "required";
|
|
431
|
+
none: "none";
|
|
432
|
+
optional: "optional";
|
|
433
433
|
}>;
|
|
434
434
|
actor: z.ZodEnum<{
|
|
435
|
-
optional: "optional";
|
|
436
|
-
none: "none";
|
|
437
435
|
required: "required";
|
|
436
|
+
none: "none";
|
|
437
|
+
optional: "optional";
|
|
438
438
|
}>;
|
|
439
439
|
roles: z.ZodArray<z.ZodString>;
|
|
440
440
|
credential_types: z.ZodArray<z.ZodString>;
|
|
441
441
|
rate_limit: z.ZodNullable<z.ZodEnum<{
|
|
442
|
+
both: "both";
|
|
442
443
|
account: "account";
|
|
443
444
|
ip: "ip";
|
|
444
|
-
both: "both";
|
|
445
445
|
}>>;
|
|
446
446
|
}, z.core.$strict>>;
|
|
447
447
|
}, z.core.$strict>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/integration.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAsB7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAMhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AAkB1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,OAAO,EAAW,KAAK,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAKpF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAG1D;;GAEG;AACH,MAAM,WAAW,8BAA8B;IAC9C;;;;OAIG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,kEAAkE;IAClE,YAAY,EAAE,mBAAmB,CAAC;IAClC;;;;OAIG;IACH,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;;;;;;;;;;OAYG;IACH,aAAa,EAAE,uBAAuB,CAAC;IACvC;;;;;;;OAOG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,mCAAmC,GAC/C,SAAS,8BAA8B,KACrC,IAw+CF,CAAC"}
|
|
@@ -72,6 +72,17 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
72
72
|
const { cookie_name } = options.session_options;
|
|
73
73
|
// Error coverage tracking across test groups
|
|
74
74
|
const error_collector = new ErrorCoverageCollector();
|
|
75
|
+
// the auth routes this suite drives: login / logout / password, signup
|
|
76
|
+
// (when wired), and the shared RPC endpoint — admin-role routes belong to
|
|
77
|
+
// the admin suite
|
|
78
|
+
const is_suite_auth_route = (s) => {
|
|
79
|
+
if (s.auth.roles?.includes('admin') ?? false)
|
|
80
|
+
return false;
|
|
81
|
+
const rest_suffixes = ['/login', '/logout', '/password', '/signup'];
|
|
82
|
+
if (rest_suffixes.some((suffix) => s.path.endsWith(suffix)))
|
|
83
|
+
return true;
|
|
84
|
+
return s.path === rpc_path;
|
|
85
|
+
};
|
|
75
86
|
afterAll(() => {
|
|
76
87
|
// Scope coverage to auth routes this suite actually exercises:
|
|
77
88
|
// login / logout / password drivers + signup invite edge cases
|
|
@@ -83,14 +94,7 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
83
94
|
// `create_test_app_for_bootstrap`. Consumer-specific
|
|
84
95
|
// routes would dilute the ratio; admin-role routes are scoped to
|
|
85
96
|
// the admin suite instead.
|
|
86
|
-
const auth_routes = route_specs.filter(
|
|
87
|
-
if (s.auth.roles?.includes('admin') ?? false)
|
|
88
|
-
return false;
|
|
89
|
-
const rest_suffixes = ['/login', '/logout', '/password', '/signup'];
|
|
90
|
-
if (rest_suffixes.some((suffix) => s.path.endsWith(suffix)))
|
|
91
|
-
return true;
|
|
92
|
-
return s.path === rpc_path;
|
|
93
|
-
});
|
|
97
|
+
const auth_routes = route_specs.filter(is_suite_auth_route);
|
|
94
98
|
assert_error_coverage(error_collector, auth_routes.length > 0 ? auth_routes : route_specs, {
|
|
95
99
|
min_coverage: options.error_coverage_min ?? DEFAULT_INTEGRATION_ERROR_COVERAGE,
|
|
96
100
|
// Authorization denials (403) on these scoped auth routes — the
|
|
@@ -233,7 +237,7 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
233
237
|
// Logout
|
|
234
238
|
const logout_res = await fixture.transport(logout_route.path, {
|
|
235
239
|
method: 'POST',
|
|
236
|
-
headers: create_headers()
|
|
240
|
+
headers: { ...create_headers(), 'content-type': 'application/json' }
|
|
237
241
|
});
|
|
238
242
|
assert.strictEqual(logout_res.status, 200);
|
|
239
243
|
const logout_body = await logout_res.json();
|
|
@@ -633,7 +637,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
633
637
|
path: rpc_path,
|
|
634
638
|
spec: account_verify_action_spec,
|
|
635
639
|
params: undefined,
|
|
636
|
-
headers: {
|
|
640
|
+
headers: {
|
|
641
|
+
...bearer_headers,
|
|
642
|
+
origin: 'http://localhost:5173',
|
|
643
|
+
'content-type': 'application/json'
|
|
644
|
+
}
|
|
637
645
|
});
|
|
638
646
|
assert.strictEqual(res.status, 401);
|
|
639
647
|
});
|
|
@@ -1029,7 +1037,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1029
1037
|
});
|
|
1030
1038
|
const res = await fixture.fresh_transport()(logout_route.path, {
|
|
1031
1039
|
method: 'POST',
|
|
1032
|
-
headers: {
|
|
1040
|
+
headers: {
|
|
1041
|
+
...bearer_headers,
|
|
1042
|
+
origin: 'http://localhost:5173',
|
|
1043
|
+
'content-type': 'application/json'
|
|
1044
|
+
}
|
|
1033
1045
|
});
|
|
1034
1046
|
assert.strictEqual(res.status, 401, 'Bearer with Origin should be discarded → unauthenticated');
|
|
1035
1047
|
error_collector.record(route_specs, 'POST', logout_route.path, 401);
|
|
@@ -1049,6 +1061,23 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1049
1061
|
error_collector.record(route_specs, 'POST', password_route.path, 401);
|
|
1050
1062
|
});
|
|
1051
1063
|
});
|
|
1064
|
+
// --- 12b. Content type: every JSON POST route refuses an untyped body ---
|
|
1065
|
+
describe('JSON routes refuse an untyped POST', () => {
|
|
1066
|
+
test('each scoped JSON POST route answers an untyped POST with 415', async () => {
|
|
1067
|
+
const fixture = await options.setup_test();
|
|
1068
|
+
const json_post_routes = route_specs.filter((s) => s.method === 'POST' && !s.raw_body && is_suite_auth_route(s));
|
|
1069
|
+
assert.ok(json_post_routes.length > 0, 'Expected JSON POST routes');
|
|
1070
|
+
for (const route of json_post_routes) {
|
|
1071
|
+
// a byte body, so `fetch` adds no `Content-Type` of its own
|
|
1072
|
+
const res = await fixture.fresh_transport()(route.path, {
|
|
1073
|
+
method: 'POST',
|
|
1074
|
+
body: new TextEncoder().encode('{}')
|
|
1075
|
+
});
|
|
1076
|
+
assert.strictEqual(res.status, 415, `untyped POST ${route.path}`);
|
|
1077
|
+
await error_collector.assert_and_record(route_specs, 'POST', route.path, res);
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
});
|
|
1052
1081
|
// --- 13. Password change revokes API tokens ---
|
|
1053
1082
|
describe('password change revokes API tokens', () => {
|
|
1054
1083
|
test('API tokens are invalidated after password change', async () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_helpers.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"rpc_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/rpc_helpers.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,6 @@ import './assert_dev_env.js';
|
|
|
9
9
|
* @module
|
|
10
10
|
*/
|
|
11
11
|
import { assert } from 'vitest';
|
|
12
|
-
import { z } from 'zod';
|
|
13
12
|
import { JSONRPC_VERSION, JsonrpcErrorResponse, JsonrpcResponse } from '../http/jsonrpc.js';
|
|
14
13
|
import { create_stub_app_server_context } from './stubs.js';
|
|
15
14
|
/**
|
|
@@ -146,6 +145,23 @@ const RPC_CALL_DEFAULT_HEADERS_BASE = {
|
|
|
146
145
|
host: 'localhost',
|
|
147
146
|
'Content-Type': 'application/json'
|
|
148
147
|
};
|
|
148
|
+
/**
|
|
149
|
+
* Merge header records case-insensitively, later sources replacing earlier
|
|
150
|
+
* ones — a plain spread would keep `Content-Type` and `content-type` as two
|
|
151
|
+
* keys, which `fetch` joins into one comma-separated value. Names come out
|
|
152
|
+
* lowercased; values pass through unvalidated, so a test can still send a
|
|
153
|
+
* malformed header to the server.
|
|
154
|
+
*/
|
|
155
|
+
const merge_header_records = (...sources) => {
|
|
156
|
+
const merged = {};
|
|
157
|
+
for (const source of sources) {
|
|
158
|
+
if (!source)
|
|
159
|
+
continue;
|
|
160
|
+
for (const [name, value] of Object.entries(source))
|
|
161
|
+
merged[name.toLowerCase()] = value;
|
|
162
|
+
}
|
|
163
|
+
return merged;
|
|
164
|
+
};
|
|
149
165
|
/** Default headers merged into every `rpc_call` request. Includes `origin`. */
|
|
150
166
|
const RPC_CALL_DEFAULT_HEADERS = {
|
|
151
167
|
...RPC_CALL_DEFAULT_HEADERS_BASE,
|
|
@@ -172,18 +188,14 @@ export const rpc_call = async (args) => {
|
|
|
172
188
|
let init;
|
|
173
189
|
if (verb === 'GET') {
|
|
174
190
|
url = create_rpc_get_url(path, method, params, id);
|
|
175
|
-
init = { method: 'GET', headers:
|
|
191
|
+
init = { method: 'GET', headers: merge_header_records(defaults, headers) };
|
|
176
192
|
}
|
|
177
193
|
else {
|
|
178
194
|
url = path;
|
|
179
195
|
const post = create_rpc_post_init(method, params, id);
|
|
180
196
|
init = {
|
|
181
197
|
method: 'POST',
|
|
182
|
-
headers:
|
|
183
|
-
...defaults,
|
|
184
|
-
...post.headers,
|
|
185
|
-
...headers
|
|
186
|
-
},
|
|
198
|
+
headers: merge_header_records(defaults, post.headers, headers),
|
|
187
199
|
body: post.body
|
|
188
200
|
};
|
|
189
201
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"stubs.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/stubs.ts"],"names":[],"mappings":"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"}
|
package/dist/testing/stubs.js
CHANGED
|
@@ -313,6 +313,9 @@ export const create_test_app_surface_spec = (options) => {
|
|
|
313
313
|
{ ...bootstrap_route_shape, handler: stub_handler }
|
|
314
314
|
])
|
|
315
315
|
: [];
|
|
316
|
+
for (const route of bootstrap_route_specs) {
|
|
317
|
+
assert_endpoint_in_auth_scope('bootstrap.route_prefix', route.path);
|
|
318
|
+
}
|
|
316
319
|
// the surface route mirrors `create_app_server`'s opt-in mount — its handler
|
|
317
320
|
// serves the surface generated below, backfilled into the same ref
|
|
318
321
|
const surface_ref = {
|
|
@@ -19,7 +19,6 @@ import '../assert_dev_env.js';
|
|
|
19
19
|
* @module
|
|
20
20
|
*/
|
|
21
21
|
import { create_jsonrpc_error_response, create_jsonrpc_response, is_jsonrpc_error_response, is_jsonrpc_notification, is_jsonrpc_request, is_jsonrpc_response } from '../../http/jsonrpc_helpers.js';
|
|
22
|
-
import { JSONRPC_VERSION } from '../../http/jsonrpc.js';
|
|
23
22
|
// ---------------------------------------------------------------------
|
|
24
23
|
// Predicates — compose with `WsClient.wait_for` and
|
|
25
24
|
// `messages.filter(...)`. Both in-process and cross-process tests use
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/auth_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,MAAM,2BAA2B,CAAC;AAGnC;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;CAA8B,CAAC;AAE9D,qBAAa,SAAS;IACrB,SAAS,UAAqB;IAC9B,QAAQ,UAAqB;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC/C,OAAO,EAAE,cAAc,GAAG,IAAI,CAAoB;IAClD,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAoB;IAClD,WAAW,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAAkB;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAEtD;IACF;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAExC;IACF,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAA+C;IAEzE,gEAAgE;IAChE,eAAe,UAAqB;IAEpC;;;;;;OAMG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAItC;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BpC;;;;;;;OAOG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CjE;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCpF;;;;;;;OAOG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8ClF;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"auth_state.svelte.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/ui/auth_state.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAMH,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EACnB,MAAM,2BAA2B,CAAC;AAGnC;;;GAGG;AACH,eAAO,MAAM,kBAAkB;;;;CAA8B,CAAC;AAE9D,qBAAa,SAAS;IACrB,SAAS,UAAqB;IAC9B,QAAQ,UAAqB;IAC7B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAoB;IAC/C,OAAO,EAAE,cAAc,GAAG,IAAI,CAAoB;IAClD,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAoB;IAClD,WAAW,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAAkB;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,kBAAkB,EAAE,KAAK,CAAC,oBAAoB,CAAC,CAEtD;IACF;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAExC;IACF,wFAAwF;IACxF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAA+C;IAEzE,gEAAgE;IAChE,eAAe,UAAqB;IAEpC;;;;;;OAMG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAItC;;;;;;;;OAQG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BpC;;;;;;;OAOG;IACG,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0CjE;;;;;OAKG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCpF;;;;;;;OAOG;IACG,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8ClF;;;;;OAKG;IACG,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAe7B"}
|
|
@@ -268,7 +268,11 @@ export class AuthState {
|
|
|
268
268
|
*/
|
|
269
269
|
async logout() {
|
|
270
270
|
try {
|
|
271
|
-
|
|
271
|
+
// the content type is required of every API POST, body or not
|
|
272
|
+
await ui_fetch('/api/account/logout', {
|
|
273
|
+
method: 'POST',
|
|
274
|
+
headers: { 'Content-Type': 'application/json' }
|
|
275
|
+
});
|
|
272
276
|
}
|
|
273
277
|
catch {
|
|
274
278
|
// Best-effort — clear local state regardless
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fuzdev/fuz_app",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.132.0",
|
|
4
4
|
"description": "fullstack app library",
|
|
5
5
|
"glyph": "🗝",
|
|
6
6
|
"logo": "logo.svg",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"@hono/node-server": "^1.19.14",
|
|
98
98
|
"@hono/node-ws": "^1.3.1",
|
|
99
99
|
"@node-rs/argon2": "^2.0.2",
|
|
100
|
-
"@ryanatkn/eslint-config": "^0.
|
|
100
|
+
"@ryanatkn/eslint-config": "^0.13.0",
|
|
101
101
|
"@sveltejs/acorn-typescript": "^1.0.9",
|
|
102
102
|
"@sveltejs/adapter-static": "^4.0.0",
|
|
103
103
|
"@sveltejs/kit": "^3.0.1",
|