Password reset page
This commit is contained in:
parent
c89703a359
commit
8ee5abb1a5
8 changed files with 226 additions and 0 deletions
|
@ -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 })
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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
|
||||
|
|
62
src/components/password_reset/password_reset.js
Normal file
62
src/components/password_reset/password_reset.js
Normal 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
|
116
src/components/password_reset/password_reset.vue
Normal file
116
src/components/password_reset/password_reset.vue
Normal 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>
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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вяжитесь с администратором вашего сервера."
|
||||
}
|
||||
}
|
||||
|
|
18
src/services/new_api/password_reset.js
Normal file
18
src/services/new_api/password_reset.js
Normal 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
|
Loading…
Reference in a new issue