Skip to content

Commit

Permalink
Merge pull request #211 from silinternational/fix-error-messages
Browse files Browse the repository at this point in the history
use a dialog to display errors, fix recovery validation, add more rules
  • Loading branch information
hobbitronics authored Jan 29, 2025
2 parents 5e11d69 + 174c135 commit e125622
Show file tree
Hide file tree
Showing 14 changed files with 311 additions and 216 deletions.
12 changes: 6 additions & 6 deletions installed-versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"name": "idp-profile-ui",
"dependencies": {
"@eslint/js": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz",
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
"overridden": false
},
"@intlify/unplugin-vue-i18n": {
Expand Down Expand Up @@ -72,8 +72,8 @@
"overridden": false
},
"eslint": {
"version": "9.18.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz",
"version": "9.19.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
"overridden": false
},
"prettier": {
Expand Down Expand Up @@ -132,8 +132,8 @@
"overridden": false
},
"vuetify": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.7.tgz",
"version": "3.7.8",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.7.8.tgz",
"overridden": false
},
"zxcvbn": {
Expand Down
392 changes: 196 additions & 196 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion src/2sv/key/Insert.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
<!-- TODO: Add translations for this label -->
<label v-if="showLabelInput">
{{ $t('2sv.key.insert.label') }}
<v-text-field v-model="input" required variant="outlined" autofocus @keyup="onKeyup" />
<v-text-field
v-model="input"
required
variant="outlined"
autofocus
:rules="[(v) => v.length < 65 || $t('global.mfaLabelTooLong')]"
@keyup="onKeyup"
/>
</label>
</BasePage>

Expand Down Expand Up @@ -89,6 +96,10 @@ export default {
this.snackBarMessage = this.$t('2sv.key.insert.label')
this.snackbarIsOpen = true
return
} else if (attemptedKeyLabel.length > 65) {
this.snackBarMessage = this.$t('global.mfaLabelTooLong')
this.snackbarIsOpen = true
return
} else if (this.isDuplicateKeyLabel(mfa.keys, attemptedKeyLabel)) {
this.snackbarIsOpen = true
this.snackBarMessage = this.$t('2sv.key.insert.duplicate')
Expand Down
8 changes: 7 additions & 1 deletion src/2sv/smartphone/VerifyQrCode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ export default {
}),
methods: {
async verify() {
if (this.$refs.form.validate()) {
const { valid, errors } = await this.$refs.form.validate()
if (valid) {
try {
await verify(this.$route.query.id, this.code.trim())
Expand All @@ -67,6 +69,10 @@ export default {
this.errors.push(this.$t('2sv.smartphone.verifyQrCode.hint'))
}
}
} else {
errors.forEach((error) => {
throw Error(error.errorMessages.join('\n'))
})
}
},
blur(event) {
Expand Down
29 changes: 26 additions & 3 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,23 @@
</v-row>

<v-container>
<v-alert v-if="!!message" type="error" icon="mdi-alert" closable class="my-2" @click:close="message = ''">
<span v-sanitize.basic="message" />
</v-alert>
<v-dialog v-model="isModalOpen" max-width="400" @after-leave="closeModal">
<v-card>
<v-card-title>
<v-icon color="error" class="mr-2">mdi-alert</v-icon>
{{ $t('global.error') }}
</v-card-title>
<v-card-text>
<span v-sanitize.basic="message" />
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn color="primary" @click="closeModal">
{{ $t('global.button.close') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>

<!-- adding key here helps produce more predictable view behavior (see https://youtu.be/7YZ5DwlLSt8?t=21m22s) -->
<router-view :key="$route.fullPath" />
Expand All @@ -58,6 +72,7 @@ import { useRoute } from 'vue-router'
import { useDisplay } from 'vuetify'
const message = ref('')
const isModalOpen = ref(false)
const { xs } = useDisplay()
Expand All @@ -70,6 +85,10 @@ watch(
},
)
watch(message, () => {
isModalOpen.value = !!message.value
})
onMounted(() => {
eventBus.on('clear-messages', () => {
message.value = ''
Expand All @@ -86,6 +105,10 @@ const logout = () => {
const login = () => {
user.login()
}
const closeModal = () => {
message.value = ''
}
</script>

<style scoped>
Expand Down
5 changes: 4 additions & 1 deletion src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"close": "Close"
},
"learnMore": "learn more",
"areYouSure": "Are you sure?"
"areYouSure": "Are you sure?",
"error": "Error",
"mfaLabelTooLong": "The label should contain at most 64 characters"
},
"app": {
"login": "Log in",
Expand Down Expand Up @@ -169,6 +171,7 @@
"unsaved": "You have not saved your changes yet.",
"dontRemoveLastOne": "Please add another alternate address before removing this one.",
"invalidEmail": "Invalid email",
"isPrimaryEmail": "You can't use your primary email address or alias as an alternate",
"personalHeader": "Alternate(s)",
"noPersonalMethods": "None at this time"
},
Expand Down
5 changes: 4 additions & 1 deletion src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"close": "Cerrar"
},
"learnMore": "más información",
"areYouSure": "¿Seguro que quiere continuar?"
"areYouSure": "¿Seguro que quiere continuar?",
"error": "Error",
"mfaLabelTooLong": "La etiqueta debe contener como máximo 64"
},
"app": {
"login": "Iniciar sesión",
Expand Down Expand Up @@ -169,6 +171,7 @@
"unsaved": "Aún no ha guardado sus cambios.",
"dontRemoveLastOne": "Por favor agregue una nueva dirección alternativa antes de eliminar esta.",
"invalidEmail": "Dirección de correo electrónica no válida",
"isPrimaryEmail": "No puede utilizar su dirección de correo electrónico principal o alias como alternativa",
"personalHeader": "Alternativo(s)",
"noPersonalMethods": "Ninguno en este momento"
},
Expand Down
5 changes: 4 additions & 1 deletion src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"close": "Fermer"
},
"learnMore": "en savoir plus",
"areYouSure": "Êtes-vous certain ?"
"areYouSure": "Êtes-vous certain ?",
"error": "Erreur",
"mfaLabelTooLong": "L'étiquette doit contenir au maximum 64"
},
"app": {
"login": "S'identifier",
Expand Down Expand Up @@ -169,6 +171,7 @@
"unsaved": "Vous n'avez pas encore enregistré vos modifications.",
"dontRemoveLastOne": "Veuillez ajouter une autre adresse e-mail alternative avant de supprimer celle-ci.",
"invalidEmail": "Adresse email invalide",
"isPrimaryEmail": "Vous ne pouvez pas utiliser votre adresse e-mail principale ou votre alias comme alternative",
"personalHeader": "Alternative(s)",
"noPersonalMethods": "Aucun en ce moment"
},
Expand Down
5 changes: 4 additions & 1 deletion src/locales/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"close": "닫기"
},
"learnMore": "자세히 알아보기",
"areYouSure": "계속 진행하시겠습니까?"
"areYouSure": "계속 진행하시겠습니까?",
"error": "오류",
"mfaLabelTooLong": "라벨에는 최대 64개가 포함되어야 합니다."
},
"app": {
"login": "로그인",
Expand Down Expand Up @@ -169,6 +171,7 @@
"unsaved": "아직 변경 사항을 저장하지 않았습니다.",
"dontRemoveLastOne": "이 주소를 제거하기 전에 다른 대체 주소를 추가하십시오.",
"invalidEmail": "잘못된 이메일",
"isPrimaryEmail": "기본 이메일 주소나 별칭을 대안으로 사용할 수 없습니다.",
"personalHeader": "대체",
"noPersonalMethods": "현재 없음"
},
Expand Down
7 changes: 6 additions & 1 deletion src/password/Confirm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,19 @@ export default {
},
methods: {
async confirm() {
if (this.$refs.form.validate()) {
const { valid, errors } = await this.$refs.form.validate()
if (valid) {
await this.$API.put('password', {
password: this.password,
})
this.$refs.wizard.completed()
this.$router.push('/password/saved')
} else {
errors.forEach((error) => {
throw Error(error.errorMessages.join('\n'))
})
}
},
blur(event) {
Expand Down
8 changes: 7 additions & 1 deletion src/password/Create.vue
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,9 @@ export default {
this.wizardKey += 1
},
async save() {
if (this.$refs.form.validate()) {
const { valid, errors } = await this.$refs.form.validate()
if (valid) {
try {
await this.$API.put('password/assess', {
password: this.password,
Expand All @@ -174,6 +176,10 @@ export default {
})
}
}
} else {
errors.forEach((error) => {
throw Error(error.errorMessages.join('\n'))
})
}
},
blur(event) {
Expand Down
8 changes: 7 additions & 1 deletion src/password/Forgot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ export default {
this.recaptchaResponse = ''
},
async send() {
if (this.$refs.form.validate()) {
const { valid, errors } = await this.$refs.form.validate()
if (valid) {
try {
const reset = await this.$API.post('reset', {
username: this.uname,
Expand All @@ -74,6 +76,10 @@ export default {
grecaptcha.reset()
throw error
}
} else {
errors.forEach((error) => {
throw Error(error.errorMessages.join('\n'))
})
}
},
},
Expand Down
19 changes: 18 additions & 1 deletion src/password/Recovery.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
(v) =>
/^$|^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(v) ||
$t('password.recovery.invalidEmail'),
(v) => !isAlias(v, primary.value) || $t('password.recovery.isPrimaryEmail'),
]"
validate-on-input
autofocus
Expand Down Expand Up @@ -107,12 +108,17 @@ export default {
},
methods: {
async add() {
if (this.$refs.form.validate()) {
const { valid, errors } = await this.$refs.form.validate()
if (valid) {
await add(this.newEmail)
this.newEmail = ''
this.$root.$emit('clear-messages') // listener in App.vue (this is a temporary hack hopefully)
} else {
errors.forEach((error) => {
throw Error(error.errorMessages.join('\n'))
})
}
},
remove,
Expand All @@ -124,6 +130,17 @@ export default {
this.$refs.wizard.completed()
this.$refs.wizard.next()
},
isAlias(email, primaryEmail) {
const normalizeEmail = (email) => {
const [localPart, domain] = email.split('@')
const cleanedLocalPart = localPart.includes('+') ? localPart.split('+')[0] : localPart
const normalizedLocalPart = cleanedLocalPart.replace(/\./g, '')
return `${normalizedLocalPart}@${domain}`.toLowerCase()
}
return normalizeEmail(email) === normalizeEmail(primaryEmail)
},
},
}
</script>
Expand Down
11 changes: 10 additions & 1 deletion src/profile/MfaCardLabel.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
<template>
<v-row v-if="editing" no-gutters align="center">
<v-col cols="9">
<v-text-field v-model="newLabel" autofocus @keyup.enter="save" @focus="$event.target.select()" />
<v-text-field
v-model="newLabel"
autofocus
:rules="[(v) => v.length < 65 || $t('global.mfaLabelTooLong')]"
@keyup.enter="save"
@focus="$event.target.select()"
/>
</v-col>

<v-col>
Expand Down Expand Up @@ -72,6 +78,9 @@ export default {
this.editing = false
},
async save() {
if (this.newLabel.length > 65) {
throw Error(this.$t('global.mfaLabelTooLong'))
}
const mfa = this.isWebauthn
? await changeWebauthn(this.mfaId, this.keyId, {
label: this.newLabel,
Expand Down

0 comments on commit e125622

Please sign in to comment.