Initial commit — energy dashboard frontend (TimescaleDB + Vue/Chart.js)
This commit is contained in:
113
internal/api/auth.go
Normal file
113
internal/api/auth.go
Normal file
@@ -0,0 +1,113 @@
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user