From 629d8d6f70cf83b55011dff53bfe1c4a95ac3433 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 5 Aug 2016 18:04:08 +0200 Subject: Client: implement password change --- client/src/app/account/account.component.html | 27 +++++++++++++++ client/src/app/account/account.component.ts | 45 +++++++++++++++++++++++++ client/src/app/account/account.routes.ts | 5 +++ client/src/app/account/account.service.ts | 19 +++++++++++ client/src/app/account/index.ts | 2 ++ client/src/app/app.component.html | 15 +++++++-- client/src/app/app.routes.ts | 2 ++ client/src/app/shared/auth/auth-http.service.ts | 6 ++-- client/src/app/shared/auth/auth.service.ts | 37 +++++++++++++++----- client/src/app/shared/auth/user.model.ts | 23 ++++++++++--- 10 files changed, 165 insertions(+), 16 deletions(-) create mode 100644 client/src/app/account/account.component.html create mode 100644 client/src/app/account/account.component.ts create mode 100644 client/src/app/account/account.routes.ts create mode 100644 client/src/app/account/account.service.ts create mode 100644 client/src/app/account/index.ts (limited to 'client/src/app') 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 @@ +

Account

+ +
{{ information }}
+
{{ error }}
+ +
+
+ + +
+ The password should have more than 5 characters +
+
+ +
+ + +
+ + +
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 @@ +import { Control, ControlGroup, Validators } from '@angular/common'; +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; + +import { AccountService } from './account.service'; + +@Component({ + selector: 'my-account', + template: require('./account.component.html'), + providers: [ AccountService ] +}) + +export class AccountComponent implements OnInit { + changePasswordForm: ControlGroup; + information: string = null; + error: string = null; + + constructor( + private accountService: AccountService, + private router: Router + ) {} + + ngOnInit() { + this.changePasswordForm = new ControlGroup({ + newPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + newConfirmedPassword: new Control('', Validators.compose([ Validators.required, Validators.minLength(6) ])), + }); + } + + changePassword(newPassword: string, newConfirmedPassword: string) { + this.information = null; + this.error = null; + + if (newPassword !== newConfirmedPassword) { + this.error = 'The new password and the confirmed password do not correspond.'; + return; + } + + this.accountService.changePassword(newPassword).subscribe( + ok => this.information = 'Password updated.', + + err => this.error = err + ); + } +} 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 @@ +import { AccountComponent } from './account.component'; + +export const AccountRoutes = [ + { path: 'account', component: AccountComponent } +]; 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 @@ +import { Injectable } from '@angular/core'; + +import { AuthHttp, AuthService } from '../shared'; + +@Injectable() +export class AccountService { + private static BASE_USERS_URL = '/api/v1/users/'; + + constructor(private authHttp: AuthHttp, private authService: AuthService) { } + + changePassword(newPassword: string) { + const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; + const body = { + password: newPassword + }; + + return this.authHttp.put(url, body); + } +} 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 @@ +export * from './account.component'; +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 @@
+ + + Login + + + + + Logout + +
+ +
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 @@ import { RouterConfig } from '@angular/router'; +import { AccountRoutes } from './account'; import { LoginRoutes } from './login'; import { VideosRoutes } from './videos'; @@ -10,6 +11,7 @@ export const routes: RouterConfig = [ pathMatch: 'full' }, + ...AccountRoutes, ...LoginRoutes, ...VideosRoutes ]; 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 { return this.request(url, options); } - post(url: string, options?: RequestOptionsArgs): Observable { + post(url: string, body: any, options?: RequestOptionsArgs): Observable { if (!options) options = {}; options.method = RequestMethod.Post; + options.body = body; return this.request(url, options); } - put(url: string, options?: RequestOptionsArgs): Observable { + put(url: string, body: any, options?: RequestOptionsArgs): Observable { if (!options) options = {}; options.method = RequestMethod.Put; + options.body = body; return this.request(url, options); } 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'; export class AuthService { private static BASE_CLIENT_URL = '/api/v1/clients/local'; private static BASE_TOKEN_URL = '/api/v1/users/token'; + private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me'; loginChangedSource: Observable; @@ -99,6 +100,7 @@ export class AuthService { res.username = username; return res; }) + .flatMap(res => this.fetchUserInformations(res)) .map(res => this.handleLogin(res)) .catch(this.handleError); } @@ -136,31 +138,50 @@ export class AuthService { .catch(this.handleError); } - private setStatus(status: AuthStatus) { - this.loginChanged.next(status); + private fetchUserInformations (obj: any) { + // Do not call authHttp here to avoid circular dependencies headaches + + const headers = new Headers(); + headers.set('Authorization', `Bearer ${obj.access_token}`); + + return this.http.get(AuthService.BASE_USER_INFORMATIONS_URL, { headers }) + .map(res => res.json()) + .map(res => { + obj.id = res.id; + obj.role = res.role; + return obj; + } + ); + } + + private handleError (error: Response) { + console.error(error); + return Observable.throw(error.json() || { error: 'Server error' }); } private handleLogin (obj: any) { + const id = obj.id; const username = obj.username; + const role = obj.role; const hash_tokens = { access_token: obj.access_token, token_type: obj.token_type, refresh_token: obj.refresh_token }; - this.user = new User(username, hash_tokens); + this.user = new User(id, username, role, hash_tokens); this.user.save(); this.setStatus(AuthStatus.LoggedIn); } - private handleError (error: Response) { - console.error(error); - return Observable.throw(error.json() || { error: 'Server error' }); - } - private handleRefreshToken (obj: any) { this.user.refreshTokens(obj.access_token, obj.refresh_token); this.user.save(); } + + private setStatus(status: AuthStatus) { + this.loginChanged.next(status); + } + } 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 @@ export class User { private static KEYS = { + ID: 'id', + ROLE: 'role', USERNAME: 'username' }; + id: string; + role: string; username: string; tokens: Tokens; static load() { const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); if (usernameLocalStorage) { - return new User(localStorage.getItem(this.KEYS.USERNAME), Tokens.load()); + return new User( + localStorage.getItem(this.KEYS.ID), + localStorage.getItem(this.KEYS.USERNAME), + localStorage.getItem(this.KEYS.ROLE), + Tokens.load() + ); } return null; @@ -17,11 +26,15 @@ export class User { static flush() { localStorage.removeItem(this.KEYS.USERNAME); + localStorage.removeItem(this.KEYS.ID); + localStorage.removeItem(this.KEYS.ROLE); Tokens.flush(); } - constructor(username: string, hash_tokens: any) { + constructor(id: string, username: string, role: string, hash_tokens: any) { + this.id = id; this.username = username; + this.role = role; this.tokens = new Tokens(hash_tokens); } @@ -43,12 +56,14 @@ export class User { } save() { - localStorage.setItem('username', this.username); + localStorage.setItem(User.KEYS.ID, this.id); + localStorage.setItem(User.KEYS.USERNAME, this.username); + localStorage.setItem(User.KEYS.ROLE, this.role); this.tokens.save(); } } -// Private class used only by User +// Private class only used by User class Tokens { private static KEYS = { ACCESS_TOKEN: 'access_token', -- cgit v1.2.3