SPB Git

spb/spbgit Public MIT

SPB Git — the platform hosting itself

JavaScript 73.9% CSS 11.7% Nunjucks 11.6% Shell 2.7%
2.9 KB · 74 lines javascript
Raw Blame History
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