diff options
Diffstat (limited to 'backend/internal/api/auth.go')
| -rw-r--r-- | backend/internal/api/auth.go | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/backend/internal/api/auth.go b/backend/internal/api/auth.go new file mode 100644 index 0000000..5095194 --- /dev/null +++ b/backend/internal/api/auth.go @@ -0,0 +1,144 @@ +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 "" +} |