diff --git a/src/api/common.js b/src/api/common.js index ae6d4517..dffe3e30 100644 --- a/src/api/common.js +++ b/src/api/common.js @@ -12,6 +12,7 @@ import { getCurrentLangAsV1Format } from 'src/i18n' import { i18n } from 'src/boot/i18n' import saveAs from 'file-saver' +import { PATH_CHANGE_PASSWORD } from 'src/router/routes' export const LIST_DEFAULT_PAGE = 1 export const LIST_DEFAULT_ROWS = 24 export const LIST_ALL_ROWS = 1000 @@ -177,6 +178,11 @@ function handleResponseError (err) { if (code === 403 && message === 'Invalid license') { message = i18n.global.tc('Contact your administrator to activate this functionality') } + if (code === 403 && message === 'Password expired') { + message = i18n.global.tc('Password Expired') + return this.$router.push({ path: PATH_CHANGE_PASSWORD }) + } + if (code !== null && message !== null) { throw new ApiResponseError(code, message) } diff --git a/src/api/user.js b/src/api/user.js index e32484e5..bf3f970e 100644 --- a/src/api/user.js +++ b/src/api/user.js @@ -1,5 +1,6 @@ import _ from 'lodash' +import { i18n } from 'boot/i18n' import { get, post, @@ -51,6 +52,15 @@ export async function loginByExchangeToken (token) { } } +export async function getPreLoginPasswordInfo () { + try { + const res = await httpApi.get('api/platforminfo') + return res.data.security.password + } catch (err) { + throw new Error(err.response.data.message) + } +} + export async function getUserData (id) { const allPromise = Promise.all([ getSubscriberById(id), @@ -167,6 +177,29 @@ export async function getPlatformInfo () { }) } +export function changeExpiredPassword (payload) { + return new Promise((resolve, reject) => { + httpApi.post('/api/passwordchange/', { + new_password: payload.new_password + }, { + auth: { + username: payload.username, + password: payload.old_password + } + }).then((result) => { + resolve(result) + }).catch((err) => { + if (err.response.status === 401) { + reject(`Unauthorized. ${i18n.global.tc('Wrong username or password')}`) + } else if (err.response.status === 422) { + reject(_formatPasswordError(err.response.data.message)) + } else { + reject('Unexpected error') + } + }) + }) +} + export async function createAuthToken (tokenExpiringTime) { const response = await post({ resource: 'authtokens', @@ -177,3 +210,7 @@ export async function createAuthToken (tokenExpiringTime) { }) return response.token } + +function _formatPasswordError (error) { + return error.split("'").slice(-2, -1)[0].replaceAll(',', ', ') +} diff --git a/src/boot/routes.js b/src/boot/routes.js index dcccf37d..a25e9137 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -9,7 +9,7 @@ import { export default ({ app, router, store }) => { router.beforeEach((to, from, next) => { - const publicUrls = ['/login', '/recoverpassword'] + const publicUrls = ['/login', '/recoverpassword', '/changepassword'] // not authorized user if (!hasJwt()) { if (!publicUrls.includes(to.path)) { diff --git a/src/i18n/de.json b/src/i18n/de.json index e46d6895..585cd1b3 100644 --- a/src/i18n/de.json +++ b/src/i18n/de.json @@ -58,6 +58,7 @@ "Block Mode for outbounds calls": "Block Mode for outbounds calls", "Block Outgoing": "Ausgehende Anrufe blockieren", "Block anonymous inbound calls": "Block anonymous inbound calls", + "Block outgoing": "Block outgoing", "Busy Greeting": "Begrüßung, wenn besetzt", "Busy Lamp Field": "Besetztlampenfeld", "CDR": "CDR", @@ -107,7 +108,7 @@ "Conference name": "Konferenzname", "Confirm": "Bestätigen", "Contact": "Kontakt", - "Contact your administrator to activate this functionality":"Zur Aktivierung dieser Funktion kontaktieren Sie Ihren Administrator.", + "Contact your administrator to activate this functionality": "Zur Aktivierung dieser Funktion kontaktieren Sie Ihren Administrator.", "Content": "Inhalt", "Conversations": "Konversationen", "Copy link": "Link kopieren", @@ -119,6 +120,7 @@ "Create Call Queue": "Anrufwarteschlange hinzufügen", "Create Config": "Konfiguration hinzufügen", "Create List": "Liste erstellen", + "Create New Password": "Create New Password", "Create destination": "Ziel erstellen", "Create device": "Gerät hinzufügen", "Create group": "Gruppe hinzufügen", @@ -128,6 +130,7 @@ "Created device {device} successfully": "Gerät {device} hinzugefügt", "Created manager secretary config for {msConfig} successfully": "Konfiguration für {msConfig} hinzugefügt", "Created sound set {soundSet} successfully": "Sound-Set {soundSet} hinzugefügt", + "Current Password": "Current Password", "Custom Announcement": "Benutzerdefinierte Ansage", "Custom Announcements": "Individuelle Ansagen", "Custom sound": "Benutzerdefinierter Sound", @@ -300,6 +303,7 @@ "Ncos Set": "Ncos Set", "Never": "Nie", "New Messages": "Neue Nachrichten", + "New Password": "New Password", "New SIP Password": "Neues SIP-Passwort", "New SIP Password confirm": "Neues SIP-Passwort bestätigen", "New Web Password": "Neues Web-Passwort", @@ -356,10 +360,18 @@ "Parallel Ringing": "Paralleles Klingeln", "Parent": "Eltern", "Password": "Passwort", + "Password Expired": "Password Expired", "Password Retype": "Passwort-Neueingabe", "Password changed successfully": "Passwort erfolgreich geändert", "Password confirm": "Passwort bestätigen", + "Password is considered weak": "Password is considered weak", "Password is not strong enough": "Passwort ist nicht sicher genug", + "Password is not strong enough, add more digits": "Password is not strong enough, add more digits", + "Password is not strong enough, add more lowercase letters": "Password is not strong enough, add more lowercase letters", + "Password is not strong enough, add more special characters": "Password is not strong enough, add more special characters", + "Password is not strong enough, add more uppercase letters": "Password is not strong enough, add more uppercase letters", + "Password must be at least {max} characters long": "Password must be at least {max} characters long", + "Password must be at least {min} characters long": "Password must be at least {min} characters long", "Passwords must be equal": "Passwörter müssen übereinstimmen", "Phone model": "Telefonmodell", "Phone number": "Rufnummer", @@ -602,6 +614,7 @@ "Your number is visible to the callee": "Rufnummer wird dem Angerufenen angezeigt", "Your number is visible to the callee within own PBX": "Ihre Rufnummer wird dem Angerufenen bei PBX-internen Verbindungen angezeigt", "Your password has been changed successfully": "Ihr Passwort wurde erfolgreich geändert", + "Your password has expired": "Your password has expired", "Your web password has been changed successfully": "Ihr Web-Passwort wurde erfolgreich geändert", "ago": "vor", "and": "und", diff --git a/src/i18n/en.json b/src/i18n/en.json index 7226aaf3..a63caf11 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -57,6 +57,7 @@ "Block Mode for outbounds calls": "Block Mode for outbounds calls", "Block Outgoing": "Block Outgoing", "Block anonymous inbound calls": "Block anonymous inbound calls", + "Block outgoing": "Block outgoing", "Busy Greeting": "Busy Greeting", "Busy Lamp Field": "Busy Lamp Field", "CDR": "CDR", @@ -105,7 +106,7 @@ "Conference": "Conference", "Confirm": "Confirm", "Contact": "Contact", - "Contact your administrator to activate this functionality":"Contact your administrator to activate this functionality", + "Contact your administrator to activate this functionality": "Contact your administrator to activate this functionality", "Content": "Content", "Conversations": "Conversations", "Cost": "Cost", @@ -116,6 +117,7 @@ "Create Call Queue": "Create Call Queue", "Create Config": "Create Config", "Create List": "Create List", + "Create New Password": "Create New Password", "Create destination": "Create destination", "Create device": "Create device", "Create group": "Create group", @@ -125,6 +127,7 @@ "Created device {device} successfully": "Created device {device} successfully", "Created manager secretary config for {msConfig} successfully": "Created manager secretary config for {msConfig} successfully", "Created sound set {soundSet} successfully": "Created sound set {soundSet} successfully", + "Current Password": "Current Password", "Custom Announcement": "Custom Announcement", "Custom Announcements": "Custom Announcements", "Custom sound": "Custom sound", @@ -288,6 +291,7 @@ "Ncos": "Ncos", "Ncos Set": "Ncos Set", "Never": "Never", + "New Password": "New Password", "New SIP Password": "New SIP Password", "New SIP Password confirm": "New SIP Password confirm", "New Web Password": "New Web Password", @@ -342,10 +346,18 @@ "Parallel Ringing": "Parallel Ringing", "Parent": "Parent", "Password": "Password", + "Password Expired": "Password Expired", "Password Retype": "Password Retype", "Password changed successfully": "Password changed successfully", "Password confirm": "Password confirm", + "Password is considered weak": "Password is considered weak", "Password is not strong enough": "Password is not strong enough", + "Password is not strong enough, add more digits": "Password is not strong enough, add more digits", + "Password is not strong enough, add more lowercase letters": "Password is not strong enough, add more lowercase letters", + "Password is not strong enough, add more special characters": "Password is not strong enough, add more special characters", + "Password is not strong enough, add more uppercase letters": "Password is not strong enough, add more uppercase letters", + "Password must be at least {max} characters long": "Password must be at least {max} characters long", + "Password must be at least {min} characters long": "Password must be at least {min} characters long", "Passwords must be equal": "Passwords must be equal", "Phone model": "Phone model", "Phone number": "Phone number", @@ -581,6 +593,7 @@ "Your number is hidden to the callee within own PBX": "Your number is hidden to the callee within own PBX", "Your number is visible to the callee": "Your number is visible to the callee", "Your number is visible to the callee within own PBX": "Your number is visible to the callee within own PBX", + "Your password has expired": "Your password has expired", "ago": "ago", "and": "and", "and call from": "and call from", diff --git a/src/i18n/es.json b/src/i18n/es.json index dfe5e448..7508893a 100644 --- a/src/i18n/es.json +++ b/src/i18n/es.json @@ -58,6 +58,7 @@ "Block Mode for outbounds calls": "Block Mode for outbounds calls", "Block Outgoing": "Bloquear Salientes", "Block anonymous inbound calls": "Block anonymous inbound calls", + "Block outgoing": "Block outgoing", "Busy Greeting": "Saludo de Ocupado", "Busy Lamp Field": "Campo de lámpara ocupado", "CDR": "CDR", @@ -107,7 +108,7 @@ "Conference name": "Nombre de la conferencia", "Confirm": "Confirmar", "Contact": "Contacto", - "Contact your administrator to activate this functionality":"Póngase en contacto con su administrador para activar esta funcionalidad", + "Contact your administrator to activate this functionality": "Póngase en contacto con su administrador para activar esta funcionalidad", "Content": "Contenido", "Conversations": "Conversaciones", "Copy link": "Copiar enlace", @@ -119,6 +120,7 @@ "Create Call Queue": "Crear Cola de Llamadas", "Create Config": "Crear Configuración", "Create List": "Crear Lista", + "Create New Password": "Create New Password", "Create destination": "Crear destino", "Create device": "Crear dispositivo", "Create group": "Crear grupo", @@ -128,6 +130,7 @@ "Created device {device} successfully": "Dispositivo {device} creado exitosamente", "Created manager secretary config for {msConfig} successfully": "Se creó exitosamente la configuración de Jefe-Asistente para {msConfig}", "Created sound set {soundSet} successfully": "Conjunto de sonido {soundSet} creado exitosamente", + "Current Password": "Current Password", "Custom Announcement": "Anuncio Personalizado", "Custom Announcements": "Anuncios Personalizados", "Custom sound": "Sonido personalizado", @@ -301,6 +304,7 @@ "Ncos Set": "Ncos Set", "Never": "Nunca", "New Messages": "Nuevos Mensajes", + "New Password": "New Password", "New SIP Password": "New SIP Password", "New SIP Password confirm": "New SIP Password confirm", "New Web Password": "New Web Password", @@ -355,10 +359,18 @@ "Parallel Ringing": "Llamada en paralelo", "Parent": "Parent", "Password": "Contraseña", + "Password Expired": "Password Expired", "Password Retype": "Repetir contraseña", "Password changed successfully": "La contraseña ha sido cambiada correctamente.", "Password confirm": "Password confirm", + "Password is considered weak": "Password is considered weak", "Password is not strong enough": "La contraseña no es lo suficientemente fuerte", + "Password is not strong enough, add more digits": "Password is not strong enough, add more digits", + "Password is not strong enough, add more lowercase letters": "Password is not strong enough, add more lowercase letters", + "Password is not strong enough, add more special characters": "Password is not strong enough, add more special characters", + "Password is not strong enough, add more uppercase letters": "Password is not strong enough, add more uppercase letters", + "Password must be at least {max} characters long": "Password must be at least {max} characters long", + "Password must be at least {min} characters long": "Password must be at least {min} characters long", "Passwords must be equal": "Las contraseñas deben ser iguales", "Phone model": "Modelo de teléfono", "Phone number": "Número de teléfono", @@ -606,6 +618,7 @@ "Your number is visible to the callee": "Su número es visible para la persona que llama", "Your number is visible to the callee within own PBX": "Su número es visible para el receptor de la llamada en su propia centralita.", "Your password has been changed successfully": "Su contraseña ha sido cambiada exitosamente", + "Your password has expired": "Your password has expired", "ago": "atrás", "and": "y", "and call from": "y llamar desde", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index 48f300e1..8342e06d 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -58,6 +58,7 @@ "Block Mode for outbounds calls": "Mode blocage pour les appels sortants", "Block Outgoing": "Bloquer les sortants", "Block anonymous inbound calls": "Bloquer les appels entrants anonymes", + "Block outgoing": "Block outgoing", "Busy Greeting": "Message d'accueil en cas d'occupation", "Busy Lamp Field": "Champ de la lampe occupée", "CDR": "CDR", @@ -107,7 +108,7 @@ "Conference name": "Nom de la conférence", "Confirm": "Confirmer", "Contact": "Contact", - "Contact your administrator to activate this functionality":"Contactez votre administrateur pour activer cette fonctionnalité", + "Contact your administrator to activate this functionality": "Contactez votre administrateur pour activer cette fonctionnalité", "Content": "Contenu", "Conversations": "Conversations", "Copy link": "Copier le lien", @@ -119,6 +120,7 @@ "Create Call Queue": "Créer une file d’attente", "Create Config": "Créer Configuration", "Create List": "Créer une liste", + "Create New Password": "Create New Password", "Create destination": "Créer une destination", "Create device": "Créer un appareil", "Create group": "Créer un groupe", @@ -128,6 +130,7 @@ "Created device {device} successfully": "Le poste {device} a été créé avec succès", "Created manager secretary config for {msConfig} successfully": "Création réussie de la configuration du secrétaire du gestionnaire pour {msConfig}", "Created sound set {soundSet} successfully": "Création du jeu de sons {soundSet} avec succès", + "Current Password": "Current Password", "Custom Announcement": "Annonce personnalisée", "Custom Announcements": "Annonces personnalisées", "Custom sound": "Son personnalisé", @@ -300,6 +303,7 @@ "Ncos Set": "Ncos Set", "Never": "Jamais", "New Messages": "Nouveaux messages", + "New Password": "New Password", "New SIP Password": "New SIP Password", "New SIP Password confirm": "New SIP Password confirm", "New Web Password": "New Web Password", @@ -356,10 +360,18 @@ "Parallel Ringing": "Sonnerie parallèle", "Parent": "Parent", "Password": "Mot de passe", + "Password Expired": "Password Expired", "Password Retype": "Retaper le mot de passe", "Password changed successfully": "Le mot de passe a été modifié avec succès", "Password confirm": "Password confirm", + "Password is considered weak": "Password is considered weak", "Password is not strong enough": "Le mot de passe n'est pas assez fort", + "Password is not strong enough, add more digits": "Password is not strong enough, add more digits", + "Password is not strong enough, add more lowercase letters": "Password is not strong enough, add more lowercase letters", + "Password is not strong enough, add more special characters": "Password is not strong enough, add more special characters", + "Password is not strong enough, add more uppercase letters": "Password is not strong enough, add more uppercase letters", + "Password must be at least {max} characters long": "Password must be at least {max} characters long", + "Password must be at least {min} characters long": "Password must be at least {min} characters long", "Passwords must be equal": "Les mots de passe doivent être égaux", "Phone model": "Modèle du poste", "Phone number": "Numéro de téléphone", @@ -602,6 +614,7 @@ "Your number is visible to the callee": "Votre numéro est visible par le destinataire de l'appel", "Your number is visible to the callee within own PBX": "Votre numéro est visible par appelant dans son propre PBX", "Your password has been changed successfully": "Votre mot de passe a été modifié avec succès", + "Your password has expired": "Your password has expired", "ago": ".", "and": "et", "and call from": "et appeler de", diff --git a/src/i18n/it.json b/src/i18n/it.json index 61bcf8aa..c6da040b 100644 --- a/src/i18n/it.json +++ b/src/i18n/it.json @@ -56,6 +56,7 @@ "Block Mode for outbounds calls": "Modalità di blocco per le chiamate in uscita", "Block Outgoing": "Blocca chiamate in uscita", "Block anonymous inbound calls": "Blocca chiamate anonime in entrata", + "Block outgoing": "Block outgoing", "Busy Greeting": "Messaggio per numero occupato", "Busy Lamp Field": "Busy Lamp Field", "CDR": "CDR", @@ -117,6 +118,7 @@ "Create Call Queue": "Crea coda chiamate", "Create Config": "Crea configurazione", "Create List": "Crea lista", + "Create New Password": "Create New Password", "Create destination": "Crea destinazione", "Create device": "Crea dispositivo", "Create group": "Crea gruppo", @@ -126,6 +128,7 @@ "Created device {device} successfully": "Dispositivo {device} creato correttamente", "Created manager secretary config for {msConfig} successfully": "Configurazione segreteria per {msConfig} creata correttamente", "Created sound set {soundSet} successfully": "Set di messaggi audio {soundSet} creato correttamente", + "Current Password": "Current Password", "Custom Announcement": "Custom Announcement", "Custom Announcements": "Custom Announcements", "Custom sound": "Audio personalizzato", @@ -292,6 +295,7 @@ "Ncos Set": "Ncos Set", "Never": "Never", "New Messages": "Nuovi messaggi", + "New Password": "New Password", "New SIP Password": "New SIP Password", "New SIP Password confirm": "New SIP Password confirm", "New Web Password": "New Web Password", @@ -348,10 +352,18 @@ "Parallel Ringing": "Squilli in parallelo", "Parent": "Parent", "Password": "Password", + "Password Expired": "Password Expired", "Password Retype": "Password Retype", "Password changed successfully": "Password changed successfully", "Password confirm": "Password confirm", + "Password is considered weak": "Password is considered weak", "Password is not strong enough": "Password is not strong enough", + "Password is not strong enough, add more digits": "Password is not strong enough, add more digits", + "Password is not strong enough, add more lowercase letters": "Password is not strong enough, add more lowercase letters", + "Password is not strong enough, add more special characters": "Password is not strong enough, add more special characters", + "Password is not strong enough, add more uppercase letters": "Password is not strong enough, add more uppercase letters", + "Password must be at least {max} characters long": "Password must be at least {max} characters long", + "Password must be at least {min} characters long": "Password must be at least {min} characters long", "Passwords must be equal": "Le passwords devono essere identiche", "Phone model": "Modello telefono", "Phone number": "Numero di telefono", @@ -590,6 +602,7 @@ "Your number is visible to the callee": "Il tuo numero è visible al chiamato", "Your number is visible to the callee within own PBX": "Your number is visible to the callee within own PBX", "Your password has been changed successfully": "Password modificata", + "Your password has expired": "Your password has expired", "ago": "fa", "and": "e", "and call from": "and call from", diff --git a/src/pages/CscChangeExpiredPassword.vue b/src/pages/CscChangeExpiredPassword.vue new file mode 100644 index 00000000..f74f005c --- /dev/null +++ b/src/pages/CscChangeExpiredPassword.vue @@ -0,0 +1,293 @@ +<template> + <q-layout + id="csc-layout-login" + view="lHh lpR lFf" + class="bg-page" + > + <q-header + id="csc-header-login" + class="bg-transparent" + > + <q-toolbar + id="csc-header-toolbar-login" + > + <csc-selection-language /> + </q-toolbar> + </q-header> + <q-page-container> + <q-page + id="csc-page-login" + class="flex flex-center row" + > + <q-card + id="csc-login-card" + class="bg-main-menu no-shadow no-border-radius col-xs-12 col-sm-6 col-md-4 q-pa-sm" + > + <q-card-section class="text-h5 text-center"> + {{ $t('Create New Password') }} + </q-card-section> + <q-card-section> + <q-banner + dense + inline-actions + class="text-white text-center bg-red-14" + > + {{ $t('Your password has expired') }} + </q-banner> + </q-card-section> + <q-card-section> + <div + v-if="validationGuidelines && validationGuidelines.length > 0" + inline-actions + class="q-mb-md q-pa-md" + > + <p>Suggested password format:</p> + <q-item + v-for="(message, index) in validationGuidelines" + :key="index" + dense + > + <q-item-section> + <span> + <q-icon + name="lock" + size="1em" + class="q-pa-xs" + /> {{ message }} + </span> + </q-item-section> + </q-item> + </div> + </q-card-section> + <q-card-section> + <q-form key="login-form"> + <csc-input + v-model.trim="username" + outlined + :label="$t('Username')" + data-cy="csc-login-username" + :error="v$.username && v$.username.$errors.length > 0" + :error-message="$errMsg(v$.username.$errors)" + @keyup.enter="changePasswordAction" + > + <template + #prepend + > + <q-icon + name="person" + /> + </template> + </csc-input> + + <csc-input-password + v-model.trim="currentPassword" + outlined + clearable + :label="$t('Current Password')" + data-cy="csc-login-password" + :error="v$.currentPassword && v$.currentPassword.$errors.length > 0" + :error-message="$errMsg(v$.currentPassword.$errors)" + @keyup.enter="changePasswordAction" + /> + + <csc-input-password + v-model.trim="newPassword" + outlined + clearable + :label="$t('New Password')" + data-cy="csc-login-password" + :error="v$.newPassword && v$.newPassword.$errors.length > 0" + :error-message="$errMsg(v$.newPassword.$errors)" + @keyup.enter="changePasswordAction" + /> + + <csc-input + ref="passwordRetypeInput" + v-model.trim="passwordRetype" + outlined + clearable + icon="lock" + color="secondary" + :label="$t('Password Retype')" + data-cy="password-retype-field" + type="password" + autocomplete="new-password" + :error="v$.passwordRetype && v$.passwordRetype.$errors.length > 0" + :error-message="$errMsg(v$.passwordRetype.$errors)" + @keyup.enter="changePasswordAction" + > + <template + #prepend + > + <q-icon + name="lock" + /> + </template> + </csc-input> + + <div class="row justify-center q-pa-md"> + <q-btn + data-cy="sign-in" + unelevated + color="primary" + icon="arrow_forward" + :label="$t('Change Password')" + @click="changePasswordAction" + /> + </div> + </q-form> + </q-card-section> + </q-card> + </q-page> + </q-page-container> + </q-layout> +</template> + +<script> +import useValidate from '@vuelidate/core' +import { required } from '@vuelidate/validators' +import { mapActions, mapState } from 'vuex' +import CscSelectionLanguage from 'src/components/CscSelectionLanguage.vue' +import CscInput from 'src/components/form/CscInput.vue' +import CscInputPassword from 'src/components/form/CscInputPassword.vue' +import { RequestState } from 'src/store/common' +import { mapWaitingActions } from 'vue-wait' + +export default { + name: 'ChangeExpiredPassword', + components: { + CscSelectionLanguage, + CscInput, + CscInputPassword + }, + data () { + return { + v$: useValidate(), + username: '', + currentPassword: '', + newPassword: '', + passwordStrengthScore: null, + passwordRetype: '', + validationGuidelines: [] + } + }, + validations () { + return { + username: { + required + }, + currentPassword: { + required + }, + newPassword: { + required + }, + passwordRetype: { + sameAsPassword (val) { + return val === this.newPassword + } + } + } + }, + computed: { + ...mapState('user', [ + 'changePasswordState', + 'changePasswordError' + ]) + }, + watch: { + changePasswordState (state) { + if (state === RequestState.succeeded) { + this.$q.notify({ + position: 'top', + color: 'positive', + icon: 'check', + message: this.$t('Password changed successfully') + }) + this.redirectToLogin() + } else if (state === RequestState.failed) { + this.$q.notify({ + position: 'top', + color: 'negative', + icon: 'error', + timeout: 10000, + message: this.changePasswordError || this.$t('There was an error, please retry later') + }) + } + } + }, + async mounted () { + const guidelines = await this.getValidationGuidelines() + this.validationGuidelines = this.formatValidationGuidelines(guidelines) + }, + methods: { + ...mapWaitingActions('user', [ + 'fetchPreLoginPasswordInfo' + ]), + ...mapActions('user', [ + 'changeExpiredPassword' + ]), + strengthMeterScoreUpdate (score) { + this.passwordStrengthScore = score + }, + async changePasswordAction () { + this.v$.$touch() + if (this.v$.$errors.length === 0) { + return this.changeExpiredPassword({ + username: this.username, + old_password: this.currentPassword, + new_password: this.newPassword + }) + } + }, + formatValidationGuidelines (validationRulesObject) { + const guidelines = [] + for (const rule in validationRulesObject) { + switch (rule) { + case 'min_length': + guidelines.push(`Minimum ${validationRulesObject[rule]} characters long.`) + break + case 'max_length': + guidelines.push(`Maximum ${validationRulesObject[rule]} characters long.`) + break + case 'musthave_digit': + guidelines.push(`Contains a minimum of ${validationRulesObject[rule]} digits.`) + break + case 'musthave_lowercase': + guidelines.push(`Contains a minimum of ${validationRulesObject[rule]} lowercases.`) + break + case 'musthave_specialchar': + guidelines.push(`Contains a minimum of ${validationRulesObject[rule]} special characters.`) + break + case 'musthave_uppercase': + guidelines.push(`Contains a minimum of ${validationRulesObject[rule]} uppercases.`) + break + default: + } + } + + return guidelines + }, + async getValidationGuidelines () { + const defaultGuidelines = { + max_length: 40, + min_length: 12, + musthave_digit: 3, + musthave_lowercase: 3, + musthave_specialchar: 3, + musthave_uppercase: 3 + } + const customGuidelines = await this.fetchPreLoginPasswordInfo() + + return customGuidelines ?? defaultGuidelines + }, + redirectToLogin () { + this.$router.push({ path: '/login' }) + } + } +} + +</script> + +<style> +</style> diff --git a/src/pages/CscPageLogin.vue b/src/pages/CscPageLogin.vue index f2859739..1a684ee8 100644 --- a/src/pages/CscPageLogin.vue +++ b/src/pages/CscPageLogin.vue @@ -157,7 +157,7 @@ export default { }, loginError (error) { if (error) { - showGlobalError(this.$t('Wrong username or password')) + showGlobalError(error) } } }, diff --git a/src/router/routes.js b/src/router/routes.js index 8c70d6bd..5a6bfcd4 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -8,6 +8,7 @@ import CscPageCallBlockingPrivacy from 'src/pages/CscPageCallBlockingPrivacy' import CscPageCallRecording from 'src/pages/CscPageCallRecording' import CscPageCallSettings from 'pages/CscPageCallSettings' import CscPageCf from 'pages/CscPageCf' +import CscPageChangePassword from 'src/pages/CscChangeExpiredPassword.vue' import CscPageConversations from 'src/pages/CscPageConversations' import CscPageCustomerPhonebook from 'src/pages/CscPageCustomerPhonebook' import CscPageCustomerPhonebookAdd from 'src/pages/CscPageCustomerPhonebookAdd' @@ -45,6 +46,8 @@ import CscRecoverPassword from 'src/pages/CscRecoverPassword' import CscPageCustomerPreferences from 'src/pages/CscPageCustomerPreferences' import { i18n } from 'src/boot/i18n' +export const PATH_CHANGE_PASSWORD = '/changepassword' + const getToken = (route) => { return { token: route.query.token @@ -543,6 +546,11 @@ const routes = [ } } }, + { + name: 'passwordChange', + path: PATH_CHANGE_PASSWORD, + component: CscPageChangePassword + }, { path: '/recoverpassword', component: CscLayoutLogin, diff --git a/src/store/user.js b/src/store/user.js index 8eb3f77f..29838fb5 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -6,7 +6,9 @@ import { import { login, getUserData, - createAuthToken + createAuthToken, + changeExpiredPassword, + getPreLoginPasswordInfo } from '../api/user' import { changePassword, @@ -46,6 +48,7 @@ import { httpApi, apiDownloadFile } from 'src/api/common' +import { PATH_CHANGE_PASSWORD } from 'src/router/routes' export default { namespaced: true, @@ -340,6 +343,9 @@ export default { await this.$router.push({ name: 'dashboard' }) } catch (err) { context.commit('loginFailed', err.message) + if (err.message === 'Password expired') { + this.$router.push({ path: PATH_CHANGE_PASSWORD }) + } } }, logout () { @@ -378,6 +384,16 @@ export default { } } }, + async changeExpiredPassword (context, newPassword) { + context.commit('userPasswordRequesting') + try { + await changeExpiredPassword(newPassword) + } catch (error) { + return context.commit('userPasswordFailed', error) + } + + context.commit('userPasswordSucceeded') + }, async changePassword (context, newPassword) { const subscriberId = getSubscriberId() await changePassword(subscriberId, newPassword) @@ -572,6 +588,9 @@ export default { commit('setQrCode', null) } }, + async fetchPreLoginPasswordInfo () { + return await getPreLoginPasswordInfo() + }, async generatePasswordUser () { const password = await generateGeneralPassword()