mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 13:21:46 +00:00
5b8c61d215
Add @simplewebauthn/server registration and primary (discoverable) login ceremonies under /api/auth/passkey, a webauthn_credentials + single-use webauthn_challenges schema (migration), the instance-wide passkey_login toggle (default off) enforced before auth by a guard, and require_mfa satisfaction via a verified passkey. RP ID/origin come only from server config (webauthn_rp_id/origins -> APP_URL), never request headers.
104 lines
3.8 KiB
TypeScript
104 lines
3.8 KiB
TypeScript
/**
|
|
* webauthnConfig.test.ts
|
|
*
|
|
* The RP-ID / allowed-origin resolver is the single highest-risk piece of the
|
|
* passkey feature: a wrong RP ID permanently bricks every enrolled credential.
|
|
* These tests pin the security-relevant rules — config wins over APP_URL, bare
|
|
* IPs are rejected, localhost dev uses the browser (Vite) origin, and the
|
|
* resolver NEVER reads request headers.
|
|
*/
|
|
|
|
const { settingsStore, appUrlRef } = vi.hoisted(() => ({
|
|
settingsStore: new Map<string, string>(),
|
|
appUrlRef: { value: '' },
|
|
}));
|
|
|
|
vi.mock('../../../src/db/database', () => ({
|
|
db: {
|
|
prepare: (_sql: string) => ({
|
|
get: (key: string) => {
|
|
const v = settingsStore.get(key);
|
|
return v === undefined ? undefined : { value: v };
|
|
},
|
|
}),
|
|
},
|
|
}));
|
|
|
|
vi.mock('../../../src/services/notifications', () => ({
|
|
getAppUrl: () => appUrlRef.value,
|
|
}));
|
|
|
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
import { resolveWebauthnConfig, isPasskeyConfigured } from '../../../src/services/webauthnConfig';
|
|
|
|
beforeEach(() => {
|
|
settingsStore.clear();
|
|
appUrlRef.value = '';
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
describe('resolveWebauthnConfig', () => {
|
|
it('WAC-001: derives the RP ID and single origin from a real APP_URL domain', () => {
|
|
appUrlRef.value = 'https://trek.example.org';
|
|
const cfg = resolveWebauthnConfig();
|
|
expect(cfg).not.toBeNull();
|
|
expect(cfg!.rpID).toBe('trek.example.org');
|
|
expect(cfg!.origins).toEqual(['https://trek.example.org']);
|
|
expect(isPasskeyConfigured()).toBe(true);
|
|
});
|
|
|
|
it('WAC-002: returns null for a bare-IP host (IPs are not valid RP IDs)', () => {
|
|
appUrlRef.value = 'http://192.168.1.50:3001';
|
|
expect(resolveWebauthnConfig()).toBeNull();
|
|
expect(isPasskeyConfigured()).toBe(false);
|
|
});
|
|
|
|
it('WAC-003: returns null when nothing is configured', () => {
|
|
expect(resolveWebauthnConfig()).toBeNull();
|
|
expect(isPasskeyConfigured()).toBe(false);
|
|
});
|
|
|
|
it('WAC-004: localhost dev uses the browser (Vite :5173) origin, not just the API port', () => {
|
|
appUrlRef.value = 'http://localhost:3001';
|
|
const cfg = resolveWebauthnConfig();
|
|
expect(cfg!.rpID).toBe('localhost');
|
|
expect(cfg!.origins).toContain('http://localhost:5173');
|
|
expect(cfg!.origins).toContain('http://localhost:3001');
|
|
});
|
|
|
|
it('WAC-005: an explicit webauthn_rp_id app-setting overrides APP_URL', () => {
|
|
appUrlRef.value = 'https://internal.example.org';
|
|
settingsStore.set('webauthn_rp_id', 'public.example.org');
|
|
settingsStore.set('webauthn_origins', 'https://public.example.org');
|
|
const cfg = resolveWebauthnConfig();
|
|
expect(cfg!.rpID).toBe('public.example.org');
|
|
expect(cfg!.origins).toEqual(['https://public.example.org']);
|
|
});
|
|
|
|
it('WAC-006: webauthn_origins is parsed as a comma-separated, trimmed list', () => {
|
|
settingsStore.set('webauthn_rp_id', 'example.org');
|
|
settingsStore.set('webauthn_origins', 'https://a.example.org , https://b.example.org/');
|
|
const cfg = resolveWebauthnConfig();
|
|
expect(cfg!.origins).toEqual(['https://a.example.org', 'https://b.example.org']);
|
|
});
|
|
|
|
it('WAC-007: the WEBAUTHN_RP_ID env var takes priority', () => {
|
|
vi.stubEnv('WEBAUTHN_RP_ID', 'env.example.org');
|
|
vi.stubEnv('WEBAUTHN_ORIGINS', 'https://env.example.org');
|
|
appUrlRef.value = 'https://ignored.example.org';
|
|
const cfg = resolveWebauthnConfig();
|
|
expect(cfg!.rpID).toBe('env.example.org');
|
|
expect(cfg!.origins).toEqual(['https://env.example.org']);
|
|
});
|
|
|
|
it('WAC-008: a configured RP ID with no origins falls back to the APP_URL origin', () => {
|
|
appUrlRef.value = 'https://trek.example.org';
|
|
settingsStore.set('webauthn_rp_id', 'trek.example.org');
|
|
const cfg = resolveWebauthnConfig();
|
|
expect(cfg!.origins).toEqual(['https://trek.example.org']);
|
|
});
|
|
});
|