feat(settings): let users clear stored secrets from the UI

Redacted secrets (SMTP password, Telegram bot token, LDAP password) are
always served blank to the browser, so the update path treats a blank
submission as "unchanged" and silently restores the stored value. That
made a once-set secret impossible to remove without editing the database
— e.g. switching to a passwordless localhost SMTP relay kept sending the
old credentials forever.

Blank stays "unchanged"; clearing is now its own signal. The update
request carries explicit clear flags (request-scoped fields on the
controller form, so they are never persisted as settings rows), and
preserveRedactedSecrets skips the restore for a flagged secret. Each
secret field gets a Clear/Undo button that arms the flag; typing a new
value disarms it. The 2FA token keeps its existing behavior: it is
already clearable by disabling 2FA.

Closes #5724
This commit is contained in:
MHSanaei
2026-07-02 13:57:34 +02:00
parent fb3a1559b2
commit 92303094fd
21 changed files with 188 additions and 34 deletions
+13 -2
View File
@@ -26,9 +26,16 @@ type updateUserForm struct {
TwoFactorCode string `json:"twoFactorCode" form:"twoFactorCode"`
}
// updateSettingForm carries the persisted settings plus request-scoped fields
// that must never land in the settings table: the 2FA confirmation code and
// the explicit clear flags for redacted secrets (a blank secret alone means
// "unchanged", so clearing needs its own signal — see #5724).
type updateSettingForm struct {
entity.AllSetting
TwoFactorCode string `json:"twoFactorCode" form:"twoFactorCode"`
TwoFactorCode string `json:"twoFactorCode" form:"twoFactorCode"`
ClearTgBotToken bool `json:"clearTgBotToken" form:"clearTgBotToken"`
ClearLdapPassword bool `json:"clearLdapPassword" form:"clearLdapPassword"`
ClearSmtpPassword bool `json:"clearSmtpPassword" form:"clearSmtpPassword"`
}
// SettingController handles settings and user management operations.
@@ -105,7 +112,11 @@ func (a *SettingController) updateSetting(c *gin.Context) {
return
}
}
err := a.settingService.UpdateAllSetting(allSetting)
err := a.settingService.UpdateAllSetting(allSetting, service.SecretClears{
TgBotToken: form.ClearTgBotToken,
LdapPassword: form.ClearLdapPassword,
SmtpPassword: form.ClearSmtpPassword,
})
if err == nil && twoFactorErr == nil && !oldTwoFactor && allSetting.TwoFactorEnable {
if bumpErr := a.userService.BumpLoginEpoch(); bumpErr != nil {
err = bumpErr