- auth.Mount(env, key) wraps csrf.Protect with locked D-14/D-24 options - auth.LoadKeyFromEnv() reads SESSION_SECRET, hex-decodes, validates 32 bytes; fails fast on error - ui.CSRFField(token) templ component renders hidden _csrf input - Layout, LoginPage/Fragment, SignupPage/Fragment, Index all embed @ui.CSRFField(csrfToken) - Handlers thread csrf.Token(r) into every page/fragment render call - NewRouter mounts auth.Mount after ResolveSession, before all route groups (D-24) - main.go calls auth.LoadKeyFromEnv(); logs.Fatalf on missing/invalid SESSION_SECRET - SESSION_SECRET documented in .env.example with openssl rand -hex 32 instruction - go.mod: gorilla/csrf v1.7.3 (direct); prior tests updated with getCSRFToken helper - All Plan 04/05/06 tests updated to acquire and submit valid _csrf tokens
66 lines
1.7 KiB
Go
66 lines
1.7 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
|
|
"backend/internal/auth"
|
|
"backend/internal/db/sqlc"
|
|
)
|
|
|
|
func TestCSRF_Debug(t *testing.T) {
|
|
pool, cleanup := setupTestDB(t)
|
|
defer cleanup()
|
|
|
|
q := sqlc.New(pool)
|
|
store := auth.NewStore(q)
|
|
router := newTestRouter(q, store)
|
|
|
|
// GET /login and collect cookies
|
|
getReq := httptest.NewRequest(http.MethodGet, "/login", nil)
|
|
getRec := httptest.NewRecorder()
|
|
router.ServeHTTP(getRec, getReq)
|
|
|
|
t.Logf("GET /login status: %d", getRec.Code)
|
|
t.Logf("GET /login cookies:")
|
|
for _, c := range getRec.Result().Cookies() {
|
|
t.Logf(" %s=%s (httponly=%v, secure=%v)", c.Name, c.Value[:min(len(c.Value), 20)], c.HttpOnly, c.Secure)
|
|
}
|
|
|
|
body := getRec.Body.String()
|
|
const needle = `name="_csrf" value="`
|
|
idx := strings.Index(body, needle)
|
|
if idx == -1 {
|
|
t.Fatal("no _csrf hidden input found")
|
|
}
|
|
rest := body[idx+len(needle):]
|
|
end := strings.Index(rest, `"`)
|
|
token := rest[:end]
|
|
t.Logf("Extracted CSRF token: %s...", token[:min(len(token), 20)])
|
|
|
|
form := url.Values{"email": {"x@y.com"}, "password": {"correct-horse-12"}, "_csrf": {token}}
|
|
postReq := httptest.NewRequest(http.MethodPost, "/login", strings.NewReader(form.Encode()))
|
|
postReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
for _, c := range getRec.Result().Cookies() {
|
|
t.Logf("Adding cookie to POST: %s", c.Name)
|
|
postReq.AddCookie(c)
|
|
}
|
|
t.Logf("POST cookies count: %d", len(getRec.Result().Cookies()))
|
|
|
|
postRec := httptest.NewRecorder()
|
|
router.ServeHTTP(postRec, postReq)
|
|
t.Logf("POST /login status: %d", postRec.Code)
|
|
if postRec.Code == 403 {
|
|
t.Logf("403 body: %s", postRec.Body.String()[:min(len(postRec.Body.String()), 200)])
|
|
}
|
|
}
|
|
|
|
func min(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|