2026-08-12 21:14:19 +01:00
{{define "title"}}App Passwords - Email Server{{end}}
{{define "page_title"}}App Passwords{{end}}
{{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 >
2026-08-13 07:03:40 +01:00
< 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 >
2026-08-12 21:14:19 +01:00
</ div >
< div class = "alert alert-info" >
2026-08-13 07:03:40 +01:00
< 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.
2026-08-12 21:14:19 +01:00
</ div >
< div class = "card mb-4" >
< div class = "card-header" >< h5 class = "mb-0" >< i class = "bi bi-plus-circle me-2" ></ i > Create App Password</ h5 ></ div >
< div class = "card-body" >
< form method = "POST" action = "/pymta-manager/mailboxes/{{.mailbox.ID}}/apppasswords/add" class = "row g-2 align-items-end" >
< div class = "col-auto" >
< label for = "label" class = "form-label" > Label</ label >
< input type = "text" class = "form-control" id = "label" name = "label" placeholder = "e.g. Thunderbird laptop" >
</ div >
2026-08-13 07:03:40 +01:00
< 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 >
2026-08-12 21:14:19 +01:00
< div class = "col-auto" >
< button type = "submit" class = "btn btn-success" >< i class = "bi bi-key me-2" ></ i > Generate</ button >
</ div >
</ form >
</ div >
</ div >
< div class = "card" >
< div class = "card-header" >< h5 class = "mb-0" >< i class = "bi bi-list-ul me-2" ></ i > Existing App Passwords</ h5 ></ div >
< div class = "card-body p-0" >
{{if .passwords}}
< div class = "table-responsive" >
< table class = "table table-dark table-hover mb-0" >
2026-08-13 07:03:40 +01:00
< thead >< tr >< th > Label</ th >< th > Created</ th >< th > Last Used</ th >< th > Expires</ th >< th > Status</ th >< th > Actions</ th ></ tr ></ thead >
2026-08-12 21:14:19 +01:00
< 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 >
2026-08-13 07:03:40 +01:00
< 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 >
2026-08-12 21:14:19 +01:00
< 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" >
< button type = "submit" class = "btn btn-outline-danger btn-sm" title = "Revoke" data-confirm = "Revoke app password "{{.Label}}"? Any client using it will stop working." >< i class = "bi bi-trash" ></ i ></ button >
</ form >
</ td >
</ tr >
{{end}}
</ tbody >
</ table >
</ div >
{{else}}
< div class = "text-center py-5" >
< i class = "bi bi-key text-muted" style = "font-size: 4rem;" ></ i >
< h4 class = "text-muted mt-3" > No app passwords yet</ h4 >
< p class = "text-muted" > Create one above to connect a mail client to this mailbox.</ p >
</ div >
{{end}}
</ div >
</ div >
2026-08-13 07:03:40 +01:00
{{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 >
2026-08-12 21:14:19 +01:00
{{end}}