agent-credentials-integration.test.mjs
128 lines 4.6 KB
Raw
sha256:e4c529f14a0bb908c1caaaeb3f95f3623a1a82e636e7e3722ca2cd3dc9821263 security: npm audit fix pre-bridge 2026-07-29 Human 40 days ago
1 /**
2 * Phase C — integration: mint → exchange → REST propose; revoke; wrong vault.
3 */
4
5 import { describe, it, before, after } from 'node:test';
6 import assert from 'node:assert/strict';
7 import fs from 'node:fs/promises';
8 import os from 'node:os';
9 import path from 'node:path';
10 import http from 'node:http';
11 import jwt from 'jsonwebtoken';
12 import { createAgentCredentialRouter } from '../hub/gateway/agent-credential-routes.mjs';
13 import {
14 subFromVerifiedPayload,
15 assertAgentVaultAllowed,
16 } from '../hub/gateway/access-token-authz.mjs';
17 import express from 'express';
18
19 const SECRET = 'phase-c-integration-test-secret-32b!!';
20
21 async function withTempStore(fn) {
22 const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'kt-agent-cred-'));
23 const prev = process.env.KNOWTATION_GATEWAY_DATA_DIR;
24 process.env.KNOWTATION_GATEWAY_DATA_DIR = dir;
25 try {
26 await fn(dir);
27 } finally {
28 if (prev === undefined) delete process.env.KNOWTATION_GATEWAY_DATA_DIR;
29 else process.env.KNOWTATION_GATEWAY_DATA_DIR = prev;
30 await fs.rm(dir, { recursive: true, force: true });
31 }
32 }
33
34 function sessionJwt(sub = 'google:tester') {
35 return jwt.sign({ sub, type: 'session', role: 'member' }, SECRET, { expiresIn: '1h' });
36 }
37
38 describe('Phase C integration — agent credentials', () => {
39 it('mint → exchange → propose path authorized; revoke blocks exchange; wrong vault denied', async () => {
40 await withTempStore(async () => {
41 const app = express();
42 const { router } = createAgentCredentialRouter({
43 sessionSecret: SECRET,
44 getSessionSub: (req) => {
45 const auth = req.headers.authorization || '';
46 const t = auth.startsWith('Bearer ') ? auth.slice(7) : '';
47 try {
48 const p = jwt.verify(t, SECRET);
49 return p.sub || null;
50 } catch {
51 return null;
52 }
53 },
54 getSessionPayload: (req) => {
55 const auth = req.headers.authorization || '';
56 const t = auth.startsWith('Bearer ') ? auth.slice(7) : '';
57 try {
58 return jwt.verify(t, SECRET);
59 } catch {
60 return null;
61 }
62 },
63 grantedScopes: () => ['vault:read', 'vault:write'],
64 });
65 app.use('/api/v1/auth/agent', router);
66 const server = http.createServer(app);
67 await new Promise((r) => server.listen(0, r));
68 const port = server.address().port;
69 const base = `http://127.0.0.1:${port}`;
70
71 try {
72 const mintRes = await fetch(`${base}/api/v1/auth/agent/credentials`, {
73 method: 'POST',
74 headers: {
75 Authorization: `Bearer ${sessionJwt()}`,
76 'Content-Type': 'application/json',
77 },
78 body: JSON.stringify({
79 name: 'videofactory-trend-agent',
80 vault_ids: ['default'],
81 scopes: ['propose', 'vault:read'],
82 }),
83 });
84 assert.equal(mintRes.status, 201);
85 const minted = await mintRes.json();
86 assert.ok(String(minted.credential).startsWith('kt_agent_'));
87
88 const tokRes = await fetch(`${base}/api/v1/auth/agent/token`, {
89 method: 'POST',
90 headers: { 'Content-Type': 'application/json' },
91 body: JSON.stringify({ credential: minted.credential }),
92 });
93 assert.equal(tokRes.status, 200);
94 const tok = await tokRes.json();
95 assert.equal(tok.expires_in, 900);
96 const access = jwt.verify(tok.access_token, SECRET);
97 assert.equal(access.type, 'agent_access');
98 assert.equal(
99 subFromVerifiedPayload(access, { method: 'POST', path: '/api/v1/proposals' }),
100 'google:tester'
101 );
102 assert.equal(
103 subFromVerifiedPayload(access, { method: 'POST', path: '/api/v1/notes' }),
104 null
105 );
106 assert.equal(assertAgentVaultAllowed(access, 'default'), true);
107 assert.equal(assertAgentVaultAllowed(access, 'other'), false);
108
109 const rev = await fetch(`${base}/api/v1/auth/agent/credentials/${minted.id}`, {
110 method: 'DELETE',
111 headers: { Authorization: `Bearer ${sessionJwt()}` },
112 });
113 assert.equal(rev.status, 200);
114
115 const tok2 = await fetch(`${base}/api/v1/auth/agent/token`, {
116 method: 'POST',
117 headers: { 'Content-Type': 'application/json' },
118 body: JSON.stringify({ credential: minted.credential }),
119 });
120 assert.equal(tok2.status, 401);
121 const body = await tok2.json();
122 assert.equal(body.code, 'AGENT_CREDENTIAL_INVALID');
123 } finally {
124 await new Promise((r) => server.close(r));
125 }
126 });
127 });
128 });
File History 1 commit
sha256:e4c529f14a0bb908c1caaaeb3f95f3623a1a82e636e7e3722ca2cd3dc9821263 security: npm audit fix pre-bridge 2026-07-29 Human 40 days ago