add MFA, user web mail portal

This commit is contained in:
2026-08-13 08:07:19 +01:00
parent 70c05cc777
commit bc4bbe6e56
38 changed files with 1072 additions and 59 deletions
+6
View File
@@ -2,6 +2,12 @@
{{define "page_title"}}Account Settings{{end}}
{{define "content"}}
{{if .mfa_required}}
<div class="alert alert-warning">
<i class="bi bi-shield-exclamation me-2"></i>
Your administrator requires two-factor authentication for all admin accounts. Set up an authenticator app or a passkey below to continue using the dashboard.
</div>
{{end}}
<div class="row">
<div class="col-lg-6 mb-4">
<div class="card">
+7
View File
@@ -35,14 +35,21 @@
</td>
<td><small class="text-muted">{{strftime "%Y-%m-%d %H:%M" $u.CreatedAt}}</small></td>
<td>
{{if eq $u.ID $.current_user_id}}
<span class="text-muted small"><i class="bi bi-person-check me-1"></i>This is you</span>
{{else}}
<div class="btn-group btn-group-sm" role="group">
{{if not $u.IsGlobalAdmin}}
<a href="/pymta-manager/admins/{{$u.ID}}/edit" class="btn btn-outline-primary" title="Edit Domain Access"><i class="bi bi-pencil"></i></a>
{{end}}
<form method="post" action="/pymta-manager/admins/{{$u.ID}}/reset_mfa" class="d-inline">
<button type="submit" class="btn btn-outline-warning" data-confirm="Reset MFA for {{$u.Username}}? They will need to re-enroll an authenticator app or passkey." title="Reset MFA"><i class="bi bi-shield-x"></i></button>
</form>
<form method="post" action="/pymta-manager/admins/{{$u.ID}}/remove" class="d-inline">
<button type="submit" class="btn btn-outline-danger" data-confirm="Permanently remove admin {{$u.Username}}? This cannot be undone." title="Remove Admin"><i class="bi bi-trash"></i></button>
</form>
</div>
{{end}}
</td>
</tr>
{{end}}
+4
View File
@@ -190,7 +190,11 @@
<div class="col-md-3 mb-3"><div class="d-grid"><a href="/pymta-manager/domains/add" class="btn btn-outline-primary"><i class="bi bi-plus-circle me-2"></i>Add Domain</a></div></div>
<div class="col-md-3 mb-3"><div class="d-grid"><a href="/pymta-manager/senders/add" class="btn btn-outline-success"><i class="bi bi-person-plus me-2"></i>Add Sender</a></div></div>
<div class="col-md-3 mb-3"><div class="d-grid"><a href="/pymta-manager/ips/add" class="btn btn-outline-warning"><i class="bi bi-shield-plus me-2"></i>Whitelist IP</a></div></div>
{{if dget . "is_global_admin"}}
<div class="col-md-3 mb-3"><div class="d-grid"><a href="/pymta-manager/settings" class="btn btn-outline-info"><i class="bi bi-gear me-2"></i>Settings</a></div></div>
{{else}}
<div class="col-md-3 mb-3"><div class="d-grid"><a href="/pymta-manager/mailboxes/add" class="btn btn-outline-info"><i class="bi bi-mailbox me-2"></i>Add Mailbox</a></div></div>
{{end}}
</div>
</div>
</div>
@@ -19,6 +19,12 @@
<div class="form-text"><i class="bi bi-info-circle me-1"></i>Enter a fully qualified domain name (e.g., example.com)</div>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="mfa_exempt" name="mfa_exempt" {{if .domain.MFAExempt}}checked{{end}}>
<label class="form-check-label" for="mfa_exempt">Exempt this domain's mailboxes from mandatory MFA</label>
<div class="form-text">Only relevant if "Enforce MFA for Mailboxes" is turned on in Server Settings.</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="alert alert-info">
@@ -21,6 +21,11 @@
<label for="quota_gb" class="form-label">Storage Quota (GB)</label>
<input type="number" class="form-control" id="quota_gb" name="quota_gb" min="0.1" step="0.1" value="{{printf "%.2f" .quota_gb}}">
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="mfa_exempt" name="mfa_exempt" {{if .mailbox.MFAExempt}}checked{{end}}>
<label class="form-check-label" for="mfa_exempt">Exempt this mailbox from mandatory MFA</label>
<div class="form-text">Only relevant if "Enforce MFA for Mailboxes" is turned on in Server Settings.</div>
</div>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i>Update Mailbox</button>
<a href="/pymta-manager/mailboxes" class="btn btn-secondary"><i class="bi bi-x-lg me-1"></i>Cancel</a>
+11 -2
View File
@@ -4,22 +4,31 @@
{{define "content"}}
<div class="row justify-content-center">
<div class="col-lg-6">
{{if .must_change_username}}
<div class="alert alert-warning">
<i class="bi bi-exclamation-triangle me-2"></i>
You're signed in with the default admin account. Choose a new username and password before continuing.
</div>
{{else}}
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>
Set your own password before continuing.
</div>
{{end}}
<div class="card">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-person-gear me-2"></i>Choose your credentials</h5></div>
<div class="card-header"><h5 class="mb-0"><i class="bi bi-person-gear me-2"></i>Choose your {{if .must_change_username}}credentials{{else}}password{{end}}</h5></div>
<div class="card-body">
{{if .error}}<div class="alert alert-danger">{{.error}}</div>{{end}}
<form method="POST" action="/pymta-manager/first-login">
{{if .must_change_username}}
<div class="mb-3">
<label for="username" class="form-label">New username</label>
<input type="text" class="form-control" id="username" name="username" value="{{.username}}" required autofocus>
</div>
{{end}}
<div class="mb-3">
<label for="password" class="form-label">New password</label>
<input type="password" class="form-control" id="password" name="password" required minlength="10">
<input type="password" class="form-control" id="password" name="password" required minlength="10" {{if not .must_change_username}}autofocus{{end}}>
<div class="form-text">At least 10 characters, with a letter, a number, and a symbol.</div>
</div>
<div class="mb-4">
+4
View File
@@ -37,6 +37,10 @@
<button type="submit" class="btn btn-primary"><i class="bi bi-box-arrow-in-right me-1"></i>Sign in</button>
</div>
</form>
<hr>
<div class="d-grid">
<a href="/webmail/login" class="btn btn-outline-secondary btn-sm"><i class="bi bi-inbox me-1"></i>Login as Mailbox User</a>
</div>
</div>
</div>
</div>
+8
View File
@@ -7,6 +7,11 @@
<a href="/pymta-manager/mailboxes/add" class="btn btn-primary"><i class="bi bi-mailbox me-2"></i>Add Mailbox</a>
</div>
<div class="alert alert-info">
<i class="bi bi-info-circle me-2"></i>Mailbox owners manage their own password, MFA, and IMAP/SMTP app passwords at
<a href="/webmail/login" target="_blank" class="alert-link">/webmail/login</a> — using their mailbox email and password, not an admin account.
</div>
<div class="card">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-list-ul me-2"></i>All Mailboxes</h5></div>
<div class="card-body p-0">
@@ -38,6 +43,9 @@
<a href="/pymta-manager/mailboxes/{{$mailbox.ID}}/apppasswords" class="btn btn-outline-secondary btn-sm" title="App Passwords"><i class="bi bi-key"></i></a>
<a href="/pymta-manager/mailboxes/{{$mailbox.ID}}/aliases" class="btn btn-outline-secondary btn-sm" title="Aliases"><i class="bi bi-signpost-split"></i></a>
<a href="/pymta-manager/mailboxes/{{$mailbox.ID}}/edit" class="btn btn-outline-primary btn-sm" title="Edit Mailbox"><i class="bi bi-pencil"></i></a>
<form method="post" action="/pymta-manager/mailboxes/{{$mailbox.ID}}/reset_mfa" class="d-inline">
<button type="submit" class="btn btn-outline-warning btn-sm" data-confirm="Reset MFA for {{$mailbox.Email}}? They will need to re-enroll an authenticator app or passkey." title="Reset MFA"><i class="bi bi-shield-x"></i></button>
</form>
{{if $mailbox.IsActive}}
<form method="post" action="/pymta-manager/mailboxes/{{$mailbox.ID}}/delete" class="d-inline">
<button type="submit" class="btn btn-outline-warning btn-sm" title="Disable Mailbox" data-confirm="Disable mailbox {{$mailbox.Email}}?"><i class="bi bi-pause-circle"></i></button>
+24
View File
@@ -159,6 +159,30 @@
</div>
</div>
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-shield-check me-2"></i>Two-Factor Authentication (MFA)</h5></div>
<div class="card-body">
<div class="setting-section">
<div class="row">
<div class="col-md-6"><div class="mb-3"><label class="form-label">Enforce MFA for Admins</label>
<div class="setting-description">Require every admin account (global or domain-scoped) to set up TOTP/passkey MFA before using the dashboard</div>
<select class="form-select" name="Auth.enforce_admin_mfa">
<option value="true" {{if eq .settings.Auth.enforce_admin_mfa "true"}}selected{{end}}>Yes</option>
<option value="false" {{if eq .settings.Auth.enforce_admin_mfa "false"}}selected{{end}}>No</option>
</select>
</div></div>
<div class="col-md-6"><div class="mb-3"><label class="form-label">Enforce MFA for Mailboxes</label>
<div class="setting-description">Require MFA before a mailbox owner can create new app passwords in the self-service portal — overridable per-domain or per-mailbox</div>
<select class="form-select" name="Auth.enforce_mailbox_mfa">
<option value="true" {{if eq .settings.Auth.enforce_mailbox_mfa "true"}}selected{{end}}>Yes</option>
<option value="false" {{if eq .settings.Auth.enforce_mailbox_mfa "false"}}selected{{end}}>No</option>
</select>
</div></div>
</div>
</div>
</div>
</div>
<div class="card mb-4">
<div class="card-header"><h5 class="mb-0"><i class="bi bi-shield-lock me-2"></i>TLS/SSL Configuration</h5></div>
<div class="card-body">
@@ -70,12 +70,14 @@
</a>
</li>
{{if dget . "is_global_admin"}}
<li class="nav-item mb-1">
<a href="/pymta-manager/letsencrypt" class="nav-link text-white {{if eq (dget . "active") "letsencrypt"}}active{{end}}">
<i class="bi bi-patch-check me-2"></i>
Let's Encrypt
</a>
</li>
{{end}}
<li class="nav-item mb-1">
<a href="/pymta-manager/logs" class="nav-link text-white {{if eq (dget . "active") "logs"}}active{{end}}">
@@ -91,12 +93,14 @@
</h6>
</li>
{{if dget . "is_global_admin"}}
<li class="nav-item mb-1">
<a href="/pymta-manager/settings" class="nav-link text-white {{if eq (dget . "active") "settings"}}active{{end}}">
<i class="bi bi-sliders me-2"></i>
Server Settings
</a>
</li>
{{end}}
<li class="nav-item mb-1">
<a href="/pymta-manager/admins" class="nav-link text-white {{if eq (dget . "active") "admins"}}active{{end}}">
@@ -112,6 +116,14 @@
</a>
</li>
<li class="nav-item mb-1">
<a href="/webmail/login" target="_blank" class="nav-link text-white" title="Give this link to mailbox owners for self-service password/MFA/app-password management">
<i class="bi bi-mailbox2-flag me-2"></i>
Webmail Portal
<i class="bi bi-box-arrow-up-right ms-1 small"></i>
</a>
</li>
<li class="nav-item mb-1">
<form method="post" action="/pymta-manager/logout">
<button type="submit" class="nav-link text-white w-100 text-start border-0 bg-transparent">
@@ -37,6 +37,10 @@
<button type="submit" class="btn btn-primary"><i class="bi bi-box-arrow-in-right me-1"></i>Sign in</button>
</div>
</form>
<hr>
<div class="d-grid">
<a href="/pymta-manager/login" class="btn btn-outline-secondary btn-sm"><i class="bi bi-shield-lock me-1"></i>Login as Admin</a>
</div>
</div>
</div>
</div>