package auth import ( "context" "errors" "net/http" "net/http/httptest" "testing" "time" ) // principal_test.go: that "authenticated, but nobody can revoke them" is not a state this package // can hand out. // // PD-379 was exactly that state, and the first shape of its fix kept it reachable: the identity and // the means to re-ask travelled as two separate context values, so a route mounted outside the guard // — or a future one that minted a principal some other way — produced a caller with an id and no way // to check it, and a handler could only fail closed by remembering to. They are ONE value now, and // what that buys is checked here rather than described. // A principal that did not come from Require answers "not live". The direction is the whole point: // the only way this can be wrong must be the way that refuses. // // Mutation caught: returning nil from StillLive when the store is absent. func TestAPrincipalNobodyAuthenticatedIsNotLive(t *testing.T) { for name, p := range map[string]Principal{ "the zero value": {}, "an id someone set": {UserID: "u1"}, "an id and a presence": {UserID: "u1", Via: ViaBearer}, } { t.Run(name, func(t *testing.T) { if err := p.StillLive(context.Background()); !errors.Is(err, ErrNoSession) { t.Errorf("StillLive = %v, want %v: a caller whose session nothing can look up must "+ "not be able to hold a long-lived response open", err, ErrNoSession) } }) } } // The probe a real principal carries asks about the SAME credential the guard admitted, and asks the // store rather than remembering the guard's answer. // // Mutation caught: binding an empty digest; caching Lookup's verdict instead of re-asking. func TestTheProbeAsksTheStoreAboutTheCredentialThatWasPresented(t *testing.T) { now := time.Now() token := NewToken() store := &fakeStore{session: Session{ UserID: "u1", IdleExpiresAt: now.Add(time.Hour), AbsoluteExpiresAt: now.Add(time.Hour), }} a, _ := newAuth(store, now) var got Principal h := a.Require(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) { got, _ = FromContext(r.Context()) })) r := httptest.NewRequest("GET", "/v0/books/bk_1/events", nil) r.Header.Set("Authorization", "Bearer "+token) h.ServeHTTP(httptest.NewRecorder(), r) if got.UserID != "u1" { t.Fatalf("the guard produced %+v", got) } // Live now… if err := got.StillLive(context.Background()); err != nil { t.Fatalf("a freshly admitted caller is not live: %v", err) } if want := Digest(token); string(store.digest) != string(want) { t.Errorf("the probe asked about a different credential than the one presented") } if store.asked == 0 { t.Error("the probe answered without asking the store: a verdict remembered from the door is " + "the defect, not the fix") } // …and gone a moment later, WITHOUT the request being made again. That is the whole mechanism. store.live = ErrNoSession if err := got.StillLive(context.Background()); !errors.Is(err, ErrNoSession) { t.Errorf("StillLive = %v after the session was revoked, want %v", err, ErrNoSession) } }