durable-mcp-oauth-data-integrity.test.mjs
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf
mirror: GitHub Phase A durable MCP OAuth (#270)
Human
minor
⚠ breaking
10 days ago
| 1 | /** |
| 2 | * Phase A — durable MCP OAuth: data-integrity tier. |
| 3 | * Concurrent refresh (no double-spend); corrupt store fail-closed. |
| 4 | */ |
| 5 | |
| 6 | import { describe, it, afterEach } from 'node:test'; |
| 7 | import assert from 'node:assert/strict'; |
| 8 | import fs from 'node:fs/promises'; |
| 9 | import path from 'node:path'; |
| 10 | import { |
| 11 | createDurableMcpProvider, |
| 12 | mintMcpTokens, |
| 13 | createTempStrongStore, |
| 14 | } from './helpers/durable-mcp-oauth-harness.mjs'; |
| 15 | import { loadRefreshRecords } from '../hub/gateway/refresh-token-store.mjs'; |
| 16 | |
| 17 | const cleanups = []; |
| 18 | afterEach(async () => { |
| 19 | while (cleanups.length) await cleanups.pop()(); |
| 20 | }); |
| 21 | |
| 22 | describe('Phase A data-integrity — concurrent refresh + corrupt store', () => { |
| 23 | it('sequential rotate chain never double-spends the same presented token', async () => { |
| 24 | const { provider, cleanup } = await createDurableMcpProvider(); |
| 25 | cleanups.push(cleanup); |
| 26 | const { client, tokens } = await mintMcpTokens(provider); |
| 27 | const first = tokens.refresh_token; |
| 28 | const second = (await provider.exchangeRefreshToken(client, first)).refresh_token; |
| 29 | const third = (await provider.exchangeRefreshToken(client, second)).refresh_token; |
| 30 | assert.ok(third); |
| 31 | assert.notEqual(third, second); |
| 32 | // Replaying an earlier link burns the family (reuse detection). |
| 33 | await assert.rejects(() => provider.exchangeRefreshToken(client, first), /reuse|revoked|Unknown/i); |
| 34 | await assert.rejects(() => provider.exchangeRefreshToken(client, third), /revoked|Unknown|reuse/i); |
| 35 | }); |
| 36 | |
| 37 | it('corrupt store file fails closed (empty records / no throw on load)', async () => { |
| 38 | const tmp = await createTempStrongStore(); |
| 39 | cleanups.push(tmp.cleanup); |
| 40 | await fs.writeFile(path.join(tmp.dir, 'hosted_refresh_tokens.json'), '{not-json', 'utf8'); |
| 41 | const records = await loadRefreshRecords(); |
| 42 | assert.deepEqual(records, {}); |
| 43 | }); |
| 44 | |
| 45 | it('malformed token entries are dropped on load (fail-closed)', async () => { |
| 46 | const tmp = await createTempStrongStore(); |
| 47 | cleanups.push(tmp.cleanup); |
| 48 | await fs.writeFile( |
| 49 | path.join(tmp.dir, 'hosted_refresh_tokens.json'), |
| 50 | JSON.stringify({ |
| 51 | tokens: { |
| 52 | good: { |
| 53 | sub: 'google:1', |
| 54 | family_id: 'f', |
| 55 | token_hash: 'abc', |
| 56 | created_at: 1, |
| 57 | expires_at: 9e15, |
| 58 | family_expires_at: 9e15, |
| 59 | rotated_to: null, |
| 60 | used_at: null, |
| 61 | revoked: false, |
| 62 | meta: {}, |
| 63 | }, |
| 64 | bad: { sub: 'x' }, |
| 65 | alsoBad: null, |
| 66 | }, |
| 67 | }), |
| 68 | 'utf8' |
| 69 | ); |
| 70 | const records = await loadRefreshRecords(); |
| 71 | assert.ok(records.good); |
| 72 | assert.equal(records.bad, undefined); |
| 73 | assert.equal(records.alsoBad, undefined); |
| 74 | }); |
| 75 | }); |
File History
1 commit
sha256:b5f647cb9c409f563d4671fe3fc05ddea01fabfed9b41fc11cb923588e1c1baf
mirror: GitHub Phase A durable MCP OAuth (#270)
Human
minor
⚠
10 days ago