@fuzdev/fuz_app 0.33.0 → 0.34.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_rpc.d.ts.map +1 -1
- package/dist/actions/action_rpc.js +6 -1
- package/dist/testing/admin_integration.d.ts +7 -5
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +146 -145
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +34 -31
- package/dist/testing/integration.d.ts +2 -2
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +187 -124
- package/dist/testing/schema_generators.d.ts.map +1 -1
- package/dist/testing/schema_generators.js +25 -1
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@ import { AUTH_MIGRATION_NS } from '../auth/migrations.js';
|
|
|
21
21
|
import { create_test_app } from './app_server.js';
|
|
22
22
|
import { create_pglite_factory, create_describe_db, AUTH_INTEGRATION_TRUNCATE_TABLES, } from './db.js';
|
|
23
23
|
import { find_auth_route, assert_response_matches_spec, create_expired_test_cookie, assert_no_error_info_leakage, } from './integration_helpers.js';
|
|
24
|
-
import { find_rpc_action,
|
|
24
|
+
import { find_rpc_action, rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
25
25
|
import { RateLimiter } from '../rate_limiter.js';
|
|
26
26
|
import { run_migrations } from '../db/migrate.js';
|
|
27
27
|
import { ErrorCoverageCollector, assert_error_coverage, DEFAULT_INTEGRATION_ERROR_COVERAGE, } from './error_coverage.js';
|
|
@@ -221,10 +221,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
221
221
|
cookie: `${cookie_name}=${login_cookie}`,
|
|
222
222
|
});
|
|
223
223
|
// Verify works
|
|
224
|
-
const verify_res = await
|
|
224
|
+
const verify_res = await rpc_call_for_spec({
|
|
225
225
|
app: test_app.app,
|
|
226
226
|
path: rpc_path,
|
|
227
|
-
|
|
227
|
+
spec: account_verify_action_spec,
|
|
228
|
+
params: null,
|
|
228
229
|
headers: create_headers(),
|
|
229
230
|
});
|
|
230
231
|
assert.strictEqual(verify_res.status, 200);
|
|
@@ -238,10 +239,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
238
239
|
assert.strictEqual(logout_body.ok, true);
|
|
239
240
|
assert.strictEqual(logout_body.username, test_app.backend.account.username, 'Logout response should include the username');
|
|
240
241
|
// Verify fails after logout (session revoked)
|
|
241
|
-
const verify_after = await
|
|
242
|
+
const verify_after = await rpc_call_for_spec({
|
|
242
243
|
app: test_app.app,
|
|
243
244
|
path: rpc_path,
|
|
244
|
-
|
|
245
|
+
spec: account_verify_action_spec,
|
|
246
|
+
params: null,
|
|
245
247
|
headers: create_headers(),
|
|
246
248
|
});
|
|
247
249
|
assert.strictEqual(verify_after.status, 401);
|
|
@@ -309,20 +311,22 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
309
311
|
describe('session security', () => {
|
|
310
312
|
test('no cookie on protected route returns 401', async () => {
|
|
311
313
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
312
|
-
const res = await
|
|
314
|
+
const res = await rpc_call_for_spec({
|
|
313
315
|
app: test_app.app,
|
|
314
316
|
path: rpc_path,
|
|
315
|
-
|
|
317
|
+
spec: account_verify_action_spec,
|
|
318
|
+
params: null,
|
|
316
319
|
headers: { host: 'localhost' },
|
|
317
320
|
});
|
|
318
321
|
assert.strictEqual(res.status, 401);
|
|
319
322
|
});
|
|
320
323
|
test('corrupted cookie returns 401', async () => {
|
|
321
324
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
322
|
-
const res = await
|
|
325
|
+
const res = await rpc_call_for_spec({
|
|
323
326
|
app: test_app.app,
|
|
324
327
|
path: rpc_path,
|
|
325
|
-
|
|
328
|
+
spec: account_verify_action_spec,
|
|
329
|
+
params: null,
|
|
326
330
|
headers: { cookie: `${cookie_name}=random_garbage_value` },
|
|
327
331
|
});
|
|
328
332
|
assert.strictEqual(res.status, 401);
|
|
@@ -330,10 +334,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
330
334
|
test('expired cookie returns 401', async () => {
|
|
331
335
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
332
336
|
const expired_cookie = await create_expired_test_cookie(test_app.backend.keyring, options.session_options);
|
|
333
|
-
const res = await
|
|
337
|
+
const res = await rpc_call_for_spec({
|
|
334
338
|
app: test_app.app,
|
|
335
339
|
path: rpc_path,
|
|
336
|
-
|
|
340
|
+
spec: account_verify_action_spec,
|
|
341
|
+
params: null,
|
|
337
342
|
headers: { cookie: `${cookie_name}=${expired_cookie}` },
|
|
338
343
|
});
|
|
339
344
|
assert.strictEqual(res.status, 401);
|
|
@@ -345,33 +350,33 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
345
350
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
346
351
|
const headers = test_app.create_session_headers();
|
|
347
352
|
// List own sessions to get the session ID
|
|
348
|
-
const list_res = await
|
|
353
|
+
const list_res = await rpc_call_for_spec({
|
|
349
354
|
app: test_app.app,
|
|
350
355
|
path: rpc_path,
|
|
351
|
-
|
|
356
|
+
spec: account_session_list_action_spec,
|
|
357
|
+
params: null,
|
|
352
358
|
headers,
|
|
353
359
|
});
|
|
354
360
|
assert.ok(list_res.ok, 'account_session_list should succeed');
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const session_id = list_body.sessions[0].id;
|
|
361
|
+
assert.ok(list_res.result.sessions.length >= 1);
|
|
362
|
+
const session_id = list_res.result.sessions[0].id;
|
|
358
363
|
// Revoke that session by ID
|
|
359
|
-
const revoke_res = await
|
|
364
|
+
const revoke_res = await rpc_call_for_spec({
|
|
360
365
|
app: test_app.app,
|
|
361
366
|
path: rpc_path,
|
|
362
|
-
|
|
367
|
+
spec: account_session_revoke_action_spec,
|
|
363
368
|
params: { session_id },
|
|
364
369
|
headers,
|
|
365
370
|
});
|
|
366
371
|
assert.ok(revoke_res.ok, 'account_session_revoke should succeed');
|
|
367
|
-
|
|
368
|
-
assert.strictEqual(
|
|
369
|
-
assert.strictEqual(revoke_body.revoked, true);
|
|
372
|
+
assert.strictEqual(revoke_res.result.ok, true);
|
|
373
|
+
assert.strictEqual(revoke_res.result.revoked, true);
|
|
370
374
|
// Session should no longer work
|
|
371
|
-
const after = await
|
|
375
|
+
const after = await rpc_call_for_spec({
|
|
372
376
|
app: test_app.app,
|
|
373
377
|
path: rpc_path,
|
|
374
|
-
|
|
378
|
+
spec: account_verify_action_spec,
|
|
379
|
+
params: null,
|
|
375
380
|
headers,
|
|
376
381
|
});
|
|
377
382
|
assert.strictEqual(after.status, 401);
|
|
@@ -380,26 +385,29 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
380
385
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
381
386
|
const headers = test_app.create_session_headers();
|
|
382
387
|
// Verify works
|
|
383
|
-
const before = await
|
|
388
|
+
const before = await rpc_call_for_spec({
|
|
384
389
|
app: test_app.app,
|
|
385
390
|
path: rpc_path,
|
|
386
|
-
|
|
391
|
+
spec: account_verify_action_spec,
|
|
392
|
+
params: null,
|
|
387
393
|
headers,
|
|
388
394
|
});
|
|
389
395
|
assert.strictEqual(before.status, 200);
|
|
390
396
|
// Revoke all sessions
|
|
391
|
-
const revoke_res = await
|
|
397
|
+
const revoke_res = await rpc_call_for_spec({
|
|
392
398
|
app: test_app.app,
|
|
393
399
|
path: rpc_path,
|
|
394
|
-
|
|
400
|
+
spec: account_session_revoke_all_action_spec,
|
|
401
|
+
params: null,
|
|
395
402
|
headers,
|
|
396
403
|
});
|
|
397
404
|
assert.ok(revoke_res.ok, 'account_session_revoke_all should succeed');
|
|
398
405
|
// Verify fails after revocation
|
|
399
|
-
const after = await
|
|
406
|
+
const after = await rpc_call_for_spec({
|
|
400
407
|
app: test_app.app,
|
|
401
408
|
path: rpc_path,
|
|
402
|
-
|
|
409
|
+
spec: account_verify_action_spec,
|
|
410
|
+
params: null,
|
|
403
411
|
headers,
|
|
404
412
|
});
|
|
405
413
|
assert.strictEqual(after.status, 401);
|
|
@@ -431,10 +439,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
431
439
|
assert.ok(typeof change_body.sessions_revoked === 'number', 'Expected sessions_revoked count');
|
|
432
440
|
assert.ok(change_body.sessions_revoked >= 1, 'Expected at least 1 session revoked');
|
|
433
441
|
// Old session should be invalid
|
|
434
|
-
const verify_after = await
|
|
442
|
+
const verify_after = await rpc_call_for_spec({
|
|
435
443
|
app: test_app.app,
|
|
436
444
|
path: rpc_path,
|
|
437
|
-
|
|
445
|
+
spec: account_verify_action_spec,
|
|
446
|
+
params: null,
|
|
438
447
|
headers: test_app.create_session_headers(),
|
|
439
448
|
});
|
|
440
449
|
assert.strictEqual(verify_after.status, 401);
|
|
@@ -472,10 +481,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
472
481
|
assert.strictEqual(res.status, 401);
|
|
473
482
|
error_collector.record(test_app.route_specs, 'POST', password_route.path, 401);
|
|
474
483
|
// Session should still be valid (password didn't change)
|
|
475
|
-
const verify_res = await
|
|
484
|
+
const verify_res = await rpc_call_for_spec({
|
|
476
485
|
app: test_app.app,
|
|
477
486
|
path: rpc_path,
|
|
478
|
-
|
|
487
|
+
spec: account_verify_action_spec,
|
|
488
|
+
params: null,
|
|
479
489
|
headers: test_app.create_session_headers(),
|
|
480
490
|
});
|
|
481
491
|
assert.strictEqual(verify_res.status, 200);
|
|
@@ -507,23 +517,26 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
507
517
|
});
|
|
508
518
|
test('valid origin is accepted', async () => {
|
|
509
519
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
510
|
-
const res = await
|
|
520
|
+
const res = await rpc_call_for_spec({
|
|
511
521
|
app: test_app.app,
|
|
512
522
|
path: rpc_path,
|
|
513
|
-
|
|
523
|
+
spec: account_verify_action_spec,
|
|
524
|
+
params: null,
|
|
514
525
|
headers: test_app.create_session_headers(),
|
|
515
526
|
});
|
|
516
527
|
assert.strictEqual(res.status, 200);
|
|
517
528
|
});
|
|
518
529
|
test('no origin header is allowed (direct access)', async () => {
|
|
519
530
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
520
|
-
// Probe the "no Origin / no Referer" path; `
|
|
521
|
-
//
|
|
522
|
-
const res = await
|
|
531
|
+
// Probe the "no Origin / no Referer" path; `suppress_default_origin`
|
|
532
|
+
// skips the default `origin` header.
|
|
533
|
+
const res = await rpc_call_for_spec({
|
|
523
534
|
app: test_app.app,
|
|
524
535
|
path: rpc_path,
|
|
525
|
-
|
|
536
|
+
spec: account_verify_action_spec,
|
|
537
|
+
params: null,
|
|
526
538
|
headers: { cookie: `${cookie_name}=${test_app.backend.session_cookie}` },
|
|
539
|
+
suppress_default_origin: true,
|
|
527
540
|
});
|
|
528
541
|
assert.notStrictEqual(res.status, 403);
|
|
529
542
|
});
|
|
@@ -532,21 +545,25 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
532
545
|
describe('bearer auth', () => {
|
|
533
546
|
test('valid bearer token authenticates', async () => {
|
|
534
547
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
535
|
-
const res = await
|
|
548
|
+
const res = await rpc_call_for_spec({
|
|
536
549
|
app: test_app.app,
|
|
537
550
|
path: rpc_path,
|
|
538
|
-
|
|
551
|
+
spec: account_verify_action_spec,
|
|
552
|
+
params: null,
|
|
539
553
|
headers: test_app.create_bearer_headers(),
|
|
554
|
+
suppress_default_origin: true,
|
|
540
555
|
});
|
|
541
556
|
assert.strictEqual(res.status, 200);
|
|
542
557
|
});
|
|
543
558
|
test('invalid bearer token returns 401', async () => {
|
|
544
559
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
545
|
-
const res = await
|
|
560
|
+
const res = await rpc_call_for_spec({
|
|
546
561
|
app: test_app.app,
|
|
547
562
|
path: rpc_path,
|
|
548
|
-
|
|
563
|
+
spec: account_verify_action_spec,
|
|
564
|
+
params: null,
|
|
549
565
|
headers: { authorization: 'Bearer secret_fuz_token_invalid' },
|
|
566
|
+
suppress_default_origin: true,
|
|
550
567
|
});
|
|
551
568
|
assert.strictEqual(res.status, 401);
|
|
552
569
|
});
|
|
@@ -554,18 +571,21 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
554
571
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
555
572
|
const bearer_headers = test_app.create_bearer_headers();
|
|
556
573
|
// Without Origin — works.
|
|
557
|
-
const ok_res = await
|
|
574
|
+
const ok_res = await rpc_call_for_spec({
|
|
558
575
|
app: test_app.app,
|
|
559
576
|
path: rpc_path,
|
|
560
|
-
|
|
577
|
+
spec: account_verify_action_spec,
|
|
578
|
+
params: null,
|
|
561
579
|
headers: bearer_headers,
|
|
580
|
+
suppress_default_origin: true,
|
|
562
581
|
});
|
|
563
582
|
assert.strictEqual(ok_res.status, 200);
|
|
564
583
|
// With Origin — bearer silently discarded (browser context), falls through to no auth.
|
|
565
|
-
const res = await
|
|
584
|
+
const res = await rpc_call_for_spec({
|
|
566
585
|
app: test_app.app,
|
|
567
586
|
path: rpc_path,
|
|
568
|
-
|
|
587
|
+
spec: account_verify_action_spec,
|
|
588
|
+
params: null,
|
|
569
589
|
headers: { ...bearer_headers, origin: 'http://localhost:5173' },
|
|
570
590
|
});
|
|
571
591
|
assert.strictEqual(res.status, 401);
|
|
@@ -576,38 +596,42 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
576
596
|
test('revoked API token returns 401', async () => {
|
|
577
597
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
578
598
|
// Create a new token via RPC
|
|
579
|
-
const create_res = await
|
|
599
|
+
const create_res = await rpc_call_for_spec({
|
|
580
600
|
app: test_app.app,
|
|
581
601
|
path: rpc_path,
|
|
582
|
-
|
|
602
|
+
spec: account_token_create_action_spec,
|
|
583
603
|
params: { name: 'test-revoke' },
|
|
584
604
|
headers: test_app.create_session_headers(),
|
|
585
605
|
});
|
|
586
606
|
assert.ok(create_res.ok, 'account_token_create should succeed');
|
|
587
607
|
const { token, id } = create_res.result;
|
|
588
608
|
// Verify token works
|
|
589
|
-
const use_res = await
|
|
609
|
+
const use_res = await rpc_call_for_spec({
|
|
590
610
|
app: test_app.app,
|
|
591
611
|
path: rpc_path,
|
|
592
|
-
|
|
612
|
+
spec: account_verify_action_spec,
|
|
613
|
+
params: null,
|
|
593
614
|
headers: { authorization: `Bearer ${token}` },
|
|
615
|
+
suppress_default_origin: true,
|
|
594
616
|
});
|
|
595
617
|
assert.strictEqual(use_res.status, 200);
|
|
596
618
|
// Revoke via RPC
|
|
597
|
-
const revoke_res = await
|
|
619
|
+
const revoke_res = await rpc_call_for_spec({
|
|
598
620
|
app: test_app.app,
|
|
599
621
|
path: rpc_path,
|
|
600
|
-
|
|
622
|
+
spec: account_token_revoke_action_spec,
|
|
601
623
|
params: { token_id: id },
|
|
602
624
|
headers: test_app.create_session_headers(),
|
|
603
625
|
});
|
|
604
626
|
assert.ok(revoke_res.ok, 'account_token_revoke should succeed');
|
|
605
627
|
// Token should no longer work
|
|
606
|
-
const after_res = await
|
|
628
|
+
const after_res = await rpc_call_for_spec({
|
|
607
629
|
app: test_app.app,
|
|
608
630
|
path: rpc_path,
|
|
609
|
-
|
|
631
|
+
spec: account_verify_action_spec,
|
|
632
|
+
params: null,
|
|
610
633
|
headers: { authorization: `Bearer ${token}` },
|
|
634
|
+
suppress_default_origin: true,
|
|
611
635
|
});
|
|
612
636
|
assert.strictEqual(after_res.status, 401);
|
|
613
637
|
});
|
|
@@ -634,18 +658,20 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
634
658
|
// Create a second account
|
|
635
659
|
const user_b = await test_app.create_account({ username: 'user_b' });
|
|
636
660
|
// User A revokes all their own sessions
|
|
637
|
-
const revoke_res = await
|
|
661
|
+
const revoke_res = await rpc_call_for_spec({
|
|
638
662
|
app: test_app.app,
|
|
639
663
|
path: rpc_path,
|
|
640
|
-
|
|
664
|
+
spec: account_session_revoke_all_action_spec,
|
|
665
|
+
params: null,
|
|
641
666
|
headers: test_app.create_session_headers(),
|
|
642
667
|
});
|
|
643
668
|
assert.ok(revoke_res.ok, 'account_session_revoke_all should succeed');
|
|
644
669
|
// User B's session should still work
|
|
645
|
-
const verify_b = await
|
|
670
|
+
const verify_b = await rpc_call_for_spec({
|
|
646
671
|
app: test_app.app,
|
|
647
672
|
path: rpc_path,
|
|
648
|
-
|
|
673
|
+
spec: account_verify_action_spec,
|
|
674
|
+
params: null,
|
|
649
675
|
headers: { cookie: `${cookie_name}=${user_b.session_cookie}` },
|
|
650
676
|
});
|
|
651
677
|
assert.strictEqual(verify_b.status, 200);
|
|
@@ -655,32 +681,32 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
655
681
|
const user_b = await test_app.create_account({ username: 'user_b' });
|
|
656
682
|
const user_b_headers = { cookie: `${cookie_name}=${user_b.session_cookie}` };
|
|
657
683
|
// Get user B's session ID by listing as user B
|
|
658
|
-
const list_res = await
|
|
684
|
+
const list_res = await rpc_call_for_spec({
|
|
659
685
|
app: test_app.app,
|
|
660
686
|
path: rpc_path,
|
|
661
|
-
|
|
687
|
+
spec: account_session_list_action_spec,
|
|
688
|
+
params: null,
|
|
662
689
|
headers: user_b_headers,
|
|
663
690
|
});
|
|
664
691
|
assert.ok(list_res.ok, 'account_session_list should succeed');
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
const session_id_b = list_body.sessions[0].id;
|
|
692
|
+
assert.ok(list_res.result.sessions.length >= 1);
|
|
693
|
+
const session_id_b = list_res.result.sessions[0].id;
|
|
668
694
|
// User A tries to revoke user B's session by ID
|
|
669
|
-
const revoke_res = await
|
|
695
|
+
const revoke_res = await rpc_call_for_spec({
|
|
670
696
|
app: test_app.app,
|
|
671
697
|
path: rpc_path,
|
|
672
|
-
|
|
698
|
+
spec: account_session_revoke_action_spec,
|
|
673
699
|
params: { session_id: session_id_b },
|
|
674
700
|
headers: test_app.create_session_headers(),
|
|
675
701
|
});
|
|
676
702
|
assert.ok(revoke_res.ok, 'account_session_revoke should succeed');
|
|
677
|
-
|
|
678
|
-
assert.strictEqual(revoke_body.revoked, false, 'Should not revoke another account session');
|
|
703
|
+
assert.strictEqual(revoke_res.result.revoked, false, 'Should not revoke another account session');
|
|
679
704
|
// User B's session should still work
|
|
680
|
-
const verify_b = await
|
|
705
|
+
const verify_b = await rpc_call_for_spec({
|
|
681
706
|
app: test_app.app,
|
|
682
707
|
path: rpc_path,
|
|
683
|
-
|
|
708
|
+
spec: account_verify_action_spec,
|
|
709
|
+
params: null,
|
|
684
710
|
headers: user_b_headers,
|
|
685
711
|
});
|
|
686
712
|
assert.strictEqual(verify_b.status, 200);
|
|
@@ -690,33 +716,34 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
690
716
|
const user_b = await test_app.create_account({ username: 'user_b' });
|
|
691
717
|
const user_b_headers = { cookie: `${cookie_name}=${user_b.session_cookie}` };
|
|
692
718
|
// Get user B's token ID by listing as user B
|
|
693
|
-
const list_res = await
|
|
719
|
+
const list_res = await rpc_call_for_spec({
|
|
694
720
|
app: test_app.app,
|
|
695
721
|
path: rpc_path,
|
|
696
|
-
|
|
722
|
+
spec: account_token_list_action_spec,
|
|
723
|
+
params: null,
|
|
697
724
|
headers: user_b_headers,
|
|
698
725
|
});
|
|
699
726
|
assert.ok(list_res.ok, 'account_token_list should succeed');
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
const token_id_b = list_body.tokens[0].id;
|
|
727
|
+
assert.ok(list_res.result.tokens.length >= 1);
|
|
728
|
+
const token_id_b = list_res.result.tokens[0].id;
|
|
703
729
|
// User A tries to revoke user B's token by ID
|
|
704
|
-
const revoke_res = await
|
|
730
|
+
const revoke_res = await rpc_call_for_spec({
|
|
705
731
|
app: test_app.app,
|
|
706
732
|
path: rpc_path,
|
|
707
|
-
|
|
733
|
+
spec: account_token_revoke_action_spec,
|
|
708
734
|
params: { token_id: token_id_b },
|
|
709
735
|
headers: test_app.create_session_headers(),
|
|
710
736
|
});
|
|
711
737
|
assert.ok(revoke_res.ok, 'account_token_revoke should succeed');
|
|
712
|
-
|
|
713
|
-
assert.strictEqual(revoke_body.revoked, false, 'Should not revoke another account token');
|
|
738
|
+
assert.strictEqual(revoke_res.result.revoked, false, 'Should not revoke another account token');
|
|
714
739
|
// User B's bearer token should still work
|
|
715
|
-
const verify_b = await
|
|
740
|
+
const verify_b = await rpc_call_for_spec({
|
|
716
741
|
app: test_app.app,
|
|
717
742
|
path: rpc_path,
|
|
718
|
-
|
|
743
|
+
spec: account_verify_action_spec,
|
|
744
|
+
params: null,
|
|
719
745
|
headers: { authorization: `Bearer ${user_b.api_token}` },
|
|
746
|
+
suppress_default_origin: true,
|
|
720
747
|
});
|
|
721
748
|
assert.strictEqual(verify_b.status, 200);
|
|
722
749
|
});
|
|
@@ -724,16 +751,16 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
724
751
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
725
752
|
const user_b = await test_app.create_account({ username: 'user_b' });
|
|
726
753
|
// User A lists sessions
|
|
727
|
-
const res = await
|
|
754
|
+
const res = await rpc_call_for_spec({
|
|
728
755
|
app: test_app.app,
|
|
729
756
|
path: rpc_path,
|
|
730
|
-
|
|
757
|
+
spec: account_session_list_action_spec,
|
|
758
|
+
params: null,
|
|
731
759
|
headers: test_app.create_session_headers(),
|
|
732
760
|
});
|
|
733
761
|
assert.ok(res.ok, 'account_session_list should succeed');
|
|
734
|
-
const body = res.result;
|
|
735
762
|
// Sessions should only belong to user A's account
|
|
736
|
-
for (const session of
|
|
763
|
+
for (const session of res.result.sessions) {
|
|
737
764
|
assert.strictEqual(session.account_id, test_app.backend.account.id, `Session ${session.id} should belong to user A, not user B (${user_b.account.id})`);
|
|
738
765
|
}
|
|
739
766
|
});
|
|
@@ -741,16 +768,16 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
741
768
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
742
769
|
const user_b = await test_app.create_account({ username: 'user_b' });
|
|
743
770
|
// User A lists tokens
|
|
744
|
-
const res = await
|
|
771
|
+
const res = await rpc_call_for_spec({
|
|
745
772
|
app: test_app.app,
|
|
746
773
|
path: rpc_path,
|
|
747
|
-
|
|
774
|
+
spec: account_token_list_action_spec,
|
|
775
|
+
params: null,
|
|
748
776
|
headers: test_app.create_session_headers(),
|
|
749
777
|
});
|
|
750
778
|
assert.ok(res.ok, 'account_token_list should succeed');
|
|
751
|
-
const body = res.result;
|
|
752
779
|
// Tokens should only belong to user A's account
|
|
753
|
-
for (const token of
|
|
780
|
+
for (const token of res.result.tokens) {
|
|
754
781
|
assert.strictEqual(token.account_id, test_app.backend.account.id, `Token ${token.id} should belong to user A, not user B (${user_b.account.id})`);
|
|
755
782
|
}
|
|
756
783
|
});
|
|
@@ -864,24 +891,54 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
864
891
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
865
892
|
// Hit several auth-required RPC methods without credentials to
|
|
866
893
|
// broaden error coverage beyond just /login. RPC 401s are tracked
|
|
867
|
-
// against the shared endpoint path.
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
894
|
+
// against the shared endpoint path. The dispatcher runs auth before
|
|
895
|
+
// params validation, so any well-formed param body works — we just
|
|
896
|
+
// need each call to be type-correct wrt its spec.
|
|
897
|
+
const session_list = await rpc_call_for_spec({
|
|
898
|
+
app: test_app.app,
|
|
899
|
+
path: rpc_path,
|
|
900
|
+
spec: account_session_list_action_spec,
|
|
901
|
+
params: null,
|
|
902
|
+
headers: { host: 'localhost' },
|
|
903
|
+
});
|
|
904
|
+
assert.strictEqual(session_list.status, 401);
|
|
905
|
+
error_collector.record(test_app.route_specs, 'POST', rpc_path, 401);
|
|
906
|
+
const session_revoke_all = await rpc_call_for_spec({
|
|
907
|
+
app: test_app.app,
|
|
908
|
+
path: rpc_path,
|
|
909
|
+
spec: account_session_revoke_all_action_spec,
|
|
910
|
+
params: null,
|
|
911
|
+
headers: { host: 'localhost' },
|
|
912
|
+
});
|
|
913
|
+
assert.strictEqual(session_revoke_all.status, 401);
|
|
914
|
+
error_collector.record(test_app.route_specs, 'POST', rpc_path, 401);
|
|
915
|
+
const token_list = await rpc_call_for_spec({
|
|
916
|
+
app: test_app.app,
|
|
917
|
+
path: rpc_path,
|
|
918
|
+
spec: account_token_list_action_spec,
|
|
919
|
+
params: null,
|
|
920
|
+
headers: { host: 'localhost' },
|
|
921
|
+
});
|
|
922
|
+
assert.strictEqual(token_list.status, 401);
|
|
923
|
+
error_collector.record(test_app.route_specs, 'POST', rpc_path, 401);
|
|
924
|
+
const token_create = await rpc_call_for_spec({
|
|
925
|
+
app: test_app.app,
|
|
926
|
+
path: rpc_path,
|
|
927
|
+
spec: account_token_create_action_spec,
|
|
928
|
+
params: { name: 'unauth-probe' },
|
|
929
|
+
headers: { host: 'localhost' },
|
|
930
|
+
});
|
|
931
|
+
assert.strictEqual(token_create.status, 401);
|
|
932
|
+
error_collector.record(test_app.route_specs, 'POST', rpc_path, 401);
|
|
933
|
+
const verify = await rpc_call_for_spec({
|
|
934
|
+
app: test_app.app,
|
|
935
|
+
path: rpc_path,
|
|
936
|
+
spec: account_verify_action_spec,
|
|
937
|
+
params: null,
|
|
938
|
+
headers: { host: 'localhost' },
|
|
939
|
+
});
|
|
940
|
+
assert.strictEqual(verify.status, 401);
|
|
941
|
+
error_collector.record(test_app.route_specs, 'POST', rpc_path, 401);
|
|
885
942
|
// Also exercise POST /logout without auth (still REST)
|
|
886
943
|
const logout_route = find_auth_route(test_app.route_specs, '/logout', 'POST');
|
|
887
944
|
if (logout_route) {
|
|
@@ -899,10 +956,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
899
956
|
describe('error response information leakage', () => {
|
|
900
957
|
test('401 responses contain no leaky fields', async () => {
|
|
901
958
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
902
|
-
const res = await
|
|
959
|
+
const res = await rpc_call_for_spec({
|
|
903
960
|
app: test_app.app,
|
|
904
961
|
path: rpc_path,
|
|
905
|
-
|
|
962
|
+
spec: account_verify_action_spec,
|
|
963
|
+
params: null,
|
|
906
964
|
headers: { host: 'localhost' },
|
|
907
965
|
});
|
|
908
966
|
assert.strictEqual(res.status, 401);
|
|
@@ -918,10 +976,11 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
918
976
|
test('expired session cookie returns 401', async () => {
|
|
919
977
|
const test_app = await create_test_app(build_test_app_options(options, get_db()));
|
|
920
978
|
const expired_cookie = await create_expired_test_cookie(test_app.backend.keyring, options.session_options);
|
|
921
|
-
const res = await
|
|
979
|
+
const res = await rpc_call_for_spec({
|
|
922
980
|
app: test_app.app,
|
|
923
981
|
path: rpc_path,
|
|
924
|
-
|
|
982
|
+
spec: account_verify_action_spec,
|
|
983
|
+
params: null,
|
|
925
984
|
headers: { cookie: `${cookie_name}=${expired_cookie}` },
|
|
926
985
|
});
|
|
927
986
|
assert.strictEqual(res.status, 401, 'Expired session cookie should be rejected');
|
|
@@ -980,10 +1039,10 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
980
1039
|
const password_route = find_auth_route(test_app.route_specs, '/password', 'POST');
|
|
981
1040
|
assert.ok(password_route, 'Expected POST /password route');
|
|
982
1041
|
// Create an API token via RPC
|
|
983
|
-
const create_res = await
|
|
1042
|
+
const create_res = await rpc_call_for_spec({
|
|
984
1043
|
app: test_app.app,
|
|
985
1044
|
path: rpc_path,
|
|
986
|
-
|
|
1045
|
+
spec: account_token_create_action_spec,
|
|
987
1046
|
params: { name: 'test-token' },
|
|
988
1047
|
headers: test_app.create_session_headers(),
|
|
989
1048
|
});
|
|
@@ -991,11 +1050,13 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
991
1050
|
const { token: raw_token } = create_res.result;
|
|
992
1051
|
assert.ok(raw_token, 'Expected raw token in create response');
|
|
993
1052
|
// Verify bearer token works
|
|
994
|
-
const verify_before = await
|
|
1053
|
+
const verify_before = await rpc_call_for_spec({
|
|
995
1054
|
app: test_app.app,
|
|
996
1055
|
path: rpc_path,
|
|
997
|
-
|
|
1056
|
+
spec: account_verify_action_spec,
|
|
1057
|
+
params: null,
|
|
998
1058
|
headers: { authorization: `Bearer ${raw_token}` },
|
|
1059
|
+
suppress_default_origin: true,
|
|
999
1060
|
});
|
|
1000
1061
|
assert.strictEqual(verify_before.status, 200, 'Bearer token should work before password change');
|
|
1001
1062
|
// Change password (still REST)
|
|
@@ -1012,11 +1073,13 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1012
1073
|
assert.ok(typeof change_body.tokens_revoked === 'number', 'Expected tokens_revoked count');
|
|
1013
1074
|
assert.ok(change_body.tokens_revoked >= 1, 'Expected at least 1 token revoked');
|
|
1014
1075
|
// Bearer token should now be invalid
|
|
1015
|
-
const verify_after = await
|
|
1076
|
+
const verify_after = await rpc_call_for_spec({
|
|
1016
1077
|
app: test_app.app,
|
|
1017
1078
|
path: rpc_path,
|
|
1018
|
-
|
|
1079
|
+
spec: account_verify_action_spec,
|
|
1080
|
+
params: null,
|
|
1019
1081
|
headers: { authorization: `Bearer ${raw_token}` },
|
|
1082
|
+
suppress_default_origin: true,
|
|
1020
1083
|
});
|
|
1021
1084
|
assert.strictEqual(verify_after.status, 401, 'Bearer token should be rejected after password change');
|
|
1022
1085
|
});
|
|
@@ -1039,10 +1102,10 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1039
1102
|
roles: ['admin'],
|
|
1040
1103
|
});
|
|
1041
1104
|
// Create invite for alice@example.com via RPC
|
|
1042
|
-
const invite_res = await
|
|
1105
|
+
const invite_res = await rpc_call_for_spec({
|
|
1043
1106
|
app: test_app.app,
|
|
1044
1107
|
path: rpc_path,
|
|
1045
|
-
|
|
1108
|
+
spec: invite_create_action_spec,
|
|
1046
1109
|
params: { email: 'alice@example.com' },
|
|
1047
1110
|
headers: { cookie: `${cookie_name}=${admin.session_cookie}` },
|
|
1048
1111
|
});
|
|
@@ -1086,10 +1149,10 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1086
1149
|
const admin_headers = { cookie: `${cookie_name}=${admin.session_cookie}` };
|
|
1087
1150
|
// Create an invite for a specific test email via RPC
|
|
1088
1151
|
const test_email = 'signup-test@example.com';
|
|
1089
|
-
const invite_res = await
|
|
1152
|
+
const invite_res = await rpc_call_for_spec({
|
|
1090
1153
|
app: test_app.app,
|
|
1091
1154
|
path: rpc_path,
|
|
1092
|
-
|
|
1155
|
+
spec: invite_create_action_spec,
|
|
1093
1156
|
params: { email: test_email },
|
|
1094
1157
|
headers: admin_headers,
|
|
1095
1158
|
});
|
|
@@ -1116,14 +1179,14 @@ export const describe_standard_integration_tests = (options) => {
|
|
|
1116
1179
|
const existing_user = await test_app.create_account({ username: 'existing_user' });
|
|
1117
1180
|
// Create invite for a different email via RPC
|
|
1118
1181
|
const conflict_email = 'conflict-test@example.com';
|
|
1119
|
-
const invite2_res = await
|
|
1182
|
+
const invite2_res = await rpc_call_for_spec({
|
|
1120
1183
|
app: test_app.app,
|
|
1121
1184
|
path: rpc_path,
|
|
1122
|
-
|
|
1185
|
+
spec: invite_create_action_spec,
|
|
1123
1186
|
params: { email: conflict_email },
|
|
1124
1187
|
headers: admin_headers,
|
|
1125
1188
|
});
|
|
1126
|
-
assert.ok(invite2_res.ok, `
|
|
1189
|
+
assert.ok(invite2_res.ok, `invite2_create failed: ${invite2_res.ok ? '' : JSON.stringify(invite2_res.error)}`);
|
|
1127
1190
|
// Attempt 2: signup with the invited email but a colliding username → 409
|
|
1128
1191
|
const conflict_res = await test_app.app.request(signup_route.path, {
|
|
1129
1192
|
method: 'POST',
|