Limit user role to archive viewer

This commit is contained in:
DonVoo 2026-07-13 13:19:12 +02:00
parent 53a87e57dd
commit 3426cea58c
2 changed files with 100 additions and 45 deletions

View file

@ -119,6 +119,10 @@ func splitRecipients(value string) ([]string, error) {
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
if !CanManageAccounts(r) {
http.Redirect(w, r, "/view", http.StatusSeeOther)
return
}
renderStartPage(w, r)
}
@ -128,28 +132,18 @@ func renderStartPage(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<script src="/static/app.js?v=20260713-15" defer></script></head>
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
%s
<nav class="ribbon-tabs">
%s
%s
</nav>
%s
</header>
<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">
<a class="backstage-item active" href="/">Start</a>
<a class="backstage-item" href="/view">Archiv-Boxen</a>
<a class="backstage-item" href="/import-export">Import/Export</a>
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
<a class="backstage-item" href="/users">Benutzerverwaltung</a>
<a class="backstage-item" href="/logout">Logout</a>
</aside>
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
%s
<main class="start-page">
<section class="welcome-panel">
<div class="welcome-kicker">Willkommen</div>
@ -163,7 +157,7 @@ func renderStartPage(w http.ResponseWriter, r *http.Request) {
</section>
</main>
<footer class="statusbar"><span>bereit</span></footer>
</body></html>`, renderRibbonEmailBoxMenu("home"), renderRibbonTransferMenu(""), renderRibbonActions(false, user), html.EscapeString(userDisplayLabel(user)))
</body></html>`, renderAppMenuButton(user), renderRibbonEmailBoxMenuForUser("home", user), renderRibbonTransferMenuForUser("", user), renderRibbonActions(false, user), renderBackstageForUser("home", user), html.EscapeString(userDisplayLabel(user)))
}
// renderShell ist das Grundgeruest im Outlook-2013-Look:
@ -176,32 +170,19 @@ func renderShell(w http.ResponseWriter, r *http.Request, active, readingPane str
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
%s
<nav class="ribbon-tabs">
%s
%s
</nav>
%s
</header>
<aside class="backstage-menu" aria-label="Mail-Graveyard-Menue">
<a class="backstage-item active" href="/view">Archiv-Boxen</a>
<a class="backstage-item" href="/import-export">Import/Export</a>
<a class="backstage-item" href="/accounts">Konten-Verwaltung</a>
<a class="backstage-item" href="/archives">Archiv-mbox Verwaltung</a>
<a class="backstage-item" href="/transfer/postfach-archiv">Mail-Transfer</a>
<a class="backstage-item transfer-sub" href="/transfer/postfach-archiv">Postfach &lt;--&gt; Archiv-MBox</a>
<a class="backstage-item transfer-sub" href="/transfer/archiv-postfach">Archiv-MBox &lt;--&gt; Postfach</a>
<a class="backstage-item transfer-sub" href="/transfer/postfach-postfach">Postfach &lt;--&gt; Postfach</a>
<a class="backstage-item transfer-sub" href="/transfer/archiv-archiv">Archiv-MBox &lt;--&gt; Archiv-MBox</a>
<a class="backstage-item" href="/users">Benutzerverwaltung</a>
<a class="backstage-item" href="/logout">Logout</a>
</aside>
<button class="backstage-shade" type="button" aria-label="Menue schliessen" data-backstage-close></button>
%s
<div class="three-pane">
<aside class="pane pane-tree" id="tree"
hx-get="/view" hx-trigger="load" hx-target="#tree" hx-swap="innerHTML">
@ -218,7 +199,7 @@ func renderShell(w http.ResponseWriter, r *http.Request, active, readingPane str
</div>
<footer class="statusbar"><span id="mig-status"
hx-get="/migrate/status" hx-trigger="every 3s" hx-swap="innerHTML">bereit</span></footer>
</body></html>`, renderRibbonEmailBoxMenu("archive"), renderRibbonTransferMenu(""), renderRibbonActions(false, user), renderInitialReadPane(readingPane))
</body></html>`, renderAppMenuButton(user), renderRibbonEmailBoxMenuForUser("archive", user), renderRibbonTransferMenuForUser("", user), renderRibbonActions(false, user), renderBackstageForUser("archive", user), renderInitialReadPane(readingPane))
}
func accountsHandler(w http.ResponseWriter, r *http.Request) {
@ -359,6 +340,9 @@ func transferHandler(w http.ResponseWriter, r *http.Request) {
}
func emailBoxHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
accounts, err := ListAccounts()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@ -392,6 +376,9 @@ func emailBoxHandler(w http.ResponseWriter, r *http.Request) {
}
func sourceMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
accountName := strings.TrimSpace(r.URL.Query().Get("source"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
@ -414,6 +401,9 @@ func sourceMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
}
func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
accountName := strings.TrimSpace(r.URL.Query().Get("source"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
index, err := strconv.Atoi(r.URL.Query().Get("index"))
@ -436,6 +426,9 @@ func sourceMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
}
func targetMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
accountName := strings.TrimSpace(r.URL.Query().Get("target"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
w.Header().Set("Content-Type", "text/html; charset=utf-8")
@ -458,6 +451,9 @@ func targetMailboxMessagesHandler(w http.ResponseWriter, r *http.Request) {
}
func targetMailboxMessageHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
accountName := strings.TrimSpace(r.URL.Query().Get("target"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
index, err := strconv.Atoi(r.URL.Query().Get("index"))
@ -570,6 +566,10 @@ func migrateRunHandler(w http.ResponseWriter, r *http.Request) {
}
func migrateStatusHandler(w http.ResponseWriter, r *http.Request) {
if !CanManageAccounts(r) {
fmt.Fprint(w, "bereit")
return
}
type job struct {
Account string
Started string
@ -752,9 +752,9 @@ func renderAccountsPage(w http.ResponseWriter, r *http.Request, accounts []Accou
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Konten-Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -798,9 +798,9 @@ func renderArchivesPage(w http.ResponseWriter, r *http.Request, archives []strin
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Archiv-mbox Verwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -846,9 +846,9 @@ func renderUsersPage(w http.ResponseWriter, r *http.Request, users []AppUser, ed
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Benutzerverwaltung - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -920,9 +920,9 @@ func renderTransferPage(w http.ResponseWriter, r *http.Request, mode transferMod
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>%s - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -964,6 +964,9 @@ func renderTransferPage(w http.ResponseWriter, r *http.Request, mode transferMod
}
func transferSideHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
side := transferSide(r.URL.Query().Get("side"))
kind := strings.TrimSpace(r.URL.Query().Get("kind"))
value := strings.TrimSpace(r.URL.Query().Get("value"))
@ -984,6 +987,9 @@ func transferSideHandler(w http.ResponseWriter, r *http.Request) {
}
func transferMessagesHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
side := transferSide(r.URL.Query().Get("side"))
kind := strings.TrimSpace(r.URL.Query().Get("kind"))
value := strings.TrimSpace(r.URL.Query().Get("value"))
@ -994,6 +1000,9 @@ func transferMessagesHandler(w http.ResponseWriter, r *http.Request) {
}
func transferMessageHandler(w http.ResponseWriter, r *http.Request) {
if !requireManager(w, r) {
return
}
kind := strings.TrimSpace(r.URL.Query().Get("kind"))
value := strings.TrimSpace(r.URL.Query().Get("value"))
folder := strings.TrimSpace(r.URL.Query().Get("folder"))
@ -1372,9 +1381,9 @@ func renderSourceEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Quell-Postfach - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -1416,9 +1425,9 @@ func renderTargetEmailBoxPage(w http.ResponseWriter, r *http.Request, accounts [
fmt.Fprintf(w, `<!doctype html><html lang="de"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Ziel-Postfach - Mail-Graveyard</title>
<link rel="stylesheet" href="/static/style.css?v=20260713-15">
<link rel="stylesheet" href="/static/style.css?v=20260713-16">
<script src="/static/htmx.min.js"></script>
<script src="/static/app.js?v=20260713-15" defer></script></head>
<script src="/static/app.js?v=20260713-16" defer></script></head>
<body class="ol2013">
<header class="ribbon">
<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>
@ -1973,11 +1982,47 @@ func renderRibbonEmailBoxMenu(active string) string {
return b.String()
}
func renderAppMenuButton(user AppUser) string {
if !hasRole(user, roleManager) {
return ""
}
return `<button class="app-menu-button" type="button" aria-label="Mail-Graveyard-Menue" aria-expanded="false" data-backstage-toggle><span class="mail-logo" aria-hidden="true"></span></button>`
}
func renderRibbonEmailBoxMenuForUser(active string, user AppUser) string {
if hasRole(user, roleManager) {
return renderRibbonEmailBoxMenu(active)
}
var b strings.Builder
homeClass := "tab app-tab"
if active == "home" {
homeClass += " active"
}
archiveClass := "tab"
if active == "archive" {
archiveClass += " active"
}
fmt.Fprintf(&b, `<a class="%s" href="/">Mail-Graveyard</a>`, homeClass)
fmt.Fprintf(&b, `<a class="%s" href="/view">Archiv-Boxen</a>`, archiveClass)
return b.String()
}
func renderRibbonTransferMenuForUser(active string, user AppUser) string {
if !hasRole(user, roleManager) {
return ""
}
return renderRibbonTransferMenu(active)
}
func renderRibbonActions(settingsActive bool, user AppUser) string {
settingsClass := "ribbon-icon"
if settingsActive {
settingsClass += " active"
}
if !hasRole(user, roleManager) {
return fmt.Sprintf(`<div class="ribbon-spacer"></div><span class="ribbon-user" title="%s">%s</span><a class="ribbon-icon" href="/logout" title="Logout" aria-label="Logout">&#x23FB;</a>`,
html.EscapeString(user.Username), html.EscapeString(userDisplayName(user)))
}
return fmt.Sprintf(`<div class="ribbon-spacer"></div><span class="ribbon-user" title="%s">%s</span><a class="%s" href="/users" title="Einstellungen" aria-label="Einstellungen">&#9881;</a><a class="ribbon-icon" href="/logout" title="Logout" aria-label="Logout">&#x23FB;</a>`,
html.EscapeString(user.Username), html.EscapeString(userDisplayName(user)), settingsClass)
}
@ -2037,6 +2082,13 @@ func renderBackstage(active string) string {
return b.String()
}
func renderBackstageForUser(active string, user AppUser) string {
if !hasRole(user, roleManager) {
return ""
}
return renderBackstage(active)
}
func renderTransferPicker(kind string, accounts []Account, archives []string) string {
var b strings.Builder
b.WriteString(`<div class="transfer-picker">`)

View file

@ -4,6 +4,7 @@
if (!ribbon || document.querySelector(".mail-actionbar")) return;
const bar = document.createElement("div");
const managerTools = !!document.querySelector("[data-ribbon-menu-toggle]");
bar.className = "mail-actionbar";
bar.innerHTML = `
<button class="action-btn primary" type="button" data-action-new><span aria-hidden="true">&#x2709;</span> Neu</button>
@ -16,6 +17,7 @@
<button class="action-btn" type="button" data-action-reply><span aria-hidden="true">&#x21A9;</span> Antworten</button>
<button class="action-btn" type="button" data-action-forward><span aria-hidden="true">&#x21AA;</span> Weiterleiten</button>
<button class="action-btn muted" type="button" disabled><span aria-hidden="true">&#x232B;</span> Loeschen</button>
${managerTools ? `
<span class="action-separator"></span>
<div class="action-menu">
<button class="action-btn action-menu-button" type="button" aria-expanded="false" data-action-menu-toggle><span aria-hidden="true">&#x21C4;</span> Ordner Sync</button>
@ -33,6 +35,7 @@
<button type="button" class="action-dropdown-item" data-sync-kind="email" data-sync-direction="pull">Email &lt;-- Email</button>
</div>
</div>
` : ""}
`;
ribbon.insertAdjacentElement("afterend", bar);
}