From 36bd59ef02cff73e90ba793d423f1b1fd6a7cce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 26 Sep 2023 10:31:09 +0200 Subject: [PATCH 1/5] Automatically correct invalid values for preferences --- CHANGELOG.md | 3 +++ src/const.js | 8 ++++++++ src/main.js | 10 +++++++--- 3 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 src/const.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fc2e3d0..d2864dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The style has been entirely reworked using Bootstrap instead of Bulma - It is now impossible to include the separator in the dedicated name +## Fixed +- Invalid preferences are now automatically corrected + ## [0.3.0] - 2023-08-25 diff --git a/src/const.js b/src/const.js new file mode 100644 index 0000000..f8ed9a5 --- /dev/null +++ b/src/const.js @@ -0,0 +1,8 @@ +export const allowedColorModes = [ + 'light', + 'dark', +]; +export const allowedLocales = [ + 'en', + 'fr', +]; diff --git a/src/main.js b/src/main.js index 04f0db4..77afc05 100644 --- a/src/main.js +++ b/src/main.js @@ -1,5 +1,6 @@ import './assets/main.sass'; +import { allowedColorModes, allowedLocales } from './const'; import { createApp } from 'vue'; import { createI18n } from 'vue-i18n'; import { useStorage } from '@vueuse/core'; @@ -7,19 +8,22 @@ import App from './App.vue'; import router from './router'; import messages from '@intlify/unplugin-vue-i18n/messages'; -const setGlobalAttribute = (attrName, storageName, defaultValue) => { +const setGlobalAttribute = (attrName, storageName, defaultValue, allowedValues) => { const stored_value = useStorage(storageName, ''); if (!stored_value.value) { stored_value.value = defaultValue; } document.documentElement.setAttribute(attrName, stored_value.value); + if (!allowedValues.includes(stored_value.value)) { + stored_value.value = defaultValue; + } return { 'stored': stored_value, 'defaultValue': defaultValue, }; }; -const locale = setGlobalAttribute('lang', 'sake-locale', 'en'); -const colorMode = setGlobalAttribute('data-bs-theme', 'sake-color-mode', 'light'); +const locale = setGlobalAttribute('lang', 'sake-locale', 'en', allowedLocales); +const colorMode = setGlobalAttribute('data-bs-theme', 'sake-color-mode', 'light', allowedColorModes); const i18n = createI18n({ legacy: false, From dd3b797ec2f579ae4349b34dde2c7a64acabea6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 26 Sep 2023 10:58:29 +0200 Subject: [PATCH 2/5] Use the browser's preferred language if available --- CHANGELOG.md | 1 + src/locales_utils.js | 19 +++++++++++++++++++ src/main.js | 3 ++- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/locales_utils.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d2864dc..2138cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Possibility to set a default account - Dark mode +- Use the browser's preferred language if available ### Changed - The style has been entirely reworked using Bootstrap instead of Bulma diff --git a/src/locales_utils.js b/src/locales_utils.js new file mode 100644 index 0000000..eead437 --- /dev/null +++ b/src/locales_utils.js @@ -0,0 +1,19 @@ +import { allowedLocales } from './const'; + +const fallBackValue = 'en'; + +const getShortLanguageCode = (language) => { + language = language.split('-')[0]; + language = language.split('_')[0]; + return language; +}; + +export const getDefaultLocale = () => { + for (const lang of navigator.languages) { + const lang_short = getShortLanguageCode(lang); + if (allowedLocales.includes(lang_short)) { + return lang_short; + } + } + return fallBackValue; +}; diff --git a/src/main.js b/src/main.js index 77afc05..489809b 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,7 @@ import './assets/main.sass'; import { allowedColorModes, allowedLocales } from './const'; +import { getDefaultLocale } from './locales_utils'; import { createApp } from 'vue'; import { createI18n } from 'vue-i18n'; import { useStorage } from '@vueuse/core'; @@ -22,7 +23,7 @@ const setGlobalAttribute = (attrName, storageName, defaultValue, allowedValues) 'defaultValue': defaultValue, }; }; -const locale = setGlobalAttribute('lang', 'sake-locale', 'en', allowedLocales); +const locale = setGlobalAttribute('lang', 'sake-locale', getDefaultLocale(), allowedLocales); const colorMode = setGlobalAttribute('data-bs-theme', 'sake-color-mode', 'light', allowedColorModes); const i18n = createI18n({ From c59e56fba7c68e799349b61bb018d9e0626d7d12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 26 Sep 2023 11:13:26 +0200 Subject: [PATCH 3/5] Fix a typo --- src/locales/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locales/fr.json b/src/locales/fr.json index 168ccf0..125e2f2 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -37,7 +37,7 @@ "error": { "invalidBase64": "La clé doit être une chaîne de caractère en base64.", "invalidKeyLength": "La longueur de la clé doit être de 128 bits (16 bytes) ou de 256 bits (32 bytes).", - "invalidSeparator": "La séparateur doit être un unique caractère.", + "invalidSeparator": "Le séparateur doit être un unique caractère.", "cameraNotAllowed": "L'accès à la caméra n'a pas été autorisé.", "cameraNotFound": "Aucune caméra détectée.", "cameraInsecureContext": "Impossible d'accéder à la caméra depuis une liaison non-sécurisée.", From 43b73bbe5f586b01cfae6ed5ac1c4b0cf6791f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 26 Sep 2023 11:58:33 +0200 Subject: [PATCH 4/5] Use Bootstrap's form validation style --- CHANGELOG.md | 1 + src/views/AddAccountView.vue | 52 ++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2138cdb..5c1a78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - The style has been entirely reworked using Bootstrap instead of Bulma - It is now impossible to include the separator in the dedicated name +- When adding a new account, error messages are displayed alongside each affected elements whenever possible ## Fixed - Invalid preferences are now automatically corrected diff --git a/src/views/AddAccountView.vue b/src/views/AddAccountView.vue index 7bc3531..3ae1b08 100644 --- a/src/views/AddAccountView.vue +++ b/src/views/AddAccountView.vue @@ -15,9 +15,13 @@ const localPart = ref(''); const separator = ref('+'); const domainName = ref(''); const privateKey = ref(''); -const errorMessageId = ref(''); const authorizedKeyLengths = [16, 32]; +const errorMessageId = ref(''); +const separatorErrorMessageId = ref(''); +const localPartErrorMessageId = ref(''); +const addrKeyErrorMessageId = ref(''); + const base64Decode = (str_b64) => { try { const raw_str = atob(str_b64); @@ -44,17 +48,24 @@ const addDisabled = computed(() => { }); const addAccount = () => { if (!addDisabled.value) { + resetErrorMessage(); + var hasError = false; + var key = null; + if (separator.value.length != 1) { + hasError = setErrorMessage('addAccount.error.invalidSeparator', separatorErrorMessageId); + } + if (localPart.value.includes(separator.value)) { + hasError = setErrorMessage('addAccount.error.localPartSeparator', localPartErrorMessageId); + } try { - if (separator.value.length != 1) { - throw new Error('addAccount.error.invalidSeparator'); - } - if (localPart.value.includes(separator.value)) { - throw new Error('addAccount.error.localPartSeparator'); - } - const key = base64Decode(privateKey.value); + key = base64Decode(privateKey.value); if (!authorizedKeyLengths.includes(key.length)) { throw new Error('addAccount.error.invalidKeyLength'); } + } catch (e) { + hasError = setErrorMessage(e.message, addrKeyErrorMessageId); + } + if (!hasError) { const hash = sha256(`${localPart.value}@${domainName.value}`); const newAccount = { id: base32Encode(hash, 'RFC4648', { padding: false }).toLowerCase(), @@ -66,8 +77,6 @@ const addAccount = () => { }; accounts.value.push(newAccount); return toMainView(); - } catch (e) { - errorMessageId.value = e.message; } } }; @@ -119,15 +128,21 @@ const toMainView = () => { }; // Error message -const setErrorMessage = (messageId) => { - if (messageId.startsWith('addAccount.error.')) { - errorMessageId.value = messageId; +const setErrorMessage = (messageId, messageType) => { + const messageIdClean = messageId.startsWith('addAccount.error.') ? messageId : 'addAccount.error.unknown'; + if (messageType) { + messageType.value = messageIdClean; } else { - errorMessageId.value = 'addAccount.error.unknown'; + errorMessageId.value = messageIdClean; } + return true; }; const resetErrorMessage = () => { errorMessageId.value = ''; + + separatorErrorMessageId.value = ''; + localPartErrorMessageId.value = ''; + addrKeyErrorMessageId.value = ''; }; @@ -142,11 +157,13 @@ const resetErrorMessage = () => {
- + +
{{ $t(localPartErrorMessageId) }}
- + +
{{ $t(separatorErrorMessageId) }}
@@ -155,8 +172,9 @@ const resetErrorMessage = () => {
- + +
{{ $t(addrKeyErrorMessageId) }}
From 55ce44aebfda999741106f43ede3f6194831e0f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodolphe=20Br=C3=A9ard?= Date: Tue, 26 Sep 2023 12:15:44 +0200 Subject: [PATCH 5/5] Forbid to add the same account twice --- CHANGELOG.md | 1 + src/locales/en.json | 1 + src/locales/fr.json | 1 + src/views/AddAccountView.vue | 21 +++++++++++++++++---- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c1a78e..5b0c522 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Fixed - Invalid preferences are now automatically corrected +- It is now impossible to add the same account twice ## [0.3.0] - 2023-08-25 diff --git a/src/locales/en.json b/src/locales/en.json index 7c13534..802096b 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -35,6 +35,7 @@ "addAccount": "Add account", "cancel": "@:invariants.controls.cancel", "error": { + "accountAlreadyExists": "You already have an account on this domain that uses this local part.", "invalidBase64": "The key must be a valid base64 string.", "invalidKeyLength": "The key's length must be either 128 bits (16 bytes) or 256 bits (32 bytes).", "invalidSeparator": "The separator must be a single character.", diff --git a/src/locales/fr.json b/src/locales/fr.json index 125e2f2..b796013 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -35,6 +35,7 @@ "addAccount": "Ajouter", "cancel": "@:invariants.controls.cancel", "error": { + "accountAlreadyExists": "Vous avez déjà un compte sur ce nom de domaine qui utilise cette partie locale.", "invalidBase64": "La clé doit être une chaîne de caractère en base64.", "invalidKeyLength": "La longueur de la clé doit être de 128 bits (16 bytes) ou de 256 bits (32 bytes).", "invalidSeparator": "Le séparateur doit être un unique caractère.", diff --git a/src/views/AddAccountView.vue b/src/views/AddAccountView.vue index 3ae1b08..9570c01 100644 --- a/src/views/AddAccountView.vue +++ b/src/views/AddAccountView.vue @@ -51,11 +51,24 @@ const addAccount = () => { resetErrorMessage(); var hasError = false; var key = null; + var accountId = null; if (separator.value.length != 1) { hasError = setErrorMessage('addAccount.error.invalidSeparator', separatorErrorMessageId); } - if (localPart.value.includes(separator.value)) { - hasError = setErrorMessage('addAccount.error.localPartSeparator', localPartErrorMessageId); + try { + if (localPart.value.includes(separator.value)) { + throw new Error('addAccount.error.localPartSeparator'); + } + accountId = `${localPart.value}@${domainName.value}`; + for (const acc of accounts.value) { + const comp = `${acc.localPart}@${acc.domain}`; + if (accountId == comp) { + throw new Error('addAccount.error.accountAlreadyExists'); + } + } + } catch (e) { + console.log(e); + hasError = setErrorMessage(e.message, localPartErrorMessageId); } try { key = base64Decode(privateKey.value); @@ -65,8 +78,8 @@ const addAccount = () => { } catch (e) { hasError = setErrorMessage(e.message, addrKeyErrorMessageId); } - if (!hasError) { - const hash = sha256(`${localPart.value}@${domainName.value}`); + if (!hasError && key && accountId) { + const hash = sha256(accountId); const newAccount = { id: base32Encode(hash, 'RFC4648', { padding: false }).toLowerCase(), localPart: localPart.value,