updated mailbox app password

This commit is contained in:
2026-08-13 07:03:40 +01:00
parent 70fa1a5f2c
commit 70c05cc777
21 changed files with 618 additions and 33 deletions
+14 -7
View File
@@ -26,7 +26,7 @@ func GenerateAppPassword(minLen int) string {
}
func (d *DB) ListAppPasswordsForMailbox(mailboxID int64) ([]MailboxAppPassword, error) {
rows, err := d.Query(`SELECT id, mailbox_id, label, password_hash, is_active, created_at, last_used_at
rows, err := d.Query(`SELECT id, mailbox_id, label, password_hash, is_active, created_at, last_used_at, expires_at
FROM esrv_mailbox_app_passwords WHERE mailbox_id = ? ORDER BY created_at`, mailboxID)
if err != nil {
return nil, err
@@ -36,8 +36,8 @@ func (d *DB) ListAppPasswordsForMailbox(mailboxID int64) ([]MailboxAppPassword,
for rows.Next() {
var p MailboxAppPassword
var createdAt string
var lastUsedAt sql.NullString
if err := rows.Scan(&p.ID, &p.MailboxID, &p.Label, &p.PasswordHash, &p.IsActive, &createdAt, &lastUsedAt); err != nil {
var lastUsedAt, expiresAt sql.NullString
if err := rows.Scan(&p.ID, &p.MailboxID, &p.Label, &p.PasswordHash, &p.IsActive, &createdAt, &lastUsedAt, &expiresAt); err != nil {
return nil, err
}
p.CreatedAt, _ = parseTime(createdAt)
@@ -45,14 +45,20 @@ func (d *DB) ListAppPasswordsForMailbox(mailboxID int64) ([]MailboxAppPassword,
t, _ := parseTime(lastUsedAt.String)
p.LastUsedAt = &t
}
if expiresAt.Valid {
t, _ := parseTime(expiresAt.String)
p.ExpiresAt = &t
}
out = append(out, p)
}
return out, rows.Err()
}
func (d *DB) CreateAppPassword(mailboxID int64, label, passwordHash string) (int64, error) {
res, err := d.Exec(`INSERT INTO esrv_mailbox_app_passwords (mailbox_id, label, password_hash) VALUES (?, ?, ?)`,
mailboxID, label, passwordHash)
// CreateAppPassword inserts a new app password. expiresAt is nil for one that never
// expires (the default).
func (d *DB) CreateAppPassword(mailboxID int64, label, passwordHash string, expiresAt *time.Time) (int64, error) {
res, err := d.Exec(`INSERT INTO esrv_mailbox_app_passwords (mailbox_id, label, password_hash, expires_at) VALUES (?, ?, ?, ?)`,
mailboxID, label, passwordHash, expiresAt)
if err != nil {
return 0, err
}
@@ -67,7 +73,8 @@ func (d *DB) VerifyMailboxAppPassword(email, password string) (*Mailbox, error)
if err != nil || mbox == nil {
return nil, err
}
rows, err := d.Query(`SELECT id, password_hash FROM esrv_mailbox_app_passwords WHERE mailbox_id = ? AND is_active = 1`, mbox.ID)
rows, err := d.Query(`SELECT id, password_hash FROM esrv_mailbox_app_passwords
WHERE mailbox_id = ? AND is_active = 1 AND (expires_at IS NULL OR expires_at > ?)`, mbox.ID, time.Now())
if err != nil {
return nil, err
}
+3 -1
View File
@@ -79,7 +79,8 @@ type MailboxFilterRule struct {
}
// MailboxAppPassword is the only credential an IMAP/SMTP client ever uses. Plaintext
// is shown once at creation and never stored.
// is shown once at creation and never stored. ExpiresAt is nil for a password that
// never expires (the default).
type MailboxAppPassword struct {
ID int64
MailboxID int64
@@ -88,6 +89,7 @@ type MailboxAppPassword struct {
IsActive bool
CreatedAt time.Time
LastUsedAt *time.Time
ExpiresAt *time.Time
}
// MailboxMessage is one stored message. CachedFrom/CachedSubject are plaintext by
+3 -1
View File
@@ -203,7 +203,8 @@ CREATE TABLE IF NOT EXISTS esrv_mailbox_app_passwords (
password_hash TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_used_at DATETIME
last_used_at DATETIME,
expires_at DATETIME
);
-- A mailbox's receive-only (or, with can_send_as, send-as too) alternate addresses.
@@ -284,6 +285,7 @@ func migrateAddedColumns(db *sql.DB) {
`ALTER TABLE esrv_domains ADD COLUMN default_mailbox_quota_bytes INTEGER NOT NULL DEFAULT 5368709120`,
`ALTER TABLE esrv_mailboxes ADD COLUMN totp_secret TEXT NOT NULL DEFAULT ''`,
`ALTER TABLE esrv_mailboxes ADD COLUMN totp_enabled INTEGER NOT NULL DEFAULT 0`,
`ALTER TABLE esrv_mailbox_app_passwords ADD COLUMN expires_at DATETIME`,
}
for _, stmt := range stmts {
db.Exec(stmt)