diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d6a8a4..3f52ab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.1.0.0048 — 2026-07-15 + +### Fixed +- CSV import: loading spinner with "Importing..." message during upload +- CSV import: better error display and proper modal close after completion +- Admin panel: Import modal shows results and allows closing on success/failure + ## 0.1.0.0046 — 2026-07-11 ### Added diff --git a/VERSION b/VERSION index 9ee9f65..f03fd7c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0.0045 +0.1.0.0048 diff --git a/config/authelia/configuration.yml b/config/authelia/configuration.yml index 9b6c705..73263b0 100644 --- a/config/authelia/configuration.yml +++ b/config/authelia/configuration.yml @@ -35,6 +35,12 @@ access_control: - "group:admins" policy: one_factor + # Users with TFA enforcement — two-factor required + - domain: "app.{DOMAIN}" + subject: + - "group:tfa_required" + policy: two_factor + # Everything else — any authenticated user - domain: "app.{DOMAIN}" policy: one_factor @@ -65,7 +71,8 @@ storage: notifier: smtp: - address: "submission://{SMTP_HOST}:{SMTP_PORT}" + host: "{SMTP_HOST}" + port: {SMTP_PORT} username: "{SMTP_USER}" password: "{SMTP_PASS}" sender: "{SMTP_USER}" diff --git a/main.go b/main.go index c0cad7a..7333541 100644 --- a/main.go +++ b/main.go @@ -916,6 +916,44 @@ func enforceTOTP(w http.ResponseWriter, r *http.Request) { exec.Command("sqlite3", "/opt/nextworkspace/data/authelia/db.sqlite", "INSERT OR REPLACE INTO user_preferences (username, method) VALUES ('"+user+"', 'totp')").Run() + // Add user to tfa_required group (enforces two_factor via access_control) + token = os.Getenv("AUTHELIA_SECRET") + userReq, _ := http.NewRequest("GET", "http://authelia:8080/api/users/"+user, nil) + userReq.Header.Set("Authorization", "Bearer "+token) + if userResp, err := http.DefaultClient.Do(userReq); err == nil && userResp.StatusCode == 200 { + var ud struct { + Username string `json:"username"` + DisplayName string `json:"display_name"` + Email string `json:"email"` + Groups []string `json:"groups"` + } + json.NewDecoder(userResp.Body).Decode(&ud) + userResp.Body.Close() + + hasTFA := false + for _, g := range ud.Groups { + if g == "tfa_required" { + hasTFA = true + break + } + } + if !hasTFA { + ud.Groups = append(ud.Groups, "tfa_required") + body, _ := json.Marshal(map[string]interface{}{"users": []interface{}{ud}}) + + delR, _ := http.NewRequest("DELETE", "http://authelia:8080/api/users/"+user, nil) + delR.Header.Set("Authorization", "Bearer "+token) + http.DefaultClient.Do(delR) + + time.Sleep(1500 * time.Millisecond) + + crR, _ := http.NewRequest("POST", "http://authelia:8080/api/users/bulk", bytes.NewReader(body)) + crR.Header.Set("Authorization", "Bearer "+token) + crR.Header.Set("Content-Type", "application/json") + http.DefaultClient.Do(crR) + } + } + if apiOk { json.NewEncoder(w).Encode(map[string]interface{}{ "status": "enforced", @@ -1714,21 +1752,28 @@ const adminHTML = `