spb/spbgit Public MIT
SPB Git — the platform hosting itself
JavaScript 73.9%
CSS 11.7%
Nunjucks 11.6%
Shell 2.7%
1/**2 * ─────────────────────────────────────────────3 * SPB Git — Personal Git Platform4 * ─────────────────────────────────────────────5 * Author : Simon-Pierre Boucher6 * Contact : contact@spboucher.ai7 * File : test/unit/token.test.mjs8 * Purpose : Unit tests — PAT lifecycle (argon2, revocation, parsing)9 * License : MIT © Simon-Pierre Boucher10 * ─────────────────────────────────────────────11 */1213import { describe, it, expect, beforeAll, afterAll } from 'vitest';14import { mkdtempSync, rmSync, readFileSync } from 'node:fs';15import { tmpdir } from 'node:os';16import { join } from 'node:path';17import { TokenStore, extractToken } from '../../src/auth/token.mjs';1819let dir;20beforeAll(() => {21 dir = mkdtempSync(join(tmpdir(), 'spbgit-tokens-'));22});23afterAll(() => {24 rmSync(dir, { recursive: true, force: true });25});2627describe('TokenStore', () => {28 it('creates, verifies, and revokes tokens', async () => {29 const store = new TokenStore(dir);30 const { token, record } = await store.create('laptop');31 expect(token).toMatch(/^spbgit_[0-9a-f]{8}_[0-9a-f]{40}$/);32 expect(record.label).toBe('laptop');3334 const verified = await store.verify(token);35 expect(verified).not.toBeNull();36 expect(verified.label).toBe('laptop');37 expect(verified.lastUsed).not.toBeNull();38 expect(verified.hash).toBeUndefined();3940 expect(await store.verify('spbgit_00000000_' + '0'.repeat(40))).toBeNull();41 expect(await store.verify('garbage')).toBeNull();42 expect(await store.verify(token.slice(0, -1) + (token.endsWith('a') ? 'b' : 'a'))).toBeNull();4344 expect(store.revoke(record.id)).toBe(true);45 expect(await store.verify(token)).toBeNull();46 expect(store.revoke(record.id)).toBe(false);47 }, 30000);4849 it('never stores the secret in clear', async () => {50 const store = new TokenStore(dir);51 const { token } = await store.create('audit');52 const raw = readFileSync(join(dir, 'tokens.json'), 'utf8');53 const secret = token.split('_')[2];54 expect(raw).not.toContain(secret);55 expect(raw).toContain('$argon2id$');56 }, 30000);57});5859describe('extractToken', () => {60 it('parses Bearer', () => {61 expect(extractToken('Bearer abc123')).toBe('abc123');62 });63 it('parses Basic with username spb', () => {64 const header = 'Basic ' + Buffer.from('spb:tok_secret').toString('base64');65 expect(extractToken(header)).toBe('tok_secret');66 });67 it('rejects wrong usernames and junk', () => {68 const wrong = 'Basic ' + Buffer.from('root:x').toString('base64');69 expect(extractToken(wrong)).toBeNull();70 expect(extractToken(undefined)).toBeNull();71 expect(extractToken('Digest xyz')).toBeNull();72 });73});74