mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 15:20:13 +01:00
Features added: - Automatic creation of default admin user on first startup (login: admin, password: admin) - Admin-only endpoint POST /api/admin/users for creating new users - Admin users can set is_admin flag when creating users - Non-admin users are blocked from accessing admin endpoints Implementation: - Added CreateDefaultAdmin() function in internal/database/database.go - Checks if any users exist, creates admin only if database is empty - Admin user: login "admin", password "admin", is_admin true - Added CreateUser() method to auth service for admin user creation - Added CreateUser() handler to auth handler - Added /api/admin/users endpoint with AuthMiddleware + AdminMiddleware - Updated README_GOLANG.md with: - Default admin credentials - Instructions for creating additional users - Admin API documentation Security: - Default admin password should be changed after first login - AdminMiddleware ensures only users with is_admin=true can access admin routes - Non-admin users receive 403 Forbidden when accessing admin endpoints Tested: - Default admin creation on startup ✓ - Admin login with default credentials ✓ - Admin can create new users ✓ - New users can login ✓ - Non-admin users blocked from admin endpoints ✓
196 lines
4.7 KiB
Go
196 lines
4.7 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/TracksApp/tracks/internal/database"
|
|
"github.com/TracksApp/tracks/internal/models"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/google/uuid"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// AuthService handles authentication logic
|
|
type AuthService struct {
|
|
jwtSecret string
|
|
}
|
|
|
|
// NewAuthService creates a new AuthService
|
|
func NewAuthService(jwtSecret string) *AuthService {
|
|
return &AuthService{
|
|
jwtSecret: jwtSecret,
|
|
}
|
|
}
|
|
|
|
// LoginRequest represents a login request
|
|
type LoginRequest struct {
|
|
Login string `json:"login" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
}
|
|
|
|
// RegisterRequest represents a registration request
|
|
type RegisterRequest struct {
|
|
Login string `json:"login" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
}
|
|
|
|
// CreateUserRequest represents an admin user creation request
|
|
type CreateUserRequest struct {
|
|
Login string `json:"login" binding:"required"`
|
|
Password string `json:"password" binding:"required"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
IsAdmin bool `json:"is_admin"`
|
|
}
|
|
|
|
// AuthResponse represents an authentication response
|
|
type AuthResponse struct {
|
|
Token string `json:"token"`
|
|
User *models.User `json:"user"`
|
|
}
|
|
|
|
// Login authenticates a user and returns a JWT token
|
|
func (s *AuthService) Login(req LoginRequest) (*AuthResponse, error) {
|
|
var user models.User
|
|
|
|
// Find user by login
|
|
if err := database.DB.Where("login = ?", req.Login).First(&user).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, errors.New("invalid login or password")
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
// Check password
|
|
if !user.CheckPassword(req.Password) {
|
|
return nil, errors.New("invalid login or password")
|
|
}
|
|
|
|
// Generate token
|
|
token, err := s.GenerateToken(&user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &AuthResponse{
|
|
Token: token,
|
|
User: &user,
|
|
}, nil
|
|
}
|
|
|
|
// Register creates a new user account
|
|
func (s *AuthService) Register(req RegisterRequest) (*AuthResponse, error) {
|
|
// Check if user already exists
|
|
var existingUser models.User
|
|
if err := database.DB.Where("login = ?", req.Login).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("user already exists")
|
|
}
|
|
|
|
// Create new user
|
|
user := models.User{
|
|
Login: req.Login,
|
|
FirstName: req.FirstName,
|
|
LastName: req.LastName,
|
|
AuthType: models.AuthTypeDatabase,
|
|
Token: uuid.New().String(),
|
|
}
|
|
|
|
// Set password
|
|
if err := user.SetPassword(req.Password); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Save user
|
|
if err := database.DB.Create(&user).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create default preference
|
|
preference := models.Preference{
|
|
UserID: user.ID,
|
|
}
|
|
if err := database.DB.Create(&preference).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate token
|
|
token, err := s.GenerateToken(&user)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &AuthResponse{
|
|
Token: token,
|
|
User: &user,
|
|
}, nil
|
|
}
|
|
|
|
// GenerateToken generates a JWT token for a user
|
|
func (s *AuthService) GenerateToken(user *models.User) (string, error) {
|
|
claims := jwt.MapClaims{
|
|
"user_id": user.ID,
|
|
"login": user.Login,
|
|
"exp": time.Now().Add(time.Hour * 24 * 7).Unix(), // 7 days
|
|
"iat": time.Now().Unix(),
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
|
return token.SignedString([]byte(s.jwtSecret))
|
|
}
|
|
|
|
// RefreshToken refreshes the user's API token
|
|
func (s *AuthService) RefreshToken(userID uint) (string, error) {
|
|
var user models.User
|
|
if err := database.DB.First(&user, userID).Error; err != nil {
|
|
return "", err
|
|
}
|
|
|
|
user.Token = uuid.New().String()
|
|
if err := database.DB.Save(&user).Error; err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return user.Token, nil
|
|
}
|
|
|
|
// CreateUser creates a new user (admin only)
|
|
func (s *AuthService) CreateUser(req CreateUserRequest) (*models.User, error) {
|
|
// Check if user already exists
|
|
var existingUser models.User
|
|
if err := database.DB.Where("login = ?", req.Login).First(&existingUser).Error; err == nil {
|
|
return nil, errors.New("user already exists")
|
|
}
|
|
|
|
// Create new user
|
|
user := models.User{
|
|
Login: req.Login,
|
|
FirstName: req.FirstName,
|
|
LastName: req.LastName,
|
|
IsAdmin: req.IsAdmin,
|
|
AuthType: models.AuthTypeDatabase,
|
|
Token: uuid.New().String(),
|
|
}
|
|
|
|
// Set password
|
|
if err := user.SetPassword(req.Password); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Save user
|
|
if err := database.DB.Create(&user).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create default preference
|
|
preference := models.Preference{
|
|
UserID: user.ID,
|
|
}
|
|
if err := database.DB.Create(&preference).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &user, nil
|
|
}
|