95 lines
3.7 KiB
Go
95 lines
3.7 KiB
Go
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestManagerRejectsTamperedAndExpiredCookies(t *testing.T) {
|
|
manager := NewManager([]byte(strings.Repeat("s", 32)), true)
|
|
request := httptest.NewRequest(http.MethodGet, "/login", nil)
|
|
response := httptest.NewRecorder()
|
|
csrf, authenticated := manager.Ensure(response, request)
|
|
if csrf == "" || authenticated {
|
|
t.Fatalf("Ensure = (%q, %t), want anonymous CSRF session", csrf, authenticated)
|
|
}
|
|
cookie := response.Result().Cookies()[0]
|
|
if !cookie.HttpOnly || !cookie.Secure || cookie.SameSite != http.SameSiteLaxMode || cookie.Path != "/" {
|
|
t.Fatalf("session cookie is missing security attributes: %#v", cookie)
|
|
}
|
|
|
|
tampered := *cookie
|
|
tampered.Value = flipCookieValue(t, cookie.Value)
|
|
tamperedRequest := httptest.NewRequest(http.MethodPost, "/login", nil)
|
|
tamperedRequest.AddCookie(&tampered)
|
|
if _, ok := manager.VerifyCSRF(tamperedRequest, csrf); ok {
|
|
t.Fatal("tampered signed cookie passed CSRF verification")
|
|
}
|
|
|
|
manager.now = func() time.Time { return time.Now().Add(9 * time.Hour) }
|
|
expiredRequest := httptest.NewRequest(http.MethodPost, "/login", nil)
|
|
expiredRequest.AddCookie(cookie)
|
|
if _, ok := manager.VerifyCSRF(expiredRequest, csrf); ok {
|
|
t.Fatal("expired cookie passed CSRF verification")
|
|
}
|
|
}
|
|
|
|
func TestIsAuthenticatedDoesNotCreateOrDependOnCSRFValidation(t *testing.T) {
|
|
manager := NewManager([]byte(strings.Repeat("s", 32)), false)
|
|
missingSession := httptest.NewRequest(http.MethodPost, "/tasks/start-purchases", nil)
|
|
if manager.IsAuthenticated(missingSession) {
|
|
t.Fatal("missing session was treated as authenticated")
|
|
}
|
|
if len(manager.sessions) != 0 {
|
|
t.Fatalf("read-only authentication check created %d sessions", len(manager.sessions))
|
|
}
|
|
anonymousRequest := httptest.NewRequest(http.MethodGet, "/login", nil)
|
|
anonymousResponse := httptest.NewRecorder()
|
|
manager.Ensure(anonymousResponse, anonymousRequest)
|
|
anonymousCookie := anonymousResponse.Result().Cookies()[0]
|
|
anonymousCheck := httptest.NewRequest(http.MethodPost, "/tasks/start-purchases", nil)
|
|
anonymousCheck.AddCookie(anonymousCookie)
|
|
if manager.IsAuthenticated(anonymousCheck) {
|
|
t.Fatal("anonymous CSRF session was treated as authenticated")
|
|
}
|
|
|
|
loginRequest := httptest.NewRequest(http.MethodPost, "/login", nil)
|
|
loginRequest.AddCookie(anonymousCookie)
|
|
authenticatedResponse := httptest.NewRecorder()
|
|
csrf := manager.RotateAuthenticated(authenticatedResponse, loginRequest)
|
|
authenticatedCookie := authenticatedResponse.Result().Cookies()[0]
|
|
|
|
authenticatedCheck := httptest.NewRequest(http.MethodPost, "/tasks/start-purchases", nil)
|
|
authenticatedCheck.AddCookie(authenticatedCookie)
|
|
if !manager.IsAuthenticated(authenticatedCheck) {
|
|
t.Fatal("valid authenticated session was not recognized")
|
|
}
|
|
if authenticated, csrfOK := manager.VerifyCSRF(authenticatedCheck, "wrong-token"); authenticated || csrfOK {
|
|
t.Fatalf("wrong token result = (%t, %t), want (false, false)", authenticated, csrfOK)
|
|
}
|
|
|
|
validRequest := httptest.NewRequest(http.MethodPost, "/tasks/start-purchases", nil)
|
|
validRequest.AddCookie(authenticatedCookie)
|
|
if authenticated, csrfOK := manager.VerifyCSRF(validRequest, csrf); !authenticated || !csrfOK {
|
|
t.Fatalf("valid token result = (%t, %t), want (true, true)", authenticated, csrfOK)
|
|
}
|
|
|
|
if authenticated, csrfOK := manager.VerifyCSRF(httptest.NewRequest(http.MethodPost, "/tasks/start-purchases", nil), csrf); authenticated || csrfOK {
|
|
t.Fatalf("missing session result = (%t, %t), want (false, false)", authenticated, csrfOK)
|
|
}
|
|
}
|
|
|
|
func flipCookieValue(t *testing.T, value string) string {
|
|
t.Helper()
|
|
if value == "" {
|
|
t.Fatal("cannot tamper with an empty cookie")
|
|
}
|
|
if value[0] == 'A' {
|
|
return "B" + value[1:]
|
|
}
|
|
return "A" + value[1:]
|
|
}
|