Client: implement password change
This commit is contained in:
parent
99a64bfed2
commit
629d8d6f70
11 changed files with 177 additions and 22 deletions
27
client/src/app/account/account.component.html
Normal file
27
client/src/app/account/account.component.html
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<h3>Account</h3>
|
||||||
|
|
||||||
|
<div *ngIf="information" class="alert alert-success">{{ information }}</div>
|
||||||
|
<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
|
||||||
|
|
||||||
|
<form role="form" (ngSubmit)="changePassword(newPassword.value, newConfirmedPassword.value)" [ngFormModel]="changePasswordForm">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="new-password">New password</label>
|
||||||
|
<input
|
||||||
|
type="password" class="form-control" name="new-password" id="new-password"
|
||||||
|
ngControl="newPassword" #newPassword="ngForm"
|
||||||
|
>
|
||||||
|
<div [hidden]="newPassword.valid || newPassword.pristine" class="alert alert-warning">
|
||||||
|
The password should have more than 5 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="name">Confirm new password</label>
|
||||||
|
<input
|
||||||
|
type="password" class="form-control" name="new-confirmed-password" id="new-confirmed-password"
|
||||||
|
ngControl="newConfirmedPassword" #newConfirmedPassword="ngForm"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="submit" value="Change password" class="btn btn-default" [disabled]="!changePasswordForm.valid">
|
||||||
|
</form>
|
45
client/src/app/account/account.component.ts
Normal file
45
client/src/app/account/account.component.ts
Normal file
|
@ -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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
5
client/src/app/account/account.routes.ts
Normal file
5
client/src/app/account/account.routes.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import { AccountComponent } from './account.component';
|
||||||
|
|
||||||
|
export const AccountRoutes = [
|
||||||
|
{ path: 'account', component: AccountComponent }
|
||||||
|
];
|
19
client/src/app/account/account.service.ts
Normal file
19
client/src/app/account/account.service.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
2
client/src/app/account/index.ts
Normal file
2
client/src/app/account/index.ts
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './account.component';
|
||||||
|
export * from './account.routes';
|
|
@ -18,9 +18,20 @@
|
||||||
<menu class="col-md-2 col-sm-3 col-xs-3">
|
<menu class="col-md-2 col-sm-3 col-xs-3">
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<div id="panel-user-login" class="panel-button">
|
<div id="panel-user-login" class="panel-button">
|
||||||
<span class="hidden-xs glyphicon glyphicon-user"></span>
|
<span *ngIf="!isLoggedIn" >
|
||||||
<a *ngIf="!isLoggedIn" [routerLink]="['/login']">Login</a>
|
<span class="hidden-xs glyphicon glyphicon-log-in"></span>
|
||||||
|
<a [routerLink]="['/login']">Login</a>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span *ngIf="isLoggedIn">
|
||||||
|
<span class="hidden-xs glyphicon glyphicon-log-out"></span>
|
||||||
<a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
|
<a *ngIf="isLoggedIn" (click)="logout()">Logout</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="isLoggedIn" id="panel-user-account" class="panel-button">
|
||||||
|
<span class="hidden-xs glyphicon glyphicon-user"></span>
|
||||||
|
<a [routerLink]="['/account']">My account</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { RouterConfig } from '@angular/router';
|
import { RouterConfig } from '@angular/router';
|
||||||
|
|
||||||
|
import { AccountRoutes } from './account';
|
||||||
import { LoginRoutes } from './login';
|
import { LoginRoutes } from './login';
|
||||||
import { VideosRoutes } from './videos';
|
import { VideosRoutes } from './videos';
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ export const routes: RouterConfig = [
|
||||||
pathMatch: 'full'
|
pathMatch: 'full'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
...AccountRoutes,
|
||||||
...LoginRoutes,
|
...LoginRoutes,
|
||||||
...VideosRoutes
|
...VideosRoutes
|
||||||
];
|
];
|
||||||
|
|
|
@ -49,16 +49,18 @@ export class AuthHttp extends Http {
|
||||||
return this.request(url, options);
|
return this.request(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
post(url: string, options?: RequestOptionsArgs): Observable<Response> {
|
post(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
options.method = RequestMethod.Post;
|
options.method = RequestMethod.Post;
|
||||||
|
options.body = body;
|
||||||
|
|
||||||
return this.request(url, options);
|
return this.request(url, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
put(url: string, options?: RequestOptionsArgs): Observable<Response> {
|
put(url: string, body: any, options?: RequestOptionsArgs): Observable<Response> {
|
||||||
if (!options) options = {};
|
if (!options) options = {};
|
||||||
options.method = RequestMethod.Put;
|
options.method = RequestMethod.Put;
|
||||||
|
options.body = body;
|
||||||
|
|
||||||
return this.request(url, options);
|
return this.request(url, options);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { User } from './user.model';
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private static BASE_CLIENT_URL = '/api/v1/clients/local';
|
private static BASE_CLIENT_URL = '/api/v1/clients/local';
|
||||||
private static BASE_TOKEN_URL = '/api/v1/users/token';
|
private static BASE_TOKEN_URL = '/api/v1/users/token';
|
||||||
|
private static BASE_USER_INFORMATIONS_URL = '/api/v1/users/me';
|
||||||
|
|
||||||
loginChangedSource: Observable<AuthStatus>;
|
loginChangedSource: Observable<AuthStatus>;
|
||||||
|
|
||||||
|
@ -99,6 +100,7 @@ export class AuthService {
|
||||||
res.username = username;
|
res.username = username;
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
|
.flatMap(res => this.fetchUserInformations(res))
|
||||||
.map(res => this.handleLogin(res))
|
.map(res => this.handleLogin(res))
|
||||||
.catch(this.handleError);
|
.catch(this.handleError);
|
||||||
}
|
}
|
||||||
|
@ -136,22 +138,20 @@ export class AuthService {
|
||||||
.catch(this.handleError);
|
.catch(this.handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setStatus(status: AuthStatus) {
|
private fetchUserInformations (obj: any) {
|
||||||
this.loginChanged.next(status);
|
// 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 handleLogin (obj: any) {
|
|
||||||
const username = obj.username;
|
|
||||||
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.save();
|
|
||||||
|
|
||||||
this.setStatus(AuthStatus.LoggedIn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleError (error: Response) {
|
private handleError (error: Response) {
|
||||||
|
@ -159,8 +159,29 @@ export class AuthService {
|
||||||
return Observable.throw(error.json() || { error: 'Server 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(id, username, role, hash_tokens);
|
||||||
|
this.user.save();
|
||||||
|
|
||||||
|
this.setStatus(AuthStatus.LoggedIn);
|
||||||
|
}
|
||||||
|
|
||||||
private handleRefreshToken (obj: any) {
|
private handleRefreshToken (obj: any) {
|
||||||
this.user.refreshTokens(obj.access_token, obj.refresh_token);
|
this.user.refreshTokens(obj.access_token, obj.refresh_token);
|
||||||
this.user.save();
|
this.user.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setStatus(status: AuthStatus) {
|
||||||
|
this.loginChanged.next(status);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,24 @@
|
||||||
export class User {
|
export class User {
|
||||||
private static KEYS = {
|
private static KEYS = {
|
||||||
|
ID: 'id',
|
||||||
|
ROLE: 'role',
|
||||||
USERNAME: 'username'
|
USERNAME: 'username'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
id: string;
|
||||||
|
role: string;
|
||||||
username: string;
|
username: string;
|
||||||
tokens: Tokens;
|
tokens: Tokens;
|
||||||
|
|
||||||
static load() {
|
static load() {
|
||||||
const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME);
|
const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME);
|
||||||
if (usernameLocalStorage) {
|
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;
|
return null;
|
||||||
|
@ -17,11 +26,15 @@ export class User {
|
||||||
|
|
||||||
static flush() {
|
static flush() {
|
||||||
localStorage.removeItem(this.KEYS.USERNAME);
|
localStorage.removeItem(this.KEYS.USERNAME);
|
||||||
|
localStorage.removeItem(this.KEYS.ID);
|
||||||
|
localStorage.removeItem(this.KEYS.ROLE);
|
||||||
Tokens.flush();
|
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.username = username;
|
||||||
|
this.role = role;
|
||||||
this.tokens = new Tokens(hash_tokens);
|
this.tokens = new Tokens(hash_tokens);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,12 +56,14 @@ export class User {
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
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();
|
this.tokens.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private class used only by User
|
// Private class only used by User
|
||||||
class Tokens {
|
class Tokens {
|
||||||
private static KEYS = {
|
private static KEYS = {
|
||||||
ACCESS_TOKEN: 'access_token',
|
ACCESS_TOKEN: 'access_token',
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
"typings/main.d.ts"
|
"typings/main.d.ts"
|
||||||
],
|
],
|
||||||
"files": [
|
"files": [
|
||||||
|
"src/app/account/account.component.ts",
|
||||||
|
"src/app/account/account.routes.ts",
|
||||||
|
"src/app/account/account.service.ts",
|
||||||
|
"src/app/account/index.ts",
|
||||||
"src/app/app.component.ts",
|
"src/app/app.component.ts",
|
||||||
"src/app/app.routes.ts",
|
"src/app/app.routes.ts",
|
||||||
"src/app/friends/friend.service.ts",
|
"src/app/friends/friend.service.ts",
|
||||||
|
@ -45,6 +49,8 @@
|
||||||
"src/app/shared/search/search.component.ts",
|
"src/app/shared/search/search.component.ts",
|
||||||
"src/app/shared/search/search.model.ts",
|
"src/app/shared/search/search.model.ts",
|
||||||
"src/app/shared/search/search.service.ts",
|
"src/app/shared/search/search.service.ts",
|
||||||
|
"src/app/shared/user/index.ts",
|
||||||
|
"src/app/shared/user/user.service.ts",
|
||||||
"src/app/videos/index.ts",
|
"src/app/videos/index.ts",
|
||||||
"src/app/videos/shared/index.ts",
|
"src/app/videos/shared/index.ts",
|
||||||
"src/app/videos/shared/loader/index.ts",
|
"src/app/videos/shared/loader/index.ts",
|
||||||
|
|
Loading…
Reference in a new issue