Files
atlas/internal/auth/user_store.go
Othman H. Suseno 27b0400ef3
Some checks failed
CI / test-build (push) Has been cancelled
fix authentication
2025-12-16 01:15:20 +07:00

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
}