verify-agent-credential-smoke.mjs
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6
docs: record AIP-b SD-21 land (KN #308)
Human
9 days ago
| 1 | #!/usr/bin/env node |
| 2 | /** |
| 3 | * Live Phase C smoke: opaque kt_agent_ credential → access JWT → vault:read. |
| 4 | * |
| 5 | * Does not print secrets. Exit 0 only on full PASS. |
| 6 | * |
| 7 | * Usage: |
| 8 | * KNOWTATION_HUB_AGENT_CREDENTIAL='kt_agent_…' \ |
| 9 | * KNOWTATION_HUB_VAULT_ID=default \ |
| 10 | * node scripts/verify-agent-credential-smoke.mjs |
| 11 | * |
| 12 | * Or: |
| 13 | * KNOWTATION_HUB_AGENT_CREDENTIAL_FILE=~/.config/knowtation/agent_cred \ |
| 14 | * node scripts/verify-agent-credential-smoke.mjs |
| 15 | */ |
| 16 | |
| 17 | import fs from 'node:fs'; |
| 18 | import path from 'node:path'; |
| 19 | |
| 20 | const apiBase = (process.env.KNOWTATION_HUB_URL || 'https://api.knowtation.store').replace(/\/$/, ''); |
| 21 | const vaultId = process.env.KNOWTATION_HUB_VAULT_ID || 'default'; |
| 22 | |
| 23 | function resolveCredential() { |
| 24 | let c = (process.env.KNOWTATION_HUB_AGENT_CREDENTIAL || '').trim(); |
| 25 | const fp = (process.env.KNOWTATION_HUB_AGENT_CREDENTIAL_FILE || '').trim(); |
| 26 | if (!c && fp) { |
| 27 | const expanded = fp.startsWith('~') |
| 28 | ? path.join(process.env.HOME || '', fp.slice(1)) |
| 29 | : fp; |
| 30 | c = fs.readFileSync(expanded, 'utf8').trim(); |
| 31 | } |
| 32 | return c; |
| 33 | } |
| 34 | |
| 35 | function redactJwtClaims(jwt) { |
| 36 | try { |
| 37 | const payload = JSON.parse(Buffer.from(jwt.split('.')[1], 'base64url').toString('utf8')); |
| 38 | return { |
| 39 | type: payload.type, |
| 40 | typ: payload.typ, |
| 41 | aud: payload.aud, |
| 42 | scopes: payload.scopes, |
| 43 | vault_ids: payload.vault_ids, |
| 44 | exp_in_s: typeof payload.exp === 'number' ? payload.exp - Math.floor(Date.now() / 1000) : null, |
| 45 | }; |
| 46 | } catch { |
| 47 | return { parse: 'failed' }; |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | function fail(step, detail) { |
| 52 | console.log(`FAIL ${step}: ${detail}`); |
| 53 | process.exit(1); |
| 54 | } |
| 55 | |
| 56 | const credential = resolveCredential(); |
| 57 | if (!credential) { |
| 58 | fail('setup', 'set KNOWTATION_HUB_AGENT_CREDENTIAL or KNOWTATION_HUB_AGENT_CREDENTIAL_FILE'); |
| 59 | } |
| 60 | if (!credential.startsWith('kt_agent_')) { |
| 61 | fail('setup', 'credential must start with kt_agent_'); |
| 62 | } |
| 63 | |
| 64 | console.log(`api=${apiBase}`); |
| 65 | console.log(`vault=${vaultId}`); |
| 66 | console.log(`credential_prefix=${credential.slice(0, 12)}… len=${credential.length}`); |
| 67 | |
| 68 | const health = await fetch(`${apiBase}/health`); |
| 69 | const healthBody = await health.text(); |
| 70 | console.log(`health status=${health.status} body=${healthBody.slice(0, 120)}`); |
| 71 | if (!health.ok) fail('health', `HTTP ${health.status}`); |
| 72 | |
| 73 | const exch = await fetch(`${apiBase}/api/v1/auth/agent/token`, { |
| 74 | method: 'POST', |
| 75 | headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, |
| 76 | body: JSON.stringify({ credential }), |
| 77 | }); |
| 78 | const exchText = await exch.text(); |
| 79 | let exchJson; |
| 80 | try { |
| 81 | exchJson = JSON.parse(exchText); |
| 82 | } catch { |
| 83 | fail('exchange', `HTTP ${exch.status} non-JSON: ${exchText.slice(0, 160)}`); |
| 84 | } |
| 85 | if (!exch.ok) { |
| 86 | fail('exchange', `HTTP ${exch.status} code=${exchJson.code || '?'} error=${exchJson.error || exchText.slice(0, 120)}`); |
| 87 | } |
| 88 | const access = exchJson.access_token; |
| 89 | if (!access || typeof access !== 'string') fail('exchange', 'missing access_token'); |
| 90 | console.log('exchange OK', { |
| 91 | token_type: exchJson.token_type, |
| 92 | expires_in: exchJson.expires_in, |
| 93 | scopes: exchJson.scopes, |
| 94 | vault_ids: exchJson.vault_ids, |
| 95 | claims: redactJwtClaims(access), |
| 96 | }); |
| 97 | |
| 98 | const headers = { |
| 99 | Accept: 'application/json', |
| 100 | Authorization: `Bearer ${access}`, |
| 101 | 'X-Vault-Id': vaultId, |
| 102 | }; |
| 103 | |
| 104 | const vaults = await fetch(`${apiBase}/api/v1/vaults`, { headers }); |
| 105 | const vaultsText = await vaults.text(); |
| 106 | console.log(`vaults status=${vaults.status} bytes=${vaultsText.length}`); |
| 107 | if (!vaults.ok) { |
| 108 | fail('vaults', `HTTP ${vaults.status} ${vaultsText.slice(0, 200)}`); |
| 109 | } |
| 110 | |
| 111 | const notes = await fetch(`${apiBase}/api/v1/notes?limit=3`, { headers }); |
| 112 | const notesText = await notes.text(); |
| 113 | console.log(`notes status=${notes.status} bytes=${notesText.length}`); |
| 114 | if (!notes.ok) { |
| 115 | fail('notes', `HTTP ${notes.status} ${notesText.slice(0, 200)}`); |
| 116 | } |
| 117 | |
| 118 | console.log('PASS agent-credential smoke (exchange + vaults + notes read)'); |
| 119 | process.exit(0); |
File History
1 commit
sha256:700fafdd1afa490919f9515d660ca6e75456bcd5bb67513abcd8757a634c01f6
docs: record AIP-b SD-21 land (KN #308)
Human
9 days ago