companion-oauth-pkce-integration.test.mjs
81 lines 3.8 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Tier 2 — INTEGRATION: the pure functions composed across the real OAuth/PKCE sequence
3 * (no I/O): pair → authorization URL → parse callback params → validate response → token request
4 * → validate token response → refresh decision. Asserts the pieces fit and the state/PKCE bindings
5 * survive a round-trip through a URL.
6 */
7 import { describe, it } from 'node:test';
8 import assert from 'node:assert/strict';
9 import crypto from 'node:crypto';
10 import {
11 createPkcePair,
12 createOAuthState,
13 buildAuthorizationUrl,
14 validateAuthorizationResponse,
15 buildTokenRequest,
16 validateTokenResponse,
17 decideTokenRefresh,
18 } from '../lib/companion-oauth-pkce.mjs';
19
20 const AUTH_EP = 'https://knowtation.store/authorize';
21 const TOKEN_EP = 'https://knowtation.store/token';
22 const ISSUER = 'https://knowtation.store';
23 const CLIENT_ID = 'companion-public-client';
24 const REDIRECT = 'http://127.0.0.1:49321/callback';
25 const SCOPES = ['vault:read', 'vault:write'];
26
27 describe('Integration — authorization request round-trips through a URL', () => {
28 it('the challenge, state, and redirect survive serialization into the auth URL', () => {
29 const { codeChallenge } = createPkcePair();
30 const state = createOAuthState();
31 const url = new URL(buildAuthorizationUrl({
32 authorizationEndpoint: AUTH_EP, clientId: CLIENT_ID, redirectUri: REDIRECT,
33 scopes: SCOPES, state, codeChallenge,
34 }));
35 assert.equal(url.searchParams.get('code_challenge'), codeChallenge);
36 assert.equal(url.searchParams.get('state'), state);
37 assert.equal(url.searchParams.get('redirect_uri'), REDIRECT);
38 });
39 });
40
41 describe('Integration — callback validation feeds the token request', () => {
42 it('a matching-state callback yields a code that buildTokenRequest consumes with the verifier', () => {
43 const { codeVerifier, codeChallenge } = createPkcePair();
44 const state = createOAuthState();
45 // The auth server (simulated) verifies S256(verifier) === challenge and returns a code.
46 assert.equal(crypto.createHash('sha256').update(codeVerifier, 'ascii').digest('base64url'), codeChallenge);
47 const callbackParams = { code: 'srv-issued-code', state, iss: ISSUER };
48
49 const resp = validateAuthorizationResponse({ params: callbackParams, expectedState: state, expectedIssuer: ISSUER });
50 assert.equal(resp.ok, true);
51
52 const tokenReq = buildTokenRequest({
53 tokenEndpoint: TOKEN_EP, clientId: CLIENT_ID, code: resp.code, codeVerifier, redirectUri: REDIRECT,
54 });
55 assert.equal(tokenReq.bodyParams.code, 'srv-issued-code');
56 assert.equal(tokenReq.bodyParams.code_verifier, codeVerifier);
57 });
58 });
59
60 describe('Integration — token response drives the refresh decision', () => {
61 it('a validated token response gives an expiry the refresh decision can act on', () => {
62 const tr = validateTokenResponse({ access_token: 'jwt', token_type: 'Bearer', expires_in: 3600, refresh_token: 'r' });
63 assert.equal(tr.ok, true);
64 const now = 1_000_000;
65 const expiresAt = now + tr.expiresIn * 1000;
66 assert.equal(decideTokenRefresh({ expiresAt, now, skewMs: 30_000 }), 'valid');
67 assert.equal(decideTokenRefresh({ expiresAt, now: expiresAt - 10, skewMs: 30_000 }), 'refresh');
68 assert.equal(decideTokenRefresh({ expiresAt, now: expiresAt + 1, refreshExpiresAt: expiresAt + 100 }), 'refresh');
69 assert.equal(decideTokenRefresh({ expiresAt, now: expiresAt + 1000, refreshExpiresAt: expiresAt + 100 }), 'reauth');
70 });
71 });
72
73 describe('Integration — a denied authorization aborts before any token request', () => {
74 it('an access_denied callback never produces a code', () => {
75 const state = createOAuthState();
76 const resp = validateAuthorizationResponse({ params: { error: 'access_denied', state }, expectedState: state });
77 assert.equal(resp.ok, false);
78 assert.equal(resp.errorCode, 'access_denied');
79 assert.equal(resp.code, undefined);
80 });
81 });
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 2 days ago