Files
orca/internal/webauthn/connector_test.go
T
Jon Chery c726a6a9e2 feat(P05): WebAuthn connector for Dex (REQ-148, D-240, C-38)
---ci---
project: orca
phase: 5
milestone: v0.12
status: execute
---/ci---

internal/webauthn/store.go: SQLite credential store (0600, public
keys only). Put/Get/List/Delete/UpdateSignCount.
internal/webauthn/connector.go: WebAuthn ceremony handler for the
bundled Dex. BeginRegistration/FinishRegistration/BeginLogin/FinishLogin
at /orca/webauthn/{register,login}. go-webauthn library for crypto.
RP ID = cluster Traefik domain (C-38). Public-key credentials only
(private key never leaves authenticator; R-021 invariant holds).
9 tests pass (4 store + 5 connector). go vet clean. Full build green.
2026-08-07 11:02:07 +00:00

103 lines
3.1 KiB
Go

package webauthn
import (
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"testing"
)
// TestConnectorBuild verifies a Connector can be built with a valid
// RP ID + origin (C-38).
func TestConnectorBuild(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, err := NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore: %v", err)
}
defer store.Close()
c, err := NewConnector(store, "cluster.example.com", "https://cluster.example.com")
if err != nil {
t.Fatalf("NewConnector: %v", err)
}
if c.RPID() != "cluster.example.com" {
t.Errorf("RPID = %q, want cluster.example.com", c.RPID())
}
}
// TestConnectorHealthz verifies the /orca/webauthn/healthz endpoint
// responds with the RP ID.
func TestConnectorHealthz(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/healthz", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("healthz status = %d, want 200", rec.Code)
}
if !strings.Contains(rec.Body.String(), "test.cluster") {
t.Errorf("healthz body should contain rp_id: %s", rec.Body.String())
}
}
// TestConnectorBeginRegistrationNoUsername verifies the register
// endpoint rejects requests without a username.
func TestConnectorBeginRegistrationNoUsername(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/register", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusBadRequest {
t.Errorf("register without username: %d, want 400", rec.Code)
}
}
// TestConnectorBeginLoginNotRegistered verifies login for an
// unregistered user returns 404.
func TestConnectorBeginLoginNotRegistered(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c, _ := NewConnector(store, "test.cluster", "https://test.cluster")
mux := c.Routes()
req := httptest.NewRequest("GET", "/orca/webauthn/login?username=ghost", nil)
rec := httptest.NewRecorder()
mux.ServeHTTP(rec, req)
if rec.Code != http.StatusNotFound {
t.Errorf("login unregistered: %d, want 404", rec.Code)
}
}
// TestStoreModeEnforced verifies the DB file is 0600 after creation.
func TestStoreModeEnforced(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, err := NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore: %v", err)
}
defer store.Close()
// Trigger a write so the DB file is created on disk.
store.PutCredential(&Credential{
UserID: "u",
CredentialID: []byte("c"),
PublicKey: []byte("p"),
})
info, err := os.Stat(dbPath)
if err != nil {
t.Fatalf("stat db: %v", err)
}
if info.Mode().Perm()&0o077 != 0 {
t.Errorf("db mode = %o, want 0600", info.Mode().Perm())
}
}