package auth import ( "errors" "fmt" "sync" "time" "gitea.avt.data-center.id/othman.suseno/atlas/internal/models" ) var ( ErrUserNotFound = errors.New("user not found") ErrUserExists = errors.New("user already exists") ErrInvalidCredentials = errors.New("invalid credentials") ) // UserStore manages users in memory type UserStore struct { mu sync.RWMutex users map[string]*models.User passwordHashes map[string]string // Maps user ID to password hash nextID int64 auth *Service } // NewUserStore creates a new user store func NewUserStore(auth *Service) *UserStore { store := &UserStore{ users: make(map[string]*models.User), passwordHashes: make(map[string]string), nextID: 1, auth: auth, } // Create default admin user if no users exist store.createDefaultAdmin() return store } // createDefaultAdmin creates a default administrator user func (s *UserStore) createDefaultAdmin() { // Check if any users exist s.mu.RLock() hasUsers := len(s.users) > 0 s.mu.RUnlock() if hasUsers { return } // Create default admin: admin / admin (should be changed on first login) hashedPassword, err := s.auth.HashPassword("admin") if err != nil { // If hashing fails, we can't create the admin user return } admin := &models.User{ ID: "user-1", Username: "admin", Role: models.RoleAdministrator, Active: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } // Store password hash s.mu.Lock() s.users[admin.ID] = admin s.passwordHashes[admin.ID] = hashedPassword s.nextID = 2 s.mu.Unlock() } // Create creates a new user func (s *UserStore) Create(username, email, password string, role models.Role) (*models.User, error) { s.mu.Lock() defer s.mu.Unlock() // Check if username already exists for _, user := range s.users { if user.Username == username { return nil, ErrUserExists } } id := fmt.Sprintf("user-%d", s.nextID) s.nextID++ hashedPassword, err := s.auth.HashPassword(password) if err != nil { return nil, err } user := &models.User{ ID: id, Username: username, Email: email, Role: role, Active: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } s.users[user.ID] = user s.passwordHashes[user.ID] = hashedPassword return user, nil } // GetByID returns a user by ID func (s *UserStore) GetByID(id string) (*models.User, error) { s.mu.RLock() defer s.mu.RUnlock() user, exists := s.users[id] if !exists { return nil, ErrUserNotFound } return user, nil } // GetByUsername returns a user by username func (s *UserStore) GetByUsername(username string) (*models.User, error) { s.mu.RLock() defer s.mu.RUnlock() for _, user := range s.users { if user.Username == username { return user, nil } } return nil, ErrUserNotFound } // Authenticate verifies username and password func (s *UserStore) Authenticate(username, password string) (*models.User, error) { user, err := s.GetByUsername(username) if err != nil { return nil, ErrInvalidCredentials } if !user.Active { return nil, errors.New("user account is disabled") } // Get stored password hash s.mu.RLock() storedHash, exists := s.passwordHashes[user.ID] s.mu.RUnlock() if !exists { // Fallback: for backward compatibility, check if it's the default admin // This allows existing installations to still work if username == "admin" && password == "admin" { // Store the default password hash for future use hashedPassword, err := s.auth.HashPassword("admin") if err == nil { s.mu.Lock() s.passwordHashes[user.ID] = hashedPassword s.mu.Unlock() } return user, nil } return nil, ErrInvalidCredentials } // Verify password against stored hash if !s.auth.VerifyPassword(storedHash, password) { return nil, ErrInvalidCredentials } return user, nil } // List returns all users func (s *UserStore) List() []models.User { s.mu.RLock() defer s.mu.RUnlock() users := make([]models.User, 0, len(s.users)) for _, user := range s.users { users = append(users, *user) } return users } // Update updates a user func (s *UserStore) Update(id string, email string, role models.Role, active bool) error { s.mu.Lock() defer s.mu.Unlock() user, exists := s.users[id] if !exists { return ErrUserNotFound } user.Email = email user.Role = role user.Active = active user.UpdatedAt = time.Now() return nil } // Delete deletes a user func (s *UserStore) Delete(id string) error { s.mu.Lock() defer s.mu.Unlock() if _, exists := s.users[id]; !exists { return ErrUserNotFound } delete(s.users, id) delete(s.passwordHashes, id) return nil } // UpdatePassword updates a user's password func (s *UserStore) UpdatePassword(id, newPassword string) error { s.mu.Lock() defer s.mu.Unlock() user, exists := s.users[id] if !exists { return ErrUserNotFound } hashedPassword, err := s.auth.HashPassword(newPassword) if err != nil { return err } // Store the new password hash s.passwordHashes[user.ID] = hashedPassword user.UpdatedAt = time.Now() return nil }