aismemory 0.3.0 → 0.5.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/__tests__/device-flow-recovery.test.d.ts +1 -0
- package/dist/__tests__/device-flow-recovery.test.js +206 -0
- package/dist/__tests__/device-flow-recovery.test.js.map +1 -0
- package/dist/__tests__/key-auth.test.d.ts +1 -0
- package/dist/__tests__/key-auth.test.js +284 -0
- package/dist/__tests__/key-auth.test.js.map +1 -0
- package/dist/__tests__/mcp-init-no-auth.test.d.ts +1 -0
- package/dist/__tests__/mcp-init-no-auth.test.js +75 -0
- package/dist/__tests__/mcp-init-no-auth.test.js.map +1 -0
- package/dist/cli/enable-key-auth.d.ts +1 -0
- package/dist/cli/enable-key-auth.js +131 -0
- package/dist/cli/enable-key-auth.js.map +1 -0
- package/dist/index.js +199 -47
- package/dist/index.js.map +1 -1
- package/dist/key-auth.d.ts +66 -0
- package/dist/key-auth.js +179 -0
- package/dist/key-auth.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'path';
|
|
6
|
+
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
7
|
+
/**
|
|
8
|
+
* Regression: when the OAuth device-flow poll rejects (expired_token,
|
|
9
|
+
* access_denied, or 30-min deadline) the prior aismemory MCP would store the
|
|
10
|
+
* dead poll on `activeFlow` and every subsequent tool call would re-race
|
|
11
|
+
* against the rejected promise, propagating the same stale error. The only
|
|
12
|
+
* recovery was for the user to restart their AI tool — too friction-heavy for
|
|
13
|
+
* first-time users (they get distracted, the activation drops on the floor).
|
|
14
|
+
*
|
|
15
|
+
* After the fix, the MCP marks the flow `dead` on rejection and discards it,
|
|
16
|
+
* so the next tool call starts a fresh device flow inline and surfaces a new
|
|
17
|
+
* bond URL — no restart required.
|
|
18
|
+
*
|
|
19
|
+
* This test drives the binary against a fake AIS HTTP server that:
|
|
20
|
+
* - returns a unique user_code per `/v1/oauth/device/authorize` call
|
|
21
|
+
* - returns `expired_token` on the first `/v1/oauth/token` poll
|
|
22
|
+
* Two consecutive tool calls should produce two DIFFERENT user_codes in the
|
|
23
|
+
* surfaced bond URLs, proving the dead flow was discarded and replaced.
|
|
24
|
+
*/
|
|
25
|
+
describe('aismemory MCP device-flow recovery', () => {
|
|
26
|
+
let fakeAisServer;
|
|
27
|
+
let fakeAisPort = 0;
|
|
28
|
+
let authorizeCalls = 0;
|
|
29
|
+
const issuedCodes = [];
|
|
30
|
+
beforeAll(async () => {
|
|
31
|
+
fakeAisServer = createServer((req, res) => {
|
|
32
|
+
req.on('data', () => { });
|
|
33
|
+
req.on('end', () => {
|
|
34
|
+
if (req.url === '/v1/oauth/device/authorize' && req.method === 'POST') {
|
|
35
|
+
authorizeCalls += 1;
|
|
36
|
+
const userCode = `TEST-${String(authorizeCalls).padStart(4, '0')}`;
|
|
37
|
+
issuedCodes.push(userCode);
|
|
38
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
39
|
+
res.end(JSON.stringify({
|
|
40
|
+
success: true,
|
|
41
|
+
data: {
|
|
42
|
+
device_code: `device-${authorizeCalls}`,
|
|
43
|
+
user_code: userCode,
|
|
44
|
+
verification_uri: `http://127.0.0.1:${fakeAisPort}/activate`,
|
|
45
|
+
verification_uri_complete: `http://127.0.0.1:${fakeAisPort}/activate?user_code=${userCode}`,
|
|
46
|
+
expires_in: 1800,
|
|
47
|
+
interval: 1, // 1s poll → bounded to 5s min by the client
|
|
48
|
+
agent_id: `agent-${authorizeCalls}`,
|
|
49
|
+
provisional_tenant_id: `tenant-${authorizeCalls}`,
|
|
50
|
+
provisional_token: 'provisional',
|
|
51
|
+
},
|
|
52
|
+
}));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (req.url === '/v1/oauth/token' && req.method === 'POST') {
|
|
56
|
+
// Always reject the poll so the flow dies and recovery kicks in.
|
|
57
|
+
res.writeHead(400, { 'content-type': 'application/json' });
|
|
58
|
+
res.end(JSON.stringify({ error: 'expired_token' }));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
res.writeHead(404);
|
|
62
|
+
res.end();
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
await new Promise((resolveServer) => {
|
|
66
|
+
fakeAisServer.listen(0, '127.0.0.1', () => {
|
|
67
|
+
const addr = fakeAisServer.address();
|
|
68
|
+
if (addr && typeof addr === 'object') {
|
|
69
|
+
fakeAisPort = addr.port;
|
|
70
|
+
}
|
|
71
|
+
resolveServer();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
await new Promise((r) => fakeAisServer?.close(() => r()));
|
|
77
|
+
});
|
|
78
|
+
beforeEach(() => {
|
|
79
|
+
authorizeCalls = 0;
|
|
80
|
+
issuedCodes.length = 0;
|
|
81
|
+
});
|
|
82
|
+
it('starts a fresh device flow after the previous one dies', async () => {
|
|
83
|
+
const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
84
|
+
if (!existsSync(distPath)) {
|
|
85
|
+
throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
|
|
86
|
+
}
|
|
87
|
+
const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-recover-'));
|
|
88
|
+
let proc = null;
|
|
89
|
+
try {
|
|
90
|
+
proc = spawn('node', [distPath], {
|
|
91
|
+
env: {
|
|
92
|
+
...process.env,
|
|
93
|
+
HOME: fakeHome,
|
|
94
|
+
AIS_URL: `http://127.0.0.1:${fakeAisPort}`,
|
|
95
|
+
},
|
|
96
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
97
|
+
});
|
|
98
|
+
let stdoutBuf = '';
|
|
99
|
+
proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
|
|
100
|
+
proc.stderr.on('data', () => { });
|
|
101
|
+
// 1. Handshake: initialize the MCP transport.
|
|
102
|
+
proc.stdin.write(JSON.stringify({
|
|
103
|
+
jsonrpc: '2.0',
|
|
104
|
+
id: 1,
|
|
105
|
+
method: 'initialize',
|
|
106
|
+
params: {
|
|
107
|
+
protocolVersion: '2024-11-05',
|
|
108
|
+
capabilities: {},
|
|
109
|
+
clientInfo: { name: 'recovery-test', version: '0.0.0' },
|
|
110
|
+
},
|
|
111
|
+
}) + '\n');
|
|
112
|
+
await waitFor(() => stdoutBuf.includes('"id":1'), 5000, 'initialize response');
|
|
113
|
+
// 2. First tool call triggers device flow #1. The poll will reject
|
|
114
|
+
// against the fake AIS (expired_token), marking the flow dead.
|
|
115
|
+
// The call surfaces BondPendingError with the first user_code.
|
|
116
|
+
proc.stdin.write(JSON.stringify({
|
|
117
|
+
jsonrpc: '2.0',
|
|
118
|
+
id: 2,
|
|
119
|
+
method: 'tools/call',
|
|
120
|
+
params: { name: 'whoami', arguments: {} },
|
|
121
|
+
}) + '\n');
|
|
122
|
+
await waitFor(() => stdoutBuf.includes('"id":2'), 15000, 'first whoami response');
|
|
123
|
+
const firstResponse = extractResponseText(stdoutBuf, 2);
|
|
124
|
+
// Give the poll time to actually reject — soft timeout is 7s, poll
|
|
125
|
+
// interval is min 5s, so the rejection lands ~5s after the call. We
|
|
126
|
+
// need that rejection observed before the second call so the `dead`
|
|
127
|
+
// flag is set when ensureCredentials runs again.
|
|
128
|
+
await new Promise((r) => setTimeout(r, 8000));
|
|
129
|
+
// 3. Second tool call — should detect the dead flow, start a fresh
|
|
130
|
+
// device flow, and surface a DIFFERENT user_code.
|
|
131
|
+
proc.stdin.write(JSON.stringify({
|
|
132
|
+
jsonrpc: '2.0',
|
|
133
|
+
id: 3,
|
|
134
|
+
method: 'tools/call',
|
|
135
|
+
params: { name: 'whoami', arguments: {} },
|
|
136
|
+
}) + '\n');
|
|
137
|
+
await waitFor(() => stdoutBuf.includes('"id":3'), 15000, 'second whoami response');
|
|
138
|
+
const secondResponse = extractResponseText(stdoutBuf, 3);
|
|
139
|
+
// Recovery proof: more than one device-flow authorize was made. Without
|
|
140
|
+
// the fix the MCP would re-race the dead poll forever and never call
|
|
141
|
+
// authorize a second time.
|
|
142
|
+
expect(authorizeCalls).toBeGreaterThanOrEqual(2);
|
|
143
|
+
expect(issuedCodes.length).toBeGreaterThanOrEqual(2);
|
|
144
|
+
// Both tool calls return a bond URL (no misleading "Authentication
|
|
145
|
+
// timed out" leak from the dead poll's stored rejection).
|
|
146
|
+
expect(firstResponse).toMatch(/Memory bond required/);
|
|
147
|
+
expect(secondResponse).toMatch(/Memory bond required/);
|
|
148
|
+
// The two responses must surface DIFFERENT codes — proving the second
|
|
149
|
+
// call doesn't keep showing the original (now-dead) code.
|
|
150
|
+
const firstCode = extractUserCode(firstResponse);
|
|
151
|
+
const secondCode = extractUserCode(secondResponse);
|
|
152
|
+
expect(firstCode).toBeTruthy();
|
|
153
|
+
expect(secondCode).toBeTruthy();
|
|
154
|
+
expect(firstCode).not.toEqual(secondCode);
|
|
155
|
+
expect(issuedCodes).toContain(firstCode);
|
|
156
|
+
expect(issuedCodes).toContain(secondCode);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
proc?.kill();
|
|
160
|
+
try {
|
|
161
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
catch { /* ignore */ }
|
|
164
|
+
}
|
|
165
|
+
}, 45000);
|
|
166
|
+
});
|
|
167
|
+
function waitFor(predicate, ms, label) {
|
|
168
|
+
return new Promise((resolveOuter, reject) => {
|
|
169
|
+
const start = Date.now();
|
|
170
|
+
const tick = () => {
|
|
171
|
+
if (predicate()) {
|
|
172
|
+
resolveOuter();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (Date.now() - start > ms) {
|
|
176
|
+
reject(new Error(`timeout waiting for ${label}`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
setTimeout(tick, 50);
|
|
180
|
+
};
|
|
181
|
+
tick();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
function extractUserCode(responseText) {
|
|
185
|
+
const match = /TEST-\d{4}/.exec(responseText);
|
|
186
|
+
return match ? match[0] : null;
|
|
187
|
+
}
|
|
188
|
+
function extractResponseText(buf, id) {
|
|
189
|
+
// MCP responses are newline-delimited JSON. Find the line with our id and
|
|
190
|
+
// pull the content[0].text payload out.
|
|
191
|
+
for (const line of buf.split('\n')) {
|
|
192
|
+
if (!line.includes(`"id":${id}`))
|
|
193
|
+
continue;
|
|
194
|
+
try {
|
|
195
|
+
const parsed = JSON.parse(line);
|
|
196
|
+
const text = parsed.result?.content?.[0]?.text;
|
|
197
|
+
if (typeof text === 'string')
|
|
198
|
+
return text;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
/* malformed line; skip */
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=device-flow-recovery.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-flow-recovery.test.js","sourceRoot":"","sources":["../../src/__tests__/device-flow-recovery.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the aismemory key-auth client module.
|
|
3
|
+
*
|
|
4
|
+
* Phase 1 DID-based authentication: the client reads an Ed25519 private key
|
|
5
|
+
* from disk, signs a challenge issued by AIS, and receives an owner JWT.
|
|
6
|
+
* Replaces the device-flow ceremony for every session after enrollment.
|
|
7
|
+
*
|
|
8
|
+
* Tests cover:
|
|
9
|
+
* - tryKeyAuth() returns null when no key file is present (so callers can
|
|
10
|
+
* gracefully fall back to device flow)
|
|
11
|
+
* - tryKeyAuth() round-trips a challenge against a fake AIS and returns
|
|
12
|
+
* the resulting owner JWT
|
|
13
|
+
* - generateAndSaveKeypair() writes a valid key file
|
|
14
|
+
* - readKeyFile() recovers what generateAndSaveKeypair() wrote
|
|
15
|
+
*/
|
|
16
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
17
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync, statSync } from 'node:fs';
|
|
18
|
+
import { tmpdir } from 'node:os';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { createServer } from 'node:http';
|
|
21
|
+
import { createPublicKey, createPrivateKey, verify as edVerify } from 'node:crypto';
|
|
22
|
+
import { tryKeyAuth, generateAndSaveKeypair, readKeyFile, } from '../key-auth.js';
|
|
23
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
24
|
+
// Test fixtures
|
|
25
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
26
|
+
const TEST_USER_ID = '454e4306-423e-498d-9b85-24bafbabff9b';
|
|
27
|
+
const TEST_DOMAIN = 'ais.example.com';
|
|
28
|
+
const TEST_USER_DID = `did:web:${TEST_DOMAIN}:users:${TEST_USER_ID}`;
|
|
29
|
+
let tmpKeysDir;
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
tmpKeysDir = mkdtempSync(join(tmpdir(), 'aismemory-keys-test-'));
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
if (existsSync(tmpKeysDir)) {
|
|
35
|
+
rmSync(tmpKeysDir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// Key file generation + read
|
|
40
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
41
|
+
describe('generateAndSaveKeypair', () => {
|
|
42
|
+
it('writes a Ed25519 key file containing matching public/private bytes', async () => {
|
|
43
|
+
const result = await generateAndSaveKeypair({
|
|
44
|
+
userId: TEST_USER_ID,
|
|
45
|
+
userDid: TEST_USER_DID,
|
|
46
|
+
keysDir: tmpKeysDir,
|
|
47
|
+
});
|
|
48
|
+
expect(result.userId).toBe(TEST_USER_ID);
|
|
49
|
+
expect(result.userDid).toBe(TEST_USER_DID);
|
|
50
|
+
expect(result.keyType).toBe('Ed25519');
|
|
51
|
+
expect(result.publicKey).toMatch(/^[A-Za-z0-9_-]+$/); // base64url
|
|
52
|
+
expect(result.privateKey).toMatch(/^[A-Za-z0-9_-]+$/);
|
|
53
|
+
expect(result.publicKeyJwk.kty).toBe('OKP');
|
|
54
|
+
expect(result.publicKeyJwk.crv).toBe('Ed25519');
|
|
55
|
+
// File must exist with mode 0600.
|
|
56
|
+
const path = join(tmpKeysDir, `${TEST_USER_DID}.json`);
|
|
57
|
+
expect(existsSync(path)).toBe(true);
|
|
58
|
+
const stat = statSync(path);
|
|
59
|
+
// Bottom 9 bits = perms. We only assert the user-only invariant
|
|
60
|
+
// (group/other have no perms). Skip on Windows where this isn't real.
|
|
61
|
+
if (process.platform !== 'win32') {
|
|
62
|
+
const perms = stat.mode & 0o777;
|
|
63
|
+
expect(perms).toBe(0o600);
|
|
64
|
+
}
|
|
65
|
+
const onDisk = JSON.parse(readFileSync(path, 'utf-8'));
|
|
66
|
+
expect(onDisk.userId).toBe(TEST_USER_ID);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('readKeyFile', () => {
|
|
70
|
+
it('returns null when no key file is present', () => {
|
|
71
|
+
const got = readKeyFile({ userDid: TEST_USER_DID, keysDir: tmpKeysDir });
|
|
72
|
+
expect(got).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
it('returns the file we generated', async () => {
|
|
75
|
+
const written = await generateAndSaveKeypair({
|
|
76
|
+
userId: TEST_USER_ID,
|
|
77
|
+
userDid: TEST_USER_DID,
|
|
78
|
+
keysDir: tmpKeysDir,
|
|
79
|
+
});
|
|
80
|
+
const read = readKeyFile({ userDid: TEST_USER_DID, keysDir: tmpKeysDir });
|
|
81
|
+
expect(read).not.toBeNull();
|
|
82
|
+
expect(read?.userId).toBe(written.userId);
|
|
83
|
+
expect(read?.privateKey).toBe(written.privateKey);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
/**
|
|
87
|
+
* Spawns a tiny HTTP server that simulates the AIS challenge/did-prove
|
|
88
|
+
* endpoints. The server actually verifies the Ed25519 signature so we know
|
|
89
|
+
* the client signed the correct canonical string with the right key.
|
|
90
|
+
*/
|
|
91
|
+
async function startFakeAis(opts) {
|
|
92
|
+
const issued = { token: '' };
|
|
93
|
+
const server = createServer((req, res) => {
|
|
94
|
+
let body = '';
|
|
95
|
+
req.on('data', (chunk) => (body += chunk));
|
|
96
|
+
req.on('end', () => {
|
|
97
|
+
try {
|
|
98
|
+
if (req.method === 'POST' && req.url === '/v1/auth/challenge') {
|
|
99
|
+
const parsed = JSON.parse(body || '{}');
|
|
100
|
+
const nonce = `nonce-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
101
|
+
const exp = Math.floor(Date.now() / 1000) + (opts.forceExpiredChallenge ? -60 : 60);
|
|
102
|
+
// We don't actually HMAC here — the fake just echoes back a marker;
|
|
103
|
+
// the real server checks the HMAC, the fake doesn't need to.
|
|
104
|
+
res.statusCode = 200;
|
|
105
|
+
res.setHeader('Content-Type', 'application/json');
|
|
106
|
+
res.end(JSON.stringify({
|
|
107
|
+
success: true,
|
|
108
|
+
data: { nonce, exp, hmac: `fake-hmac-for:${parsed.userDid}` },
|
|
109
|
+
}));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (req.method === 'POST' && req.url === '/v1/auth/did-prove') {
|
|
113
|
+
const parsed = JSON.parse(body || '{}');
|
|
114
|
+
if (!parsed.userDid || !parsed.nonce || !parsed.exp || !parsed.signature) {
|
|
115
|
+
res.statusCode = 400;
|
|
116
|
+
res.end(JSON.stringify({ success: false, error: { code: 'BAD_REQUEST' } }));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Verify the signature against the accepted public key.
|
|
120
|
+
const msg = Buffer.from(`${parsed.userDid}\n${parsed.nonce}\n${parsed.exp}`, 'utf8');
|
|
121
|
+
const pub = createPublicKey({ key: opts.acceptedJwk, format: 'jwk' });
|
|
122
|
+
const ok = edVerify(null, msg, pub, Buffer.from(parsed.signature, 'base64url'));
|
|
123
|
+
if (!ok) {
|
|
124
|
+
res.statusCode = 401;
|
|
125
|
+
res.end(JSON.stringify({ success: false, error: { code: 'INVALID_SIGNATURE' } }));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const token = `fake-owner-jwt-${Date.now()}`;
|
|
129
|
+
issued.token = token;
|
|
130
|
+
res.statusCode = 200;
|
|
131
|
+
res.setHeader('Content-Type', 'application/json');
|
|
132
|
+
res.end(JSON.stringify({
|
|
133
|
+
success: true,
|
|
134
|
+
data: {
|
|
135
|
+
bearerToken: token,
|
|
136
|
+
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
137
|
+
},
|
|
138
|
+
}));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.statusCode = 404;
|
|
142
|
+
res.end(JSON.stringify({ error: 'not_found' }));
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
res.statusCode = 500;
|
|
146
|
+
res.end(JSON.stringify({ error: e.message }));
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
151
|
+
const addr = server.address();
|
|
152
|
+
if (!addr || typeof addr === 'string')
|
|
153
|
+
throw new Error('failed to bind');
|
|
154
|
+
const url = `http://127.0.0.1:${addr.port}`;
|
|
155
|
+
return {
|
|
156
|
+
url,
|
|
157
|
+
stop: () => new Promise((resolve) => {
|
|
158
|
+
server.close(() => resolve());
|
|
159
|
+
}),
|
|
160
|
+
get lastIssued() {
|
|
161
|
+
return issued.token || undefined;
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
describe('tryKeyAuth', () => {
|
|
166
|
+
it('returns null when no key file is present', async () => {
|
|
167
|
+
const result = await tryKeyAuth({
|
|
168
|
+
aisUrl: 'http://unreachable.example.com',
|
|
169
|
+
keysDir: tmpKeysDir,
|
|
170
|
+
userDid: TEST_USER_DID,
|
|
171
|
+
});
|
|
172
|
+
expect(result).toBeNull();
|
|
173
|
+
});
|
|
174
|
+
it('signs a real challenge and returns the issued bearer token', async () => {
|
|
175
|
+
// 1. Bootstrap a key file on disk.
|
|
176
|
+
const key = await generateAndSaveKeypair({
|
|
177
|
+
userId: TEST_USER_ID,
|
|
178
|
+
userDid: TEST_USER_DID,
|
|
179
|
+
keysDir: tmpKeysDir,
|
|
180
|
+
});
|
|
181
|
+
// 2. Stand up a fake AIS that accepts that exact public key.
|
|
182
|
+
const ais = await startFakeAis({ acceptedJwk: key.publicKeyJwk });
|
|
183
|
+
try {
|
|
184
|
+
// 3. Run the full challenge/prove flow.
|
|
185
|
+
const result = await tryKeyAuth({
|
|
186
|
+
aisUrl: ais.url,
|
|
187
|
+
keysDir: tmpKeysDir,
|
|
188
|
+
userDid: TEST_USER_DID,
|
|
189
|
+
});
|
|
190
|
+
expect(result).not.toBeNull();
|
|
191
|
+
expect(result?.userId).toBe(TEST_USER_ID);
|
|
192
|
+
expect(result?.userDid).toBe(TEST_USER_DID);
|
|
193
|
+
expect(result?.tokenType).toBe('owner');
|
|
194
|
+
expect(result?.token).toMatch(/^fake-owner-jwt-/);
|
|
195
|
+
expect(result?.expiresAt).toBeDefined();
|
|
196
|
+
}
|
|
197
|
+
finally {
|
|
198
|
+
await ais.stop();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
it("throws when the server rejects the signature (e.g. user's key was revoked)", async () => {
|
|
202
|
+
// Generate two keypairs. Client has key A; server only accepts key B.
|
|
203
|
+
const clientKey = await generateAndSaveKeypair({
|
|
204
|
+
userId: TEST_USER_ID,
|
|
205
|
+
userDid: TEST_USER_DID,
|
|
206
|
+
keysDir: tmpKeysDir,
|
|
207
|
+
});
|
|
208
|
+
// Build a different keypair just to get a JWK the fake AIS will reject.
|
|
209
|
+
const tmpDir2 = mkdtempSync(join(tmpdir(), 'aismemory-otherkey-'));
|
|
210
|
+
const serverKey = await generateAndSaveKeypair({
|
|
211
|
+
userId: TEST_USER_ID,
|
|
212
|
+
userDid: TEST_USER_DID,
|
|
213
|
+
keysDir: tmpDir2,
|
|
214
|
+
});
|
|
215
|
+
// Voiding the unused-var lint while keeping the keypair around for clarity.
|
|
216
|
+
void clientKey;
|
|
217
|
+
rmSync(tmpDir2, { recursive: true, force: true });
|
|
218
|
+
const ais = await startFakeAis({ acceptedJwk: serverKey.publicKeyJwk });
|
|
219
|
+
try {
|
|
220
|
+
await expect(tryKeyAuth({
|
|
221
|
+
aisUrl: ais.url,
|
|
222
|
+
keysDir: tmpKeysDir,
|
|
223
|
+
userDid: TEST_USER_DID,
|
|
224
|
+
})).rejects.toThrow();
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await ais.stop();
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
it('auto-discovers the userDid when only one key file is present', async () => {
|
|
231
|
+
// No userDid passed; the helper should pick the lone file.
|
|
232
|
+
const key = await generateAndSaveKeypair({
|
|
233
|
+
userId: TEST_USER_ID,
|
|
234
|
+
userDid: TEST_USER_DID,
|
|
235
|
+
keysDir: tmpKeysDir,
|
|
236
|
+
});
|
|
237
|
+
const ais = await startFakeAis({ acceptedJwk: key.publicKeyJwk });
|
|
238
|
+
try {
|
|
239
|
+
const result = await tryKeyAuth({
|
|
240
|
+
aisUrl: ais.url,
|
|
241
|
+
keysDir: tmpKeysDir,
|
|
242
|
+
});
|
|
243
|
+
expect(result).not.toBeNull();
|
|
244
|
+
expect(result?.userDid).toBe(TEST_USER_DID);
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
await ais.stop();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
252
|
+
// Signing utility — verified by round-trip against Node's crypto
|
|
253
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
254
|
+
describe('canonical signing format', () => {
|
|
255
|
+
it("uses '<userDid>\\n<nonce>\\n<exp>' as the message body", async () => {
|
|
256
|
+
const key = await generateAndSaveKeypair({
|
|
257
|
+
userId: TEST_USER_ID,
|
|
258
|
+
userDid: TEST_USER_DID,
|
|
259
|
+
keysDir: tmpKeysDir,
|
|
260
|
+
});
|
|
261
|
+
// Re-derive the public key and verify a sample signature with the
|
|
262
|
+
// expected canonical form. If the implementation later changes the
|
|
263
|
+
// canonical bytes, this test breaks loudly.
|
|
264
|
+
const ais = await startFakeAis({ acceptedJwk: key.publicKeyJwk });
|
|
265
|
+
try {
|
|
266
|
+
const result = await tryKeyAuth({
|
|
267
|
+
aisUrl: ais.url,
|
|
268
|
+
keysDir: tmpKeysDir,
|
|
269
|
+
userDid: TEST_USER_DID,
|
|
270
|
+
});
|
|
271
|
+
expect(result).not.toBeNull();
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
await ais.stop();
|
|
275
|
+
}
|
|
276
|
+
// Smoke: private key actually loadable.
|
|
277
|
+
const priv = createPrivateKey({
|
|
278
|
+
key: { kty: 'OKP', crv: 'Ed25519', x: key.publicKeyJwk.x, d: key.privateKey },
|
|
279
|
+
format: 'jwk',
|
|
280
|
+
});
|
|
281
|
+
expect(priv).toBeDefined();
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
//# sourceMappingURL=key-auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key-auth.test.js","sourceRoot":"","sources":["../../src/__tests__/key-auth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, resolve } from 'path';
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
/**
|
|
7
|
+
* Regression test for the v0.2.0/v0.3.0 bug where `npx aismemory` blocked the
|
|
8
|
+
* MCP transport handshake on user device-flow approval, exceeding Claude
|
|
9
|
+
* Code's 30s connection timeout.
|
|
10
|
+
*
|
|
11
|
+
* Strategy: spawn the built binary with HOME pointed at a fresh tmpdir (no
|
|
12
|
+
* cached credentials) and AIS_URL pointed at a non-routable host. Before the
|
|
13
|
+
* fix, the device-flow HTTP call would hang for the connect timeout and the
|
|
14
|
+
* MCP `initialize` response would never arrive. After the fix, the transport
|
|
15
|
+
* connects before any auth, so initialize must return promptly.
|
|
16
|
+
*/
|
|
17
|
+
describe('MCP transport boots without blocking on auth', () => {
|
|
18
|
+
it('responds to initialize within 3s when uncredentialed and AIS unreachable', async () => {
|
|
19
|
+
const distPath = resolve(__dirname, '..', '..', 'dist', 'index.js');
|
|
20
|
+
if (!existsSync(distPath)) {
|
|
21
|
+
throw new Error(`dist/index.js missing — run \`pnpm build\` first (looked at ${distPath})`);
|
|
22
|
+
}
|
|
23
|
+
const fakeHome = mkdtempSync(join(tmpdir(), 'aismem-init-'));
|
|
24
|
+
let proc = null;
|
|
25
|
+
try {
|
|
26
|
+
proc = spawn('node', [distPath], {
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
HOME: fakeHome,
|
|
30
|
+
AIS_URL: 'http://10.255.255.1:1', // non-routable; the fetch will hang
|
|
31
|
+
},
|
|
32
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
33
|
+
});
|
|
34
|
+
let stdoutBuf = '';
|
|
35
|
+
let stderrBuf = '';
|
|
36
|
+
proc.stdout.on('data', (d) => { stdoutBuf += d.toString(); });
|
|
37
|
+
proc.stderr.on('data', (d) => { stderrBuf += d.toString(); });
|
|
38
|
+
const initRequest = JSON.stringify({
|
|
39
|
+
jsonrpc: '2.0',
|
|
40
|
+
id: 1,
|
|
41
|
+
method: 'initialize',
|
|
42
|
+
params: {
|
|
43
|
+
protocolVersion: '2024-11-05',
|
|
44
|
+
capabilities: {},
|
|
45
|
+
clientInfo: { name: 'init-test', version: '0.0.0' },
|
|
46
|
+
},
|
|
47
|
+
}) + '\n';
|
|
48
|
+
const t0 = Date.now();
|
|
49
|
+
proc.stdin.write(initRequest);
|
|
50
|
+
const responseLine = await new Promise((resolveOuter, reject) => {
|
|
51
|
+
const timeout = setTimeout(() => {
|
|
52
|
+
reject(new Error(`No MCP response in 3s. stderr:\n${stderrBuf}`));
|
|
53
|
+
}, 3000);
|
|
54
|
+
const interval = setInterval(() => {
|
|
55
|
+
const line = stdoutBuf.split('\n').find((l) => l.includes('"jsonrpc"'));
|
|
56
|
+
if (line) {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
clearInterval(interval);
|
|
59
|
+
resolveOuter(line);
|
|
60
|
+
}
|
|
61
|
+
}, 25);
|
|
62
|
+
});
|
|
63
|
+
const elapsed = Date.now() - t0;
|
|
64
|
+
const parsed = JSON.parse(responseLine);
|
|
65
|
+
expect(elapsed).toBeLessThan(3000);
|
|
66
|
+
expect(parsed.result?.serverInfo?.name).toBe('aismemory');
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
if (proc && !proc.killed)
|
|
70
|
+
proc.kill('SIGKILL');
|
|
71
|
+
rmSync(fakeHome, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
}, 10_000);
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=mcp-init-no-auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-init-no-auth.test.js","sourceRoot":"","sources":["../../src/__tests__/mcp-init-no-auth.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|