package auth import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "errors" "fmt" "net/http" "strings" "time" ) const ( cookieName = "gitea_token" cookieMaxAge = 30 * 24 * 60 * 60 // 30 days in seconds ) var ( ErrInvalidSignature = errors.New("invalid cookie signature") ErrMalformedCookie = errors.New("malformed cookie value") ) // SetTokenCookie stores a Gitea API token in a signed HTTP-only cookie. func SetTokenCookie(w http.ResponseWriter, token string, secret string, secure bool) { signed := sign(token, secret) http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: signed, Path: "/", MaxAge: cookieMaxAge, HttpOnly: true, Secure: secure, SameSite: http.SameSiteStrictMode, Expires: time.Now().Add(30 * 24 * time.Hour), }) } // ClearTokenCookie removes the token cookie. func ClearTokenCookie(w http.ResponseWriter, secure bool) { http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, Secure: secure, SameSite: http.SameSiteStrictMode, }) } // GetToken extracts and verifies the Gitea API token from the request cookie. // Returns the token string or an error if the cookie is missing or invalid. func GetToken(r *http.Request, secret string) (string, error) { cookie, err := r.Cookie(cookieName) if err != nil { return "", err } token, err := verify(cookie.Value, secret) if err != nil { return "", err } return token, nil } // sign creates a signed cookie value: base64(token).base64(hmac-sha256(token)) func sign(token string, secret string) string { encodedToken := base64.URLEncoding.EncodeToString([]byte(token)) mac := computeHMAC(encodedToken, secret) return fmt.Sprintf("%s.%s", encodedToken, mac) } // verify checks the HMAC signature and returns the original token. func verify(signed string, secret string) (string, error) { parts := strings.SplitN(signed, ".", 2) if len(parts) != 2 { return "", ErrMalformedCookie } encodedToken := parts[0] providedMAC := parts[1] expectedMAC := computeHMAC(encodedToken, secret) if !hmac.Equal([]byte(providedMAC), []byte(expectedMAC)) { return "", ErrInvalidSignature } tokenBytes, err := base64.URLEncoding.DecodeString(encodedToken) if err != nil { return "", ErrMalformedCookie } return string(tokenBytes), nil } // computeHMAC generates a base64-encoded HMAC-SHA256 of the given data. func computeHMAC(data string, secret string) string { h := hmac.New(sha256.New, []byte(secret)) h.Write([]byte(data)) return base64.URLEncoding.EncodeToString(h.Sum(nil)) }