Files
orca/internal/webauthn/store_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
2.8 KiB
Go

package webauthn
import (
"path/filepath"
"testing"
"time"
)
// TestStoreRoundTrip verifies Put + Get + Delete + List + UpdateSignCount.
func TestStoreRoundTrip(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()
cred := &Credential{
UserID: "user-1",
CredentialID: []byte("cred-id-123"),
PublicKey: []byte("pub-key-bytes"),
SignCount: 0,
AAGUID: "test-aaguid",
CreatedAt: time.Now(),
}
if err := store.PutCredential(cred); err != nil {
t.Fatalf("Put: %v", err)
}
got, err := store.GetCredential("user-1")
if err != nil {
t.Fatalf("Get: %v", err)
}
if got == nil {
t.Fatal("Get returned nil")
}
if got.UserID != "user-1" || string(got.CredentialID) != "cred-id-123" {
t.Errorf("got = %+v", got)
}
if err := store.UpdateSignCount("user-1", 42); err != nil {
t.Fatalf("UpdateSignCount: %v", err)
}
got, _ = store.GetCredential("user-1")
if got.SignCount != 42 {
t.Errorf("SignCount = %d, want 42", got.SignCount)
}
list, err := store.ListCredentials()
if err != nil {
t.Fatalf("List: %v", err)
}
if len(list) != 1 {
t.Errorf("List = %d, want 1", len(list))
}
if err := store.DeleteCredential("user-1"); err != nil {
t.Fatalf("Delete: %v", err)
}
got, _ = store.GetCredential("user-1")
if got != nil {
t.Error("Get after delete should return nil")
}
}
// TestStorePutReplace verifies Put replaces on conflict.
func TestStorePutReplace(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
c1 := &Credential{UserID: "u", CredentialID: []byte("old"), PublicKey: []byte("pk1"), CreatedAt: time.Now()}
store.PutCredential(c1)
c2 := &Credential{UserID: "u", CredentialID: []byte("new"), PublicKey: []byte("pk2"), CreatedAt: time.Now()}
store.PutCredential(c2)
got, _ := store.GetCredential("u")
if string(got.CredentialID) != "new" {
t.Errorf("CredentialID = %q, want new", got.CredentialID)
}
}
// TestStoreGetMissing verifies Get returns nil, nil for missing.
func TestStoreGetMissing(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "webauthn-creds.db")
store, _ := NewStore(dbPath)
defer store.Close()
got, err := store.GetCredential("nonexistent")
if err != nil {
t.Errorf("Get missing should not error: %v", err)
}
if got != nil {
t.Error("Get missing should return nil")
}
}
// TestEncodeDecodeID verifies base64 round-trip.
func TestEncodeDecodeID(t *testing.T) {
original := []byte("test-credential-id-12345")
encoded := EncodeID(original)
decoded, err := DecodeID(encoded)
if err != nil {
t.Fatalf("Decode: %v", err)
}
if string(decoded) != string(original) {
t.Errorf("decode = %q, want %q", decoded, original)
}
}