diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2016-08-05 18:04:08 +0200 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2016-08-05 18:04:08 +0200 |
commit | 629d8d6f70cf83b55011dff53bfe1c4a95ac3433 (patch) | |
tree | 9d5a145609d9c693ddacfbc42ae75ca3c841aef0 /client/src/app | |
parent | 99a64bfed25e45547df3045cf249bc895e6f220b (diff) | |
download | PeerTube-629d8d6f70cf83b55011dff53bfe1c4a95ac3433.tar.gz PeerTube-629d8d6f70cf83b55011dff53bfe1c4a95ac3433.tar.zst PeerTube-629d8d6f70cf83b55011dff53bfe1c4a95ac3433.zip |
Client: implement password change
Diffstat (limited to 'client/src/app')
-rw-r--r-- | client/src/app/account/account.component.html | 27 | ||||
-rw-r--r-- | client/src/app/account/account.component.ts | 45 | ||||
-rw-r--r-- | client/src/app/account/account.routes.ts | 5 | ||||
-rw-r--r-- | client/src/app/account/account.service.ts | 19 | ||||
-rw-r--r-- | client/src/app/account/index.ts | 2 | ||||
-rw-r--r-- | client/src/app/app.component.html | 15 | ||||
-rw-r--r-- | client/src/app/app.routes.ts | 2 | ||||
-rw-r--r-- | client/src/app/shared/auth/auth-http.service.ts | 6 | ||||
-rw-r--r-- | client/src/app/shared/auth/auth.service.ts | 37 | ||||
-rw-r--r-- | client/src/app/shared/auth/user.model.ts | 23 |
10 files changed, 165 insertions, 16 deletions
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html new file mode 100644 index 000000000..ad8f690bd --- /dev/null +++ b/client/src/app/account/account.component.html | |||
@@ -0,0 +1,27 @@ | |||
1 | <h3>Account</h3> | ||
2 | |||
3 | <div *ngIf="information" class="alert alert-success">{{ information }}</div> | ||
4 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
5 | |||
6 | <form role="form" (ngSubmit)="changePassword(newPassword.value, newConfirmedPassword.value)" [ngFormModel]="changePasswordForm"> | ||
7 | <div class="form-group"> | ||
8 | <label for="new-password">New password</label> | ||
9 | <input | ||
10 | type="password" class="form-control" name="new-password" id="new-password" | ||
11 | ngControl="newPassword" #newPassword="ngForm" | ||
12 | > | ||
13 | <div [hidden]="newPassword.valid || newPassword.pristine" class="alert alert-warning"> | ||
14 | The password should have more than 5 characters | ||
15 | </div> | ||
16 | </div> | ||
17 | |||
18 | <div class="form-group"> | ||
19 | <label for="name">Confirm new password</label> | ||
20 | <input | ||
21 | type="password" class="form-control" name="new-confirmed-password" id="new-confirmed-password" | ||
22 | ngControl="newConfirmedPassword" #newConfirmedPassword="ngForm" | ||
23 | > | ||
24 | </div> | ||
25 | |||
26 | <input type="submit" value="Change password" class="btn btn-default" [disabled]="!changePasswordForm.valid"> | ||
27 | </form> | ||
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts new file mode 100644 index 000000000..5c42103f8 --- /dev/null +++ b/client/src/app/account/account.component.ts | |||
@@ -0,0 +1,45 @@ | |||
1 | import { Control, ControlGroup, Validators } from '@angular/common'; | ||
2 | import { Component, OnInit } from '@angular/core'; | ||
3 | import { Router } from '@angular/router'; | ||
4 | |||
5 | import { AccountService } from './account.service'; | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-account', | ||
9 | template: require('./account.component.html'), | ||
10 | providers: [ AccountService ] | ||
11 | }) | ||
12 | |||
13 | export class AccountComponent implements OnInit { | ||
14 | changePasswordForm: ControlGroup; | ||
15 | information: string = null; | ||
16 | error: string = null; | ||
17 | |||
18 | constructor( | ||
19 | private accountService: AccountService, | ||
20 | private router: Router | ||
21 | ) {} | ||
22 | |||
23 | ngOnInit() { | ||
24 | this.changePasswordForm = new ControlGroup({ | ||
25 | newPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), | ||
26 | newConfirmedPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), | ||
27 | }); | ||
28 | } | ||
29 | |||
30 | changePassword(newPassword: string, newConfirmedPassword: string) { | ||
31 | this.information = null; | ||
32 | this.error = null; | ||
33 | |||
34 | if (newPassword !== newConfirmedPassword) { | ||
35 | this.error = 'The new password and the confirmed password do not correspond.'; | ||
36 | return; | ||
37 | } | ||
38 | |||
39 | this.accountService.changePassword(newPassword).subscribe( | ||
40 | ok => this.information = 'Password updated.', | ||
41 | |||
42 | err => this.error = err | ||
43 | ); | ||
44 | } | ||
45 | } | ||
diff --git a/client/src/app/account/account.routes.ts b/client/src/app/account/account.routes.ts new file mode 100644 index 000000000..e348c6ebe --- /dev/null +++ b/client/src/app/account/account.routes.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | import { AccountComponent } from './account.component'; | ||
2 | |||
3 | export const AccountRoutes = [ | ||
4 | { path: 'account', component: AccountComponent } | ||
5 | ]; | ||
diff --git a/client/src/app/account/account.service.ts b/client/src/app/account/account.service.ts new file mode 100644 index 000000000..19b4e0624 --- /dev/null +++ b/client/src/app/account/account.service.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { Injectable } from '@angular/core'; | ||
2 | |||
3 | import { AuthHttp, AuthService } from '../shared'; | ||
4 | |||
5 | @Injectable() | ||
6 | export class AccountService { | ||
7 | private static BASE_USERS_URL = '/api/v1/users/'; | ||
8 | |||
9 | constructor(private authHttp: AuthHttp, private authService: AuthService) { } | ||
10 | |||
11 | changePassword(newPassword: string) { | ||
12 | const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; | ||
13 | const body = { | ||
14 | password: newPassword | ||
15 | }; | ||
16 | |||
17 | return this.authHttp.put(url, body); | ||
18 | } | ||
19 | } | ||
diff --git a/client/src/app/account/index.ts b/client/src/app/account/index.ts new file mode 100644 index 000000000..7445003fd --- /dev/null +++ b/client/src/app/account/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './account.component'; | ||
2 | export * from './account.routes'; | ||
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index f2acffea4..ea4b31421 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -18,9 +18,20 @@ | |||
18 | <menu class="col-md-2 col-sm-3 col-xs-3"> | 18 | <menu class="col-md-2 col-sm-3 col-xs-3"> |
19 | <div class="panel-block"> | 19 | <div class="panel-block"> |
20 | <div id="panel-user-login" class="panel-button"> | 20 | <div id="panel-user-login" class="panel-button"> |
21 | <span *ngIf="!isLoggedIn" > | ||
22 | <span class="hidden-xs glyphicon glyphicon-log-in"></span> | ||
23 | <a [routerLink]="['/login']">Login</a> | ||
24 | </span> | ||
25 | |||
26 | <span *ngIf="isLoggedIn"> | ||
27 | <span class="hidden-xs glyphicon glyphicon-log-out"></span> | ||
28 | <a *ngIf="isLoggedIn" (click)="logout()">Logout</a> | ||
29 | </span> | ||
30 | </div> | ||
31 | |||
32 | <div *ngIf="isLoggedIn" id="panel-user-account" class="panel-button"> | ||
21 | <span class="hidden-xs glyphicon glyphicon-user"></span> | 33 | <span class="hidden-xs glyphicon glyphicon-user"></span> |
22 | <a *ngIf="!isLoggedIn" [routerLink]="['/login']">Login</a> | 34 | <a [routerLink]="['/account']">My account</a> |
23 | <a *ngIf="isLoggedIn" (click)="logout()">Logout</a> | ||
24 | </div> | 35 | </div> |
25 | </div> | 36 | </div> |
26 | 37 | ||
diff --git a/client/src/app/app.routes.ts b/client/src/app/app.routes.ts index 59ef4ce55..1c414038d 100644 --- a/client/src/app/app.routes.ts +++ b/client/src/app/app.routes.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { RouterConfig } from '@angular/router'; | 1 | import { RouterConfig } from '@angular/router'; |
2 | 2 | ||
3 | import { AccountRoutes } from './account'; | ||
3 | import { LoginRoutes } from './login'; | 4 | import { LoginRoutes } from './login'; |
4 | import { VideosRoutes } from './videos'; | 5 | import { VideosRoutes } from './videos'; |
5 | 6 | ||
@@ -10,6 +11,7 @@ export const routes: RouterConfig = [ | |||
10 | pathMatch: 'full' | 11 | pathMatch: 'full' |
11 | }, | 12 | }, |
12 | 13 | ||
14 | ...AccountRoutes, | ||
13 | ...LoginRoutes, | 15 | ...LoginRoutes, |
14 | ...VideosRoutes | 16 | ...VideosRoutes |
15 | ]; | 17 | ]; |
diff --git a/client/src/app/shared/auth/auth-http.service.ts b/client/src/app/shared/auth/auth-http.service.ts index 9c7ef4389..55bb501e6 100644 --- a/client/src/app/shared/auth/auth-http.service.ts +++ b/client/src/app/shared/auth/auth-http.service.ts | |||
@@ -49,16 +49,18 @@ export class AuthHttp extends Http { | |||
49 | return this.request(url, options); | 49 | return this.request(url, options); |
50 | } | 50 | } |
51 | 51 | ||
52 | post(url: string, options?: RequestOptionsArgs): Observable<Response> { | 52 | post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { |
53 | if (!options) options = {}; | 53 | if (!options) options = {}; |
54 | options.method = RequestMethod.Post; | 54 | options.method = RequestMethod.Post; |
55 | options.body = body; | ||
55 | 56 | ||
56 | return this.request(url, options); | 57 | return this.request(url, options); |
57 | } | 58 | } |
58 | 59 | ||
59 | put(url: string, options?: RequestOptionsArgs): Observable<Response> { | 60 | put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> { |
60 | if (!options) options = {}; | 61 | if (!options) options = {}; |
61 | options.method = RequestMethod.Put; | 62 | options.method = RequestMethod.Put; |
63 | options.body = body; | ||
62 | 64 | ||
63 | return this.request(url, options); | 65 | return this.request(url, options); |
64 | } | 66 | } |
diff --git a/client/src/app/shared/auth/auth.service.ts b/client/src/app/shared/auth/auth.service.ts index 6a5b19ffe..24d1a4fa2 100644 --- a/client/src/app/shared/auth/auth.service.ts +++ b/client/src/app/shared/auth/auth.service.ts | |||
@@ -10,6 +10,7 @@ import { User } from './user.model'; | |||
10 | export class AuthService { | 10 | export class AuthService { |
11 | private static BASE_CLIENT_URL = '/api/v1/clients/local'; | 11 | private static BASE_CLIENT_URL = '/api/v1/clients/local'; |
12 | private static BASE_TOKEN_URL = '/api/v1/users/token'; | 12 | private static BASE_TOKEN_URL = '/api/v1/users/token'; |
13 | private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; | ||
13 | 14 | ||
14 | loginChangedSource: Observable<AuthStatus>; | 15 | loginChangedSource: Observable<AuthStatus>; |
15 | 16 | ||
@@ -99,6 +100,7 @@ export class AuthService { | |||
99 | res.username = username; | 100 | res.username = username; |
100 | return res; | 101 | return res; |
101 | }) | 102 | }) |
103 | .flatMap(res => this.fetchUserInformations(res)) | ||
102 | .map(res => this.handleLogin(res)) | 104 | .map(res => this.handleLogin(res)) |
103 | .catch(this.handleError); | 105 | .catch(this.handleError); |
104 | } | 106 | } |
@@ -136,31 +138,50 @@ export class AuthService { | |||
136 | .catch(this.handleError); | 138 | .catch(this.handleError); |
137 | } | 139 | } |
138 | 140 | ||
139 | private setStatus(status: AuthStatus) { | 141 | private fetchUserInformations (obj: any) { |
140 | this.loginChanged.next(status); | 142 | // Do not call authHttp here to avoid circular dependencies headaches |
143 | |||
144 | const headers = new Headers(); | ||
145 | headers.set('Authorization', `Bearer ${obj.access_token}`); | ||
146 | |||
147 | return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) | ||
148 | .map(res => res.json()) | ||
149 | .map(res => { | ||
150 | obj.id = res.id; | ||
151 | obj.role = res.role; | ||
152 | return obj; | ||
153 | } | ||
154 | ); | ||
155 | } | ||
156 | |||
157 | private handleError (error: Response) { | ||
158 | console.error(error); | ||
159 | return Observable.throw(error.json() || { error: 'Server error' }); | ||
141 | } | 160 | } |
142 | 161 | ||
143 | private handleLogin (obj: any) { | 162 | private handleLogin (obj: any) { |
163 | const id = obj.id; | ||
144 | const username = obj.username; | 164 | const username = obj.username; |
165 | const role = obj.role; | ||
145 | const hash_tokens = { | 166 | const hash_tokens = { |
146 | access_token: obj.access_token, | 167 | access_token: obj.access_token, |
147 | token_type: obj.token_type, | 168 | token_type: obj.token_type, |
148 | refresh_token: obj.refresh_token | 169 | refresh_token: obj.refresh_token |
149 | }; | 170 | }; |
150 | 171 | ||
151 | this.user = new User(username, hash_tokens); | 172 | this.user = new User(id, username, role, hash_tokens); |
152 | this.user.save(); | 173 | this.user.save(); |
153 | 174 | ||
154 | this.setStatus(AuthStatus.LoggedIn); | 175 | this.setStatus(AuthStatus.LoggedIn); |
155 | } | 176 | } |
156 | 177 | ||
157 | private handleError (error: Response) { | ||
158 | console.error(error); | ||
159 | return Observable.throw(error.json() || { error: 'Server error' }); | ||
160 | } | ||
161 | |||
162 | private handleRefreshToken (obj: any) { | 178 | private handleRefreshToken (obj: any) { |
163 | this.user.refreshTokens(obj.access_token, obj.refresh_token); | 179 | this.user.refreshTokens(obj.access_token, obj.refresh_token); |
164 | this.user.save(); | 180 | this.user.save(); |
165 | } | 181 | } |
182 | |||
183 | private setStatus(status: AuthStatus) { | ||
184 | this.loginChanged.next(status); | ||
185 | } | ||
186 | |||
166 | } | 187 | } |
diff --git a/client/src/app/shared/auth/user.model.ts b/client/src/app/shared/auth/user.model.ts index 98852f835..e486873ab 100644 --- a/client/src/app/shared/auth/user.model.ts +++ b/client/src/app/shared/auth/user.model.ts | |||
@@ -1,15 +1,24 @@ | |||
1 | export class User { | 1 | export class User { |
2 | private static KEYS = { | 2 | private static KEYS = { |
3 | ID: 'id', | ||
4 | ROLE: 'role', | ||
3 | USERNAME: 'username' | 5 | USERNAME: 'username' |
4 | }; | 6 | }; |
5 | 7 | ||
8 | id: string; | ||
9 | role: string; | ||
6 | username: string; | 10 | username: string; |
7 | tokens: Tokens; | 11 | tokens: Tokens; |
8 | 12 | ||
9 | static load() { | 13 | static load() { |
10 | const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); | 14 | const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); |
11 | if (usernameLocalStorage) { | 15 | if (usernameLocalStorage) { |
12 | return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load()); | 16 | return new User( |
17 | localStorage.getItem(this.KEYS.ID), | ||
18 | localStorage.getItem(this.KEYS.USERNAME), | ||
19 | localStorage.getItem(this.KEYS.ROLE), | ||
20 | Tokens.load() | ||
21 | ); | ||
13 | } | 22 | } |
14 | 23 | ||
15 | return null; | 24 | return null; |
@@ -17,11 +26,15 @@ export class User { | |||
17 | 26 | ||
18 | static flush() { | 27 | static flush() { |
19 | localStorage.removeItem(this.KEYS.USERNAME); | 28 | localStorage.removeItem(this.KEYS.USERNAME); |
29 | localStorage.removeItem(this.KEYS.ID); | ||
30 | localStorage.removeItem(this.KEYS.ROLE); | ||
20 | Tokens.flush(); | 31 | Tokens.flush(); |
21 | } | 32 | } |
22 | 33 | ||
23 | constructor(username: string, hash_tokens: any) { | 34 | constructor(id: string, username: string, role: string, hash_tokens: any) { |
35 | this.id = id; | ||
24 | this.username = username; | 36 | this.username = username; |
37 | this.role = role; | ||
25 | this.tokens = new Tokens(hash_tokens); | 38 | this.tokens = new Tokens(hash_tokens); |
26 | } | 39 | } |
27 | 40 | ||
@@ -43,12 +56,14 @@ export class User { | |||
43 | } | 56 | } |
44 | 57 | ||
45 | save() { | 58 | save() { |
46 | localStorage.setItem('username', this.username); | 59 | localStorage.setItem(User.KEYS.ID, this.id); |
60 | localStorage.setItem(User.KEYS.USERNAME, this.username); | ||
61 | localStorage.setItem(User.KEYS.ROLE, this.role); | ||
47 | this.tokens.save(); | 62 | this.tokens.save(); |
48 | } | 63 | } |
49 | } | 64 | } |
50 | 65 | ||
51 | // Private class used only by User | 66 | // Private class only used by User |
52 | class Tokens { | 67 | class Tokens { |
53 | private static KEYS = { | 68 | private static KEYS = { |
54 | ACCESS_TOKEN: 'access_token', | 69 | ACCESS_TOKEN: 'access_token', |