feat(security): mask saved webhook URLs instead of returning encrypted values

Encrypted webhook URLs are no longer returned to the frontend. Both user
and admin webhook fields now show '••••••••' as a placeholder when a URL
is already saved, and the sentinel value is skipped on save/test so the
stored secret is never exposed or accidentally overwritten.
This commit is contained in:
jubnl
2026-04-05 06:08:30 +02:00
parent d8ee545002
commit 959015928f
7 changed files with 38 additions and 16 deletions
+6 -5
View File
@@ -1258,9 +1258,9 @@ export default function AdminPage(): React.ReactElement {
<label className="block text-xs font-medium text-slate-500 mb-1">{t('admin.notifications.adminWebhookPanel.title')}</label>
<input
type="text"
value={smtpValues.admin_webhook_url || ''}
value={smtpValues.admin_webhook_url === '••••••••' ? '' : smtpValues.admin_webhook_url || ''}
onChange={e => setSmtpValues(prev => ({ ...prev, admin_webhook_url: e.target.value }))}
placeholder="https://discord.com/api/webhooks/..."
placeholder={smtpValues.admin_webhook_url === '••••••••' ? '••••••••' : 'https://discord.com/api/webhooks/...'}
className="w-full px-3 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-slate-400 focus:border-transparent"
/>
</div>
@@ -1279,10 +1279,11 @@ export default function AdminPage(): React.ReactElement {
</button>
<button
onClick={async () => {
if (!smtpValues.admin_webhook_url) return
const url = smtpValues.admin_webhook_url === '••••••••' ? undefined : smtpValues.admin_webhook_url
if (!url && smtpValues.admin_webhook_url !== '••••••••') return
try {
await authApi.updateAppSettings({ admin_webhook_url: smtpValues.admin_webhook_url }).catch(() => {})
const result = await notificationsApi.testWebhook(smtpValues.admin_webhook_url)
if (url) await authApi.updateAppSettings({ admin_webhook_url: url }).catch(() => {})
const result = await notificationsApi.testWebhook(url)
if (result.success) toast.success(t('admin.notifications.adminWebhookPanel.testSuccess'))
else toast.error(result.error || t('admin.notifications.adminWebhookPanel.testFailed'))
} catch { toast.error(t('admin.notifications.adminWebhookPanel.testFailed')) }