munin/internal/auth/csrf.go
2026-09-15 20:36:26 +02:00

49 lines
1.6 KiB
Go

package auth
import (
"crypto/subtle"
"net/http"
)
// CSRFFieldName ist der Name des versteckten Formularfelds in den Templates.
const CSRFFieldName = "csrf_token"
// CSRFHeaderName erlaubt es, das Token bei einem fetch()-Aufruf im Header statt
// im Formular zu schicken.
const CSRFHeaderName = "X-CSRF-Token"
// CSRFToken berechnet das Token zu einer Sitzung.
//
// # Wogegen schützt das?
//
// CSRF ("Cross-Site Request Forgery") nutzt aus, dass der Browser Cookies
// automatisch mitschickt. Eine fremde Seite kann ein Formular auf unseren Server
// abschicken lassen, und für den Server sieht es aus wie eine Aktion des
// angemeldeten Nutzers:
//
// <form method="POST" action="https://munin.intern/projects/x/delete">
// <script>document.forms[0].submit()</script>
//
// Das Token unterbindet das: Die fremde Seite kennt es nicht und kann es wegen
// der Same-Origin-Policy des Browsers auch nicht auslesen.
//
// Das Präfix "csrf:" sorgt für Domänentrennung — dasselbe Geheimnis signiert auch
// Session-Cookies, und eine Signatur aus dem einen Zusammenhang darf im anderen
// nicht gelten.
func (s *Signer) CSRFToken(subject string) string {
return s.sign("csrf:" + subject)
}
// ValidCSRFToken prüft ein übermitteltes Token gegen die Sitzung.
func (s *Signer) ValidCSRFToken(token, subject string) bool {
expected := s.CSRFToken(subject)
return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
}
// CSRFTokenFromRequest holt das Token aus dem Formular oder dem Header.
func CSRFTokenFromRequest(r *http.Request) string {
if token := r.Header.Get(CSRFHeaderName); token != "" {
return token
}
return r.FormValue(CSRFFieldName)
}