Files
dance-lessons-coach/pkg/user/api/user_handler.go
Gabriel Radureau 52a4ce4139 feat: implement user authentication system with JWT and PostgreSQL
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>
2026-04-09 00:25:43 +02:00

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"})
}