check / check (push) Waiting to run
A GET /logout, or a POST /logout without the CSRF token, must end no login session; the generator page must carry a logout form with the token; and after a logout from that form, a login opens the generator page while a cookie copied before it is refused, also after a restart. Model: opus-5-5
149 lines
3.9 KiB
Go
149 lines
3.9 KiB
Go
package session_test
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"sneak.berlin/go/pixa/internal/database"
|
|
"sneak.berlin/go/pixa/internal/session"
|
|
)
|
|
|
|
const logoutTestSigningKey = "test-signing-key-12345"
|
|
|
|
// newTestDatabase opens an empty in-memory database with pixa's schema. It
|
|
// has one connection, as pixad's has: each connection to ":memory:" would
|
|
// open a database of its own.
|
|
func newTestDatabase(t *testing.T) *sql.DB {
|
|
t.Helper()
|
|
|
|
db, err := sql.Open("sqlite", ":memory:")
|
|
if err != nil {
|
|
t.Fatalf("sql.Open() error = %v", err)
|
|
}
|
|
|
|
db.SetMaxOpenConns(1)
|
|
t.Cleanup(func() { _ = db.Close() })
|
|
|
|
err = database.ApplyMigrations(t.Context(), db, nil)
|
|
if err != nil {
|
|
t.Fatalf("ApplyMigrations() error = %v", err)
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
// createSessionCookie logs in with mgr and returns the session cookie it
|
|
// sets.
|
|
func createSessionCookie(t *testing.T, mgr *session.Manager) *http.Cookie {
|
|
t.Helper()
|
|
|
|
rec := httptest.NewRecorder()
|
|
|
|
err := mgr.CreateSession(rec)
|
|
if err != nil {
|
|
t.Fatalf("CreateSession() error = %v", err)
|
|
}
|
|
|
|
for _, c := range rec.Result().Cookies() {
|
|
if c.Name == session.CookieName {
|
|
return c
|
|
}
|
|
}
|
|
|
|
t.Fatalf("CreateSession() did not set a %q cookie", session.CookieName)
|
|
|
|
return nil
|
|
}
|
|
|
|
// requestWithCookie returns a GET request carrying cookie, or no cookie when
|
|
// cookie is nil.
|
|
func requestWithCookie(t *testing.T, cookie *http.Cookie) *http.Request {
|
|
t.Helper()
|
|
|
|
req := httptest.NewRequestWithContext(t.Context(), http.MethodGet, "/", nil)
|
|
if cookie != nil {
|
|
req.AddCookie(cookie)
|
|
}
|
|
|
|
return req
|
|
}
|
|
|
|
// TestLogout_EndsEverySessionMadeBeforeIt verifies that after a logout a
|
|
// copy of the session cookie taken before it is refused and a login after it
|
|
// works, and that both still hold for a new Manager on the same database, as
|
|
// after a restart.
|
|
func TestLogout_EndsEverySessionMadeBeforeIt(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := newTestDatabase(t)
|
|
|
|
mgr, err := session.NewManagerWithDatabase(
|
|
t.Context(), logoutTestSigningKey, db)
|
|
if err != nil {
|
|
t.Fatalf("NewManagerWithDatabase() error = %v", err)
|
|
}
|
|
|
|
copied := createSessionCookie(t, mgr)
|
|
|
|
err = mgr.Logout(httptest.NewRecorder(), requestWithCookie(t, copied))
|
|
if err != nil {
|
|
t.Fatalf("Logout() error = %v", err)
|
|
}
|
|
|
|
_, err = mgr.ValidateSession(requestWithCookie(t, copied))
|
|
t.Logf("cookie copied before the logout: ValidateSession() error = %v", err)
|
|
|
|
if !errors.Is(err, session.ErrInvalidSession) {
|
|
t.Errorf("cookie copied before the logout: error = %v, want %v",
|
|
err, session.ErrInvalidSession)
|
|
}
|
|
|
|
loginAfter := createSessionCookie(t, mgr)
|
|
|
|
_, err = mgr.ValidateSession(requestWithCookie(t, loginAfter))
|
|
if err != nil {
|
|
t.Errorf("login after the logout: ValidateSession() error = %v", err)
|
|
}
|
|
|
|
restarted, err := session.NewManagerWithDatabase(
|
|
t.Context(), logoutTestSigningKey, db)
|
|
if err != nil {
|
|
t.Fatalf("NewManagerWithDatabase() after restart error = %v", err)
|
|
}
|
|
|
|
if restarted.IsAuthenticated(requestWithCookie(t, copied)) {
|
|
t.Error("after a restart, a cookie copied before the logout is accepted")
|
|
}
|
|
|
|
if !restarted.IsAuthenticated(requestWithCookie(t, loginAfter)) {
|
|
t.Error("after a restart, a login made after the logout is refused")
|
|
}
|
|
}
|
|
|
|
// TestLogout_WithoutSession_EndsNoSession verifies that a logout from a
|
|
// request without a session cookie ends no session, so someone who has not
|
|
// logged in cannot end the sessions of those who have.
|
|
func TestLogout_WithoutSession_EndsNoSession(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
mgr, err := session.NewManagerWithDatabase(
|
|
t.Context(), logoutTestSigningKey, newTestDatabase(t))
|
|
if err != nil {
|
|
t.Fatalf("NewManagerWithDatabase() error = %v", err)
|
|
}
|
|
|
|
cookie := createSessionCookie(t, mgr)
|
|
|
|
err = mgr.Logout(httptest.NewRecorder(), requestWithCookie(t, nil))
|
|
if err != nil {
|
|
t.Fatalf("Logout() error = %v", err)
|
|
}
|
|
|
|
if !mgr.IsAuthenticated(requestWithCookie(t, cookie)) {
|
|
t.Error("a logout without a session cookie ended a session")
|
|
}
|
|
}
|