Upload files to "internal/api"
This commit is contained in:
2162
internal/api/webui.go
Normal file
2162
internal/api/webui.go
Normal file
File diff suppressed because it is too large
Load Diff
272
internal/api/webui_credentials.go
Normal file
272
internal/api/webui_credentials.go
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user