| 1 | import assert from "node:assert/strict"; |
| 2 | import { createHash } from "node:crypto"; |
| 3 | import { test } from "node:test"; |
| 4 | |
| 5 | import { credentialHashes, holdCredentials, revokeCredentials, sha256Hex } from "./credentials.ts"; |
| 6 | |
| 7 | test("tokens are hashed the way identity stores them", async () => { |
| 8 | const token = "g1t_0123456789abcdef"; |
| 9 | assert.equal(await sha256Hex(token), createHash("sha256").update(token).digest("hex")); |
| 10 | const hashes = await credentialHashes({ G1T_TOKEN: token, OTHER: "g1t_y", CHECK_TOKEN: "c" }); |
| 11 | assert.equal(hashes.length, 1); |
| 12 | assert.ok(hashes.every((hash) => /^[0-9a-f]{64}$/.test(hash))); |
| 13 | }); |
| 14 | |
| 15 | /** Identity, as far as the credentials' lifecycle uses it. */ |
| 16 | function fakeIdentity() { |
| 17 | const calls: { method: string; body: unknown }[] = []; |
| 18 | return { |
| 19 | calls, |
| 20 | fetch: async (url: string, init?: RequestInit) => { |
| 21 | calls.push({ method: url.split("/rpc/")[1], body: JSON.parse(String(init?.body)) }); |
| 22 | return new Response("true"); |
| 23 | }, |
| 24 | }; |
| 25 | } |
| 26 | |
| 27 | function memoryStorage() { |
| 28 | const map = new Map<string, unknown>(); |
| 29 | return { |
| 30 | map, |
| 31 | put: async (key: string, value: unknown) => void map.set(key, value), |
| 32 | get: async <T>(key: string) => map.get(key) as T | undefined, |
| 33 | delete: async (key: string) => map.delete(key), |
| 34 | }; |
| 35 | } |
| 36 | |
| 37 | test("a sandbox's credentials are bound to its run and revoked once when it stops", async () => { |
| 38 | const identity = fakeIdentity(); |
| 39 | const storage = memoryStorage(); |
| 40 | await holdCredentials(identity, storage, { G1T_TOKEN: "g1t_a" }, "run_1"); |
| 41 | assert.equal(identity.calls[0].method, "bind_run_credentials"); |
| 42 | assert.deepEqual((identity.calls[0].body as { runId: string }).runId, "run_1"); |
| 43 | await revokeCredentials(identity, storage); |
| 44 | await revokeCredentials(identity, storage); |
| 45 | const revokes = identity.calls.filter((call) => call.method === "revoke_run_credentials"); |
| 46 | assert.equal(revokes.length, 1); |
| 47 | assert.equal((revokes[0].body as { tokenHashes: string[] }).tokenHashes.length, 1); |
| 48 | }); |
| 49 | |
| 50 | test("a sandbox with no run record still has its credentials revoked", async () => { |
| 51 | const identity = fakeIdentity(); |
| 52 | const storage = memoryStorage(); |
| 53 | await holdCredentials(identity, storage, { G1T_TOKEN: "g1t_a" }, null); |
| 54 | assert.equal(identity.calls.length, 0); |
| 55 | await revokeCredentials(identity, storage); |
| 56 | assert.equal(identity.calls[0].method, "revoke_run_credentials"); |
| 57 | }); |