243 lines
5.0 KiB
Go
243 lines
5.0 KiB
Go
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
|
|
}
|