package api import ( "crypto/hmac" "crypto/sha256" "crypto/subtle" "encoding/hex" "encoding/json" "fmt" "net/http" "strconv" "strings" "time" "energy-frontend/internal/config" ) const ( cookieName = "energy_session" cookieMaxAge = 7 * 24 * 3600 // 7 days cookiePath = "/energy" ) type authHandler struct { cfg config.AuthConfig } func newAuthHandler(cfg config.AuthConfig) *authHandler { return &authHandler{cfg: cfg} } func (h *authHandler) loginHandler(w http.ResponseWriter, r *http.Request) { var req struct { Username string `json:"username"` Password string `json:"password"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"}) return } if subtle.ConstantTimeCompare([]byte(req.Username), []byte(h.cfg.Username)) != 1 || subtle.ConstantTimeCompare([]byte(req.Password), []byte(h.cfg.Password)) != 1 { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid credentials"}) return } http.SetCookie(w, h.createSessionCookie(req.Username)) writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (h *authHandler) logoutHandler(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, &http.Cookie{ Name: cookieName, Value: "", Path: cookiePath, MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteLaxMode, }) writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (h *authHandler) checkHandler(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (h *authHandler) middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { cookie, err := r.Cookie(cookieName) if err != nil || !h.validateSession(cookie.Value) { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "unauthorized"}) return } next.ServeHTTP(w, r) }) } func (h *authHandler) createSessionCookie(username string) *http.Cookie { expires := time.Now().Add(time.Duration(cookieMaxAge) * time.Second) payload := fmt.Sprintf("%s|%d", username, expires.Unix()) sig := h.sign(payload) return &http.Cookie{ Name: cookieName, Value: fmt.Sprintf("%s|%s", payload, sig), Path: cookiePath, MaxAge: cookieMaxAge, HttpOnly: true, SameSite: http.SameSiteLaxMode, } } func (h *authHandler) validateSession(value string) bool { parts := strings.SplitN(value, "|", 3) if len(parts) != 3 { return false } payload := parts[0] + "|" + parts[1] if !hmac.Equal([]byte(h.sign(payload)), []byte(parts[2])) { return false } exp, err := strconv.ParseInt(parts[1], 10, 64) if err != nil { return false } return time.Now().Unix() < exp } func (h *authHandler) sign(payload string) string { mac := hmac.New(sha256.New, []byte(h.cfg.Secret)) mac.Write([]byte(payload)) return hex.EncodeToString(mac.Sum(nil)) }