diff --git a/src/api/subscriber.js b/src/api/subscriber.js index 5b7359a1..900a2a84 100644 --- a/src/api/subscriber.js +++ b/src/api/subscriber.js @@ -517,3 +517,20 @@ export function changePassword (subscriber, newPassword) { }) }) } + +export async function resetPassword (userName) { + const payLoad = { + domain: Vue.$config.baseHttpUrl.replace(/(^\w+:|^)\/\//, ''), + type: 'subscriber', + username: userName + } + return await Vue.http.post('api/passwordreset/', payLoad) +} + +export async function recoverPassword (data) { + const payLoad = { + new_password: data.password, + token: data.token + } + return await Vue.http.post('api/passwordrecovery/', payLoad) +} diff --git a/src/boot/routes.js b/src/boot/routes.js index 27ad2738..473c18b6 100644 --- a/src/boot/routes.js +++ b/src/boot/routes.js @@ -11,22 +11,35 @@ import { export default ({ app, router, store }) => { router.beforeEach((to, from, next) => { - if (!hasJwt() && to.path !== '/login') { - next({ - path: '/login' - }) - } else if (hasJwt() && to.path === '/login') { - next({ - path: '/' - }) - } else if (hasJwt() && to.path === '/conference') { - next({ - path: '/conference/room123' - }) + const publicUrls = ['/login', '/recoverpassword'] + // not authorized user + if (!hasJwt()) { + if (!publicUrls.includes(to.path)) { + next({ + path: '/login' + }) + } else { + next() + } } else { - next() + // already authorized user + switch (to.path) { + case '/login': + next({ + path: '/' + }) + break + case '/conference': + next({ + path: '/conference/room123' + }) + break + default: + next() + } } }) + router.afterEach((to, from) => { const mainTitle = app.i18n.t('title') let title = _.get(to, 'meta.title', '') diff --git a/src/boot/vuelidate.js b/src/boot/vuelidate.js index e22676ad..b3488d5f 100644 --- a/src/boot/vuelidate.js +++ b/src/boot/vuelidate.js @@ -1,6 +1,15 @@ - import Vuelidate from 'vuelidate' +import _ from 'lodash' -export default ({ Vue, store }) => { +export default ({ Vue, app }) => { Vue.use(Vuelidate) + Vue.prototype.$errorMessage = (def) => { + let message = null + _.forEach(def.$params, (param, paramName) => { + if (def[paramName] === false) { + message = app.i18n.t('validators.' + paramName) + } + }) + return message + } } diff --git a/src/components/CscChangePasswordDialog.vue b/src/components/CscChangePasswordDialog.vue index 3f3c3563..f0ea5790 100644 --- a/src/components/CscChangePasswordDialog.vue +++ b/src/components/CscChangePasswordDialog.vue @@ -4,9 +4,10 @@ :value="value" :loading="loading" title-icon="vpn_key" - title="Change password" + :title="$t('pages.login.changePassword')" class="csc-pbx-password-dialog" @input="$emit('input')" + @hide="$emit('dialog-closed')" >
+ + + + + + + diff --git a/src/i18n/en.json b/src/i18n/en.json index f8c87e76..ad3a5bc2 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -42,7 +42,10 @@ "callAvailable": "You are now able to start and receive calls", "callNotAvailable": "Could not initialize call functionality properly", "conferencingAvailable": "You are now able to create WebRTC multiparty conferences", - "changeSessionLanguageSuccessMessage": "Session language successfully changed" + "changeSessionLanguageSuccessMessage": "Session language successfully changed", + "passwordChangedSuccessfully": "Password changed successfully", + "errorPasswordReset": "There was an error, please retry later", + "send": "Send" }, "validationErrors": { "generic": "You have invalid form input. Please check and try again.", @@ -142,7 +145,10 @@ "username": "Username", "username_helper": "Input username or username@domain", "password": "Password", - "password_helper": "" + "password_helper": "", + "forgotPassword": "Forgot password?", + "recoverPassword": "Recover password", + "changePassword": "Change password" }, "callBlockingIncoming": { "title": "Block/Allow incoming calls", diff --git a/src/layouts/CscLayoutLogin.vue b/src/layouts/CscLayoutLogin.vue new file mode 100644 index 00000000..30fa1838 --- /dev/null +++ b/src/layouts/CscLayoutLogin.vue @@ -0,0 +1,7 @@ + diff --git a/src/pages/CscPageLogin.vue b/src/pages/CscPageLogin.vue index e9894b26..2a3f7a77 100644 --- a/src/pages/CscPageLogin.vue +++ b/src/pages/CscPageLogin.vue @@ -76,8 +76,15 @@ + + @@ -117,18 +128,21 @@ import CscLanguageMenu from 'components/CscLanguageMenu' import CscSpinner from 'components/CscSpinner' import CscInputPassword from 'components/form/CscInputPassword' import CscInput from 'components/form/CscInput' +import CscRetrievePasswordDialog from 'components/CscRetrievePasswordDialog' export default { name: 'Login', components: { CscInput, CscInputPassword, CscSpinner, - CscLanguageMenu + CscLanguageMenu, + CscRetrievePasswordDialog }, data () { return { username: '', - password: '' + password: '', + showDialog: false } }, computed: { @@ -176,6 +190,9 @@ export default { }, changeLanguage (language) { this.$store.dispatch('user/changeSessionLanguage', language) + }, + showRetrievePasswordDialog () { + this.showDialog = true } } } diff --git a/src/pages/CscRecoverPassword.vue b/src/pages/CscRecoverPassword.vue new file mode 100644 index 00000000..5099d14e --- /dev/null +++ b/src/pages/CscRecoverPassword.vue @@ -0,0 +1,84 @@ + + + diff --git a/src/router/routes.js b/src/router/routes.js index 83329014..f693525d 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -1,6 +1,7 @@ import CscLayoutConference from 'src/layouts/CscLayoutConference' import CscLayoutMain from 'src/layouts/CscLayoutMain' +import CscLayoutLogin from 'src/layouts/CscLayoutLogin' import CscPageLogin from 'src/pages/CscPageLogin' import CscPageHome from 'src/pages/CscPageHome' @@ -24,6 +25,13 @@ import CscPagePbxSettings from 'src/pages/CscPagePbxSettings' import CscPageVoicebox from 'src/pages/CscPageVoicebox' import CscPageUserSettings from 'src/pages/CscPageUserSettings' import CscPageError404 from 'src/pages/CscPageError404' +import CscRecoverPassword from 'src/pages/CscRecoverPassword' + +const getToken = (route) => { + return { + token: route.query.token + } +} export default function routes (app) { const i18n = app.i18n @@ -214,6 +222,21 @@ export default function routes (app) { title: 'Conference' } }, + { + path: '/recoverpassword', + component: CscLayoutLogin, + children: [ + { + path: '', + component: CscRecoverPassword, + props: getToken, + meta: { + title: 'Reset Password', + permission: 'public' + } + } + ] + }, { path: '/', redirect: { @@ -226,21 +249,3 @@ export default function routes (app) { } ] } - -// const routes = [ -// { -// path: '/', -// component: () => import('layouts/MainLayout.vue'), -// children: [ -// { path: '', component: () => import('pages/Index.vue') } -// ] -// }, -// // Always leave this as last one, -// // but you can also remove it -// { -// path: '*', -// component: () => import('pages/Error404.vue') -// } -// ] -// -// export default routes diff --git a/src/store/user.js b/src/store/user.js index b3433049..fcf9db4d 100644 --- a/src/store/user.js +++ b/src/store/user.js @@ -13,7 +13,7 @@ import { login, getUserData } from '../api/user' -import { changePassword } from '../api/subscriber' +import { changePassword, resetPassword, recoverPassword } from '../api/subscriber' import { deleteJwt, getJwt, getSubscriberId, setJwt, setSubscriberId } from 'src/auth' import { setSession } from 'src/storage' @@ -41,7 +41,8 @@ export default { changeSessionLocaleError: null, languageLabels: [], changePasswordState: RequestState.initiated, - changePasswordError: null + changePasswordError: null, + newPasswordRequesting: false }, getters: { isLogged (state) { @@ -243,8 +244,11 @@ export default { state.changePasswordError = null }, userPasswordFailed (state, error) { - state.changePasswordState = RequestState.failed state.changePasswordError = error + state.changePasswordState = RequestState.failed + }, + newPasswordRequesting (state, isRequesting) { + state.newPasswordRequesting = isRequesting } }, actions: { @@ -317,6 +321,25 @@ export default { context.commit('userPasswordFailed', err.message) }) }, + async resetPassword ({ commit }, data) { + commit('newPasswordRequesting', true) + const response = await resetPassword(data) + commit('newPasswordRequesting', false) + return response + }, + async recoverPassword ({ commit, dispatch, state, rootGetters }, data) { + commit('userPasswordRequesting') + try { + const res = await recoverPassword(data) + if (res.status === 200 || res.status === 201) { + commit('userPasswordSucceeded') + } else { + commit('userPasswordFailed') + } + } catch (err) { + commit('userPasswordFailed', err.message) + } + }, async forwardHome (context) { if (context.rootState.route.path === '/user/home' && !context.getters.isRtcEngineUiVisible) { await router.push({ path: '/user/conversations' })