diff --git a/internal/api/webui_credentials.go b/internal/api/webui_credentials.go deleted file mode 100644 index 7a97f4f..0000000 --- a/internal/api/webui_credentials.go +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Copyright 2026 Safronov Grigorii - * - * Licensed under the CDDL, Version 1.0 (the "License"); - * you may not use this file except in compliance with the License. - * - * You may obtain a copy of the License at - * https://opensource.org/licenses/CDDL-1.0 - */ - -// Файл: internal/api/webui_credentials.go -// Назначение: Управление учётными данными для веб-интерфейса -// Хранит логин/пароль в скрытом файле .credentials в директории futriis -// Поддерживает множественных пользователей-администраторов - -package api - -import ( - "crypto/sha256" - "encoding/base64" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sync" - "time" -) - -// UserCredential представляет учётные данные одного пользователя -type UserCredential struct { - Password string `json:"password"` // хранится в виде хеша - Avatar string `json:"avatar,omitempty"` // base64 encoded avatar - IsAdmin bool `json:"is_admin"` // является ли администратором - CreatedAt int64 `json:"created_at"` // время создания - LastLogin int64 `json:"last_login,omitempty"` // время последнего входа -} - -// Credentials структура для хранения всех учётных данных -type Credentials struct { - Users map[string]*UserCredential `json:"users"` - Settings map[string]interface{} `json:"settings,omitempty"` -} - -// CredentialManager управляет учётными данными веб-интерфейса -type CredentialManager struct { - mu sync.RWMutex - credentials *Credentials - credFile string - currentUser string -} - -// NewCredentialManager создаёт новый менеджер учётных данных -func NewCredentialManager() *CredentialManager { - // Определяем путь к директории futriis - execPath, err := os.Executable() - if err != nil { - execPath = "." - } - futriisDir := filepath.Dir(execPath) - - // Ищем директорию futriis (поднимаемся вверх, если нужно) - for { - if _, err := os.Stat(filepath.Join(futriisDir, "futriis")); err == nil { - futriisDir = filepath.Join(futriisDir, "futriis") - break - } - parent := filepath.Dir(futriisDir) - if parent == futriisDir { - // Не нашли директорию futriis, создаём в текущей - futriisDir = filepath.Join(execPath, "futriis") - os.MkdirAll(futriisDir, 0700) - break - } - futriisDir = parent - } - - credFile := filepath.Join(futriisDir, ".credentials") - - return &CredentialManager{ - credentials: &Credentials{ - Users: make(map[string]*UserCredential), - Settings: make(map[string]interface{}), - }, - credFile: credFile, - } -} - -// hashPassword создаёт хеш пароля -func (cm *CredentialManager) hashPassword(password string) string { - hash := sha256.Sum256([]byte(password)) - return base64.StdEncoding.EncodeToString(hash[:]) -} - -// Load загружает учётные данные из файла -func (cm *CredentialManager) Load() error { - cm.mu.Lock() - defer cm.mu.Unlock() - - data, err := os.ReadFile(cm.credFile) - if err != nil { - if os.IsNotExist(err) { - return cm.createDefaultFile() - } - return err - } - - var creds Credentials - if err := json.Unmarshal(data, &creds); err != nil { - return err - } - - cm.credentials = &creds - if cm.credentials.Users == nil { - cm.credentials.Users = make(map[string]*UserCredential) - } - if cm.credentials.Settings == nil { - cm.credentials.Settings = make(map[string]interface{}) - } - - return nil -} - -// createDefaultFile создаёт файл с учётными данными по умолчанию -func (cm *CredentialManager) createDefaultFile() error { - // Создаём пользователя admin по умолчанию - cm.credentials.Users["admin"] = &UserCredential{ - Password: cm.hashPassword("admin"), - IsAdmin: true, - CreatedAt: time.Now().Unix(), - } - cm.credentials.Settings["version"] = "1.0" - - return cm.save() -} - -// CreateDefault создаёт учётные данные по умолчанию (если файл не существует) -func (cm *CredentialManager) CreateDefault() error { - cm.mu.Lock() - defer cm.mu.Unlock() - - // Проверяем, существует ли файл - if _, err := os.Stat(cm.credFile); err == nil { - return nil // файл уже существует - } - - cm.credentials.Users["admin"] = &UserCredential{ - Password: cm.hashPassword("admin"), - IsAdmin: true, - CreatedAt: time.Now().Unix(), - } - cm.credentials.Settings["version"] = "1.0" - - return cm.save() -} - -// save сохраняет учётные данные в файл -func (cm *CredentialManager) save() error { - data, err := json.MarshalIndent(cm.credentials, "", " ") - if err != nil { - return err - } - - // Устанавливаем права доступа 0600 (только владелец может читать/писать) - return os.WriteFile(cm.credFile, data, 0600) -} - -// Validate проверяет учётные данные -func (cm *CredentialManager) Validate(username, password string) bool { - cm.mu.RLock() - defer cm.mu.RUnlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return false - } - - user, exists := cm.credentials.Users[username] - if !exists { - return false - } - - return user.Password == cm.hashPassword(password) -} - -// IsAdmin проверяет, является ли пользователь администратором -func (cm *CredentialManager) IsAdmin(username string) bool { - cm.mu.RLock() - defer cm.mu.RUnlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return false - } - - user, exists := cm.credentials.Users[username] - if !exists { - return false - } - - return user.IsAdmin -} - -// CreateUser создаёт нового пользователя (только для администраторов) -func (cm *CredentialManager) CreateUser(username, password string, isAdmin bool) error { - if username == "" { - return fmt.Errorf("username cannot be empty") - } - - if len(password) < 4 { - return fmt.Errorf("password must be at least 4 characters") - } - - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil { - cm.credentials = &Credentials{ - Users: make(map[string]*UserCredential), - Settings: make(map[string]interface{}), - } - } - - if _, exists := cm.credentials.Users[username]; exists { - return fmt.Errorf("user %s already exists", username) - } - - cm.credentials.Users[username] = &UserCredential{ - Password: cm.hashPassword(password), - IsAdmin: isAdmin, - CreatedAt: time.Now().Unix(), - } - - return cm.save() -} - -// DeleteUser удаляет пользователя (только для администраторов) -func (cm *CredentialManager) DeleteUser(username string) error { - if username == "admin" { - return fmt.Errorf("cannot delete default admin user") - } - - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return fmt.Errorf("no users found") - } - - if _, exists := cm.credentials.Users[username]; !exists { - return fmt.Errorf("user %s not found", username) - } - - delete(cm.credentials.Users, username) - return cm.save() -} - -// ListUsers возвращает список всех пользователей (только для администраторов) -func (cm *CredentialManager) ListUsers() []map[string]interface{} { - cm.mu.RLock() - defer cm.mu.RUnlock() - - users := make([]map[string]interface{}, 0) - - for username, user := range cm.credentials.Users { - users = append(users, map[string]interface{}{ - "username": username, - "is_admin": user.IsAdmin, - "created_at": user.CreatedAt, - "last_login": user.LastLogin, - "has_avatar": user.Avatar != "", - }) - } - - return users -} - -// ChangePassword изменяет пароль пользователя -func (cm *CredentialManager) ChangePassword(username, currentPassword, newPassword string) error { - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return fmt.Errorf("credentials not loaded") - } - - user, exists := cm.credentials.Users[username] - if !exists { - return fmt.Errorf("user not found") - } - - if user.Password != cm.hashPassword(currentPassword) { - return fmt.Errorf("current password is incorrect") - } - - if len(newPassword) < 4 { - return fmt.Errorf("new password must be at least 4 characters") - } - - user.Password = cm.hashPassword(newPassword) - - return cm.save() -} - -// AdminChangePassword изменяет пароль пользователя (без проверки старого, только для админов) -func (cm *CredentialManager) AdminChangePassword(username, newPassword string) error { - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return fmt.Errorf("credentials not loaded") - } - - user, exists := cm.credentials.Users[username] - if !exists { - return fmt.Errorf("user not found") - } - - if len(newPassword) < 4 { - return fmt.Errorf("password must be at least 4 characters") - } - - user.Password = cm.hashPassword(newPassword) - - return cm.save() -} - -// SetAvatar устанавливает аватар для пользователя -func (cm *CredentialManager) SetAvatar(username, avatarBase64 string) error { - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return fmt.Errorf("credentials not loaded") - } - - user, exists := cm.credentials.Users[username] - if !exists { - return fmt.Errorf("user not found") - } - - user.Avatar = avatarBase64 - - return cm.save() -} - -// GetAvatar возвращает аватар пользователя -func (cm *CredentialManager) GetAvatar(username string) (string, error) { - cm.mu.RLock() - defer cm.mu.RUnlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return "", fmt.Errorf("credentials not loaded") - } - - user, exists := cm.credentials.Users[username] - if !exists { - return "", fmt.Errorf("user not found") - } - - return user.Avatar, nil -} - -// DeleteAvatar удаляет аватар пользователя -func (cm *CredentialManager) DeleteAvatar(username string) error { - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return fmt.Errorf("credentials not loaded") - } - - user, exists := cm.credentials.Users[username] - if !exists { - return fmt.Errorf("user not found") - } - - user.Avatar = "" - - return cm.save() -} - -// UpdateLastLogin обновляет время последнего входа пользователя -func (cm *CredentialManager) UpdateLastLogin(username string) { - cm.mu.Lock() - defer cm.mu.Unlock() - - if cm.credentials == nil || cm.credentials.Users == nil { - return - } - - user, exists := cm.credentials.Users[username] - if !exists { - return - } - - user.LastLogin = time.Now().Unix() - cm.save() // игнорируем ошибку, т.к. это не критично -} - -// GetCurrentUsername возвращает имя текущего пользователя -func (cm *CredentialManager) GetCurrentUsername() string { - cm.mu.RLock() - defer cm.mu.RUnlock() - - if cm.credentials == nil { - return "" - } - - // Возвращаем последнего вошедшего пользователя или admin по умолчанию - if cm.currentUser != "" { - return cm.currentUser - } - - return "admin" -} - -// SetCurrentUsername устанавливает имя текущего пользователя -func (cm *CredentialManager) SetCurrentUsername(username string) { - cm.mu.Lock() - defer cm.mu.Unlock() - cm.currentUser = username -} - -// GetCredentialsFile возвращает путь к файлу с учётными данными -func (cm *CredentialManager) GetCredentialsFile() string { - return cm.credFile -}