package api import ( "crypto/rand" "encoding/base64" "encoding/json" "log" "net/http" "strings" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) type AuthHandler struct { username string passwordHash string jwtSecret []byte } type loginRequest struct { Username string `json:"username"` Password string `json:"password"` } type Claims struct { Username string `json:"username"` jwt.RegisteredClaims } func NewAuthHandler(username, passwordHash, jwtSecret string) *AuthHandler { if username == "" { username = "admin" } secret := []byte(jwtSecret) if len(secret) == 0 { key := make([]byte, 32) if _, err := rand.Read(key); err != nil { secret = []byte("dev-secret-do-not-use-in-production") } else { secret = []byte(base64.RawURLEncoding.EncodeToString(key)) } log.Println("[auth] jwt_secret nao configurado — usando chave gerada aleatoriamente") } if passwordHash == "" { log.Fatal("[auth] ADMIN_PASSWORD_HASH ou ADMIN_PASSWORD deve ser definido") } return &AuthHandler{ username: username, passwordHash: passwordHash, jwtSecret: secret, } } func (a *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "json invalido"}) return } if req.Username != a.username { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "credenciais invalidas"}) return } if err := bcrypt.CompareHashAndPassword([]byte(a.passwordHash), []byte(req.Password)); err != nil { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "credenciais invalidas"}) return } now := time.Now() claims := Claims{ Username: req.Username, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(7 * 24 * time.Hour)), Subject: req.Username, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signed, err := token.SignedString(a.jwtSecret) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "erro ao gerar token"}) return } writeJSON(w, http.StatusOK, map[string]string{"token": signed}) } func (a *AuthHandler) Verify(w http.ResponseWriter, r *http.Request) { tokenStr := extractToken(r) if tokenStr == "" { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "token ausente"}) return } if !a.validateToken(tokenStr) { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "token invalido ou expirado"}) return } w.WriteHeader(http.StatusNoContent) } func (a *AuthHandler) Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.HasPrefix(r.URL.Path, "/api/auth/") { next.ServeHTTP(w, r) return } if r.Method == http.MethodGet || r.Method == http.MethodOptions { next.ServeHTTP(w, r) return } tokenStr := extractToken(r) if tokenStr == "" || !a.validateToken(tokenStr) { writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "nao autorizado"}) return } next.ServeHTTP(w, r) }) } func (a *AuthHandler) validateToken(tokenStr string) bool { claims := &Claims{} token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (any, error) { return a.jwtSecret, nil }) if err != nil || !token.Valid { return false } return claims.Username == a.username } func extractToken(r *http.Request) string { auth := r.Header.Get("Authorization") if strings.HasPrefix(auth, "Bearer ") { return strings.TrimPrefix(auth, "Bearer ") } cookie, err := r.Cookie("auth_token") if err == nil && cookie.Value != "" { return cookie.Value } return "" }