Phase 17g - Double Authentification: - TOTP avec Google Authenticator/Authy - QR code pour enrôlement - Codes de backup (10 codes usage unique) - Page /admin/security pour gestion 2FA - Page /admin/users avec Reset 2FA (super_admin) - 2FA obligatoire pour rôles configurés Phase 21 - Infrastructure Management: - SQLite pour données infra (/data/infra.db) - SSH Pool avec reconnexion auto - Gestion Incus (list, start, stop, restart, sync) - Gestion Nginx (test, reload, deploy, sync, certbot) - Interface admin /admin/infra - Formulaire ajout serveur - Page détail serveur avec containers et sites Fichiers créés: - internal/infra/ (db, models, migrations, repository, ssh, incus, nginx) - cmd/sogoms/admin/totp.go - cmd/sogoms/admin/handlers_2fa.go - cmd/sogoms/admin/handlers_infra.go - Templates: 2fa_*, security, users, infra, server_* 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
164 lines
4.4 KiB
Go
164 lines
4.4 KiB
Go
// Package admin gère la configuration et les permissions de l'interface d'administration.
|
|
package admin
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// AdminConfig représente la configuration complète de l'admin.
|
|
type AdminConfig struct {
|
|
Session SessionConfig `yaml:"session"`
|
|
RateLimit RateLimitConfig `yaml:"rate_limit"`
|
|
TwoFA TwoFAConfig `yaml:"two_fa"`
|
|
Users []AdminUser `yaml:"users"`
|
|
}
|
|
|
|
// TwoFAConfig configure l'authentification à deux facteurs.
|
|
type TwoFAConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
IssuerName string `yaml:"issuer_name"`
|
|
RequiredRoles []string `yaml:"required_roles"` // rôles obligés d'avoir 2FA
|
|
}
|
|
|
|
// SessionConfig configure les sessions.
|
|
type SessionConfig struct {
|
|
SecretFile string `yaml:"secret_file"`
|
|
MaxAge int `yaml:"max_age"` // secondes
|
|
CookieName string `yaml:"cookie_name"`
|
|
Secret string `yaml:"-"` // chargé depuis fichier
|
|
}
|
|
|
|
// RateLimitConfig configure le rate limiting.
|
|
type RateLimitConfig struct {
|
|
LoginMax int `yaml:"login_max"`
|
|
LoginWindow int `yaml:"login_window"` // secondes
|
|
}
|
|
|
|
// AdminUser représente un utilisateur admin.
|
|
type AdminUser struct {
|
|
Username string `yaml:"username"`
|
|
PasswordHash string `yaml:"password_hash"`
|
|
Role string `yaml:"role"`
|
|
Email string `yaml:"email"`
|
|
Apps []string `yaml:"apps,omitempty"` // pour app_admin
|
|
Permissions []string `yaml:"permissions,omitempty"` // pour app_admin
|
|
// 2FA
|
|
TwoFAEnabled bool `yaml:"two_fa_enabled,omitempty"`
|
|
TwoFASecret string `yaml:"two_fa_secret,omitempty"` // base32 encoded
|
|
BackupCodes []string `yaml:"backup_codes,omitempty"` // bcrypt hashed
|
|
}
|
|
|
|
// IsSuperAdmin retourne true si l'utilisateur est super_admin.
|
|
func (u *AdminUser) IsSuperAdmin() bool {
|
|
return u.Role == "super_admin"
|
|
}
|
|
|
|
// NeedsTwoFA retourne true si l'utilisateur doit utiliser 2FA.
|
|
func (u *AdminUser) NeedsTwoFA(cfg *TwoFAConfig) bool {
|
|
if !cfg.Enabled {
|
|
return false
|
|
}
|
|
// Si 2FA activé pour cet utilisateur
|
|
if u.TwoFAEnabled {
|
|
return true
|
|
}
|
|
// Si le rôle est dans la liste des rôles obligés
|
|
for _, role := range cfg.RequiredRoles {
|
|
if u.Role == role {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// LoadAdminConfig charge la configuration admin depuis un fichier YAML.
|
|
func LoadAdminConfig(path string) (*AdminConfig, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read admin config: %w", err)
|
|
}
|
|
|
|
var cfg AdminConfig
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parse admin config: %w", err)
|
|
}
|
|
|
|
// Valeurs par défaut
|
|
if cfg.Session.MaxAge == 0 {
|
|
cfg.Session.MaxAge = 3600 // 1 heure
|
|
}
|
|
if cfg.Session.CookieName == "" {
|
|
cfg.Session.CookieName = "sogoms_admin_sid"
|
|
}
|
|
if cfg.RateLimit.LoginMax == 0 {
|
|
cfg.RateLimit.LoginMax = 5
|
|
}
|
|
if cfg.RateLimit.LoginWindow == 0 {
|
|
cfg.RateLimit.LoginWindow = 60
|
|
}
|
|
if cfg.TwoFA.IssuerName == "" {
|
|
cfg.TwoFA.IssuerName = "SOGOMS Admin"
|
|
}
|
|
|
|
// Charger le secret de session depuis le fichier
|
|
if cfg.Session.SecretFile != "" {
|
|
secretData, err := os.ReadFile(cfg.Session.SecretFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read session secret: %w", err)
|
|
}
|
|
cfg.Session.Secret = strings.TrimSpace(string(secretData))
|
|
}
|
|
|
|
// Valider
|
|
if len(cfg.Users) == 0 {
|
|
return nil, fmt.Errorf("no users defined")
|
|
}
|
|
if cfg.Session.Secret == "" {
|
|
return nil, fmt.Errorf("session secret is required")
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
// GetUser retourne un utilisateur par son username.
|
|
func (cfg *AdminConfig) GetUser(username string) *AdminUser {
|
|
for i := range cfg.Users {
|
|
if cfg.Users[i].Username == username {
|
|
return &cfg.Users[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetUserByEmail retourne un utilisateur par son email.
|
|
func (cfg *AdminConfig) GetUserByEmail(email string) *AdminUser {
|
|
for i := range cfg.Users {
|
|
if cfg.Users[i].Email == email {
|
|
return &cfg.Users[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SaveAdminConfig sauvegarde la configuration admin dans un fichier YAML.
|
|
func SaveAdminConfig(cfg *AdminConfig, path string) error {
|
|
// Créer une copie sans le secret en mémoire pour la sauvegarde
|
|
saveCfg := *cfg
|
|
saveCfg.Session.Secret = "" // Ne pas sauvegarder le secret déchiffré
|
|
|
|
data, err := yaml.Marshal(&saveCfg)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal admin config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(path, data, 0600); err != nil {
|
|
return fmt.Errorf("write admin config: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|