updated mailbox app password
This commit is contained in:
@@ -4,11 +4,14 @@
|
||||
{{define "content"}}
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2><i class="bi bi-key me-2"></i>App Passwords <small class="text-muted fs-6">{{.mailbox.Email}}</small></h2>
|
||||
<a href="/pymta-manager/mailboxes" class="btn btn-secondary"><i class="bi bi-arrow-left me-2"></i>Back to Mailboxes</a>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="/pymta-manager/mailboxes/{{.mailbox.ID}}/edit" class="btn btn-outline-warning"><i class="bi bi-shield-lock me-2"></i>Reset Mailbox Password</a>
|
||||
<a href="/pymta-manager/mailboxes" class="btn btn-secondary"><i class="bi bi-arrow-left me-2"></i>Back to Mailboxes</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<i class="bi bi-info-circle me-2"></i>Use an app password (never the mailbox's own password) to set this mailbox up in Thunderbird or any other IMAP/SMTP client. Each one is shown only once, right after you create it.
|
||||
<i class="bi bi-info-circle me-2"></i>Use an app password (never the mailbox's own password) to set this mailbox up in Thunderbird or any other IMAP/SMTP client. Each one is shown only once, right after you create it. To reset the mailbox owner's own login password (used for the self-service portal), use <strong>Reset Mailbox Password</strong> above.
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
@@ -19,6 +22,22 @@
|
||||
<label for="label" class="form-label">Label</label>
|
||||
<input type="text" class="form-control" id="label" name="label" placeholder="e.g. Thunderbird laptop">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<label for="expires_preset" class="form-label">Expires</label>
|
||||
<select class="form-select" id="expires_preset" name="expires_preset" onchange="toggleCustomExpiry()">
|
||||
<option value="never" selected>Never</option>
|
||||
<option value="1d">1 day</option>
|
||||
<option value="7d">7 days</option>
|
||||
<option value="30d">1 month</option>
|
||||
<option value="180d">6 months</option>
|
||||
<option value="365d">1 year</option>
|
||||
<option value="custom">Custom date…</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto" id="expires_custom_wrap" style="display: none;">
|
||||
<label for="expires_custom" class="form-label">Expiration Date</label>
|
||||
<input type="date" class="form-control" id="expires_custom" name="expires_custom">
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="submit" class="btn btn-success"><i class="bi bi-key me-2"></i>Generate</button>
|
||||
</div>
|
||||
@@ -32,13 +51,19 @@
|
||||
{{if .passwords}}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-dark table-hover mb-0">
|
||||
<thead><tr><th>Label</th><th>Created</th><th>Last Used</th><th>Status</th><th>Actions</th></tr></thead>
|
||||
<thead><tr><th>Label</th><th>Created</th><th>Last Used</th><th>Expires</th><th>Status</th><th>Actions</th></tr></thead>
|
||||
<tbody>
|
||||
{{range .passwords}}
|
||||
<tr>
|
||||
<td>{{.Label}}</td>
|
||||
<td><small class="text-muted">{{strftime "%Y-%m-%d %H:%M" .CreatedAt}}</small></td>
|
||||
<td><small class="text-muted">{{if .LastUsedAt}}{{strftime "%Y-%m-%d %H:%M" .LastUsedAt}}{{else}}Never{{end}}</small></td>
|
||||
<td>
|
||||
{{if .ExpiresAt}}
|
||||
<small class="{{if isPast .ExpiresAt}}text-danger{{else}}text-muted{{end}}">{{strftime "%Y-%m-%d %H:%M" .ExpiresAt}}</small>
|
||||
{{if isPast .ExpiresAt}}<span class="badge bg-danger ms-1">Expired</span>{{end}}
|
||||
{{else}}<small class="text-muted">Never</small>{{end}}
|
||||
</td>
|
||||
<td>{{if .IsActive}}<span class="badge bg-success">Active</span>{{else}}<span class="badge bg-danger">Revoked</span>{{end}}</td>
|
||||
<td>
|
||||
<form method="post" action="/pymta-manager/mailboxes/{{$.mailbox.ID}}/apppasswords/{{.ID}}/revoke" class="d-inline">
|
||||
@@ -59,4 +84,51 @@
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .reveal}}
|
||||
<div class="modal fade" id="appPasswordRevealModal" tabindex="-1" data-bs-backdrop="static" data-bs-keyboard="false" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content bg-dark text-white border border-secondary">
|
||||
<div class="modal-header border-secondary">
|
||||
<h5 class="modal-title"><i class="bi bi-key-fill me-2"></i>App Password Created</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="mb-2">Label: <strong>{{.reveal.Label}}</strong></p>
|
||||
<p class="text-warning"><i class="bi bi-exclamation-triangle me-1"></i>Copy this password now — it will not be shown again.</p>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control font-monospace" id="revealedAppPassword" value="{{.reveal.Secret}}" readonly onclick="this.select()">
|
||||
<button class="btn btn-outline-light" type="button" onclick="copyRevealedAppPassword()"><i class="bi bi-clipboard me-1"></i>Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer border-secondary">
|
||||
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
{{define "extra_js"}}
|
||||
<script>
|
||||
function toggleCustomExpiry() {
|
||||
const preset = document.getElementById('expires_preset').value;
|
||||
const wrap = document.getElementById('expires_custom_wrap');
|
||||
wrap.style.display = preset === 'custom' ? '' : 'none';
|
||||
document.getElementById('expires_custom').required = preset === 'custom';
|
||||
}
|
||||
|
||||
{{if .reveal}}
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
new bootstrap.Modal(document.getElementById('appPasswordRevealModal')).show();
|
||||
});
|
||||
function copyRevealedAppPassword() {
|
||||
const input = document.getElementById('revealedAppPassword');
|
||||
input.select();
|
||||
navigator.clipboard.writeText(input.value)
|
||||
.then(() => showToast('Copied to clipboard', 'success'))
|
||||
.catch(() => { document.execCommand('copy'); showToast('Copied to clipboard', 'success'); });
|
||||
}
|
||||
{{end}}
|
||||
</script>
|
||||
{{end}}
|
||||
|
||||
@@ -31,6 +31,16 @@
|
||||
<input type="number" class="form-control" name="Server.smtp_tls_port" value="{{.settings.Server.smtp_tls_port}}" min="1" max="65535">
|
||||
</div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6"><div class="mb-3"><label class="form-label">Admin UI HTTP Port</label>
|
||||
<div class="setting-description">Plain HTTP port for this admin web interface</div>
|
||||
<input type="number" class="form-control" name="Server.web_http_port" value="{{.settings.Server.web_http_port}}" min="1" max="65535">
|
||||
</div></div>
|
||||
<div class="col-md-6"><div class="mb-3"><label class="form-label">Admin UI HTTPS Port</label>
|
||||
<div class="setting-description">Self-signed by default, or the Let's Encrypt cert once enabled</div>
|
||||
<input type="number" class="form-control" name="Server.web_https_port" value="{{.settings.Server.web_https_port}}" min="1" max="65535">
|
||||
</div></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6"><div class="mb-3"><label class="form-label">Bind IP Address</label>
|
||||
<input type="text" class="form-control" name="Server.bind_ip" value="{{.settings.Server.bind_ip}}">
|
||||
@@ -108,6 +118,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h5 class="mb-0"><i class="bi bi-inbox me-2"></i>IMAP Configuration</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">IMAP Port</label>
|
||||
<div class="setting-description">Plain IMAP port (no STARTTLS offered)</div>
|
||||
<input type="number" class="form-control" name="IMAP.imap_port" value="{{.settings.IMAP.imap_port}}" min="1" max="65535">
|
||||
</div></div>
|
||||
<div class="col-md-6"><div class="mb-3"><label class="form-label">IMAP TLS Port</label>
|
||||
<div class="setting-description">Implicit-TLS IMAP port (IMAPS)</div>
|
||||
<input type="number" class="form-control" name="IMAP.imap_tls_port" value="{{.settings.IMAP.imap_tls_port}}" min="1" max="65535">
|
||||
</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header"><h5 class="mb-0"><i class="bi bi-shield-exclamation me-2"></i>Rspamd (Spam Scoring)</h5></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-section">
|
||||
<div class="setting-description">Optional; the built-in heuristic spam score always runs regardless of this setting.</div>
|
||||
<div class="row">
|
||||
<div class="col-md-4"><div class="mb-3"><label class="form-label">Enabled</label>
|
||||
<select class="form-select" name="Rspamd.enabled">
|
||||
<option value="true" {{if eq .settings.Rspamd.enabled "true"}}selected{{end}}>Yes</option>
|
||||
<option value="false" {{if eq .settings.Rspamd.enabled "false"}}selected{{end}}>No</option>
|
||||
</select>
|
||||
</div></div>
|
||||
<div class="col-md-4"><div class="mb-3"><label class="form-label">Rspamd URL</label>
|
||||
<input type="text" class="form-control" name="Rspamd.url" value="{{.settings.Rspamd.url}}" placeholder="http://127.0.0.1:11333">
|
||||
</div></div>
|
||||
<div class="col-md-4"><div class="mb-3"><label class="form-label">Reject Score</label>
|
||||
<input type="number" class="form-control" name="Rspamd.reject_score" value="{{.settings.Rspamd.reject_score}}" min="1">
|
||||
</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">
|
||||
@@ -201,15 +252,15 @@
|
||||
}
|
||||
|
||||
document.querySelector('form').addEventListener('submit', function(e) {
|
||||
const ports = ['Server.smtp_port', 'Server.smtp_tls_port'];
|
||||
const ports = ['Server.smtp_port', 'Server.smtp_tls_port', 'Server.web_http_port', 'Server.web_https_port', 'IMAP.imap_port', 'IMAP.imap_tls_port'];
|
||||
const seen = {};
|
||||
for (const portField of ports) {
|
||||
const input = document.querySelector(`[name="${portField}"]`);
|
||||
const port = parseInt(input.value);
|
||||
if (port < 1 || port > 65535) { e.preventDefault(); showToast(`Invalid port number: ${port}.`, 'danger'); input.focus(); return; }
|
||||
if (seen[port]) { e.preventDefault(); showToast(`Port ${port} is used more than once — each port must be different.`, 'danger'); input.focus(); return; }
|
||||
seen[port] = true;
|
||||
}
|
||||
const smtpPort = document.querySelector('[name="Server.smtp_port"]').value;
|
||||
const tlsPort = document.querySelector('[name="Server.smtp_tls_port"]').value;
|
||||
if (smtpPort === tlsPort) { e.preventDefault(); showToast('SMTP and TLS ports must be different.', 'danger'); return; }
|
||||
|
||||
const serverBanner = document.querySelector('[name="Server.server_banner"]');
|
||||
if (serverBanner && !serverBanner.value.trim()) { serverBanner.value = '""'; }
|
||||
|
||||
Reference in New Issue
Block a user