Files
futriix/internal/api/webui_credentials.go

273 lines
7.8 KiB
Go
Raw Normal View History

2026-05-14 17:11:53 +00:00
/*
* 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"
)
// CredentialManager управляет учётными данными веб-интерфейса
type CredentialManager struct {
mu sync.RWMutex
credentials *Credentials
credFile string
currentUser string
}
// Credentials структура для хранения учётных данных
type Credentials struct {
Username string `json:"username"`
Password string `json:"password"` // хранится в виде хеша
Avatar map[string]string `json:"avatar,omitempty"` // username -> base64 avatar
}
// 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{
Username: "admin",
Password: "",
Avatar: make(map[string]string),
},
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.Avatar == nil {
cm.credentials.Avatar = make(map[string]string)
}
return nil
}
// createDefaultFile создаёт файл с учётными данными по умолчанию
func (cm *CredentialManager) createDefaultFile() error {
cm.credentials.Username = "admin"
cm.credentials.Password = cm.hashPassword("admin")
cm.credentials.Avatar = make(map[string]string)
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.Username = "admin"
cm.credentials.Password = cm.hashPassword("admin")
cm.credentials.Avatar = make(map[string]string)
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 {
return false
}
if cm.credentials.Username != username {
return false
}
return cm.credentials.Password == cm.hashPassword(password)
}
// ChangePassword изменяет пароль пользователя
func (cm *CredentialManager) ChangePassword(username, currentPassword, newPassword string) error {
cm.mu.Lock()
defer cm.mu.Unlock()
if cm.credentials == nil {
return fmt.Errorf("credentials not loaded")
}
if cm.credentials.Username != username {
return fmt.Errorf("user not found")
}
if cm.credentials.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")
}
cm.credentials.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 {
return fmt.Errorf("credentials not loaded")
}
if cm.credentials.Username != username {
return fmt.Errorf("user not found")
}
if cm.credentials.Avatar == nil {
cm.credentials.Avatar = make(map[string]string)
}
cm.credentials.Avatar[username] = avatarBase64
return cm.save()
}
// GetAvatar возвращает аватар пользователя
func (cm *CredentialManager) GetAvatar(username string) (string, error) {
cm.mu.RLock()
defer cm.mu.RUnlock()
if cm.credentials == nil {
return "", fmt.Errorf("credentials not loaded")
}
if cm.credentials.Username != username {
return "", fmt.Errorf("user not found")
}
if cm.credentials.Avatar == nil {
return "", nil
}
avatar, ok := cm.credentials.Avatar[username]
if !ok {
return "", nil
}
return avatar, nil
}
// DeleteAvatar удаляет аватар пользователя
func (cm *CredentialManager) DeleteAvatar(username string) error {
cm.mu.Lock()
defer cm.mu.Unlock()
if cm.credentials == nil {
return fmt.Errorf("credentials not loaded")
}
if cm.credentials.Username != username {
return fmt.Errorf("user not found")
}
if cm.credentials.Avatar != nil {
delete(cm.credentials.Avatar, username)
}
return cm.save()
}
// GetCurrentUsername возвращает имя текущего пользователя
func (cm *CredentialManager) GetCurrentUsername() string {
cm.mu.RLock()
defer cm.mu.RUnlock()
if cm.credentials == nil {
return ""
}
return cm.credentials.Username
}
// GetCredentialsFile возвращает путь к файлу с учётными данными
func (cm *CredentialManager) GetCredentialsFile() string {
return cm.credFile
}