package middleware import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "net/http" "os" "strconv" "strings" "time" ) const cookieName = "cf_session" const sessionDuration = 30 * 24 * time.Hour func secret() []byte { // Use APP_PASSWORD as signing secret so tokens survive container restarts. return []byte(os.Getenv("APP_PASSWORD")) } func signToken(expiry int64) string { nonce := make([]byte, 8) rand.Read(nonce) payload := fmt.Sprintf("%s.%d", hex.EncodeToString(nonce), expiry) mac := hmac.New(sha256.New, secret()) mac.Write([]byte(payload)) sig := hex.EncodeToString(mac.Sum(nil)) return payload + "." + sig } func validToken(token string) bool { parts := strings.SplitN(token, ".", 3) if len(parts) != 3 { return false } payload := parts[0] + "." + parts[1] expiry, err := strconv.ParseInt(parts[1], 10, 64) if err != nil || time.Now().Unix() > expiry { return false } mac := hmac.New(sha256.New, secret()) mac.Write([]byte(payload)) expected := hex.EncodeToString(mac.Sum(nil)) return hmac.Equal([]byte(parts[2]), []byte(expected)) } func jsonErr(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(map[string]string{"error": msg}) } func RequireAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(cookieName) if err != nil || !validToken(cookie.Value) { jsonErr(w, http.StatusUnauthorized, "unauthorized") return } next.ServeHTTP(w, r) }) } func LoginHandler(w http.ResponseWriter, r *http.Request) { var body struct { Username string `json:"username"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { jsonErr(w, http.StatusBadRequest, "corpo inválido") return } wantUser := os.Getenv("APP_USERNAME") wantPass := os.Getenv("APP_PASSWORD") if wantUser == "" || body.Username != wantUser || body.Password != wantPass { jsonErr(w, http.StatusUnauthorized, "credenciais inválidas") return } expiry := time.Now().Add(sessionDuration).Unix() token := signToken(expiry) http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: token, Path: "/", HttpOnly: true, Secure: true, SameSite: http.SameSiteStrictMode, MaxAge: int(sessionDuration.Seconds()), }) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"ok": "true"}) } func LogoutHandler(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: "", Path: "/", MaxAge: -1, }) w.WriteHeader(http.StatusNoContent) } func MeHandler(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(cookieName) if err != nil || !validToken(cookie.Value) { jsonErr(w, http.StatusUnauthorized, "unauthorized") return } w.WriteHeader(http.StatusNoContent) }