Tema
/
Interneto svetainių serverio dalies kūrimas GO

Diagnozė ir taisymas — dev išimtis + žetonas

Diagnozė. „referer not supplied" — tai kalba Origin/Referer apsaugos pusė, ne žetono pusė. gorilla/csrf daro prielaidą apie produkcijos HTTPS, tad kiekvienai nesaugiai užklausai reikalauja to paties saugaus šaltinio įrodymo: galiojančio Origin ar Referer antraštės per TLS. Tavo dev serveris — paprastas http://localhost, kuris tokio įrodymo neduoda, tad patikra atmeta užklausą dar prieš pažvelgdama į žetoną. Štai kodėl vien žetono lauko pridėjimas nebūtų padėjęs — pirma reikia praeiti pro šaltinio patikrą.

Tad taisymas — du ėjimai: (a) pasakyk middleware „ši užklausa tikrai yra plaintext HTTP" tik dev, ir (b) duok formoms žetoną, kad ir antra pusė praeitų.

Penki failai kaip skirtukai: router.gonav.goauth.goauth.templlayout.templ.

1. internal/handlers/router.go (modify) — tik-dev išimtis, pridėta prieš csrf.Protect:

if !h.Cfg.IsProd() {
	r.Use(func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			next.ServeHTTP(w, csrf.PlaintextHTTPRequest(req))
		})
	})
}

PlaintextHTTPRequest atpalaiduoja tik Origin/Referer patikrą ir tik kai !IsProd(). Produkcija (HTTPS Railway krašte) šito niekada nevykdo, tad jos griežta šaltinio apsauga lieka pilnai apšarvuota. Žetono patikra abiem atvejais nepaliesta.

2. internal/web/nav.go (modify) — Nav gauna CSRFToken lauką.

3. internal/handlers/auth.go (modify) — nav() jį užpildo: web.Nav{CSRFToken: csrf.Token(r)}. (csrf.Token perskaito užklausos žetoną, kurį nustatė middleware.)

4. internal/web/auth.templ (modify) — authCSRF pagalbininkas atvaizduoja paslėptą lauką, ir kiekviena forma jį kviečia:

templ authCSRF(token string) {
	<input type="hidden" name="gorilla.csrf.Token" value={ token }/>
}
// kiekvienos <form> viduje:  @authCSRF(nav.CSRFToken)

5. internal/web/layout.templ (modify) — atsijungimo forma antraštėje gauna tą patį paslėptą lauką.

Patikrink — dabar veikia. templ generate && go run ./cmd/signflow. Prisijunk: pasieki skydelį. Registruokis, atsijunk — viskas gerai. Peržiūrėk /login šaltinį ir pamatysi žetoną:

<input type="hidden" name="gorilla.csrf.Token" value="Ck3p...ilga-atsitiktinė...">

Ir klastotė vis dar nepavyksta: POST be lauko atmetamas net dev aplinkoje (žetono pusė visada įjungta):

$ curl -si -X POST localhost:8080/login -d 'email=a@b.co&password=x' | head -1
HTTP/1.1 403 Forbidden        ← nėra žetono → vis tiek blokuota. Būtent taip.

HTMX ir X-CSRF-Token antraštė. Vėliau įkėlimai POST'ins per HTMX su multipart kūnu. Perskaityti tą kūną vien tam, kad rastum paslėptą lauką, būtų švaistymas — tad gorilla/csrf taip pat priima žetoną X-CSRF-Token antraštėje. Įdėk žetoną į <meta> žymę ir liepk HTMX siųsti jį su kiekviena užklausa:

<meta name="csrf-token" content={ nav.CSRFToken }/>
<body hx-headers='{"X-CSRF-Token": "…"}'>

Tas pats žetonas, siunčiamas antraštėje, o ne kūne — multipart įkėlimas patvirtinamas be jokio pakartotinio analizavimo. Tai naudosi dokumentų fazėje.

Pamoka buvo ne taisymas — o 403. Dabar žinai, kad CSRF apsauga — dvi patikros: įrodyk to paties šaltinio ketinimą (Origin/Referer) ir atkartok slaptą žetoną. Žetonas — pusė, kurią visi prisimena; šaltinio patikra — pusė, kuri tave 403'ino. Abi turi praeiti.