Added comprehensive user management system: - User registration with validation (3-50 char username, 6+ char password) - JWT-based authentication with bcrypt password hashing - Admin authentication with master password - Password reset workflow with admin flagging - PostgreSQL repository implementation - SQLite repository for testing - Unified authentication service interface API Endpoints: - POST /api/v1/auth/register - User registration - POST /api/v1/auth/login - User/admin authentication - POST /api/v1/auth/password-reset/request - Request password reset - POST /api/v1/auth/password-reset/complete - Complete password reset - POST /api/v1/auth/validate - JWT token validation Security Features: - Password hashing with bcrypt - JWT token generation and validation - Admin claims in JWT tokens - Configurable token expiration - Input validation for all endpoints Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
82 lines
2.4 KiB
Go
82 lines
2.4 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"dance-lessons-coach/pkg/user"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
// UserHandler handles user management requests
|
|
type UserHandler struct {
|
|
userRepo user.UserRepository
|
|
passwordService user.PasswordService
|
|
}
|
|
|
|
// NewUserHandler creates a new user handler
|
|
func NewUserHandler(userRepo user.UserRepository, passwordService user.PasswordService) *UserHandler {
|
|
return &UserHandler{
|
|
userRepo: userRepo,
|
|
passwordService: passwordService,
|
|
}
|
|
}
|
|
|
|
// RegisterRoutes registers user routes
|
|
func (h *UserHandler) RegisterRoutes(router chi.Router) {
|
|
router.Post("/register", h.handleRegister)
|
|
}
|
|
|
|
// RegisterRequest represents a user registration request
|
|
|
|
// handleRegister handles user registration requests
|
|
func (h *UserHandler) handleRegister(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
var req RegisterRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
http.Error(w, `{"error":"invalid_request","message":"Invalid JSON request body"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Check if user already exists
|
|
exists, err := h.userRepo.UserExists(ctx, req.Username)
|
|
if err != nil {
|
|
log.Error().Ctx(ctx).Err(err).Msg("Failed to check if user exists")
|
|
http.Error(w, `{"error":"server_error","message":"Failed to process registration"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if exists {
|
|
http.Error(w, `{"error":"user_exists","message":"Username already taken"}`, http.StatusConflict)
|
|
return
|
|
}
|
|
|
|
// Hash password
|
|
hashedPassword, err := h.passwordService.HashPassword(ctx, req.Password)
|
|
if err != nil {
|
|
log.Error().Ctx(ctx).Err(err).Msg("Failed to hash password")
|
|
http.Error(w, `{"error":"server_error","message":"Failed to process registration"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Create user
|
|
newUser := &user.User{
|
|
Username: req.Username,
|
|
PasswordHash: hashedPassword,
|
|
IsAdmin: false,
|
|
}
|
|
|
|
if err := h.userRepo.CreateUser(ctx, newUser); err != nil {
|
|
log.Error().Ctx(ctx).Err(err).Msg("Failed to create user")
|
|
http.Error(w, `{"error":"server_error","message":"Failed to create user"}`, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Return success
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusCreated)
|
|
json.NewEncoder(w).Encode(map[string]string{"message": "User registered successfully"})
|
|
}
|