Implement password change in basic user settings

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-09-24 18:47:35 +02:00
parent f129d4137d
commit 3806295e83
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
5 changed files with 135 additions and 5 deletions

View file

@ -25,6 +25,14 @@ mutation ValidateUser($token: String!) {
} }
`; `;
export const CHANGE_PASSWORD = gql`
mutation ChangePassword($oldPassword: String!, $newPassword: String!) {
changePassword(oldPassword: $oldPassword, newPassword: $newPassword) {
id
}
}
`;
export const CURRENT_USER_CLIENT = gql` export const CURRENT_USER_CLIENT = gql`
query { query {
currentUser @client { currentUser @client {

View file

@ -7,6 +7,7 @@ import SendPasswordReset from '@/views/User/SendPasswordReset.vue';
import PasswordReset from '@/views/User/PasswordReset.vue'; import PasswordReset from '@/views/User/PasswordReset.vue';
import { beforeRegisterGuard } from '@/router/guards/register-guard'; import { beforeRegisterGuard } from '@/router/guards/register-guard';
import { RouteConfig } from 'vue-router'; import { RouteConfig } from 'vue-router';
import PasswordChange from '@/views/User/PasswordChange.vue';
export enum UserRouteName { export enum UserRouteName {
REGISTER = 'Register', REGISTER = 'Register',
@ -16,6 +17,7 @@ export enum UserRouteName {
PASSWORD_RESET = 'PasswordReset', PASSWORD_RESET = 'PasswordReset',
VALIDATE = 'Validate', VALIDATE = 'Validate',
LOGIN = 'Login', LOGIN = 'Login',
PASSWORD_CHANGE = 'PasswordChange',
} }
export const userRoutes: RouteConfig[] = [ export const userRoutes: RouteConfig[] = [
@ -70,4 +72,10 @@ export const userRoutes: RouteConfig[] = [
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{
path: '/my-account/password',
name: UserRouteName.PASSWORD_CHANGE,
component: PasswordChange,
meta: { requiredAuth: true },
},
]; ];

View file

@ -1,5 +1,10 @@
<template> <template>
<section class="container"> <section class="container">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li class="is-active"><router-link :to="{ name: MyAccountRouteName.UPDATE_IDENTITY }" aria-current="page">{{ $t('My account') }}</router-link></li>
</ul>
</nav>
<div v-if="currentActor"> <div v-if="currentActor">
<div class="header"> <div class="header">
<figure v-if="currentActor.banner" class="image is-3by1"> <figure v-if="currentActor.banner" class="image is-3by1">
@ -10,6 +15,9 @@
<div class="columns"> <div class="columns">
<div class="identities column is-4"> <div class="identities column is-4">
<identities v-bind:currentIdentityName="currentIdentityName"></identities> <identities v-bind:currentIdentityName="currentIdentityName"></identities>
<div class="buttons">
<b-button tag="router-link" type="is-secondary" :to="{ name: UserRouteName.PASSWORD_CHANGE }">{{ $t('Change password') }}</b-button>
</div>
</div> </div>
<div class="column is-8"> <div class="column is-8">
<router-view></router-view> <router-view></router-view>
@ -27,6 +35,10 @@
.identities { .identities {
padding-right: 45px; padding-right: 45px;
margin-right: 45px; margin-right: 45px;
.buttons {
margin-top: 1.2rem;
}
} }
</style> </style>
@ -36,6 +48,8 @@ import { Component, Vue, Watch } from 'vue-property-decorator';
import EventCard from '@/components/Event/EventCard.vue'; import EventCard from '@/components/Event/EventCard.vue';
import { IPerson } from '@/types/actor'; import { IPerson } from '@/types/actor';
import Identities from '@/components/Account/Identities.vue'; import Identities from '@/components/Account/Identities.vue';
import { UserRouteName } from '@/router/user';
import { MyAccountRouteName } from '@/router/actor';
@Component({ @Component({
components: { components: {
@ -52,6 +66,9 @@ export default class MyAccount extends Vue {
currentActor!: IPerson; currentActor!: IPerson;
currentIdentityName: string | null = null; currentIdentityName: string | null = null;
UserRouteName = UserRouteName;
MyAccountRouteName = MyAccountRouteName;
@Watch('$route.params.identityName', { immediate: true }) @Watch('$route.params.identityName', { immediate: true })
async onIdentityParamChanged (val: string) { async onIdentityParamChanged (val: string) {
await this.redirectIfNoIdentitySelected(val); await this.redirectIfNoIdentitySelected(val);

View file

@ -0,0 +1,94 @@
<template>
<section class="section">
<nav class="breadcrumb" aria-label="breadcrumbs">
<ul>
<li><router-link :to="{ name: MyAccountRouteName.UPDATE_IDENTITY }">{{ $t('My account') }}</router-link></li>
<li class="is-active"><router-link :to="{ name: UserRouteName.PASSWORD_CHANGE }" aria-current="page">{{ $t('Password change') }}</router-link></li>
</ul>
</nav>
<h1 class="title">{{ $t('Password') }}</h1>
<b-notification
type="is-danger"
has-icon
aria-close-label="Close notification"
role="alert"
:key="error"
v-for="error in errors"
>
{{ error }}
</b-notification>
<form @submit="resetAction" class="form">
<b-field :label="$t('Old password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="oldPassword"
/>
</b-field>
<b-field :label="$t('New password')">
<b-input
aria-required="true"
required
type="password"
password-reveal
minlength="6"
v-model="newPassword"
/>
</b-field>
<button class="button is-primary">
{{ $t('Change my password') }}
</button>
</form>
</section>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { CHANGE_PASSWORD } from '@/graphql/user';
import { UserRouteName } from '@/router/user';
import { MyAccountRouteName } from '@/router/actor';
@Component
export default class PasswordChange extends Vue {
oldPassword: string = '';
newPassword: string = '';
errors: string[] = [];
MyAccountRouteName = MyAccountRouteName;
UserRouteName = UserRouteName;
async resetAction(e) {
e.preventDefault();
this.errors = [];
try {
await this.$apollo.mutate({
mutation: CHANGE_PASSWORD,
variables: {
oldPassword: this.oldPassword,
newPassword: this.newPassword,
},
});
this.$notifier.success(this.$t('The password was successfully changed') as string);
} catch (err) {
this.handleError(err);
}
}
private handleError(err: any) {
console.error(err);
if (err.graphQLErrors !== undefined) {
err.graphQLErrors.forEach(({ message }) => {
this.errors.push(message);
});
}
}
}
</script>
<style lang="scss">
</style>

View file

@ -1,5 +1,5 @@
# source: http://localhost:4000/api # source: http://localhost:4000/api
# timestamp: Fri Sep 20 2019 16:55:10 GMT+0200 (GMT+02:00) # timestamp: Tue Sep 24 2019 18:20:05 GMT+0200 (GMT+02:00)
schema { schema {
query: RootQueryType query: RootQueryType
@ -184,7 +184,7 @@ enum CommentVisibility {
"""Visible only to people members of the group or followers of the person""" """Visible only to people members of the group or followers of the person"""
PRIVATE PRIVATE
"""Publically listed and federated. Can be shared.""" """Publicly listed and federated. Can be shared."""
PUBLIC PUBLIC
"""Visible only to people with the link - or invited""" """Visible only to people with the link - or invited"""
@ -885,6 +885,9 @@ type RootMutationType {
"""Change default actor for user""" """Change default actor for user"""
changeDefaultActor(preferredUsername: String!): User changeDefaultActor(preferredUsername: String!): User
"""Change an user password"""
changePassword(newPassword: String!, oldPassword: String!): User
"""Create a comment""" """Create a comment"""
createComment(actorUsername: String!, text: String!): Comment createComment(actorUsername: String!, text: String!): Comment
@ -911,7 +914,7 @@ type RootMutationType {
"""The list of tags associated to the event""" """The list of tags associated to the event"""
tags: [String] = [""] tags: [String] = [""]
title: String! title: String!
visibility: EventVisibility = PRIVATE visibility: EventVisibility = PUBLIC
): Event ): Event
"""Create a Feed Token""" """Create a Feed Token"""
@ -1044,7 +1047,7 @@ type RootMutationType {
description: String description: String
endsOn: DateTime endsOn: DateTime
eventId: ID! eventId: ID!
joinOptions: EventJoinOptions joinOptions: EventJoinOptions = FREE
onlineAddress: String onlineAddress: String
options: EventOptionsInput options: EventOptionsInput
phoneAddress: String phoneAddress: String
@ -1059,7 +1062,7 @@ type RootMutationType {
"""The list of tags associated to the event""" """The list of tags associated to the event"""
tags: [String] tags: [String]
title: String title: String
visibility: EventVisibility visibility: EventVisibility = PUBLIC
): Event ): Event
"""Update an identity""" """Update an identity"""