package auth import ( "context" "crypto/sha256" "database/sql" "encoding/hex" "log" "os" "time" "github.com/bigtux/ophion/internal/security" "github.com/gofiber/fiber/v2" "github.com/google/uuid" ) // ═══════════════════════════════════════════════════════════ // 🔐 AUTH HANDLERS // ═══════════════════════════════════════════════════════════ type AuthHandler struct { db *sql.DB authService *AuthService } type User struct { ID string `json:"id"` Email string `json:"email"` PasswordHash string `json:"-"` Role string `json:"role"` CreatedAt time.Time `json:"created_at"` } type APIKey struct { ID string `json:"id"` KeyHash string `json:"-"` Prefix string `json:"prefix"` Name string `json:"name"` UserID string `json:"user_id"` CreatedAt time.Time `json:"created_at"` LastUsed *time.Time `json:"last_used,omitempty"` } // NewAuthHandler cria novo handler de autenticação func NewAuthHandler(db *sql.DB, jwtSecret string) *AuthHandler { config := AuthConfig{ JWTSecret: []byte(jwtSecret), JWTExpiration: 24 * time.Hour, RefreshExpiration: 7 * 24 * time.Hour, Issuer: "ophion", } // Implementação simples do APIKeyStore store := &DBAPIKeyStore{db: db} authService := NewAuthService(config, nil, store) return &AuthHandler{ db: db, authService: authService, } } // GetAuthService retorna o serviço de autenticação func (h *AuthHandler) GetAuthService() *AuthService { return h.authService } // ───────────────────────────────────────────────────────────── // Login // ───────────────────────────────────────────────────────────── type LoginRequest struct { Email string `json:"email"` Password string `json:"password"` } type LoginResponse struct { Token string `json:"token"` ExpiresIn int `json:"expires_in"` User struct { ID string `json:"id"` Email string `json:"email"` Role string `json:"role"` } `json:"user"` } func (h *AuthHandler) Login(c *fiber.Ctx) error { var req LoginRequest if err := c.BodyParser(&req); err != nil { return c.Status(400).JSON(fiber.Map{"error": "Invalid request body"}) } if req.Email == "" || req.Password == "" { return c.Status(400).JSON(fiber.Map{"error": "Email and password are required"}) } // Sanitize email email, err := security.SanitizeEmail(req.Email) if err != nil { return c.Status(400).JSON(fiber.Map{"error": "Invalid email format"}) } // Check brute force protection allowed, remaining, _ := h.authService.CheckLoginAllowed(email) if !allowed { return c.Status(429).JSON(fiber.Map{ "error": "Too many failed attempts", "message": "Account temporarily locked. Try again in " + remaining.String(), }) } // Find user var user User err = h.db.QueryRow(` SELECT id, email, password_hash, role, created_at FROM users WHERE email = $1 `, email).Scan(&user.ID, &user.Email, &user.PasswordHash, &user.Role, &user.CreatedAt) if err == sql.ErrNoRows { h.authService.RecordLoginAttempt(email, false) return c.Status(401).JSON(fiber.Map{"error": "Invalid credentials"}) } if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Database error"}) } // Verify password if !security.VerifyPassword(req.Password, user.PasswordHash) { blocked, remaining := h.authService.RecordLoginAttempt(email, false) if blocked { return c.Status(429).JSON(fiber.Map{ "error": "Too many failed attempts", "message": "Account temporarily locked", }) } return c.Status(401).JSON(fiber.Map{ "error": "Invalid credentials", "remaining_attempts": remaining, }) } // Successful login h.authService.RecordLoginAttempt(email, true) // Generate token token, _, err := h.authService.GenerateTokenPair(user.ID, "", user.Email, user.Role) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Failed to generate token"}) } resp := LoginResponse{ Token: token, ExpiresIn: 86400, // 24 hours } resp.User.ID = user.ID resp.User.Email = user.Email resp.User.Role = user.Role return c.JSON(resp) } // ───────────────────────────────────────────────────────────── // Register // ───────────────────────────────────────────────────────────── type RegisterRequest struct { Email string `json:"email"` Password string `json:"password"` } func (h *AuthHandler) Register(c *fiber.Ctx) error { var req RegisterRequest if err := c.BodyParser(&req); err != nil { return c.Status(400).JSON(fiber.Map{"error": "Invalid request body"}) } if req.Email == "" || req.Password == "" { return c.Status(400).JSON(fiber.Map{"error": "Email and password are required"}) } // Sanitize email email, err := security.SanitizeEmail(req.Email) if err != nil { return c.Status(400).JSON(fiber.Map{"error": "Invalid email format"}) } // Check password (relaxed policy for simplicity) if len(req.Password) < 6 { return c.Status(400).JSON(fiber.Map{"error": "Password must be at least 6 characters"}) } // Check if any users exist (first user becomes admin) var userCount int h.db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&userCount) role := "user" if userCount == 0 { role = "admin" } // Check if email already exists var existingID string err = h.db.QueryRow(`SELECT id FROM users WHERE email = $1`, email).Scan(&existingID) if err != sql.ErrNoRows { return c.Status(409).JSON(fiber.Map{"error": "Email already registered"}) } // Hash password passwordHash, err := security.HashPassword(req.Password) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Failed to hash password"}) } // Create user userID := uuid.New().String() _, err = h.db.Exec(` INSERT INTO users (id, email, password_hash, role, created_at) VALUES ($1, $2, $3, $4, NOW()) `, userID, email, passwordHash, role) if err != nil { log.Printf("Error creating user: %v", err) return c.Status(500).JSON(fiber.Map{"error": "Failed to create user"}) } // Generate token token, _, err := h.authService.GenerateTokenPair(userID, "", email, role) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Failed to generate token"}) } return c.Status(201).JSON(fiber.Map{ "message": "User registered successfully", "token": token, "user": fiber.Map{ "id": userID, "email": email, "role": role, }, }) } // ───────────────────────────────────────────────────────────── // API Keys // ───────────────────────────────────────────────────────────── type CreateAPIKeyRequest struct { Name string `json:"name"` } func (h *AuthHandler) CreateAPIKey(c *fiber.Ctx) error { var req CreateAPIKeyRequest if err := c.BodyParser(&req); err != nil { return c.Status(400).JSON(fiber.Map{"error": "Invalid request body"}) } if req.Name == "" { return c.Status(400).JSON(fiber.Map{"error": "Name is required"}) } userID := GetUserID(c) if userID == "" { return c.Status(401).JSON(fiber.Map{"error": "Unauthorized"}) } // Generate API key key, keyHash, prefix := security.GenerateAPIKey() keyID := uuid.New().String() _, err := h.db.Exec(` INSERT INTO api_keys (id, key_hash, prefix, name, user_id, created_at) VALUES ($1, $2, $3, $4, $5, NOW()) `, keyID, keyHash, prefix, req.Name, userID) if err != nil { log.Printf("Error creating API key: %v", err) return c.Status(500).JSON(fiber.Map{"error": "Failed to create API key"}) } return c.Status(201).JSON(fiber.Map{ "message": "API key created successfully", "key": key, // Only shown once! "id": keyID, "name": req.Name, "prefix": prefix, }) } func (h *AuthHandler) ListAPIKeys(c *fiber.Ctx) error { userID := GetUserID(c) role := GetRole(c) var rows *sql.Rows var err error // Admin can see all keys if role == "admin" { rows, err = h.db.Query(` SELECT id, prefix, name, user_id, created_at, last_used FROM api_keys ORDER BY created_at DESC `) } else { rows, err = h.db.Query(` SELECT id, prefix, name, user_id, created_at, last_used FROM api_keys WHERE user_id = $1 ORDER BY created_at DESC `, userID) } if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Database error"}) } defer rows.Close() var keys []fiber.Map for rows.Next() { var k APIKey if err := rows.Scan(&k.ID, &k.Prefix, &k.Name, &k.UserID, &k.CreatedAt, &k.LastUsed); err == nil { keys = append(keys, fiber.Map{ "id": k.ID, "prefix": k.Prefix, "name": k.Name, "user_id": k.UserID, "created_at": k.CreatedAt, "last_used": k.LastUsed, }) } } if keys == nil { keys = []fiber.Map{} } return c.JSON(fiber.Map{"api_keys": keys}) } func (h *AuthHandler) DeleteAPIKey(c *fiber.Ctx) error { keyID := c.Params("id") userID := GetUserID(c) role := GetRole(c) var ownerID string err := h.db.QueryRow(`SELECT user_id FROM api_keys WHERE id = $1`, keyID).Scan(&ownerID) if err == sql.ErrNoRows { return c.Status(404).JSON(fiber.Map{"error": "API key not found"}) } if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Database error"}) } // Check permission if role != "admin" && ownerID != userID { return c.Status(403).JSON(fiber.Map{"error": "Permission denied"}) } _, err = h.db.Exec(`DELETE FROM api_keys WHERE id = $1`, keyID) if err != nil { return c.Status(500).JSON(fiber.Map{"error": "Failed to delete API key"}) } return c.JSON(fiber.Map{"message": "API key deleted"}) } // ───────────────────────────────────────────────────────────── // Me (current user) // ───────────────────────────────────────────────────────────── func (h *AuthHandler) Me(c *fiber.Ctx) error { userID := GetUserID(c) email := c.Locals("email") role := GetRole(c) return c.JSON(fiber.Map{ "id": userID, "email": email, "role": role, }) } // ═══════════════════════════════════════════════════════════ // 🗄️ DB API KEY STORE // ═══════════════════════════════════════════════════════════ type DBAPIKeyStore struct { db *sql.DB } func (s *DBAPIKeyStore) ValidateKey(ctx context.Context, keyHash string) (*APIKeyInfo, error) { var info APIKeyInfo err := s.db.QueryRowContext(ctx, ` SELECT id, name FROM api_keys WHERE key_hash = $1 `, keyHash).Scan(&info.ID, &info.Name) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, err } info.Scopes = []string{"*"} // Full access for now return &info, nil } func (s *DBAPIKeyStore) UpdateLastUsed(ctx context.Context, keyID string) error { _, err := s.db.ExecContext(ctx, ` UPDATE api_keys SET last_used = NOW() WHERE id = $1 `, keyID) return err } // ═══════════════════════════════════════════════════════════ // 🔧 ADMIN USER SETUP // ═══════════════════════════════════════════════════════════ // CreateDefaultAdmin cria usuário admin padrão se não existir func CreateDefaultAdmin(db *sql.DB) error { // Check if any users exist var count int if err := db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&count); err != nil { // Table might not exist yet return nil } if count > 0 { return nil // Admin already exists } adminEmail := "admin@ophion.local" adminPassword := os.Getenv("ADMIN_PASSWORD") if adminPassword == "" { adminPassword = "ophion123" } passwordHash, err := security.HashPassword(adminPassword) if err != nil { return err } userID := uuid.New().String() _, err = db.Exec(` INSERT INTO users (id, email, password_hash, role, created_at) VALUES ($1, $2, $3, 'admin', NOW()) ON CONFLICT (email) DO NOTHING `, userID, adminEmail, passwordHash) if err != nil { return err } log.Printf("✓ Default admin user created: %s", adminEmail) return nil } // HashAPIKey creates SHA256 hash of API key func HashAPIKey(key string) string { hash := sha256.Sum256([]byte(key)) return hex.EncodeToString(hash[:]) }