package main
import (
"net/http"
"net/http/httptest"
"testing"
"craftdesign.group/quarry/internal/oltp"
"craftdesign.group/quarry/internal/ops"
"craftdesign.group/quarry/internal/web"
)
// A SESSION OPENED BY A SIGN-IN LINK CARRIES ITS TOKEN'S CEILING TO THE
// PAGES, A PASSWORD SESSION CARRIES NO TOKEN, AND REVOKING THE TOKEN
// TAKES THE SESSION AWAY (RFC 0021, Web sign-in).
func TestALinkSessionIsCutToItsTokensCeiling(t *testing.T) {
g := newDoorRig(t)
db := g.rg.odb
_, tok := g.mint(t, oltp.TokenSpec{Scopes: scopes(oltp.ScopeWeb), Ceiling: "o"})
link, _, err := db.MintSigninLink("jan", tok.ID, "/")
if err != nil {
t.Fatal(err)
}
cookie, _, _, ok := db.RedeemSigninLink(link, "", "")
if !ok {
t.Fatal("FAIL: the link opened no session")
}
pwCookie, _, ok := db.NewSession("jan", doorPassword)
if !ok {
t.Fatal("FAIL: password sign-in")
}
// What the pages see: the token on the request context, if any.
seen := func(cookieValue string) (hasToken bool, canO, canI bool) {
var out struct{ has, o, i bool }
h := g.doors.wrap(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
if tk, ok := ops.TokenOf(req.Context()); ok {
out.has = true
out.o = tk.Caps("vault").HasAny("o")
out.i = tk.Caps("vault").HasAny("i")
}
}))
req := httptest.NewRequest(http.MethodGet, "/r/vault", nil)
req.AddCookie(&http.Cookie{Name: web.SessionCookie, Value: cookieValue})
h.ServeHTTP(httptest.NewRecorder(), req)
return out.has, out.o, out.i
}
if has, o, i := seen(cookie); !has || !o || i {
t.Errorf("FAIL: link session token=%v o=%v i=%v; want a token holding o and not i (the account holds both)", has, o, i)
}
if has, _, _ := seen(pwCookie); has {
t.Errorf("FAIL: a password session was given a token")
}
if err := db.RevokeToken("jan", tok.ID); err != nil {
t.Fatal(err)
}
if has, _, _ := seen(cookie); has {
t.Errorf("FAIL: the revoked token's session still carries it")
}
if _, ok := db.SessionAccount(cookie); ok {
t.Errorf("FAIL: the revoked token's session still resolves")
}
}