add MFA, user web mail portal
This commit is contained in:
+34
-4
@@ -3,6 +3,7 @@ package webui
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"mailgoserver/internal/db"
|
||||
@@ -53,6 +54,21 @@ func scopeFromContext(r *http.Request) accessScope {
|
||||
return s
|
||||
}
|
||||
|
||||
// requireGlobalAdmin gates a handler behind the current admin's scope being global —
|
||||
// used for server-wide settings (Server Settings, Let's Encrypt) that a domain-scoped
|
||||
// admin has no business reading or changing, even if they can guess the URL. 404 (not
|
||||
// 403) matches requireDomainAccess's reasoning: a scoped admin shouldn't be able to
|
||||
// tell "doesn't exist" from "exists but isn't mine" by probing.
|
||||
func (a *App) requireGlobalAdmin(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !scopeFromContext(r).Global {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// requireDomainAccess checks the current admin's scope covers domainID; if not, it
|
||||
// writes a 404 (not 403 — a scoped admin shouldn't be able to distinguish "doesn't
|
||||
// exist" from "exists but isn't mine" by probing IDs) and returns false, matching the
|
||||
@@ -138,13 +154,16 @@ func (a *App) requireAuth(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
needsMFA := user.TOTPEnabled
|
||||
if !needsMFA {
|
||||
// hasMFA: this account already has a second factor configured (TOTP or a
|
||||
// passkey) — distinct from sess.MFAVerified, which is about *this session*
|
||||
// having satisfied it.
|
||||
hasMFA := user.TOTPEnabled
|
||||
if !hasMFA {
|
||||
if n, _ := a.DB.CountWebAuthnCredentials(user.ID); n > 0 {
|
||||
needsMFA = true
|
||||
hasMFA = true
|
||||
}
|
||||
}
|
||||
if needsMFA && !sess.MFAVerified {
|
||||
if hasMFA && !sess.MFAVerified {
|
||||
http.Redirect(w, r, Prefix+"/login/mfa", http.StatusFound)
|
||||
return
|
||||
}
|
||||
@@ -154,6 +173,17 @@ func (a *App) requireAuth(next http.Handler) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
// enforce_admin_mfa applies to every admin, global or scoped — force setup at
|
||||
// /account (which has the TOTP/passkey enrollment forms) before anything else
|
||||
// is reachable, mirroring the must_change_password gate above. Checked after
|
||||
// must_change_password so a brand-new admin sets a real password first.
|
||||
if !hasMFA && !user.MustChangePassword && a.Cfg.Section("Auth").Key("enforce_admin_mfa").MustBool(false) {
|
||||
if r.URL.Path != Prefix+"/account" && !strings.HasPrefix(r.URL.Path, Prefix+"/account/") {
|
||||
http.Redirect(w, r, Prefix+"/account", http.StatusFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
scope, err := a.buildAccessScope(user)
|
||||
if err != nil {
|
||||
a.Logger.Error("build access scope: %v", err)
|
||||
|
||||
Reference in New Issue
Block a user