package api import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "log" "net/http" "time" "github.com/cynfo/rmm-backend/db" "github.com/cynfo/rmm-backend/models" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" ) type AuthHandler struct { db *db.Database secret []byte } type JWTClaims struct { UserID int `json:"user_id"` Username string `json:"username"` Role string `json:"role"` jwt.RegisteredClaims } func NewAuthHandler(database *db.Database, jwtSecret string) *AuthHandler { secret := jwtSecret if secret == "" { b := make([]byte, 32) rand.Read(b) secret = hex.EncodeToString(b) log.Println("JWT Secret automatisch generiert (nicht persistent)") } return &AuthHandler{db: database, secret: []byte(secret)} } func (a *AuthHandler) EnsureDefaultAdmin() { count, err := a.db.UserCount() if err != nil { log.Printf("User-Count Fehler: %v", err) return } if count == 0 { _, err := a.db.CreateUser("admin", "admin", "Administrator", "admin") if err != nil { log.Printf("Default-Admin anlegen fehlgeschlagen: %v", err) } else { log.Println("Default-Admin angelegt (admin/admin) - bitte Passwort aendern!") } } } func (a *AuthHandler) GenerateToken(user *models.User) (string, time.Time, error) { expiresAt := time.Now().Add(8 * time.Hour) claims := JWTClaims{ UserID: user.ID, Username: user.Username, Role: user.Role, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expiresAt), IssuedAt: jwt.NewNumericDate(time.Now()), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signed, err := token.SignedString(a.secret) return signed, expiresAt, err } func (a *AuthHandler) ValidateToken(tokenStr string) (*JWTClaims, error) { token, err := jwt.ParseWithClaims(tokenStr, &JWTClaims{}, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"]) } return a.secret, nil }) if err != nil { return nil, err } claims, ok := token.Claims.(*JWTClaims) if !ok || !token.Valid { return nil, fmt.Errorf("invalid token") } return claims, nil } // POST /api/v1/auth/login func (a *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { var req models.LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "Ungueltige Anfrage") return } user, err := a.db.GetUserByUsername(req.Username) if err != nil || user == nil { writeError(w, http.StatusUnauthorized, "Ungueltige Anmeldedaten") return } if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { writeError(w, http.StatusUnauthorized, "Ungueltige Anmeldedaten") return } token, expiresAt, err := a.GenerateToken(user) if err != nil { writeError(w, http.StatusInternalServerError, "Token-Generierung fehlgeschlagen") return } a.db.UpdateUserLastLogin(user.ID) writeJSON(w, http.StatusOK, models.LoginResponse{ Token: token, ExpiresAt: expiresAt.Format(time.RFC3339), User: *user, }) } // GET /api/v1/auth/me func (a *AuthHandler) Me(w http.ResponseWriter, r *http.Request) { claims, ok := r.Context().Value(UserContextKey).(*JWTClaims) if !ok { writeError(w, http.StatusUnauthorized, "Nicht authentifiziert") return } user, err := a.db.GetUserByUsername(claims.Username) if err != nil || user == nil { writeError(w, http.StatusNotFound, "User nicht gefunden") return } writeJSON(w, http.StatusOK, user) } // SetupAuthRoutes registriert Auth-Routes (ohne Auth-Middleware) func (a *AuthHandler) SetupAuthRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /api/v1/auth/login", a.Login) }