logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe
commit: 702dc6963c237c03b1fe2b0f700582107dd79190
parent: c89703a35980f900b4d206538dc07f83e87c84d8
Author: Shpuld Shpludson <shp@cock.li>
Date:   Thu,  5 Sep 2019 11:23:28 +0000

Merge branch 'password-reset' into 'develop'

Password reset page

Closes #213

See merge request pleroma/pleroma-fe!884

Diffstat:

Msrc/boot/after_store.js1+
Msrc/boot/routes.js2++
Msrc/components/login_form/login_form.vue5+++++
Asrc/components/password_reset/password_reset.js62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/password_reset/password_reset.vue116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/i18n/en.json11+++++++++++
Msrc/i18n/ru.json11+++++++++++
Asrc/services/new_api/password_reset.js18++++++++++++++++++
8 files changed, 226 insertions(+), 0 deletions(-)

diff --git a/src/boot/after_store.js b/src/boot/after_store.js @@ -246,6 +246,7 @@ const getNodeInfo = async ({ store }) => { store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') }) store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) + store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames }) store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats }) diff --git a/src/boot/routes.js b/src/boot/routes.js @@ -9,6 +9,7 @@ import UserProfile from 'components/user_profile/user_profile.vue' import Search from 'components/search/search.vue' import Settings from 'components/settings/settings.vue' import Registration from 'components/registration/registration.vue' +import PasswordReset from 'components/password_reset/password_reset.vue' import UserSettings from 'components/user_settings/user_settings.vue' import FollowRequests from 'components/follow_requests/follow_requests.vue' import OAuthCallback from 'components/oauth_callback/oauth_callback.vue' @@ -46,6 +47,7 @@ export default (store) => { { name: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute }, { name: 'settings', path: '/settings', component: Settings }, { name: 'registration', path: '/registration', component: Registration }, + { name: 'password-reset', path: '/password-reset', component: PasswordReset }, { name: 'registration-token', path: '/registration/:token', component: Registration }, { name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute }, { name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute }, diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue @@ -33,6 +33,11 @@ type="password" > </div> + <div class="form-group"> + <router-link :to="{name: 'password-reset'}"> + {{ $t('password_reset.forgot_password') }} + </router-link> + </div> </template> <div diff --git a/src/components/password_reset/password_reset.js b/src/components/password_reset/password_reset.js @@ -0,0 +1,62 @@ +import { mapState } from 'vuex' +import passwordResetApi from '../../services/new_api/password_reset.js' + +const passwordReset = { + data: () => ({ + user: { + email: '' + }, + isPending: false, + success: false, + throttled: false, + error: null + }), + computed: { + ...mapState({ + signedIn: (state) => !!state.users.currentUser, + instance: state => state.instance + }), + mailerEnabled () { + return this.instance.mailerEnabled + } + }, + created () { + if (this.signedIn) { + this.$router.push({ name: 'root' }) + } + }, + methods: { + dismissError () { + this.error = null + }, + submit () { + this.isPending = true + const email = this.user.email + const instance = this.instance.server + + passwordResetApi({ instance, email }).then(({ status }) => { + this.isPending = false + this.user.email = '' + + if (status === 204) { + this.success = true + this.error = null + } else if (status === 404 || status === 400) { + this.error = this.$t('password_reset.not_found') + this.$nextTick(() => { + this.$refs.email.focus() + }) + } else if (status === 429) { + this.throttled = true + this.error = this.$t('password_reset.too_many_requests') + } + }).catch(() => { + this.isPending = false + this.user.email = '' + this.error = this.$t('general.generic_error') + }) + } + } +} + +export default passwordReset diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue @@ -0,0 +1,116 @@ +<template> + <div class="settings panel panel-default"> + <div class="panel-heading"> + {{ $t('password_reset.password_reset') }} + </div> + <div class="panel-body"> + <form + class="password-reset-form" + @submit.prevent="submit" + > + <div class="container"> + <div v-if="!mailerEnabled"> + <p> + {{ $t('password_reset.password_reset_disabled') }} + </p> + </div> + <div v-else-if="success || throttled"> + <p v-if="success"> + {{ $t('password_reset.check_email') }} + </p> + <div class="form-group text-center"> + <router-link :to="{name: 'root'}"> + {{ $t('password_reset.return_home') }} + </router-link> + </div> + </div> + <div v-else> + <p> + {{ $t('password_reset.instruction') }} + </p> + <div class="form-group"> + <input + ref="email" + v-model="user.email" + :disabled="isPending" + :placeholder="$t('password_reset.placeholder')" + class="form-control" + type="input" + > + </div> + <div class="form-group"> + <button + :disabled="isPending" + type="submit" + class="btn btn-default btn-block" + > + {{ $t('general.submit') }} + </button> + </div> + </div> + <p + v-if="error" + class="alert error notice-dismissible" + > + <span>{{ error }}</span> + <a + class="button-icon dismiss" + @click.prevent="dismissError()" + > + <i class="icon-cancel" /> + </a> + </p> + </div> + </form> + </div> + </div> +</template> + +<script src="./password_reset.js"></script> +<style lang="scss"> +@import '../../_variables.scss'; + +.password-reset-form { + display: flex; + flex-direction: column; + align-items: center; + margin: 0.6em; + + .container { + display: flex; + flex: 1 0; + flex-direction: column; + margin-top: 0.6em; + max-width: 18rem; + } + + .form-group { + display: flex; + flex-direction: column; + margin-bottom: 1em; + padding: 0.3em 0.0em 0.3em; + line-height: 24px; + } + + .error { + text-align: center; + animation-name: shakeError; + animation-duration: 0.4s; + animation-timing-function: ease-in-out; + } + + .alert { + padding: 0.5em; + margin: 0.3em 0.0em 1em; + } + + .notice-dismissible { + padding-right: 2rem; + } + + .icon-cancel { + cursor: pointer; + } +} + +</style> diff --git a/src/i18n/en.json b/src/i18n/en.json @@ -608,5 +608,16 @@ "person_talking": "{count} person talking", "people_talking": "{count} people talking", "no_results": "No results" + }, + "password_reset": { + "forgot_password": "Forgot password?", + "password_reset": "Password reset", + "instruction": "Enter your email address or username. We will send you a link to reset your password.", + "placeholder": "Your email or username", + "check_email": "Check your email for a link to reset your password.", + "return_home": "Return to the home page", + "not_found": "We couldn't find that email or username.", + "too_many_requests": "You have reached the limit of attempts, try again later.", + "password_reset_disabled": "Password reset is disabled. Please contact your instance administrator." } } diff --git a/src/i18n/ru.json b/src/i18n/ru.json @@ -389,5 +389,16 @@ "person_talking": "Популярно у {count} человека", "people_talking": "Популярно у {count} человек", "no_results": "Ничего не найдено" + }, + "password_reset": { + "forgot_password": "Забыли пароль?", + "password_reset": "Сброс пароля", + "instruction": "Введите ваш email или имя пользователя, и мы отправим вам ссылку для сброса пароля.", + "placeholder": "Ваш email или имя пользователя", + "check_email": "Проверьте ваш email и перейдите по ссылке для сброса пароля.", + "return_home": "Вернуться на главную страницу", + "not_found": "Мы не смогли найти аккаунт с таким email-ом или именем пользователя.", + "too_many_requests": "Вы исчерпали допустимое количество попыток, попробуйте позже.", + "password_reset_disabled": "Сброс пароля отключен. Cвяжитесь с администратором вашего сервера." } } diff --git a/src/services/new_api/password_reset.js b/src/services/new_api/password_reset.js @@ -0,0 +1,18 @@ +import { reduce } from 'lodash' + +const MASTODON_PASSWORD_RESET_URL = `/auth/password` + +const resetPassword = ({ instance, email }) => { + const params = { email } + const query = reduce(params, (acc, v, k) => { + const encoded = `${k}=${encodeURIComponent(v)}` + return `${acc}&${encoded}` + }, '') + const url = `${instance}${MASTODON_PASSWORD_RESET_URL}?${query}` + + return window.fetch(url, { + method: 'POST' + }) +} + +export default resetPassword