Password reset page
This commit is contained in:
parent
c89703a359
commit
8ee5abb1a5
|
@ -246,6 +246,7 @@ const getNodeInfo = async ({ store }) => {
|
||||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
store.dispatch('setInstanceOption', { name: 'pollsAvailable', value: features.includes('polls') })
|
||||||
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
|
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: 'restrictedNicknames', value: metadata.restrictedNicknames })
|
||||||
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
|
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 Search from 'components/search/search.vue'
|
||||||
import Settings from 'components/settings/settings.vue'
|
import Settings from 'components/settings/settings.vue'
|
||||||
import Registration from 'components/registration/registration.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 UserSettings from 'components/user_settings/user_settings.vue'
|
||||||
import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
||||||
import OAuthCallback from 'components/oauth_callback/oauth_callback.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: 'dms', path: '/users/:username/dms', component: DMs, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'settings', path: '/settings', component: Settings },
|
{ name: 'settings', path: '/settings', component: Settings },
|
||||||
{ name: 'registration', path: '/registration', component: Registration },
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
|
{ name: 'password-reset', path: '/password-reset', component: PasswordReset },
|
||||||
{ name: 'registration-token', path: '/registration/:token', component: Registration },
|
{ name: 'registration-token', path: '/registration/:token', component: Registration },
|
||||||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests, beforeEnter: validateAuthenticatedRoute },
|
||||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
|
{ name: 'user-settings', path: '/user-settings', component: UserSettings, beforeEnter: validateAuthenticatedRoute },
|
||||||
|
|
|
@ -33,6 +33,11 @@
|
||||||
type="password"
|
type="password"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<router-link :to="{name: 'password-reset'}">
|
||||||
|
{{ $t('password_reset.forgot_password') }}
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -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
|
|
@ -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",
|
"person_talking": "{count} person talking",
|
||||||
"people_talking": "{count} people talking",
|
"people_talking": "{count} people talking",
|
||||||
"no_results": "No results"
|
"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} человека",
|
"person_talking": "Популярно у {count} человека",
|
||||||
"people_talking": "Популярно у {count} человек",
|
"people_talking": "Популярно у {count} человек",
|
||||||
"no_results": "Ничего не найдено"
|
"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вяжитесь с администратором вашего сервера."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 New Issue