companion-token-custody-e2e.test.mjs
52 lines 3.0 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Tier 3 — END-TO-END: a realistic companion session driving custody from first sign-in through
3 * a refresh to logout, plus the per-session loopback token rotated at each companion start. Uses
4 * the async keychain fake; no real OS keychain (Phase 5).
5 */
6 import { describe, it } from 'node:test';
7 import assert from 'node:assert/strict';
8 import crypto from 'node:crypto';
9 import { validateTokenResponse } from '../lib/companion-oauth-pkce.mjs';
10 import { buildSessionMeta, createTokenCustody } from '../lib/companion-token-custody.mjs';
11 import { makeAsyncKeychain } from './helpers/companion-keychain-fake.mjs';
12
13 describe('E2E — companion session lifecycle', () => {
14 it('start → sign-in → use → refresh → restart (rotate loopback) → logout', async () => {
15 const kc = makeAsyncKeychain();
16 const custody = createTokenCustody(kc);
17
18 // Companion start #1: mint and store a per-session loopback token (Phase 2 credential).
19 const loopback1 = crypto.randomBytes(32).toString('base64url');
20 await custody.rotateLoopbackToken(loopback1);
21 assert.equal(await custody.getLoopbackToken(), loopback1);
22
23 // Sign-in: the token endpoint returned a bearer + refresh; store the session.
24 const signin = validateTokenResponse({ access_token: 'jwt-A', token_type: 'Bearer', expires_in: 3600, refresh_token: 'refresh-A', scope: 'vault:read vault:write' });
25 const t0 = 1_700_000_000_000;
26 await custody.storeSession({ accessToken: signin.accessToken, refreshToken: signin.refreshToken, meta: buildSessionMeta(signin, { now: t0, refreshTtlMs: 90 * 86_400_000 }) });
27
28 // Use: the access token is valid now.
29 assert.equal(await custody.decide({ now: t0 + 60_000 }), 'valid');
30
31 // Later: access token near expiry → refresh.
32 const session = await custody.loadSession();
33 assert.equal(await custody.decide({ now: session.expiresAt - 5_000 }), 'refresh');
34 const refreshed = validateTokenResponse({ access_token: 'jwt-B', token_type: 'Bearer', expires_in: 3600, refresh_token: 'refresh-B' });
35 await custody.updateAccessToken({ accessToken: refreshed.accessToken, refreshToken: refreshed.refreshToken, meta: buildSessionMeta(refreshed, { now: session.expiresAt, refreshTtlMs: 90 * 86_400_000 }) });
36 assert.equal((await custody.loadSession()).accessToken, 'jwt-B');
37
38 // Companion restart #2: a NEW loopback token replaces the old one; OAuth session persists.
39 const loopback2 = crypto.randomBytes(32).toString('base64url');
40 await custody.rotateLoopbackToken(loopback2);
41 assert.equal(await custody.getLoopbackToken(), loopback2);
42 assert.notEqual(loopback2, loopback1);
43 assert.equal((await custody.loadSession()).accessToken, 'jwt-B'); // session survived restart
44
45 // Logout: clear the OAuth session and the loopback token.
46 await custody.clearSession();
47 await custody.clearLoopbackToken();
48 assert.equal(await custody.loadSession(), null);
49 assert.equal(await custody.getLoopbackToken(), null);
50 assert.equal(await custody.decide({ now: Date.now() }), 'reauth');
51 });
52 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago