/** * ───────────────────────────────────────────── * SPB Git — Personal Git Platform * ───────────────────────────────────────────── * Author : Simon-Pierre Boucher * Contact : contact@spboucher.ai * File : test/unit/token.test.mjs * Purpose : Unit tests — PAT lifecycle (argon2, revocation, parsing) * License : MIT © Simon-Pierre Boucher * ───────────────────────────────────────────── */ import { describe, it, expect, beforeAll, afterAll } from 'vitest'; import { mkdtempSync, rmSync, readFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { TokenStore, extractToken } from '../../src/auth/token.mjs'; let dir; beforeAll(() => { dir = mkdtempSync(join(tmpdir(), 'spbgit-tokens-')); }); afterAll(() => { rmSync(dir, { recursive: true, force: true }); }); describe('TokenStore', () => { it('creates, verifies, and revokes tokens', async () => { const store = new TokenStore(dir); const { token, record } = await store.create('laptop'); expect(token).toMatch(/^spbgit_[0-9a-f]{8}_[0-9a-f]{40}$/); expect(record.label).toBe('laptop'); const verified = await store.verify(token); expect(verified).not.toBeNull(); expect(verified.label).toBe('laptop'); expect(verified.lastUsed).not.toBeNull(); expect(verified.hash).toBeUndefined(); expect(await store.verify('spbgit_00000000_' + '0'.repeat(40))).toBeNull(); expect(await store.verify('garbage')).toBeNull(); expect(await store.verify(token.slice(0, -1) + (token.endsWith('a') ? 'b' : 'a'))).toBeNull(); expect(store.revoke(record.id)).toBe(true); expect(await store.verify(token)).toBeNull(); expect(store.revoke(record.id)).toBe(false); }, 30000); it('never stores the secret in clear', async () => { const store = new TokenStore(dir); const { token } = await store.create('audit'); const raw = readFileSync(join(dir, 'tokens.json'), 'utf8'); const secret = token.split('_')[2]; expect(raw).not.toContain(secret); expect(raw).toContain('$argon2id$'); }, 30000); }); describe('extractToken', () => { it('parses Bearer', () => { expect(extractToken('Bearer abc123')).toBe('abc123'); }); it('parses Basic with username spb', () => { const header = 'Basic ' + Buffer.from('spb:tok_secret').toString('base64'); expect(extractToken(header)).toBe('tok_secret'); }); it('rejects wrong usernames and junk', () => { const wrong = 'Basic ' + Buffer.from('root:x').toString('base64'); expect(extractToken(wrong)).toBeNull(); expect(extractToken(undefined)).toBeNull(); expect(extractToken('Digest xyz')).toBeNull(); }); });