MFA fix, added IP blacklist, update webmail client
This commit is contained in:
+60
-13
@@ -43,16 +43,17 @@ func (a *App) funcMap() template.FuncMap {
|
||||
// isPast reports whether a nullable expiry timestamp has already passed —
|
||||
// used to badge an app password as "Expired" even while is_active is
|
||||
// still 1 (expiry and revocation are independent states).
|
||||
"isPast": func(t *time.Time) bool { return t != nil && t.Before(time.Now()) },
|
||||
"title": strings.Title,
|
||||
"upper": strings.ToUpper,
|
||||
"lower": strings.ToLower,
|
||||
"safe": func(s string) template.HTML { return template.HTML(s) },
|
||||
"filesize": humanFileSize,
|
||||
"dotToDash": func(s string) string { return strings.ReplaceAll(s, ".", "-") },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"sub": func(a, b int) int { return a - b },
|
||||
"eq2": func(a, b any) bool { return fmt.Sprint(a) == fmt.Sprint(b) },
|
||||
"isPast": func(t *time.Time) bool { return t != nil && t.Before(time.Now()) },
|
||||
"ruleSummary": summarizeConditions,
|
||||
"title": strings.Title,
|
||||
"upper": strings.ToUpper,
|
||||
"lower": strings.ToLower,
|
||||
"safe": func(s string) template.HTML { return template.HTML(s) },
|
||||
"filesize": humanFileSize,
|
||||
"dotToDash": func(s string) string { return strings.ReplaceAll(s, ".", "-") },
|
||||
"add": func(a, b int) int { return a + b },
|
||||
"sub": func(a, b int) int { return a - b },
|
||||
"eq2": func(a, b any) bool { return fmt.Sprint(a) == fmt.Sprint(b) },
|
||||
// dget looks up an optional map key, returning "" if absent — mirrors Jinja's
|
||||
// `x if x is defined else ''` pattern used for context vars only some pages set
|
||||
// (e.g. sidebar badge counts, which only dashboard passes).
|
||||
@@ -62,7 +63,8 @@ func (a *App) funcMap() template.FuncMap {
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"list": func(items ...string) []string { return items },
|
||||
"list": func(items ...string) []string { return items },
|
||||
"isStandardFolder": isStandardFolder,
|
||||
// emailOverallStatus mirrors the delivered/failed selectattr computation
|
||||
// dashboard.html and logs.html both do in the Python templates.
|
||||
"emailOverallStatus": func(recipients []db.EmailRecipientLog) string {
|
||||
@@ -130,6 +132,7 @@ var pages = []string{
|
||||
"mailboxes.html", "add_mailbox.html", "edit_mailbox.html", "mailbox_apppasswords.html", "mailbox_aliases.html",
|
||||
"mailbox_lists.html", "mailbox_rules.html",
|
||||
"ips.html", "add_ip.html", "edit_ip.html",
|
||||
"blacklist.html",
|
||||
"dkim.html", "edit_dkim.html",
|
||||
"settings.html", "letsencrypt.html", "logs.html", "view_message_content.html", "error.html",
|
||||
"account.html", "first_login.html",
|
||||
@@ -144,6 +147,39 @@ var pages = []string{
|
||||
var standalonePages = []string{
|
||||
"login.html", "login_mfa.html", "mfa_setup_required.html", "totp_setup.html",
|
||||
"webmail_login.html", "webmail_login_mfa.html", "webmail_account.html", "webmail_totp_setup.html", "webmail_mfa_setup_required.html",
|
||||
"webmail_folder.html", "webmail_message.html", "webmail_compose.html", "webmail_rules.html", "webmail_certs.html",
|
||||
}
|
||||
|
||||
// pagesWithComposeWidget are the standalone pages that show a Compose/Reply/Forward
|
||||
// entry point and so need webmail_compose_widget.html's floating-popup markup+JS
|
||||
// parsed alongside them (see webmail_compose_widget.html's {{define "compose_widget"}}).
|
||||
// webmail_compose.html itself is excluded — it's what opens inside the popup, not
|
||||
// something that opens a popup of its own.
|
||||
var pagesWithComposeWidget = []string{
|
||||
"webmail_folder.html", "webmail_message.html", "webmail_rules.html", "webmail_certs.html", "webmail_account.html",
|
||||
}
|
||||
|
||||
func hasComposeWidget(page string) bool {
|
||||
for _, p := range pagesWithComposeWidget {
|
||||
if p == page {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// pagesWithShortcuts are the two pages keyboard shortcuts make sense on — the
|
||||
// message list (j/k/Enter/o) and a single open message (r/a/f/#). See
|
||||
// webmail_shortcuts.html's {{define "webmail_shortcuts"}}.
|
||||
var pagesWithShortcuts = []string{"webmail_folder.html", "webmail_message.html"}
|
||||
|
||||
func hasShortcuts(page string) bool {
|
||||
for _, p := range pagesWithShortcuts {
|
||||
if p == page {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// loadTemplates parses from the embedded assets FS (see embed.go), not the
|
||||
@@ -153,7 +189,7 @@ func (a *App) loadTemplates() error {
|
||||
a.templates = map[string]*template.Template{}
|
||||
for _, page := range pages {
|
||||
t := template.New("base.html").Funcs(a.funcMap())
|
||||
t, err := t.ParseFS(assets, "templates/base.html", "templates/sidebar_email.html", "templates/"+page)
|
||||
t, err := t.ParseFS(assets, "templates/base.html", "templates/sidebar_email.html", "templates/csrf_script.html", "templates/"+page)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse %s: %w", page, err)
|
||||
}
|
||||
@@ -161,7 +197,14 @@ func (a *App) loadTemplates() error {
|
||||
}
|
||||
for _, page := range standalonePages {
|
||||
t := template.New(page).Funcs(a.funcMap())
|
||||
t, err := t.ParseFS(assets, "templates/"+page)
|
||||
files := []string{"templates/" + page, "templates/csrf_script.html"}
|
||||
if hasComposeWidget(page) {
|
||||
files = append(files, "templates/webmail_compose_widget.html")
|
||||
}
|
||||
if hasShortcuts(page) {
|
||||
files = append(files, "templates/webmail_shortcuts.html")
|
||||
}
|
||||
t, err := t.ParseFS(assets, files...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parse %s: %w", page, err)
|
||||
}
|
||||
@@ -190,6 +233,9 @@ func (a *App) render(w http.ResponseWriter, r *http.Request, page string, data M
|
||||
if data == nil {
|
||||
data = M{}
|
||||
}
|
||||
// Set unconditionally for every page — pages with no session cookie yet (login)
|
||||
// just get "", which csrf_script.html's injected script treats as a no-op.
|
||||
data["csrf_token"] = a.csrfTokenFor(r)
|
||||
if isStandalonePage(page) {
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := t.ExecuteTemplate(w, page, data); err != nil {
|
||||
@@ -214,6 +260,7 @@ func (a *App) render(w http.ResponseWriter, r *http.Request, page string, data M
|
||||
data["mailbox_count"] = counts.MailboxCount
|
||||
data["ip_count"] = counts.IPCount
|
||||
data["dkim_count"] = counts.DKIMCount
|
||||
data["blacklist_count"] = counts.BlacklistCount
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
if err := t.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||
a.Logger.Error("template render error (%s): %v", page, err)
|
||||
|
||||
Reference in New Issue
Block a user