/** * test/auth.test.ts — unit tests for src/http/auth.ts (w5-access-token). * * Mirrors the discipline of test/origin.test.ts. Security-critical: the * constant-time comparison, the cookie flags, and the disabled short-circuit. */ import { describe, it, expect } from 'vitest' import { AUTH_COOKIE_NAME, buildSetCookie, constantTimeEqual, cookieIsAuthed, isAuthEnabled, isHttpsRequest, parseCookieHeader, } from '../src/http/auth.js' const TOKEN = 'super-secret-token-1234' // ≥16, safe charset // ── constantTimeEqual ───────────────────────────────────────────────────────── describe('constantTimeEqual', () => { it('returns true for equal strings', () => { expect(constantTimeEqual(TOKEN, TOKEN)).toBe(true) }) it('returns false for unequal same-length strings', () => { expect(constantTimeEqual('aaaaaaaaaaaaaaaa', 'aaaaaaaaaaaaaaab')).toBe(false) }) it('returns false for different-length strings without throwing (fixed-length guard)', () => { // The SHA-256-to-fixed-32-bytes guard means unequal lengths never throw. expect(() => constantTimeEqual('short', 'a-much-longer-candidate-value')).not.toThrow() expect(constantTimeEqual('short', 'a-much-longer-candidate-value')).toBe(false) }) it('returns false for an empty candidate (present-vs-absent short-circuit)', () => { expect(constantTimeEqual('', TOKEN)).toBe(false) expect(constantTimeEqual(TOKEN, '')).toBe(false) }) it('returns false for an undefined candidate', () => { expect(constantTimeEqual(undefined, TOKEN)).toBe(false) expect(constantTimeEqual(TOKEN, undefined)).toBe(false) }) }) // ── parseCookieHeader ───────────────────────────────────────────────────────── describe('parseCookieHeader', () => { it('parses a multi-pair cookie header into a map', () => { const map = parseCookieHeader('a=1; webterm_auth=xyz; b=2') expect(map).toEqual({ a: '1', webterm_auth: 'xyz', b: '2' }) }) it('returns {} for a missing or empty header', () => { expect(parseCookieHeader(undefined)).toEqual({}) expect(parseCookieHeader('')).toEqual({}) }) it('ignores malformed pairs (no = / empty name)', () => { const map = parseCookieHeader('novalue; =orphan; webterm_auth=ok') expect(map).toEqual({ webterm_auth: 'ok' }) }) }) // ── isAuthEnabled ───────────────────────────────────────────────────────────── describe('isAuthEnabled', () => { it('is false when the token is undefined', () => { expect(isAuthEnabled({ webtermToken: undefined })).toBe(false) }) it('is false when the token is an empty string', () => { expect(isAuthEnabled({ webtermToken: '' })).toBe(false) }) it('is true when a token is set', () => { expect(isAuthEnabled({ webtermToken: TOKEN })).toBe(true) }) }) // ── cookieIsAuthed ──────────────────────────────────────────────────────────── describe('cookieIsAuthed', () => { it('is true for a correct auth cookie', () => { expect(cookieIsAuthed({ webtermToken: TOKEN }, `${AUTH_COOKIE_NAME}=${TOKEN}`)).toBe(true) }) it('is false for a wrong cookie value', () => { expect(cookieIsAuthed({ webtermToken: TOKEN }, `${AUTH_COOKIE_NAME}=nope-nope-nope-nope`)).toBe(false) }) it('is false when the auth cookie is absent', () => { expect(cookieIsAuthed({ webtermToken: TOKEN }, 'other=1')).toBe(false) expect(cookieIsAuthed({ webtermToken: TOKEN }, undefined)).toBe(false) }) it('is false when auth is disabled (no token) regardless of cookie', () => { expect(cookieIsAuthed({ webtermToken: undefined }, `${AUTH_COOKIE_NAME}=anything`)).toBe(false) }) }) // ── buildSetCookie ──────────────────────────────────────────────────────────── describe('buildSetCookie', () => { it('includes the token value and the required flags', () => { const c = buildSetCookie({ webtermToken: TOKEN }, { secure: false }) expect(c).toContain(`${AUTH_COOKIE_NAME}=${TOKEN}`) expect(c).toContain('Path=/') expect(c).toContain('Max-Age=') expect(c).toContain('HttpOnly') expect(c).toContain('SameSite=Strict') }) it('omits Secure when opts.secure is false (LAN over http)', () => { expect(buildSetCookie({ webtermToken: TOKEN }, { secure: false })).not.toContain('Secure') }) it('includes Secure when opts.secure is true (relay over https)', () => { expect(buildSetCookie({ webtermToken: TOKEN }, { secure: true })).toContain('; Secure') }) }) // ── isHttpsRequest ──────────────────────────────────────────────────────────── describe('isHttpsRequest', () => { it('is true when x-forwarded-proto is https (TLS-terminating edge)', () => { expect(isHttpsRequest({ headers: { 'x-forwarded-proto': 'https' } })).toBe(true) }) it('reads the first value of a comma-joined x-forwarded-proto', () => { expect(isHttpsRequest({ headers: { 'x-forwarded-proto': 'https, http' } })).toBe(true) }) it('is true when the socket is encrypted (direct TLS)', () => { expect(isHttpsRequest({ headers: {}, socket: { encrypted: true } })).toBe(true) }) it('is false for a plain http request', () => { expect(isHttpsRequest({ headers: {}, socket: { encrypted: false } })).toBe(false) expect(isHttpsRequest({ headers: { 'x-forwarded-proto': 'http' } })).toBe(false) }) })