diff options
author | Chocobozzz <me@florianbigard.com> | 2019-06-11 14:30:49 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2019-06-11 14:31:12 +0200 |
commit | 0ba5f5baade94a051710d9d9d408ac8083c14ac6 (patch) | |
tree | e470a2b546d1d1ac9796b6c8ae3afffccd0aca6c | |
parent | d1ab89deb79f70c439b58750d044d9cadf1194e5 (diff) | |
download | PeerTube-0ba5f5baade94a051710d9d9d408ac8083c14ac6.tar.gz PeerTube-0ba5f5baade94a051710d9d9d408ac8083c14ac6.tar.zst PeerTube-0ba5f5baade94a051710d9d9d408ac8083c14ac6.zip |
Add ability to change email in client
13 files changed, 189 insertions, 17 deletions
diff --git a/client/proxy.config.json b/client/proxy.config.json index e5f0dfd61..4a72f1826 100644 --- a/client/proxy.config.json +++ b/client/proxy.config.json | |||
@@ -8,7 +8,8 @@ | |||
8 | "secure": false | 8 | "secure": false |
9 | }, | 9 | }, |
10 | "/socket.io": { | 10 | "/socket.io": { |
11 | "target": "http://localhost:9000", | 11 | "target": "ws://localhost:9000", |
12 | "secure": false | 12 | "secure": false, |
13 | "ws": true | ||
13 | } | 14 | } |
14 | } | 15 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts new file mode 100644 index 000000000..f42af361e --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './my-account-change-email.component' | |||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html new file mode 100644 index 000000000..ebfe3126d --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.html | |||
@@ -0,0 +1,36 @@ | |||
1 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
2 | <div *ngIf="success" class="alert alert-success">{{ success }}</div> | ||
3 | |||
4 | <div i18n class="current-email"> | ||
5 | Your current email is <span class="email">{{ user.email }}</span> | ||
6 | </div> | ||
7 | |||
8 | <div i18n class="pending-email" *ngIf="user.pendingEmail"> | ||
9 | <span class="email">{{ user.pendingEmail }}</span> is awaiting email verification | ||
10 | </div> | ||
11 | |||
12 | <form role="form" (ngSubmit)="changeEmail()" [formGroup]="form"> | ||
13 | |||
14 | <div class="form-group"> | ||
15 | <label i18n for="new-email">New email</label> | ||
16 | <input | ||
17 | type="email" id="new-email" i18n-placeholder placeholder="Your new email" | ||
18 | formControlName="new-email" [ngClass]="{ 'input-error': formErrors['new-email'] }" | ||
19 | > | ||
20 | <div *ngIf="formErrors['new-email']" class="form-error"> | ||
21 | {{ formErrors['new-email'] }} | ||
22 | </div> | ||
23 | </div> | ||
24 | |||
25 | <div class="form-group"> | ||
26 | <input | ||
27 | type="password" id="password" i18n-placeholder placeholder="Your password" | ||
28 | formControlName="password" [ngClass]="{ 'input-error': formErrors['password'] }" | ||
29 | > | ||
30 | <div *ngIf="formErrors['password']" class="form-error"> | ||
31 | {{ formErrors['password'] }} | ||
32 | </div> | ||
33 | </div> | ||
34 | |||
35 | <input type="submit" i18n-value value="Change email" [disabled]="!form.valid"> | ||
36 | </form> | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss new file mode 100644 index 000000000..81eba3ec9 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.scss | |||
@@ -0,0 +1,24 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | input[type=password], | ||
5 | input[type=email] { | ||
6 | @include peertube-input-text(340px); | ||
7 | |||
8 | display: block; | ||
9 | } | ||
10 | |||
11 | input[type=submit] { | ||
12 | @include peertube-button; | ||
13 | @include orange-button; | ||
14 | } | ||
15 | |||
16 | .current-email, | ||
17 | .pending-email { | ||
18 | font-size: 16px; | ||
19 | margin: 15px 0; | ||
20 | |||
21 | .email { | ||
22 | font-weight: $font-semibold; | ||
23 | } | ||
24 | } | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts new file mode 100644 index 000000000..577c5b102 --- /dev/null +++ b/client/src/app/+my-account/my-account-settings/my-account-change-email/my-account-change-email.component.ts | |||
@@ -0,0 +1,73 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { AuthService, Notifier, ServerService } from '@app/core' | ||
3 | import { FormReactive, UserService } from '../../../shared' | ||
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
6 | import { UserValidatorsService } from '@app/shared/forms/form-validators/user-validators.service' | ||
7 | import { User } from '../../../../../../shared' | ||
8 | import { switchMap, tap } from 'rxjs/operators' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-account-change-email', | ||
12 | templateUrl: './my-account-change-email.component.html', | ||
13 | styleUrls: [ './my-account-change-email.component.scss' ] | ||
14 | }) | ||
15 | export class MyAccountChangeEmailComponent extends FormReactive implements OnInit { | ||
16 | error: string = null | ||
17 | success: string = null | ||
18 | user: User = null | ||
19 | |||
20 | constructor ( | ||
21 | protected formValidatorService: FormValidatorService, | ||
22 | private userValidatorsService: UserValidatorsService, | ||
23 | private notifier: Notifier, | ||
24 | private authService: AuthService, | ||
25 | private userService: UserService, | ||
26 | private serverService: ServerService, | ||
27 | private i18n: I18n | ||
28 | ) { | ||
29 | super() | ||
30 | } | ||
31 | |||
32 | ngOnInit () { | ||
33 | this.buildForm({ | ||
34 | 'new-email': this.userValidatorsService.USER_EMAIL, | ||
35 | 'password': this.userValidatorsService.USER_PASSWORD | ||
36 | }) | ||
37 | |||
38 | this.user = this.authService.getUser() | ||
39 | } | ||
40 | |||
41 | changeEmail () { | ||
42 | this.error = null | ||
43 | this.success = null | ||
44 | |||
45 | const password = this.form.value[ 'password' ] | ||
46 | const email = this.form.value[ 'new-email' ] | ||
47 | |||
48 | this.userService.changeEmail(password, email) | ||
49 | .pipe( | ||
50 | tap(() => this.authService.refreshUserInformation()) | ||
51 | ) | ||
52 | .subscribe( | ||
53 | () => { | ||
54 | this.form.reset() | ||
55 | |||
56 | if (this.serverService.getConfig().signup.requiresEmailVerification) { | ||
57 | this.success = this.i18n('Please check your emails to verify your new email.') | ||
58 | } else { | ||
59 | this.success = this.i18n('Email updated.') | ||
60 | } | ||
61 | }, | ||
62 | |||
63 | err => { | ||
64 | if (err.status === 401) { | ||
65 | this.error = this.i18n('You current password is invalid.') | ||
66 | return | ||
67 | } | ||
68 | |||
69 | this.error = err.message | ||
70 | } | ||
71 | ) | ||
72 | } | ||
73 | } | ||
diff --git a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html index ae797d1bc..a39061ee3 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-change-password/my-account-change-password.component.html | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> | 3 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> |
4 | 4 | ||
5 | <label i18n for="new-password">Change password</label> | 5 | <label i18n for="current-password">Change password</label> |
6 | <input | 6 | <input |
7 | type="password" id="current-password" i18n-placeholder placeholder="Current password" | 7 | type="password" id="current-password" i18n-placeholder placeholder="Current password" |
8 | formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }" | 8 | formControlName="current-password" [ngClass]="{ 'input-error': formErrors['current-password'] }" |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html index ad64f28fe..f93d41110 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-settings.component.html | |||
@@ -13,6 +13,9 @@ | |||
13 | <div i18n class="account-title">Password</div> | 13 | <div i18n class="account-title">Password</div> |
14 | <my-account-change-password></my-account-change-password> | 14 | <my-account-change-password></my-account-change-password> |
15 | 15 | ||
16 | <div i18n class="account-title">Email</div> | ||
17 | <my-account-change-email></my-account-change-email> | ||
18 | |||
16 | <div i18n class="account-title">Video settings</div> | 19 | <div i18n class="account-title">Video settings</div> |
17 | <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> | 20 | <my-account-video-settings [user]="user" [userInformationLoaded]="userInformationLoaded"></my-account-video-settings> |
18 | 21 | ||
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 4a18a9968..ca5b1f7cb 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -36,6 +36,7 @@ import { | |||
36 | MyAccountVideoPlaylistElementsComponent | 36 | MyAccountVideoPlaylistElementsComponent |
37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' | 37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' |
38 | import { DragDropModule } from '@angular/cdk/drag-drop' | 38 | import { DragDropModule } from '@angular/cdk/drag-drop' |
39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' | ||
39 | 40 | ||
40 | @NgModule({ | 41 | @NgModule({ |
41 | imports: [ | 42 | imports: [ |
@@ -54,7 +55,10 @@ import { DragDropModule } from '@angular/cdk/drag-drop' | |||
54 | MyAccountChangePasswordComponent, | 55 | MyAccountChangePasswordComponent, |
55 | MyAccountVideoSettingsComponent, | 56 | MyAccountVideoSettingsComponent, |
56 | MyAccountProfileComponent, | 57 | MyAccountProfileComponent, |
58 | MyAccountChangeEmailComponent, | ||
59 | |||
57 | MyAccountVideosComponent, | 60 | MyAccountVideosComponent, |
61 | |||
58 | VideoChangeOwnershipComponent, | 62 | VideoChangeOwnershipComponent, |
59 | MyAccountOwnershipComponent, | 63 | MyAccountOwnershipComponent, |
60 | MyAccountAcceptOwnershipComponent, | 64 | MyAccountAcceptOwnershipComponent, |
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html index 728709ca6..47519c943 100644 --- a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html +++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.html | |||
@@ -3,13 +3,16 @@ | |||
3 | Verify account email confirmation | 3 | Verify account email confirmation |
4 | </div> | 4 | </div> |
5 | 5 | ||
6 | <my-signup-success i18n *ngIf="success; else verificationError" message="Your email has been verified and you may now login."> | 6 | <my-signup-success i18n *ngIf="!isPendingEmail && success" message="Your email has been verified and you may now login."> |
7 | </my-signup-success> | 7 | </my-signup-success> |
8 | 8 | ||
9 | <ng-template #verificationError> | 9 | <div i18n class="alert alert-success" *ngIf="isPendingEmail && success"> |
10 | <div> | 10 | Email updated. |
11 | <span i18n>An error occurred. </span> | 11 | </div> |
12 | <a i18n routerLink="/verify-account/ask-send-email">Request new verification email.</a> | 12 | |
13 | </div> | 13 | <div *ngIf="failed"> |
14 | </ng-template> | 14 | <span i18n>An error occurred.</span> |
15 | |||
16 | <a i18n routerLink="/verify-account/ask-send-email" [queryParams]="{ isPendingEmail: isPendingEmail }">Request new verification email.</a> | ||
17 | </div> | ||
15 | </div> | 18 | </div> |
diff --git a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts index 3fb2d1cd8..054f04310 100644 --- a/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts +++ b/client/src/app/+signup/+verify-account/verify-account-email/verify-account-email.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ActivatedRoute, Router } from '@angular/router' | 2 | import { ActivatedRoute, Router } from '@angular/router' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { Notifier } from '@app/core' | 4 | import { AuthService, Notifier } from '@app/core' |
5 | import { UserService } from '@app/shared' | 5 | import { UserService } from '@app/shared' |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
@@ -11,12 +11,15 @@ import { UserService } from '@app/shared' | |||
11 | 11 | ||
12 | export class VerifyAccountEmailComponent implements OnInit { | 12 | export class VerifyAccountEmailComponent implements OnInit { |
13 | success = false | 13 | success = false |
14 | failed = false | ||
15 | isPendingEmail = false | ||
14 | 16 | ||
15 | private userId: number | 17 | private userId: number |
16 | private verificationString: string | 18 | private verificationString: string |
17 | 19 | ||
18 | constructor ( | 20 | constructor ( |
19 | private userService: UserService, | 21 | private userService: UserService, |
22 | private authService: AuthService, | ||
20 | private notifier: Notifier, | 23 | private notifier: Notifier, |
21 | private router: Router, | 24 | private router: Router, |
22 | private route: ActivatedRoute, | 25 | private route: ActivatedRoute, |
@@ -25,8 +28,12 @@ export class VerifyAccountEmailComponent implements OnInit { | |||
25 | } | 28 | } |
26 | 29 | ||
27 | ngOnInit () { | 30 | ngOnInit () { |
28 | this.userId = this.route.snapshot.queryParams['userId'] | 31 | const queryParams = this.route.snapshot.queryParams |
29 | this.verificationString = this.route.snapshot.queryParams['verificationString'] | 32 | this.userId = queryParams['userId'] |
33 | this.verificationString = queryParams['verificationString'] | ||
34 | this.isPendingEmail = queryParams['isPendingEmail'] === 'true' | ||
35 | |||
36 | console.log(this.isPendingEmail) | ||
30 | 37 | ||
31 | if (!this.userId || !this.verificationString) { | 38 | if (!this.userId || !this.verificationString) { |
32 | this.notifier.error(this.i18n('Unable to find user id or verification string.')) | 39 | this.notifier.error(this.i18n('Unable to find user id or verification string.')) |
@@ -36,13 +43,17 @@ export class VerifyAccountEmailComponent implements OnInit { | |||
36 | } | 43 | } |
37 | 44 | ||
38 | verifyEmail () { | 45 | verifyEmail () { |
39 | this.userService.verifyEmail(this.userId, this.verificationString) | 46 | this.userService.verifyEmail(this.userId, this.verificationString, this.isPendingEmail) |
40 | .subscribe( | 47 | .subscribe( |
41 | () => { | 48 | () => { |
49 | this.authService.refreshUserInformation() | ||
50 | |||
42 | this.success = true | 51 | this.success = true |
43 | }, | 52 | }, |
44 | 53 | ||
45 | err => { | 54 | err => { |
55 | this.failed = true | ||
56 | |||
46 | this.notifier.error(err.message) | 57 | this.notifier.error(err.message) |
47 | } | 58 | } |
48 | ) | 59 | ) |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index e3ed2dfbd..14d13959a 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -8,6 +8,7 @@ export class User implements UserServerModel { | |||
8 | id: number | 8 | id: number |
9 | username: string | 9 | username: string |
10 | email: string | 10 | email: string |
11 | pendingEmail: string | null | ||
11 | emailVerified: boolean | 12 | emailVerified: boolean |
12 | nsfwPolicy: NSFWPolicyType | 13 | nsfwPolicy: NSFWPolicyType |
13 | 14 | ||
diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 70ff9a058..41ee87197 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts | |||
@@ -38,6 +38,20 @@ export class UserService { | |||
38 | ) | 38 | ) |
39 | } | 39 | } |
40 | 40 | ||
41 | changeEmail (password: string, newEmail: string) { | ||
42 | const url = UserService.BASE_USERS_URL + 'me' | ||
43 | const body: UserUpdateMe = { | ||
44 | currentPassword: password, | ||
45 | email: newEmail | ||
46 | } | ||
47 | |||
48 | return this.authHttp.put(url, body) | ||
49 | .pipe( | ||
50 | map(this.restExtractor.extractDataBool), | ||
51 | catchError(err => this.restExtractor.handleError(err)) | ||
52 | ) | ||
53 | } | ||
54 | |||
41 | updateMyProfile (profile: UserUpdateMe) { | 55 | updateMyProfile (profile: UserUpdateMe) { |
42 | const url = UserService.BASE_USERS_URL + 'me' | 56 | const url = UserService.BASE_USERS_URL + 'me' |
43 | 57 | ||
@@ -104,10 +118,11 @@ export class UserService { | |||
104 | ) | 118 | ) |
105 | } | 119 | } |
106 | 120 | ||
107 | verifyEmail (userId: number, verificationString: string) { | 121 | verifyEmail (userId: number, verificationString: string, isPendingEmail: boolean) { |
108 | const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email` | 122 | const url = `${UserService.BASE_USERS_URL}/${userId}/verify-email` |
109 | const body = { | 123 | const body = { |
110 | verificationString | 124 | verificationString, |
125 | isPendingEmail | ||
111 | } | 126 | } |
112 | 127 | ||
113 | return this.authHttp.post(url, body) | 128 | return this.authHttp.post(url, body) |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index a4d4ae46d..ec70fa0fd 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -205,7 +205,7 @@ const usersUpdateMeValidator = [ | |||
205 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 205 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
206 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 206 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
207 | 207 | ||
208 | if (req.body.password) { | 208 | if (req.body.password || req.body.email) { |
209 | if (!req.body.currentPassword) { | 209 | if (!req.body.currentPassword) { |
210 | return res.status(400) | 210 | return res.status(400) |
211 | .send({ error: 'currentPassword parameter is missing.' }) | 211 | .send({ error: 'currentPassword parameter is missing.' }) |