Files
LeoVasanko 84985501f5 MultiSite: one instance serves authentication across many domains (#4)
- 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
2026-09-07 22:14:42 +00:00

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:

  1. Start a fresh Paskia server with a test database
  2. Run all E2E tests against it
  3. 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 in coverage-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 (localhost vs test.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

  1. Chromium only: Virtual authenticator is a Chrome DevTools feature
  2. Multi-domain via *.localhost: Chrome resolves any *.localhost hostname to loopback, which the tests use for cross-domain scenarios; non-localhost domains are exercised only via explicit Host headers (Node-side requests)
  3. Single user per run: Bootstrap creates one admin user; additional users need admin API

Debugging Tips

  1. Check test database: e2e/test-data/paskia.kantadb is removed during teardown; comment out the cleanup in global-teardown.ts to inspect it after a run
  2. View server output: Global setup echoes server bootstrap to console
  3. Use trace viewer: npx playwright show-trace on 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