mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-16 15:20:13 +01:00
Add default admin user and admin-only user creation
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 ✓
This commit is contained in:
parent
65f1265555
commit
4e9e0b4efa
5 changed files with 196 additions and 0 deletions
|
|
@ -105,6 +105,43 @@ go run cmd/tracks/main.go
|
|||
|
||||
The application will be available at `http://localhost:3000`
|
||||
|
||||
### Default Admin User
|
||||
|
||||
On first startup, the application automatically creates a default admin user:
|
||||
|
||||
- **Username**: `admin`
|
||||
- **Password**: `admin`
|
||||
|
||||
**Important**: Change the default password immediately after first login!
|
||||
|
||||
To login, make a POST request to `/api/auth/login`:
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"login":"admin","password":"admin"}'
|
||||
```
|
||||
|
||||
The response will include a JWT token that you can use for authenticated requests.
|
||||
|
||||
### Creating Additional Users
|
||||
|
||||
As an admin, you can create new users via the admin API:
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/admin/users \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
|
||||
-d '{
|
||||
"login": "newuser",
|
||||
"password": "password123",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"is_admin": false
|
||||
}'
|
||||
```
|
||||
|
||||
Set `"is_admin": true` to grant admin privileges to the new user.
|
||||
|
||||
### Configuration
|
||||
|
||||
The application can be configured using environment variables. See `.env.example` for all available options.
|
||||
|
|
@ -175,6 +212,35 @@ GET /api/me
|
|||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### Admin Endpoints (Requires Admin Role)
|
||||
|
||||
#### Create User
|
||||
```bash
|
||||
POST /api/admin/users
|
||||
Authorization: Bearer <admin_token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"login": "newuser",
|
||||
"password": "password123",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"is_admin": false
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"id": 2,
|
||||
"login": "newuser",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe",
|
||||
"is_admin": false,
|
||||
"created_at": "2024-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Todos
|
||||
|
||||
#### List Todos
|
||||
|
|
|
|||
|
|
@ -48,6 +48,11 @@ func main() {
|
|||
log.Fatal("Failed to run migrations:", err)
|
||||
}
|
||||
|
||||
// Create default admin user if no users exist
|
||||
if err := database.CreateDefaultAdmin(); err != nil {
|
||||
log.Fatal("Failed to create default admin:", err)
|
||||
}
|
||||
|
||||
// Set Gin mode
|
||||
gin.SetMode(cfg.Server.Mode)
|
||||
|
||||
|
|
@ -148,6 +153,14 @@ func setupRoutes(router *gin.Engine, cfg *config.Config) {
|
|||
}
|
||||
}
|
||||
|
||||
// Admin routes (requires authentication + admin role)
|
||||
admin := api.Group("/admin")
|
||||
admin.Use(middleware.AuthMiddleware(cfg.Auth.JWTSecret))
|
||||
admin.Use(middleware.AdminMiddleware())
|
||||
{
|
||||
admin.POST("/users", authHandler.CreateUser)
|
||||
}
|
||||
|
||||
// CORS middleware for development
|
||||
router.Use(func(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
|
|
|
|||
|
|
@ -91,3 +91,55 @@ func Close() error {
|
|||
func GetDB() *gorm.DB {
|
||||
return DB
|
||||
}
|
||||
|
||||
// CreateDefaultAdmin creates a default admin user if no users exist
|
||||
func CreateDefaultAdmin() error {
|
||||
if DB == nil {
|
||||
return fmt.Errorf("database not initialized")
|
||||
}
|
||||
|
||||
// Check if any users exist
|
||||
var count int64
|
||||
if err := DB.Model(&models.User{}).Count(&count).Error; err != nil {
|
||||
return fmt.Errorf("failed to count users: %w", err)
|
||||
}
|
||||
|
||||
// If users exist, don't create default admin
|
||||
if count > 0 {
|
||||
log.Println("Users already exist, skipping default admin creation")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Println("Creating default admin user (login: admin, password: admin)")
|
||||
|
||||
// Create default admin user
|
||||
admin := models.User{
|
||||
Login: "admin",
|
||||
FirstName: "Admin",
|
||||
LastName: "User",
|
||||
IsAdmin: true,
|
||||
AuthType: models.AuthTypeDatabase,
|
||||
Token: "default-admin-token",
|
||||
}
|
||||
|
||||
// Set password
|
||||
if err := admin.SetPassword("admin"); err != nil {
|
||||
return fmt.Errorf("failed to set admin password: %w", err)
|
||||
}
|
||||
|
||||
// Save admin user
|
||||
if err := DB.Create(&admin).Error; err != nil {
|
||||
return fmt.Errorf("failed to create admin user: %w", err)
|
||||
}
|
||||
|
||||
// Create default preference for admin
|
||||
preference := models.Preference{
|
||||
UserID: admin.ID,
|
||||
}
|
||||
if err := DB.Create(&preference).Error; err != nil {
|
||||
return fmt.Errorf("failed to create admin preference: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("Default admin user created successfully (ID: %d)", admin.ID)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,3 +94,20 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, gin.H{"token": token})
|
||||
}
|
||||
|
||||
// CreateUser handles POST /api/admin/users (admin only)
|
||||
func (h *AuthHandler) CreateUser(c *gin.Context) {
|
||||
var req services.CreateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.authService.CreateUser(req)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, user)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,15 @@ type RegisterRequest struct {
|
|||
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"`
|
||||
|
|
@ -146,3 +155,42 @@ func (s *AuthService) RefreshToken(userID uint) (string, error) {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue