webauthn: persist credentials to JSON file instead of in-memory map

- New credentialStore type in credentials.go loads/saves
  map[id]localCred to a JSON file. Thread-safe with sync.RWMutex,
  writes to disk on every mutation.
- PasskeyHandle replaces sync.RWMutex+map with *credentialStore.
  Inline save/lookip/update closures delegate to store methods.
- newPasskeyHandle now takes a storePath parameter and returns an
  error; callers updated.
- New -passkey-file flag (default ./passkeys.json) configures the
  credential store path in main.go.
- Tests use os.CreateTemp in t.TempDir() so each test gets an
  isolated, auto-cleaned store file.
This commit is contained in:
kami
2026-07-05 02:09:56 +04:00
parent b9248ef2e6
commit 44807b612c
4 changed files with 111 additions and 37 deletions
+17 -6
View File
@@ -5,6 +5,7 @@ import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"reflect"
"strings"
"testing"
@@ -214,16 +215,26 @@ func TestEnableTool_NoInProcessAuthGate(t *testing.T) {
// --- webauthn handler wiring (contract level, not crypto) ---
func newTestPasskey() *PasskeyHandle {
return newPasskeyHandle(webauthn.Config{
func newTestPasskey(t *testing.T) *PasskeyHandle {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "passkeys-*.json")
if err != nil {
t.Fatal(err)
}
f.Close()
pk, err := newPasskeyHandle(webauthn.Config{
Origin: "https://maven.example",
RPID: "maven.example",
RPName: "maven",
}, nil) // nil core ⇒ assertFn nil; AssertFinish skips the IPC step-up
}, nil, f.Name())
if err != nil {
t.Fatal(err)
}
return pk
}
func TestWebAuthn_Finish_MethodGuards(t *testing.T) {
pk := newTestPasskey()
pk := newTestPasskey(t)
for _, tc := range []struct {
name string
h http.HandlerFunc
@@ -240,7 +251,7 @@ func TestWebAuthn_Finish_MethodGuards(t *testing.T) {
}
func TestWebAuthn_Finish_MalformedJSON_400(t *testing.T) {
pk := newTestPasskey()
pk := newTestPasskey(t)
for _, tc := range []struct {
name string
h http.HandlerFunc
@@ -258,7 +269,7 @@ func TestWebAuthn_Finish_MalformedJSON_400(t *testing.T) {
}
func TestWebAuthn_Begin_ReturnsChallenge(t *testing.T) {
pk := newTestPasskey()
pk := newTestPasskey(t)
for _, tc := range []struct {
name string
h http.HandlerFunc