- Serve multiple domains (RP IDs) from one instance: host-based dispatch, per-domain credentials and sessions, domains managed at runtime in the admin UI — previously one RP per instance - Cross-domain sign-in via Related Origin Requests: per-domain related-origins list with a served .well-known/webauthn document - Explicit per-domain origin lists with shell-glob wildcards (**. for apex + any subdomain depth, *. for one level), editable in the admin UI with validation and self-lockout guards - Per-domain auth hosts: the account/admin UI can live on a different host per domain, no longer confined to subdomains of a single RP - CLI: 'paskia init <rp-id [rp-name]' initializes or adds a domain to an existing database; 'paskia migrate' converts legacy databases BREAKING CHANGES (v2.0): - Database schema: config is now per-domain and credentials/sessions carry an rp_id — existing databases must be converted with 'paskia migrate' - Origins are now explicit: main implicitly allowed every subdomain of the RP; configure '**.' origins to reproduce that behavior - CLI: the flat '--rp-id/--rp-name/--origin/--auth/--save' flags are replaced by the 'init' and 'migrate' subcommandsReviewed-on: #4
4.9 KiB
4.9 KiB
Paskia E2E Tests
End-to-end tests for Paskia using Playwright with Chrome's Virtual Authenticator.
Overview
These tests exercise the complete WebAuthn/passkey authentication flow without requiring physical hardware. Chrome's DevTools Protocol provides a virtual authenticator that can:
- Generate passkey credentials
- Sign authentication challenges
- Store resident keys (discoverable credentials)
- Simulate user verification (biometrics/PIN)
Prerequisites
- Node.js 18+
- Python with
uv(for running the backend server)
Setup
cd e2e
npm install
npm run install:browsers
Running Tests
Basic Test Run
npm test
This will:
- Start a fresh Paskia server with a test database
- Run all E2E tests against it
- Clean up the server when done
With Coverage
npm run test:coverage
Runs tests and collects coverage for both:
- Python backend (via
coverage.py) - HTML report incoverage-html/ - Frontend JavaScript (via Chrome V8 coverage) - JSON data in
e2e/coverage-frontend/
Interactive Mode
npm run test:ui
Opens Playwright's UI mode for interactive test debugging.
Headed Mode
npm run test:headed
Runs tests with a visible browser window.
Debug Mode
npm run test:debug
Runs tests with Playwright Inspector for step-by-step debugging.
Test Structure
e2e/
├── playwright.config.js # Playwright configuration
├── package.json
├── tsconfig.json
├── test-data/ # Test database (created at runtime)
│ └── paskia.kantadb
└── tests/
├── global-setup.ts # Creates fresh DB (localhost + test.localhost domains), captures reset token
├── global-teardown.ts # Cleanup
├── 10-passkey.spec.ts # Registration, authentication, session tests
├── 20-api-auth.spec.ts # API-mode iframe flows (401/403/reauth)
├── 50-multidomain.spec.ts# Multi-domain dispatch, related origins, auth hosts, remote login
├── 99-logout.spec.ts # Logout (runs last)
└── fixtures/
├── virtual-authenticator.ts # Virtual authenticator setup
├── passkey-helpers.ts # WebSocket helpers
└── remote-auth.ts # Pairing-code remote auth helpers
What's Tested
Registration Flow
- Bootstrap admin user registration via reset token
- WebSocket challenge-response with virtual authenticator
- Session token creation and validation
Authentication Flow
- Passkey authentication via WebSocket
- Credential verification
- Session management
Session Management
- Token validation (
/auth/api/validate) - User info retrieval (
/auth/api/user-info) - Logout (
/auth/api/logout) - Invalid/missing token rejection
Multi-Domain
- Host-based domain dispatch (
localhostvstest.localhost, 421 for unknown hosts) - Related Origin Requests well-known endpoint and admin domain API
- Per-domain auth hosts (UI at the site root)
- WebSocket cross-domain rules
- Cross-domain remote login via pairing code
How Virtual Authenticator Works
The tests use Chrome DevTools Protocol (CDP) to create a virtual authenticator:
const cdpSession = await page.context().newCDPSession(page)
await cdpSession.send('WebAuthn.enable')
await cdpSession.send('WebAuthn.addVirtualAuthenticator', {
options: {
protocol: 'ctap2',
transport: 'internal',
hasResidentKey: true,
hasUserVerification: true,
isUserVerified: true,
automaticPresenceSimulation: true,
},
})
This creates an in-browser authenticator that:
- Automatically responds to WebAuthn prompts
- Stores credentials persistently during the test session
- Simulates user verification without actual biometric input
Environment Variables
| Variable | Description | Default |
|---|---|---|
BASE_URL |
Server URL | http://localhost:4404 |
CI |
CI environment flag | - |
CLEANUP_TEST_DB |
Remove test DB after run | false |
Limitations
- Chromium only: Virtual authenticator is a Chrome DevTools feature
- Multi-domain via
*.localhost: Chrome resolves any*.localhosthostname to loopback, which the tests use for cross-domain scenarios; non-localhost domains are exercised only via explicit Host headers (Node-side requests) - Single user per run: Bootstrap creates one admin user; additional users need admin API
Debugging Tips
- Check test database:
e2e/test-data/paskia.kantadbis removed during teardown; comment out the cleanup inglobal-teardown.tsto inspect it after a run - View server output: Global setup echoes server bootstrap to console
- Use trace viewer:
npx playwright show-traceon failure traces
CI Integration
The tests are designed for CI environments:
- name: Run E2E Tests
run: |
cd e2e
npm ci
npm run install:browsers
npm test
env:
CI: true