package main import ( "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/base64" "encoding/json" "net/http" "strings" "time" ) const sessionCookieName = "telesrv_admin_session" type sessionClaims struct { Actor string `json:"actor"` Exp int64 `json:"exp"` Nonce string `json:"nonce"` } func signSession(key []byte, claims sessionClaims) (string, error) { payload, err := json.Marshal(claims) if err != nil { return "", err } encPayload := base64.RawURLEncoding.EncodeToString(payload) mac := hmac.New(sha256.New, key) mac.Write([]byte(encPayload)) sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) return encPayload + "." + sig, nil } func verifySession(key []byte, value string, now time.Time) (sessionClaims, bool) { parts := strings.Split(value, ".") if len(parts) != 2 { return sessionClaims{}, false } mac := hmac.New(sha256.New, key) mac.Write([]byte(parts[0])) want := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)) if subtle.ConstantTimeCompare([]byte(parts[1]), []byte(want)) != 1 { return sessionClaims{}, false } payload, err := base64.RawURLEncoding.DecodeString(parts[0]) if err != nil { return sessionClaims{}, false } var claims sessionClaims if err := json.Unmarshal(payload, &claims); err != nil { return sessionClaims{}, false } if claims.Actor == "" || claims.Exp <= now.Unix() { return sessionClaims{}, false } return claims, true } func clearSessionCookie(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: sessionCookieName, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode, }) }