Files
Mind-o-Mat/tests/auth-cors.test.ts
T
Jannik 426bb6ddc7
CI / test (push) Canceled after 0s
CI / webapp (push) Canceled after 0s
Fix auth-cors Test: HMAC-Tampering-Test war zu schwach (konnte identisches Token erzeugen)
- Alter Test 'HMAC ist timing-safe' war fehlerhaft:
  - aenderte vorletztes Zeichen via endsWith('0')-Logik
  - in Edge-Cases konnte modified === token sein
- Zwei neue, deterministische Tests:
  1. 'HMAC-Tampering wird erkannt': aendert garantiert das ERSTE Zeichen
     des HMAC, verifiziert modified !== token UND checkAuth lehnt ab
  2. 'Token mit geaendertem Timestamp aber gleichem HMAC wird abgelehnt':
     verifiziert, dass HMAC tatsaechlich an ts gebunden ist (nicht statisch)

Verifiziert:
- 117/117 Tests gruen (vorher 116, 1 Test in 2 aufgeteilt)
- HMAC-Implementation in src/notes-api-server.ts ist korrekt
  (HMAC wird ueber ts berechnet, daher kann ein anderer ts mit gleichem hmac nicht passieren)
2026-09-05 12:51:06 +02:00

122 lines
4.8 KiB
TypeScript

// Tests fuer Auth + CORS
// Phase 9: Production-Haertung
import { describe, it, expect } from 'vitest';
import { NotesApi } from '../src/notes-api-server.js';
describe('NotesApi Auth', () => {
it('kein Token konfiguriert: alle Requests erlaubt', () => {
const api = new NotesApi('/tmp/vault');
expect(api.checkAuth(undefined).ok).toBe(true);
expect(api.checkAuth('Bearer irgendwas').ok).toBe(true);
});
it('Token konfiguriert, aber Header fehlt: 401', () => {
const api = new NotesApi('/tmp/vault', { authToken: 'secret' });
const result = api.checkAuth(undefined);
expect(result.ok).toBe(false);
expect(result.reason).toContain('fehlt');
});
it('Token mit falschem Schema: ungueltig', () => {
const api = new NotesApi('/tmp/vault', { authToken: 'secret' });
expect(api.checkAuth('Basic abc').ok).toBe(false);
});
it('Token mit falschem Format: ungueltig', () => {
const api = new NotesApi('/tmp/vault', { authToken: 'secret' });
expect(api.checkAuth('Bearer not-a-valid-token').ok).toBe(false);
});
it('Korrekter Token: erlaubt', () => {
const secret = 'my-secret';
const api = new NotesApi('/tmp/vault', { authToken: secret });
const token = NotesApi.generateToken(secret);
const result = api.checkAuth(`Bearer ${token}`);
expect(result.ok).toBe(true);
});
it('Token mit falschem Secret: ungueltig', () => {
const api = new NotesApi('/tmp/vault', { authToken: 'correct-secret' });
const wrongToken = NotesApi.generateToken('wrong-secret');
expect(api.checkAuth(`Bearer ${wrongToken}`).ok).toBe(false);
});
it('HMAC-Tampering wird erkannt (deterministisch anderes Token)', () => {
const api = new NotesApi('/tmp/vault', { authToken: 'secret' });
const token = NotesApi.generateToken('secret');
const [ts, hmac] = token.split('.');
// Aendere das ERSTE Zeichen des HMAC garantiert
const firstChar = hmac[0];
const replacement = firstChar === '0' ? '1' : '0';
const modifiedHmac = replacement + hmac.slice(1);
const modified = `${ts}.${modifiedHmac}`;
expect(modified).not.toBe(token);
expect(api.checkAuth(`Bearer ${modified}`).ok).toBe(false);
});
it('Token mit geaendertem Timestamp aber gleichem HMAC wird abgelehnt', () => {
const api = new NotesApi('/tmp/vault', { authToken: 'secret' });
const token = NotesApi.generateToken('secret');
const [, hmac] = token.split('.');
// Ersetze Timestamp durch anderen Wert, behalte HMAC -> muss abgelehnt werden
// (HMAC haengt am Timestamp, also passt der alte HMAC nicht mehr)
const fakeToken = `9999999999999.${hmac}`;
expect(api.checkAuth(`Bearer ${fakeToken}`).ok).toBe(false);
});
});
describe('NotesApi CORS', () => {
it('keine Origins konfiguriert: alle erlaubt', () => {
const api = new NotesApi('/tmp/vault');
expect(api.checkOrigin('https://evil.com')).toBe(true);
expect(api.checkOrigin(undefined)).toBe(true);
});
it('Wildcard * erlaubt alle Origins', () => {
const api = new NotesApi('/tmp/vault', { allowedOrigins: ['*'] });
expect(api.checkOrigin('https://anywhere.com')).toBe(true);
expect(api.checkOrigin('https://evil.com')).toBe(true);
});
it('Whitelist: nur konfigurierte Origins', () => {
const api = new NotesApi('/tmp/vault', {
allowedOrigins: ['https://vault.example.com', 'https://app.example.com'],
});
expect(api.checkOrigin('https://vault.example.com')).toBe(true);
expect(api.checkOrigin('https://app.example.com')).toBe(true);
expect(api.checkOrigin('https://evil.com')).toBe(false);
expect(api.checkOrigin(undefined)).toBe(false);
});
it('corsHeader liefert korrekte Header fuer erlaubte Origins', () => {
const api = new NotesApi('/tmp/vault', { allowedOrigins: ['https://app.example.com'] });
const headers = api.corsHeader('https://app.example.com');
expect(headers['Access-Control-Allow-Origin']).toBe('https://app.example.com');
expect(headers['Access-Control-Allow-Methods']).toContain('GET');
expect(headers['Access-Control-Allow-Methods']).toContain('PUT');
});
it('corsHeader liefert leere Header fuer nicht erlaubte Origins', () => {
const api = new NotesApi('/tmp/vault', { allowedOrigins: ['https://app.example.com'] });
expect(api.corsHeader('https://evil.com')).toEqual({});
});
});
describe('NotesApi.generateToken', () => {
it('Token-Format: <timestamp>.<hmac>', () => {
const token = NotesApi.generateToken('secret');
expect(token).toMatch(/^\d+\.[a-f0-9]+$/);
});
it('Verschiedene Secrets ergeben verschiedene Tokens', () => {
const t1 = NotesApi.generateToken('secret-a');
const t2 = NotesApi.generateToken('secret-b');
expect(t1).not.toBe(t2);
// Aber gleicher Timestamp-Teil moeglich
const hmac1 = t1.split('.')[1];
const hmac2 = t2.split('.')[1];
expect(hmac1).not.toBe(hmac2);
});
});