package main import ( "net/http" "net/http/httptest" "testing" "time" "github.com/go-webauthn/webauthn/webauthn" ) func TestEnrollTokenRoundTrip(t *testing.T) { key := []byte("0123456789abcdef0123456789abcdef") tok, err := mintEnrollToken(key, "alice", 10*time.Minute) if err != nil { t.Fatalf("mint: %v", err) } claims, err := verifyEnrollToken(key, tok) if err != nil { t.Fatalf("verify: %v", err) } if claims.Name != "alice" || claims.JTI == "" { t.Fatalf("claims = %+v", claims) } // wrong key rejected if _, err := verifyEnrollToken([]byte("wrongwrongwrongwrongwrongwrongwr"), tok); err == nil { t.Error("expected wrong-key rejection") } // tampered signature rejected if _, err := verifyEnrollToken(key, tok[:len(tok)-2]+"xx"); err == nil { t.Error("expected tamper rejection") } // expired rejected exp, _ := mintEnrollToken(key, "bob", -time.Minute) if _, err := verifyEnrollToken(key, exp); err == nil { t.Error("expected expiry rejection") } } func TestSessionMiddleware(t *testing.T) { s := &server{cfg: config{}, sessions: newSessionStore(time.Hour)} // adminInsecure=false id, csrf := s.sessions.create("alice") var reached string guarded := s.requireSession(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reached = adminName(r) w.WriteHeader(http.StatusOK) })) // no cookie -> 401 rw := httptest.NewRecorder() guarded.ServeHTTP(rw, httptest.NewRequest(http.MethodGet, "/api/hosts", nil)) if rw.Code != http.StatusUnauthorized { t.Fatalf("no cookie: got %d want 401", rw.Code) } withCookie := func(method string, csrfHdr string) *http.Request { r := httptest.NewRequest(method, "/api/x", nil) r.AddCookie(&http.Cookie{Name: sessionCookie, Value: id}) if csrfHdr != "" { r.Header.Set(csrfHeader, csrfHdr) } return r } // valid GET -> reaches handler as alice reached = "" rw = httptest.NewRecorder() guarded.ServeHTTP(rw, withCookie(http.MethodGet, "")) if rw.Code != http.StatusOK || reached != "alice" { t.Fatalf("valid GET: code=%d admin=%q", rw.Code, reached) } // mutating POST without CSRF -> 403 rw = httptest.NewRecorder() guarded.ServeHTTP(rw, withCookie(http.MethodPost, "")) if rw.Code != http.StatusForbidden { t.Fatalf("POST no CSRF: got %d want 403", rw.Code) } // mutating POST with correct CSRF -> reaches handler reached = "" rw = httptest.NewRecorder() guarded.ServeHTTP(rw, withCookie(http.MethodPost, csrf)) if rw.Code != http.StatusOK || reached != "alice" { t.Fatalf("POST with CSRF: code=%d admin=%q", rw.Code, reached) } // unknown cookie -> 401 rw = httptest.NewRecorder() bad := httptest.NewRequest(http.MethodGet, "/api/x", nil) bad.AddCookie(&http.Cookie{Name: sessionCookie, Value: "nope"}) guarded.ServeHTTP(rw, bad) if rw.Code != http.StatusUnauthorized { t.Fatalf("bad cookie: got %d want 401", rw.Code) } } func TestCredStorePersistence(t *testing.T) { path := t.TempDir() + "/admins.json" s, err := newCredStore(path) if err != nil { t.Fatalf("new: %v", err) } u := s.getOrCreate("alice") handle := u.IDb if err := s.addCredential("alice", webauthn.Credential{ID: []byte("cred-1"), PublicKey: []byte("pk")}); err != nil { t.Fatalf("add: %v", err) } s.markTokenUsed("jti-1", time.Now().Add(time.Hour).Unix()) // reload from disk s2, err := newCredStore(path) if err != nil { t.Fatalf("reload: %v", err) } if s2.count() != 1 { t.Fatalf("admins after reload = %d", s2.count()) } got, ok := s2.byHandle(handle) if !ok || got.Name != "alice" || len(got.Creds) != 1 { t.Fatalf("byHandle after reload: ok=%v %+v", ok, got) } if !s2.tokenUsed("jti-1") { t.Error("used-token ledger did not survive reload") } if s2.tokenUsed("jti-unknown") { t.Error("unknown token reported used") } }