Password reset page

This commit is contained in:
Eugenij 2019-09-05 11:23:28 +00:00 committed by Shpuld Shpludson
parent c89703a359
commit 8ee5abb1a5
8 changed files with 226 additions and 0 deletions

View File

@ -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 })

View File

@ -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 },

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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."
}
}

View File

@ -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вяжитесь с администратором вашего сервера."
}
}

View File

@ -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