151 lines
3.7 KiB
Go
151 lines
3.7 KiB
Go
package admin
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// AutheliaUserDB represents the full structure of Authelia's users_database.yml.
|
|
type AutheliaUserDB struct {
|
|
Users map[string]AutheliaUserEntry `yaml:"users"`
|
|
}
|
|
|
|
// AutheliaUserEntry represents a single user entry in Authelia's YAML.
|
|
type AutheliaUserEntry struct {
|
|
DisplayName string `yaml:"displayname,omitempty"`
|
|
Password string `yaml:"password"`
|
|
Email string `yaml:"email,omitempty"`
|
|
Groups []string `yaml:"groups,omitempty"`
|
|
Disabled bool `yaml:"disabled,omitempty"`
|
|
}
|
|
|
|
// SyncWriter handles writing the user database to Authelia's YAML format.
|
|
type SyncWriter struct {
|
|
usersDBPath string
|
|
store *UserStore
|
|
}
|
|
|
|
// NewSyncWriter creates a new SyncWriter.
|
|
func NewSyncWriter(usersDBPath string, store *UserStore) *SyncWriter {
|
|
return &SyncWriter{
|
|
usersDBPath: usersDBPath,
|
|
store: store,
|
|
}
|
|
}
|
|
|
|
// Sync writes the current user store to Authelia's users_database.yml.
|
|
func (sw *SyncWriter) Sync() error {
|
|
syncUsers, err := sw.store.SyncSnapshot()
|
|
if err != nil {
|
|
return fmt.Errorf("get sync snapshot: %w", err)
|
|
}
|
|
|
|
db := AutheliaUserDB{
|
|
Users: make(map[string]AutheliaUserEntry, len(syncUsers)),
|
|
}
|
|
|
|
for _, u := range syncUsers {
|
|
db.Users[u.Username] = AutheliaUserEntry{
|
|
DisplayName: u.DisplayName,
|
|
Password: u.Password,
|
|
Email: u.Email,
|
|
Groups: u.Groups,
|
|
Disabled: u.Disabled,
|
|
}
|
|
}
|
|
|
|
// Ensure the target directory exists
|
|
dir := filepath.Dir(sw.usersDBPath)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return fmt.Errorf("create authelia data directory: %w", err)
|
|
}
|
|
|
|
data, err := yaml.Marshal(&db)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal users database: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(sw.usersDBPath, data, 0644); err != nil {
|
|
return fmt.Errorf("write users database: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Bootstrap imports existing Authelia users into the SQLite store.
|
|
// This runs on first initialization to adopt existing users.
|
|
func (sw *SyncWriter) Bootstrap() (int, error) {
|
|
data, err := os.ReadFile(sw.usersDBPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return 0, nil // No existing file, nothing to bootstrap
|
|
}
|
|
return 0, fmt.Errorf("read authelia users database: %w", err)
|
|
}
|
|
|
|
var db AutheliaUserDB
|
|
if err := yaml.Unmarshal(data, &db); err != nil {
|
|
return 0, fmt.Errorf("parse authelia users database: %w", err)
|
|
}
|
|
|
|
imported := 0
|
|
for username, entry := range db.Users {
|
|
existing, _ := sw.store.GetByUsername(username)
|
|
if existing != nil {
|
|
continue // Already exists, skip
|
|
}
|
|
|
|
// Build groups string
|
|
groups := ""
|
|
for i, g := range entry.Groups {
|
|
if i > 0 {
|
|
groups += ","
|
|
}
|
|
groups += g
|
|
}
|
|
|
|
// Determine role from groups
|
|
role := "user"
|
|
if containsGroup(groups, "admins") {
|
|
role = "admin"
|
|
}
|
|
|
|
_, err := sw.store.GetDB().Exec(`
|
|
INSERT INTO users (username, display_name, email, role, groups, password_hash, disabled, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
`, username, entry.DisplayName, entry.Email, role, groups, entry.Password, entry.Disabled)
|
|
if err != nil {
|
|
return imported, fmt.Errorf("import user %s: %w", username, err)
|
|
}
|
|
imported++
|
|
}
|
|
|
|
return imported, nil
|
|
}
|
|
|
|
// FixRoles updates existing users' roles based on their groups.
|
|
func (sw *SyncWriter) FixRoles() (int, error) {
|
|
users, err := sw.store.List()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fixed := 0
|
|
for _, u := range users {
|
|
expectedRole := "user"
|
|
if containsGroup(u.Groups, "admins") {
|
|
expectedRole = "admin"
|
|
}
|
|
if u.Role != expectedRole {
|
|
_, err := sw.store.GetDB().Exec("UPDATE users SET role = ? WHERE username = ?", expectedRole, u.Username)
|
|
if err != nil {
|
|
return fixed, err
|
|
}
|
|
fixed++
|
|
}
|
|
}
|
|
return fixed, nil
|
|
}
|