Skip to content

Commit

Permalink
Mask passwords on the UI accurately with the actual passwords length.
Browse files Browse the repository at this point in the history
This PR masks all the password fields in the UI with a pseudo dot character
retaining the rune length of the original password so that the password
fields on the UI appear to be containing the entered value as-is.

The earlier implementation would revert to a fixed length dummy password
confusing certain users and making it look like the password they entered
wasn't being saved.
  • Loading branch information
knadh committed Jul 21, 2023
1 parent 0be5901 commit a628519
Show file tree
Hide file tree
Showing 26 changed files with 162 additions and 39 deletions.
15 changes: 9 additions & 6 deletions cmd/settings.go
Expand Up @@ -10,6 +10,7 @@ import (
"strings"
"syscall"
"time"
"unicode/utf8"

"github.com/gofrs/uuid"
"github.com/jmoiron/sqlx/types"
Expand All @@ -21,6 +22,8 @@ import (
"github.com/labstack/echo/v4"
)

const pwdMask = "•"

type aboutHost struct {
OS string `json:"os"`
OSRelease string `json:"os_release"`
Expand Down Expand Up @@ -57,17 +60,17 @@ func handleGetSettings(c echo.Context) error {

// Empty out passwords.
for i := 0; i < len(s.SMTP); i++ {
s.SMTP[i].Password = ""
s.SMTP[i].Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SMTP[i].Password))
}
for i := 0; i < len(s.BounceBoxes); i++ {
s.BounceBoxes[i].Password = ""
s.BounceBoxes[i].Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.BounceBoxes[i].Password))
}
for i := 0; i < len(s.Messengers); i++ {
s.Messengers[i].Password = ""
s.Messengers[i].Password = strings.Repeat(pwdMask, utf8.RuneCountInString(s.Messengers[i].Password))
}
s.UploadS3AwsSecretAccessKey = ""
s.SendgridKey = ""
s.SecurityCaptchaSecret = ""
s.UploadS3AwsSecretAccessKey = strings.Repeat(pwdMask, utf8.RuneCountInString(s.UploadS3AwsSecretAccessKey))
s.SendgridKey = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SendgridKey))
s.SecurityCaptchaSecret = strings.Repeat(pwdMask, utf8.RuneCountInString(s.SecurityCaptchaSecret))

return c.JSON(http.StatusOK, okResp{s})
}
Expand Down
64 changes: 34 additions & 30 deletions frontend/src/views/Settings.vue
Expand Up @@ -77,8 +77,6 @@ import BounceSettings from './settings/bounces.vue';
import MessengerSettings from './settings/messengers.vue';
import AppearanceSettings from './settings/appearance.vue';
const dummyPassword = ' '.repeat(8);
export default Vue.extend({
components: {
GeneralSettings,
Expand Down Expand Up @@ -113,10 +111,13 @@ export default Vue.extend({
const form = JSON.parse(JSON.stringify(this.form));
// SMTP boxes.
let hasDummy = '';
for (let i = 0; i < form.smtp.length; i += 1) {
// If it's the dummy UI password placeholder, ignore it.
if (form.smtp[i].password === dummyPassword) {
if (this.isDummy(form.smtp[i].password)) {
form.smtp[i].password = '';
} else if (this.hasDummy(form.smtp[i].password)) {
hasDummy = `smtp #${i + 1}`;
}
if (form.smtp[i].strEmailHeaders && form.smtp[i].strEmailHeaders !== '[]') {
Expand All @@ -129,30 +130,45 @@ export default Vue.extend({
// Bounces boxes.
for (let i = 0; i < form['bounce.mailboxes'].length; i += 1) {
// If it's the dummy UI password placeholder, ignore it.
if (form['bounce.mailboxes'][i].password === dummyPassword) {
if (this.isDummy(form['bounce.mailboxes'][i].password)) {
form['bounce.mailboxes'][i].password = '';
} else if (this.hasDummy(form['bounce.mailboxes'][i].password)) {
hasDummy = `bounce #${i + 1}`;
}
}
if (form['upload.s3.aws_secret_access_key'] === dummyPassword) {
if (this.isDummy(form['upload.s3.aws_secret_access_key'])) {
form['upload.s3.aws_secret_access_key'] = '';
} else if (this.hasDummy(form['upload.s3.aws_secret_access_key'])) {
hasDummy = 's3';
}
if (form['bounce.sendgrid_key'] === dummyPassword) {
if (this.isDummy(form['bounce.sendgrid_key'])) {
form['bounce.sendgrid_key'] = '';
} else if (this.hasDummy(form['bounce.sendgrid_key'])) {
hasDummy = 'sendgrid';
}
if (form['security.captcha_secret'] === dummyPassword) {
if (this.isDummy(form['security.captcha_secret'])) {
form['security.captcha_secret'] = '';
} else if (this.hasDummy(form['security.captcha_secret'])) {
hasDummy = 'captcha';
}
for (let i = 0; i < form.messengers.length; i += 1) {
// If it's the dummy UI password placeholder, ignore it.
if (form.messengers[i].password === dummyPassword) {
if (this.isDummy(form.messengers[i].password)) {
form.messengers[i].password = '';
} else if (this.hasDummy(form.messengers[i].password)) {
hasDummy = `messenger #${i + 1}`;
}
}
if (hasDummy) {
this.$utils.toast(this.$t('globals.messages.passwordChangeFull', { name: hasDummy }), 'is-danger');
return false;
}
// Domain blocklist array from multi-line strings.
form['privacy.domain_blocklist'] = form['privacy.domain_blocklist'].split('\n').map((v) => v.trim().toLowerCase()).filter((v) => v !== '');
Expand Down Expand Up @@ -181,6 +197,8 @@ export default Vue.extend({
}, () => {
this.isLoading = false;
});
return false;
},
getSettings() {
Expand All @@ -191,30 +209,8 @@ export default Vue.extend({
// Serialize the `email_headers` array map to display on the form.
for (let i = 0; i < d.smtp.length; i += 1) {
d.smtp[i].strEmailHeaders = JSON.stringify(d.smtp[i].email_headers, null, 4);
// The backend doesn't send passwords, so add a dummy so that
// the password looks filled on the UI.
d.smtp[i].password = dummyPassword;
}
for (let i = 0; i < d['bounce.mailboxes'].length; i += 1) {
// The backend doesn't send passwords, so add a dummy so that
// the password looks filled on the UI.
d['bounce.mailboxes'][i].password = dummyPassword;
}
for (let i = 0; i < d.messengers.length; i += 1) {
// The backend doesn't send passwords, so add a dummy so that it
// the password looks filled on the UI.
d.messengers[i].password = dummyPassword;
}
if (d['upload.provider'] === 's3') {
d['upload.s3.aws_secret_access_key'] = dummyPassword;
}
d['bounce.sendgrid_key'] = dummyPassword;
d['security.captcha_secret'] = dummyPassword;
// Domain blocklist array to multi-line string.
d['privacy.domain_blocklist'] = d['privacy.domain_blocklist'].join('\n');
Expand All @@ -227,6 +223,14 @@ export default Vue.extend({
});
});
},
isDummy(pwd) {
return !pwd || (pwd.match(//g) || []).length === pwd.length;
},
hasDummy(pwd) {
return pwd.includes('');
},
},
computed: {
Expand Down
5 changes: 5 additions & 0 deletions i18n/ca.json
Expand Up @@ -17,11 +17,13 @@
"bounces.unknownService": "Servei desconegut",
"bounces.view": "Veure rebots",
"campaigns.addAltText": "Afegeix un missatge de text pla alternatiu",
"campaigns.addAttachments": "Add attachments",
"campaigns.archive": "Arxiu",
"campaigns.archiveEnable": "Publica a l'arxiu públic",
"campaigns.archiveHelp": "Publica (en curs, aturada, finalitzada) el missatge de campanya a l'arxiu públic ",
"campaigns.archiveMeta": "Metadades de la campanya",
"campaigns.archiveMetaHelp": "Dades del subscriptor de prova per ser usat en el missatge públic que inclou nom, correu electrònic i qualsevol atribut opcional emprat en el missatge de campanya o plantilla.",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "No es pot actualitzar una campanya en curs o ja finalitzada.",
"campaigns.clicks": "Clics",
"campaigns.confirmDelete": "Esborra {name}",
Expand Down Expand Up @@ -181,6 +183,7 @@
"globals.messages.missingFields": "Falten camps: {name}",
"globals.messages.notFound": "No s'ha trobat {name} ",
"globals.messages.passwordChange": "Introduïu un valor per canviar",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "\"{name}\" actualitzat",
"globals.months.1": "gen.",
"globals.months.10": "oct.",
Expand Down Expand Up @@ -438,6 +441,8 @@
"settings.media.s3.url": "URL del backend S3",
"settings.media.s3.urlHelp": "Canvia només si fas servir un backend personalitzat compatible amb S3 com Minio.",
"settings.media.title": "Càrrega de mèdia",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Ruta de càrrega",
"settings.media.upload.pathHelp": "Ruta al directori on es carregaran els mèdia.",
"settings.media.upload.uri": "Carrega URI",
Expand Down
5 changes: 5 additions & 0 deletions i18n/cs-cz.json
Expand Up @@ -17,11 +17,13 @@
"bounces.unknownService": "Neznámá služba.",
"bounces.view": "Zobrazit převzetí",
"campaigns.addAltText": "Přidat alternativní zprávu ve formátu prostého textu",
"campaigns.addAttachments": "Add attachments",
"campaigns.archive": "Archiv",
"campaigns.archiveEnable": "Zveřejnit ve veřejném archivu",
"campaigns.archiveHelp": "Zveřejnit (bežící, pozastavenou, dokončenou) zprávu kampaně ve veřejném archivu",
"campaigns.archiveMeta": "Metadata kampaně",
"campaigns.archiveMetaHelp": "Použít prázdná data přihlášených ve veřejné zpráve včetně jména, emailu a jiných volitelných atributů použitých ve zprávách kampaně nebo šablonách.",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "Nelze aktualizovat spuštěnou nebo dokončenou kampaň.",
"campaigns.clicks": "Klepnutí",
"campaigns.confirmDelete": "Odstranit {name}",
Expand Down Expand Up @@ -181,6 +183,7 @@
"globals.messages.missingFields": "Chybějící pole: {name}",
"globals.messages.notFound": "{name} nebyl nalezen",
"globals.messages.passwordChange": "Zadejte hodnotu ke změně",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "\"{name}\" aktualizován",
"globals.months.1": "Led",
"globals.months.10": "Říj",
Expand Down Expand Up @@ -438,6 +441,8 @@
"settings.media.s3.url": "Adresa URL pro S3 backend",
"settings.media.s3.urlHelp": "Lze změnit, pouze pokud se použije S3 kompatibilní backend, jako je Minio.",
"settings.media.title": "Odeslání médií",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Cesta odeslání",
"settings.media.upload.pathHelp": "Cesta k adresáři, kam se odešlou média.",
"settings.media.upload.uri": "URI odeslání",
Expand Down
5 changes: 5 additions & 0 deletions i18n/cy.json
Expand Up @@ -17,11 +17,13 @@
"bounces.unknownService": "Gwasanaeth anhysbys.",
"bounces.view": "Gweld beth sydd wedi sboncio",
"campaigns.addAltText": "Ychwanegu neges destun blaen",
"campaigns.addAttachments": "Add attachments",
"campaigns.archive": "Archif",
"campaigns.archiveEnable": "Cyhoeddi i archif gyhoeddus",
"campaigns.archiveHelp": "Cyhoeddi neges yr ymgyrch (wrthi'n rhedeg",
"campaigns.archiveMeta": "Ymgyrch metaddata",
"campaigns.archiveMetaHelp": "Data tanysgrifiwr ffug i'w defnyddio yn y neges gyhoeddus",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "Does dim modd diweddaru ymgyrch fyw neu ymgyrch sydd wedi dod i ben.",
"campaigns.clicks": "Cliciau",
"campaigns.confirmDelete": "Dileu {name}",
Expand Down Expand Up @@ -181,6 +183,7 @@
"globals.messages.missingFields": "Maes/meysydd coll: {name}",
"globals.messages.notFound": "Heb ddod o hyd i {enw]",
"globals.messages.passwordChange": "Rhoi gwerth i'w newid",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "Wedi diweddaru “{name}”",
"globals.months.1": "Ion",
"globals.months.10": "Hyd",
Expand Down Expand Up @@ -438,6 +441,8 @@
"settings.media.s3.url": "URL cefn ôl S3",
"settings.media.s3.urlHelp": "Dim ond ei newid os ydych yn defnyddio URL cefn ôl personol sy'n gydnaws â S3 fel Minio.",
"settings.media.title": "Cyfryngau sydd wedi'u llwytho i fyny",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Llwytho llwybr i fyny",
"settings.media.upload.pathHelp": "Llwybr i'r gyfarwyddiaeth lle bydd cyfryngau'n cael eu llwytho i fyny.",
"settings.media.upload.uri": "Llwytho URI i fyny",
Expand Down
5 changes: 5 additions & 0 deletions i18n/de.json
Expand Up @@ -17,11 +17,13 @@
"bounces.unknownService": "Unbekannter Dienst.",
"bounces.view": "Bounces anzeigen",
"campaigns.addAltText": "Füge eine alternative Nachricht in unformatierten Text hinzu (falls HTML nicht angezeigt werden kann).",
"campaigns.addAttachments": "Add attachments",
"campaigns.archive": "Archiv",
"campaigns.archiveEnable": "Im öffentlichen Archiv veröffentlichen",
"campaigns.archiveHelp": "Veröffentliche die Nachricht (laufende, pausierte, beendete) der Kampagne im öffentlichen Archiv.",
"campaigns.archiveMeta": "Kampagne Metadaten",
"campaigns.archiveMetaHelp": "Dummy-Abonnentendaten, die in der öffentlichen Nachricht verwendet werden sollen, einschließlich Name, E-Mail und alle optionalen Attribute, die in der Kampagnennachricht oder -vorlage verwendet werden.",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "Eine laufende oder abgeschlossene Kampagne kann nicht geändert werden.",
"campaigns.clicks": "Klicks",
"campaigns.confirmDelete": "Lösche {name}",
Expand Down Expand Up @@ -181,6 +183,7 @@
"globals.messages.missingFields": "Fehlende Felder: {name}",
"globals.messages.notFound": "{name} nicht gefunden",
"globals.messages.passwordChange": "Gib dein Passwort für die Änderung ein",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "\"{name}\" aktualisiert",
"globals.months.1": "Jan",
"globals.months.10": "Okt",
Expand Down Expand Up @@ -438,6 +441,8 @@
"settings.media.s3.url": "S3 Backend-URL",
"settings.media.s3.urlHelp": "Nur bei Verwendungen eines eigenen S3-kompatiblen Backends (wie Minio) ändern.",
"settings.media.title": "Medien Uploads",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Upload Pfad",
"settings.media.upload.pathHelp": "Pfad zum Upload Verzeichnis.",
"settings.media.upload.uri": "Upload URI",
Expand Down
5 changes: 3 additions & 2 deletions i18n/en.json
Expand Up @@ -18,12 +18,12 @@
"bounces.view": "View bounces",
"campaigns.addAltText": "Add alternate plain text message",
"campaigns.addAttachments": "Add attachments",
"campaigns.attachments": "Attachments",
"campaigns.archive": "Archive",
"campaigns.archiveEnable": "Publish to public archive",
"campaigns.archiveHelp": "Publish (running, paused, finished) the campaign message on the public archive.",
"campaigns.archiveMeta": "Campaign metadata",
"campaigns.archiveMetaHelp": "Dummy subscriber data to use in the public message including name, email, and any optional attributes used in the campaign message or template.",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "Cannot update a running or a finished campaign.",
"campaigns.clicks": "Clicks",
"campaigns.confirmDelete": "Delete {name}",
Expand Down Expand Up @@ -183,6 +183,7 @@
"globals.messages.missingFields": "Missing field(s): {name}",
"globals.messages.notFound": "{name} not found",
"globals.messages.passwordChange": "Enter a value to change",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "\"{name}\" updated",
"globals.months.1": "Jan",
"globals.months.10": "Oct",
Expand Down Expand Up @@ -436,9 +437,9 @@
"settings.media.s3.url": "S3 backend URL",
"settings.media.s3.urlHelp": "Only change if using a custom S3 compatible backend like Minio.",
"settings.media.title": "Media uploads",
"settings.media.upload.path": "Upload path",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Upload path",
"settings.media.upload.pathHelp": "Path to the directory where media will be uploaded.",
"settings.media.upload.uri": "Upload URI",
"settings.media.upload.uriHelp": "Upload URI that is visible to the outside world. The media uploaded to upload_path will be publicly accessible under {root_url}, for instance, https://listmonk.yoursite.com/uploads.",
Expand Down
5 changes: 5 additions & 0 deletions i18n/es.json
Expand Up @@ -17,11 +17,13 @@
"bounces.unknownService": "Servicio desconocido.",
"bounces.view": "Ver rebotes",
"campaigns.addAltText": "Agregar mensaje en texto plano alternativo",
"campaigns.addAttachments": "Add attachments",
"campaigns.archive": "Archivo",
"campaigns.archiveEnable": "Hacer el archivo público",
"campaigns.archiveHelp": "Publicar los mensajes de las campañas (en marcha, pausadas y terminadas) en el archivo público.",
"campaigns.archiveMeta": "Metadata de la campaña",
"campaigns.archiveMetaHelp": "Información de suscripción de ejemplo (por defecto) para ser usada en el mensaje público incluido nombre, correo electrónico, o cualquier valor accesible mediante atributos `{}` opcionales tanto en el mensaje de la campaña como en la plantilla.",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "No es posible actualizar una campaña iniciada o finalizada.",
"campaigns.clicks": "Clics",
"campaigns.confirmDelete": "Eliminar {name}",
Expand Down Expand Up @@ -182,6 +184,7 @@
"globals.messages.missingFields": "Falta el campo(s): {name}",
"globals.messages.notFound": "{name} no encontrado",
"globals.messages.passwordChange": "Ingresar una contraseña para cambiar",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "\"{name}\" actualizado",
"globals.months.1": "Enero",
"globals.months.10": "Octubre",
Expand Down Expand Up @@ -439,6 +442,8 @@
"settings.media.s3.url": "URL de API de S3",
"settings.media.s3.urlHelp": "Cambiar únicamente si se utiliza un servicio S3 personalizado (por ejemplo MinIO).",
"settings.media.title": "Cargas multimedia",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Ruta de carga",
"settings.media.upload.pathHelp": "Ruta o prefijo donde los archivos seránn cargados.",
"settings.media.upload.uri": "URI de carga",
Expand Down
5 changes: 5 additions & 0 deletions i18n/fi.json
Expand Up @@ -17,11 +17,13 @@
"bounces.unknownService": "Tuntematon palvelu.",
"bounces.view": "Näytä epäonnistuneet toimitukset",
"campaigns.addAltText": "Lisää vaihtoehtoinen tekstimuotoinen viesti",
"campaigns.addAttachments": "Add attachments",
"campaigns.archive": "Archive",
"campaigns.archiveEnable": "Publish to public archive",
"campaigns.archiveHelp": "Publish (running, paused, finished) the campaign message on the public archive.",
"campaigns.archiveMeta": "Campaign metadata",
"campaigns.archiveMetaHelp": "Dummy subscriber data to use in the public message including name, email, and any optional attributes used in the campaign message or template.",
"campaigns.attachments": "Attachments",
"campaigns.cantUpdate": "Käynnissä olevaa tai päättynyttä kampanjaa ei voi päivittää.",
"campaigns.clicks": "Klikkaukset",
"campaigns.confirmDelete": "Poista {name}",
Expand Down Expand Up @@ -182,6 +184,7 @@
"globals.messages.missingFields": "Missing field(s): {name}",
"globals.messages.notFound": "{name} not found",
"globals.messages.passwordChange": "Enter a value to change",
"globals.messages.passwordChangeFull": "Clear and re-enter the full password in '{name}'.",
"globals.messages.updated": "\"{name}\" updated",
"globals.months.1": "Jan",
"globals.months.10": "Oct",
Expand Down Expand Up @@ -439,6 +442,8 @@
"settings.media.s3.url": "S3 backend URL",
"settings.media.s3.urlHelp": "Only change if using a custom S3 compatible backend like Minio.",
"settings.media.title": "Media uploads",
"settings.media.upload.extensions": "Permitted file extensions",
"settings.media.upload.extensionsHelp": "Add * to allow all extensions",
"settings.media.upload.path": "Upload path",
"settings.media.upload.pathHelp": "Path to the directory where media will be uploaded.",
"settings.media.upload.uri": "Upload URI",
Expand Down

0 comments on commit a628519

Please sign in to comment.