Skip to content

WIP: WebAuthn/passkey authentication#6

Open
burning-bush-dev wants to merge 10 commits intomasterfrom
webauthn-wip
Open

WIP: WebAuthn/passkey authentication#6
burning-bush-dev wants to merge 10 commits intomasterfrom
webauthn-wip

Conversation

@burning-bush-dev
Copy link
Copy Markdown
Owner

WIP: WebAuthn support, mentioned within issue NicTool#339 but otherwise out of the English blue

Build the base Docker image locally in CI instead of requiring a
pre-existing image in GHCR, breaking the bootstrap chicken-and-egg.
Uses GHCR + GHA cache layers when available, builds from scratch
when not. Also removes dead forums link per review feedback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add passkey support as an alternative login method. Disabled by default;
enable by setting webauthn_enabled=1 in nt_options alongside webauthn_rp_id
and webauthn_origin. Password auth is unchanged.

Server:
- NicToolServer::WebAuthn module with registration/authentication ceremonies
- TOCTOU-safe challenge consumption (atomic UPDATE, check affected rows)
- Usernameless login via discoverable credentials (empty allowCredentials)
- Feature flag: webauthn_enabled option (default off)
- Session dispatch for pre-auth WebAuthn endpoints (no session required)

Client:
- nt-webauthn.js: browser-side WebAuthn ceremony helpers (ES5/jQuery)
- webauthn.cgi: JSON API proxy with action whitelist, CSRF validation,
  parameter injection prevention, cookie sanitization via CGI::cookie()
- Login page passkey button (works without entering username)
- User profile passkey management (register, rename, revoke)

Schema:
- nt_user_webauthn_credential table (full-column UNIQUE on credential_id)
- nt_user_webauthn_challenge table (full-column UNIQUE on challenge)
- Migration in upgrade.pl with CREATE TABLE IF NOT EXISTS for robustness
- passkey_login added to session_log action ENUM

Tests:
- server/t/05_webauthn.t: 12 subtests covering feature flag, challenge
  lifecycle, registration/auth options, credential CRUD, mocked happy
  paths, cross-user isolation
- client/t/e2e/webauthn.spec.ts: CSRF protection, session requirements,
  login page UI, virtual authenticator ceremonies, revocation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
aberoham and others added 7 commits April 6, 2026 20:01
esc() is HTML-entity escaping, which is wrong inside a JS string literal.
js_escape() handles backslashes, quotes, and line separators correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add passkey support as an alternative login method. Disabled by default;
enable by setting webauthn_enabled=1 in nt_options alongside webauthn_rp_id
and webauthn_origin. Password auth is unchanged.

Server:
- NicToolServer::WebAuthn module with registration/authentication ceremonies
- TOCTOU-safe challenge consumption (atomic UPDATE, check affected rows)
- Usernameless login via discoverable credentials (empty allowCredentials)
- Feature flag: webauthn_enabled option (default off)
- Session dispatch for pre-auth WebAuthn endpoints (no session required)

Client:
- nt-webauthn.js: browser-side WebAuthn ceremony helpers (ES5/jQuery)
- webauthn.cgi: JSON API proxy with action whitelist, CSRF validation,
  parameter injection prevention, cookie sanitization via CGI::cookie()
- Login page passkey button (works without entering username)
- User profile passkey management (register, rename, revoke)

Schema:
- nt_user_webauthn_credential table (full-column UNIQUE on credential_id)
- nt_user_webauthn_challenge table (full-column UNIQUE on challenge)
- Migration in upgrade.pl with CREATE TABLE IF NOT EXISTS for robustness
- passkey_login added to session_log action ENUM

Tests:
- server/t/05_webauthn.t: 12 subtests covering feature flag, challenge
  lifecycle, registration/auth options, credential CRUD, mocked happy
  paths, cross-user isolation
- client/t/e2e/webauthn.spec.ts: CSRF protection, session requirements,
  login page UI, virtual authenticator ceremonies, revocation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uption

The -status parameter in CGI.pm's header() appears to produce extra
content in the response body under ModPerl::Registry with +ParseHeaders,
causing JSON parse errors in E2E tests. Drop -status (tests check
json.error_code, not HTTP status), add -charset utf-8, remove trailing 1;.

Also add debug logging to W1 tests to capture exact response text on failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- setup-test-env.pl: insert webauthn_enabled, webauthn_rp_id, and
  webauthn_origin into nt_options so W4-W7 ceremony tests actually
  run in CI instead of being skipped
- security.spec.ts: narrow T8 XSS assertion from /<script>/i (which
  matches legitimate script tags added for passkey support) to the
  specific injected payload

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
setup-test-env.pl now pre-populates webauthn_enabled=1 for E2E tests.
The unit test needs to save/clear/restore it like the other options,
and T1b uses ON DUPLICATE KEY UPDATE defensively.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs:
- W4, W7: cleanup used page.context().request (an APIRequestContext)
  where apiLogin() expects the playwright fixture. Changed test
  signatures to destructure { page, playwright } and wrapped cleanup
  in try/catch.
- W5: passkey login check used +verData.error_code === 200, but
  successful session responses don't include error_code. Check for
  nt_user_session instead.
- Added "Logging In" section to Docker README.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants