import { describe, it, expect } from 'vitest' import { writeFile, mkdtemp } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { peekPasetoClaims } from 'relay-auth/src/crypto/paseto.js' import { createManageTokenMinter, ManageTokenError } from '../src/manage-token.js' import { generatePkcs8Pem } from './helpers.js' async function writeKey(): Promise { const { pem } = await generatePkcs8Pem() const dir = await mkdtemp(join(tmpdir(), 'cp-key-')) const path = join(dir, 'capability-sign.key.pem') await writeFile(path, pem, 'utf8') return path } describe('createManageTokenMinter', () => { it('mints a manage token with the correct aud, sub, rights, and TTL', async () => { const keyPath = await writeKey() const minter = createManageTokenMinter({ capabilitySignKeyPath: keyPath, baseDomain: 'terminal.yaojia.wang', operatorAccountId: 'acct-op-1', }) const token = await minter.mint() expect(token.startsWith('v4.public.')).toBe(true) const claims = peekPasetoClaims(token) as Record expect(claims.aud).toBe('terminal.yaojia.wang') expect(claims.sub).toBe('acct-op-1') expect(claims.rights).toEqual(['manage']) expect(typeof claims.host).toBe('string') expect((claims.exp as number) - (claims.iat as number)).toBe(60) // The additive DPoP proof-of-possession binding must be present. expect((claims.cnf as { jkt?: string })?.jkt).toMatch(/^[A-Za-z0-9_-]{43}$/) }) it('mints a fresh token each call (distinct jti)', async () => { const keyPath = await writeKey() const minter = createManageTokenMinter({ capabilitySignKeyPath: keyPath, baseDomain: 'd', operatorAccountId: 'a', }) const a = peekPasetoClaims(await minter.mint()) as Record const b = peekPasetoClaims(await minter.mint()) as Record expect(a.jti).not.toBe(b.jti) }) it('throws ManageTokenError when the key file is missing', async () => { const minter = createManageTokenMinter({ capabilitySignKeyPath: '/nonexistent/does-not-exist.pem', baseDomain: 'd', operatorAccountId: 'a', }) await expect(minter.mint()).rejects.toBeInstanceOf(ManageTokenError) }) })