feat(panel): add domain-admin role with per-domain authorization
test / test (push) Has been cancelled
test / test (push) Has been cancelled
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
<label for="new_password_confirm">Confirm new password</label>
|
||||
<input id="new_password_confirm" name="new_password_confirm" type="password" autocomplete="new-password">
|
||||
|
||||
{{if .ShowDMARC}}
|
||||
<h3>DMARC aggregate reports</h3>
|
||||
<p class="muted">Optional default <code>rua=</code> address for every sending
|
||||
domain (can be overridden per domain). Use a mailbox on a domain that
|
||||
@@ -58,11 +59,12 @@
|
||||
{{end}}</span>{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
<button type="submit">Save changes</button>
|
||||
</form>
|
||||
<p class="muted">Leave both new-password fields empty to change the username
|
||||
or DMARC address only. Changing the password signs out every other session;
|
||||
{{if .ShowDMARC}}or DMARC address {{end}}only. Changing the password signs out every other session;
|
||||
this one stays signed in.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
{{if .Flash}}<div class="flash">{{.Flash}}</div>{{end}}
|
||||
|
||||
{{if .IsGlobal}}
|
||||
<div class="card">
|
||||
<h2>Add a sending domain</h2>
|
||||
<form method="post" action="/domains">
|
||||
@@ -16,6 +17,7 @@
|
||||
<p class="muted">A DKIM key is generated for the domain; you then publish the
|
||||
shown DNS record. Adding a domain does not create an application.</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="card">
|
||||
<h2>Domains</h2>
|
||||
@@ -31,7 +33,7 @@
|
||||
<td><a class="st st-{{.DNS}}" href="/domains/{{.ID}}#dns-status">{{.DNS}}</a></td>
|
||||
<td class="muted">{{.DKIMSelector}}</td>
|
||||
<td>{{.AppCount}}</td>
|
||||
<td class="actions"><a class="danger" href="/domains/{{.ID}}/delete">Delete</a></td>
|
||||
{{if $.IsGlobal}}<td class="actions"><a class="danger" href="/domains/{{.ID}}/delete">Delete</a></td>{{else}}<td></td>{{end}}
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
|
||||
@@ -64,14 +64,19 @@
|
||||
the column: the mark, the panel's pages, the current page's own sections,
|
||||
and the session last. The mark goes to /status, the page the panel opens
|
||||
on — named outright rather than as "/", which is only a redirect to it. */}}
|
||||
<a class="brand" href="/status"><img src="/static/logo-compact.svg" width="220" height="100" alt="SelfPost"></a>
|
||||
<a class="brand" href="{{if .IsGlobal}}/status{{else}}/domains{{end}}"><img src="/static/logo-compact.svg" width="220" height="100" alt="SelfPost"></a>
|
||||
<div class="links">
|
||||
{{if .IsGlobal}}
|
||||
{{if eq .Active "status"}}<span aria-current="page">{{template "icon-status"}}Status</span>{{else}}<a href="/status">{{template "icon-status"}}Status</a>{{end}}
|
||||
{{end}}
|
||||
{{if eq .Active "domains"}}<span aria-current="page">{{template "icon-domains"}}Domains</span>{{else}}<a href="/domains">{{template "icon-domains"}}Domains</a>{{end}}
|
||||
{{if eq .Active "deliveries"}}<span aria-current="page">{{template "icon-deliveries"}}Deliveries</span>{{else}}<a href="/deliveries">{{template "icon-deliveries"}}Deliveries</a>{{end}}
|
||||
{{if .IsGlobal}}
|
||||
{{if eq .Active "mail_queue"}}<span aria-current="page">{{template "icon-mail-queue"}}Mail queue</span>{{else}}<a href="/mail-queue">{{template "icon-mail-queue"}}Mail queue</a>{{end}}
|
||||
{{if eq .Active "system_log"}}<span aria-current="page">{{template "icon-system-log"}}System log</span>{{else}}<a href="/system-log">{{template "icon-system-log"}}System log</a>{{end}}
|
||||
{{if eq .Active "backup"}}<span aria-current="page">{{template "icon-backup"}}Backup</span>{{else}}<a href="/backup">{{template "icon-backup"}}Backup</a>{{end}}
|
||||
{{if eq .Active "users"}}<span aria-current="page">{{template "icon-account"}}Users</span>{{else}}<a href="/users">{{template "icon-account"}}Users</a>{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "sections" .}}
|
||||
<div class="session">
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
{{define "content"}}
|
||||
<h1>{{if .IsEdit}}Edit user{{else}}Create user{{end}}</h1>
|
||||
|
||||
<div class="card narrow">
|
||||
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
||||
<form method="post" action="{{if .IsEdit}}/users/{{.UserID}}{{else}}/users/new{{end}}">
|
||||
<label for="username">Username</label>
|
||||
<input id="username" name="username" autocomplete="username"
|
||||
autocapitalize="none" spellcheck="false" value="{{.FormUsername}}" required>
|
||||
|
||||
<label for="password">Password{{if .IsEdit}} (leave empty to keep){{end}}</label>
|
||||
<input id="password" name="password" type="password" autocomplete="new-password" {{if not .IsEdit}}required{{end}}>
|
||||
|
||||
<label for="role">Role</label>
|
||||
<select id="role" name="role">
|
||||
<option value="domain_admin" {{if eq .FormRole "domain_admin"}}selected{{end}}>Domain administrator</option>
|
||||
<option value="global" {{if eq .FormRole "global"}}selected{{end}}>Global administrator</option>
|
||||
</select>
|
||||
|
||||
<fieldset id="domain-pick">
|
||||
<legend>Assigned domains</legend>
|
||||
<p class="muted">Required for domain administrators.</p>
|
||||
{{range .Domains}}
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="domain_ids" value="{{.ID}}" {{if index $.FormDomains .ID}}checked{{end}}>
|
||||
{{.Name}}
|
||||
</label>
|
||||
{{end}}
|
||||
</fieldset>
|
||||
|
||||
<button type="submit">{{if .IsEdit}}Save{{else}}Create{{end}}</button>
|
||||
{{if .IsEdit}}
|
||||
<button type="submit" name="action" value="delete" class="danger">Delete user</button>
|
||||
{{end}}
|
||||
</form>
|
||||
<p class="muted"><a href="/users">Back to users</a></p>
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -0,0 +1,28 @@
|
||||
{{define "content"}}
|
||||
<h1>Panel users</h1>
|
||||
|
||||
{{if .Flash}}<div class="flash">{{.Flash}}</div>{{end}}
|
||||
|
||||
<div class="card">
|
||||
<p><a href="/users/new">Create user</a></p>
|
||||
{{if .Users}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Username</th><th>Role</th><th>Domains</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Users}}
|
||||
<tr>
|
||||
<td>{{.User.Username}}</td>
|
||||
<td>{{if eq .User.Role "global"}}Global{{else}}Domain admin{{end}}</td>
|
||||
<td class="muted">{{if eq .User.Role "global"}}All{{else}}{{range $i, $n := .DomainNames}}{{if $i}}, {{end}}{{$n}}{{end}}{{end}}</td>
|
||||
<td class="actions"><a href="/users/{{.User.ID}}">Edit</a></td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
<p class="muted">No panel users besides the global administrator yet.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -207,8 +207,9 @@ func TestNavMarksActivePage(t *testing.T) {
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = engine.Page("dashboard").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
"User": "admin",
|
||||
"Active": "mail_queue",
|
||||
"User": "admin",
|
||||
"Active": "mail_queue",
|
||||
"IsGlobal": true,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("execute nav: %v", err)
|
||||
@@ -234,8 +235,9 @@ func TestNavLeadsWithStatusAndPointsDomainsAtItsOwnPath(t *testing.T) {
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := engine.Page("status").ExecuteTemplate(&buf, "nav", map[string]any{
|
||||
"User": "admin",
|
||||
"Active": "status",
|
||||
"User": "admin",
|
||||
"Active": "status",
|
||||
"IsGlobal": true,
|
||||
}); err != nil {
|
||||
t.Fatalf("execute nav: %v", err)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ var pageFiles = map[string][]string{
|
||||
"login": {"templates/login.html"},
|
||||
"dashboard": {"templates/dashboard.html"},
|
||||
"account": {"templates/account.html"},
|
||||
"users": {"templates/users.html"},
|
||||
"user_form": {"templates/user_form.html"},
|
||||
"backup": {"templates/backup.html", "templates/encrypt_fields.html"},
|
||||
"domain_detail": {"templates/domain_detail.html", "templates/encrypt_fields.html"},
|
||||
"domain_delete": {"templates/domain_delete.html"},
|
||||
|
||||
Reference in New Issue
Block a user