c726a6a9e2
---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.
103 lines
2.8 KiB
Go
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)
|
|
}
|
|
}
|