From e2a2d6c86c7ca39074fdff3b545947d1d58dc008 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Sat, 4 Mar 2017 11:45:47 +0100 Subject: Client: check token valitidy at startup --- client/src/app/account/account.component.ts | 7 +- client/src/app/account/account.module.ts | 4 +- client/src/app/account/account.service.ts | 28 ----- client/src/app/account/index.ts | 1 - client/src/app/app.component.ts | 18 +++- client/src/app/core/auth/auth-status.model.ts | 4 + client/src/app/core/auth/auth-user.model.ts | 118 +++++++++++++++++++++ client/src/app/core/auth/auth.service.ts | 4 +- client/src/app/core/auth/index.ts | 2 + client/src/app/core/menu/menu.component.ts | 3 +- client/src/app/shared/auth/auth-status.model.ts | 4 - client/src/app/shared/auth/auth-user.model.ts | 117 -------------------- client/src/app/shared/auth/index.ts | 2 - client/src/app/shared/shared.module.ts | 4 +- client/src/app/shared/users/index.ts | 1 + client/src/app/shared/users/user.service.ts | 36 +++++++ .../app/videos/video-list/video-list.component.ts | 4 +- 17 files changed, 188 insertions(+), 169 deletions(-) delete mode 100644 client/src/app/account/account.service.ts create mode 100644 client/src/app/core/auth/auth-status.model.ts create mode 100644 client/src/app/core/auth/auth-user.model.ts delete mode 100644 client/src/app/shared/auth/auth-status.model.ts delete mode 100644 client/src/app/shared/auth/auth-user.model.ts create mode 100644 client/src/app/shared/users/user.service.ts diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts index 9b6b5fbf4..14452a73e 100644 --- a/client/src/app/account/account.component.ts +++ b/client/src/app/account/account.component.ts @@ -4,8 +4,7 @@ import { Router } from '@angular/router'; import { NotificationsService } from 'angular2-notifications'; -import { AccountService } from './account.service'; -import { FormReactive, USER_PASSWORD } from '../shared'; +import { FormReactive, UserService, USER_PASSWORD } from '../shared'; @Component({ selector: 'my-account', @@ -29,7 +28,7 @@ export class AccountComponent extends FormReactive implements OnInit { private formBuilder: FormBuilder, private router: Router, private notificationsService: NotificationsService, - private accountService: AccountService + private userService: UserService ) { super(); } @@ -58,7 +57,7 @@ export class AccountComponent extends FormReactive implements OnInit { return; } - this.accountService.changePassword(newPassword).subscribe( + this.userService.changePassword(newPassword).subscribe( () => this.notificationsService.success('Success', 'Password updated.'), err => this.error = err diff --git a/client/src/app/account/account.module.ts b/client/src/app/account/account.module.ts index 53f6ba58e..75f2ee6f9 100644 --- a/client/src/app/account/account.module.ts +++ b/client/src/app/account/account.module.ts @@ -19,8 +19,6 @@ import { SharedModule } from '../shared'; AccountComponent ], - providers: [ - AccountService - ] + providers: [] }) export class AccountModule { } diff --git a/client/src/app/account/account.service.ts b/client/src/app/account/account.service.ts deleted file mode 100644 index 046690347..000000000 --- a/client/src/app/account/account.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Injectable } from '@angular/core'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/operator/map'; - -import { AuthService } from '../core'; -import { AuthHttp, RestExtractor } from '../shared'; - -@Injectable() -export class AccountService { - private static BASE_USERS_URL = '/api/v1/users/'; - - constructor( - private authHttp: AuthHttp, - private authService: AuthService, - private restExtractor: RestExtractor - ) {} - - changePassword(newPassword: string) { - const url = AccountService.BASE_USERS_URL + this.authService.getUser().id; - const body = { - password: newPassword - }; - - return this.authHttp.put(url, body) - .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)); - } -} diff --git a/client/src/app/account/index.ts b/client/src/app/account/index.ts index be03713cb..9265fa10a 100644 --- a/client/src/app/account/index.ts +++ b/client/src/app/account/index.ts @@ -1,4 +1,3 @@ export * from './account-routing.module'; export * from './account.component'; export * from './account.module'; -export * from './account.service'; diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index f487e6c48..47d2bfdd2 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts @@ -1,14 +1,17 @@ -import { Component, ViewContainerRef } from '@angular/core'; +import { Component, OnInit, ViewContainerRef } from '@angular/core'; import { Router } from '@angular/router'; import { MetaService } from 'ng2-meta'; + +import { AuthService } from './core'; +import { UserService } from './shared'; + @Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: [ './app.component.scss' ] }) - -export class AppComponent { +export class AppComponent implements OnInit { notificationOptions = { timeOut: 3000, lastOnBottom: true, @@ -25,9 +28,18 @@ export class AppComponent { constructor( private router: Router, private metaService: MetaService, + private authService: AuthService, + private userService: UserService, viewContainerRef: ViewContainerRef ) {} + ngOnInit() { + if (this.authService.isLoggedIn()) { + // The service will automatically redirect to the login page if the token is not valid anymore + this.userService.checkTokenValidity(); + } + } + isInAdmin() { return this.router.url.indexOf('/admin/') !== -1; } diff --git a/client/src/app/core/auth/auth-status.model.ts b/client/src/app/core/auth/auth-status.model.ts new file mode 100644 index 000000000..f646bd4cf --- /dev/null +++ b/client/src/app/core/auth/auth-status.model.ts @@ -0,0 +1,4 @@ +export enum AuthStatus { + LoggedIn, + LoggedOut +} diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts new file mode 100644 index 000000000..5d61954d6 --- /dev/null +++ b/client/src/app/core/auth/auth-user.model.ts @@ -0,0 +1,118 @@ +// Do not use the barrel (dependency loop) +import { User } from '../../shared/users/user.model'; + +export class AuthUser extends User { + private static KEYS = { + ID: 'id', + ROLE: 'role', + USERNAME: 'username' + }; + + tokens: Tokens; + + static load() { + const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); + if (usernameLocalStorage) { + return new AuthUser( + { + id: parseInt(localStorage.getItem(this.KEYS.ID)), + username: localStorage.getItem(this.KEYS.USERNAME), + role: localStorage.getItem(this.KEYS.ROLE) + }, + Tokens.load() + ); + } + + return null; + } + + static flush() { + localStorage.removeItem(this.KEYS.USERNAME); + localStorage.removeItem(this.KEYS.ID); + localStorage.removeItem(this.KEYS.ROLE); + Tokens.flush(); + } + + constructor(userHash: { id: number, username: string, role: string }, hashTokens: any) { + super(userHash); + this.tokens = new Tokens(hashTokens); + } + + getAccessToken() { + return this.tokens.access_token; + } + + getRefreshToken() { + return this.tokens.refresh_token; + } + + getTokenType() { + return this.tokens.token_type; + } + + refreshTokens(access_token: string, refresh_token: string) { + this.tokens.access_token = access_token; + this.tokens.refresh_token = refresh_token; + } + + save() { + localStorage.setItem(AuthUser.KEYS.ID, this.id.toString()); + localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); + localStorage.setItem(AuthUser.KEYS.ROLE, this.role); + this.tokens.save(); + } +} + +// Private class only used by User +class Tokens { + private static KEYS = { + ACCESS_TOKEN: 'access_token', + REFRESH_TOKEN: 'refresh_token', + TOKEN_TYPE: 'token_type', + }; + + access_token: string; + refresh_token: string; + token_type: string; + + static load() { + const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN); + const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN); + const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE); + + if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) { + return new Tokens({ + access_token: accessTokenLocalStorage, + refresh_token: refreshTokenLocalStorage, + token_type: tokenTypeLocalStorage + }); + } + + return null; + } + + static flush() { + localStorage.removeItem(this.KEYS.ACCESS_TOKEN); + localStorage.removeItem(this.KEYS.REFRESH_TOKEN); + localStorage.removeItem(this.KEYS.TOKEN_TYPE); + } + + constructor(hash?: any) { + if (hash) { + this.access_token = hash.access_token; + this.refresh_token = hash.refresh_token; + + if (hash.token_type === 'bearer') { + this.token_type = 'Bearer'; + } else { + this.token_type = hash.token_type; + } + } + } + + save() { + localStorage.setItem('access_token', this.access_token); + localStorage.setItem('refresh_token', this.refresh_token); + localStorage.setItem('token_type', this.token_type); + } +} diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index a56adbbad..2e7328197 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -9,9 +9,9 @@ import 'rxjs/add/observable/throw'; import { NotificationsService } from 'angular2-notifications'; +import { AuthStatus } from './auth-status.model'; +import { AuthUser } from './auth-user.model'; // Do not use the barrel (dependency loop) -import { AuthStatus } from '../../shared/auth/auth-status.model'; -import { AuthUser } from '../../shared/auth/auth-user.model'; import { RestExtractor } from '../../shared/rest'; @Injectable() diff --git a/client/src/app/core/auth/index.ts b/client/src/app/core/auth/index.ts index cf52c9c7c..67a18cfbb 100644 --- a/client/src/app/core/auth/index.ts +++ b/client/src/app/core/auth/index.ts @@ -1 +1,3 @@ +export * from './auth-status.model'; +export * from './auth-user.model'; export * from './auth.service' diff --git a/client/src/app/core/menu/menu.component.ts b/client/src/app/core/menu/menu.component.ts index f1bf6966d..5ca60e5e0 100644 --- a/client/src/app/core/menu/menu.component.ts +++ b/client/src/app/core/menu/menu.component.ts @@ -1,8 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; -import { AuthService } from '../auth'; -import { AuthStatus } from '../../shared'; +import { AuthService, AuthStatus } from '../auth'; @Component({ selector: 'my-menu', diff --git a/client/src/app/shared/auth/auth-status.model.ts b/client/src/app/shared/auth/auth-status.model.ts deleted file mode 100644 index f646bd4cf..000000000 --- a/client/src/app/shared/auth/auth-status.model.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum AuthStatus { - LoggedIn, - LoggedOut -} diff --git a/client/src/app/shared/auth/auth-user.model.ts b/client/src/app/shared/auth/auth-user.model.ts deleted file mode 100644 index f560351f4..000000000 --- a/client/src/app/shared/auth/auth-user.model.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { User } from '../users'; - -export class AuthUser extends User { - private static KEYS = { - ID: 'id', - ROLE: 'role', - USERNAME: 'username' - }; - - tokens: Tokens; - - static load() { - const usernameLocalStorage = localStorage.getItem(this.KEYS.USERNAME); - if (usernameLocalStorage) { - return new AuthUser( - { - id: parseInt(localStorage.getItem(this.KEYS.ID)), - username: localStorage.getItem(this.KEYS.USERNAME), - role: localStorage.getItem(this.KEYS.ROLE) - }, - Tokens.load() - ); - } - - return null; - } - - static flush() { - localStorage.removeItem(this.KEYS.USERNAME); - localStorage.removeItem(this.KEYS.ID); - localStorage.removeItem(this.KEYS.ROLE); - Tokens.flush(); - } - - constructor(userHash: { id: number, username: string, role: string }, hashTokens: any) { - super(userHash); - this.tokens = new Tokens(hashTokens); - } - - getAccessToken() { - return this.tokens.access_token; - } - - getRefreshToken() { - return this.tokens.refresh_token; - } - - getTokenType() { - return this.tokens.token_type; - } - - refreshTokens(access_token: string, refresh_token: string) { - this.tokens.access_token = access_token; - this.tokens.refresh_token = refresh_token; - } - - save() { - localStorage.setItem(AuthUser.KEYS.ID, this.id.toString()); - localStorage.setItem(AuthUser.KEYS.USERNAME, this.username); - localStorage.setItem(AuthUser.KEYS.ROLE, this.role); - this.tokens.save(); - } -} - -// Private class only used by User -class Tokens { - private static KEYS = { - ACCESS_TOKEN: 'access_token', - REFRESH_TOKEN: 'refresh_token', - TOKEN_TYPE: 'token_type', - }; - - access_token: string; - refresh_token: string; - token_type: string; - - static load() { - const accessTokenLocalStorage = localStorage.getItem(this.KEYS.ACCESS_TOKEN); - const refreshTokenLocalStorage = localStorage.getItem(this.KEYS.REFRESH_TOKEN); - const tokenTypeLocalStorage = localStorage.getItem(this.KEYS.TOKEN_TYPE); - - if (accessTokenLocalStorage && refreshTokenLocalStorage && tokenTypeLocalStorage) { - return new Tokens({ - access_token: accessTokenLocalStorage, - refresh_token: refreshTokenLocalStorage, - token_type: tokenTypeLocalStorage - }); - } - - return null; - } - - static flush() { - localStorage.removeItem(this.KEYS.ACCESS_TOKEN); - localStorage.removeItem(this.KEYS.REFRESH_TOKEN); - localStorage.removeItem(this.KEYS.TOKEN_TYPE); - } - - constructor(hash?: any) { - if (hash) { - this.access_token = hash.access_token; - this.refresh_token = hash.refresh_token; - - if (hash.token_type === 'bearer') { - this.token_type = 'Bearer'; - } else { - this.token_type = hash.token_type; - } - } - } - - save() { - localStorage.setItem('access_token', this.access_token); - localStorage.setItem('refresh_token', this.refresh_token); - localStorage.setItem('token_type', this.token_type); - } -} diff --git a/client/src/app/shared/auth/index.ts b/client/src/app/shared/auth/index.ts index ce0bd8adf..c488aed69 100644 --- a/client/src/app/shared/auth/index.ts +++ b/client/src/app/shared/auth/index.ts @@ -1,3 +1 @@ export * from './auth-http.service'; -export * from './auth-status.model'; -export * from './auth-user.model'; diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index 0f57ef078..84cc86c64 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -16,6 +16,7 @@ import { Ng2SmartTableModule } from 'ng2-smart-table'; import { AUTH_HTTP_PROVIDERS } from './auth'; import { RestExtractor, RestService } from './rest'; import { SearchComponent, SearchService } from './search'; +import { UserService } from './users'; import { VideoAbuseService } from './video-abuse'; @NgModule({ @@ -65,7 +66,8 @@ import { VideoAbuseService } from './video-abuse'; RestExtractor, RestService, SearchService, - VideoAbuseService + VideoAbuseService, + UserService ] }) export class SharedModule { } diff --git a/client/src/app/shared/users/index.ts b/client/src/app/shared/users/index.ts index 5a670ce8f..ff009e89b 100644 --- a/client/src/app/shared/users/index.ts +++ b/client/src/app/shared/users/index.ts @@ -1 +1,2 @@ export * from './user.model'; +export * from './user.service'; diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts new file mode 100644 index 000000000..4cf100f0d --- /dev/null +++ b/client/src/app/shared/users/user.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import 'rxjs/add/operator/catch'; +import 'rxjs/add/operator/map'; + +import { AuthService } from '../../core'; +import { AuthHttp } from '../auth'; +import { RestExtractor } from '../rest'; + +@Injectable() +export class UserService { + private static BASE_USERS_URL = '/api/v1/users/'; + + constructor( + private authHttp: AuthHttp, + private authService: AuthService, + private restExtractor: RestExtractor + ) {} + + checkTokenValidity() { + const url = UserService.BASE_USERS_URL + 'me'; + + // AuthHttp will redirect us to the login page if the oken is not valid anymore + this.authHttp.get(url).subscribe(() => { ; }); + } + + changePassword(newPassword: string) { + const url = UserService.BASE_USERS_URL + this.authService.getUser().id; + const body = { + password: newPassword + }; + + return this.authHttp.put(url, body) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); + } +} diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts index b3780f8b6..844e14567 100644 --- a/client/src/app/videos/video-list/video-list.component.ts +++ b/client/src/app/videos/video-list/video-list.component.ts @@ -9,8 +9,8 @@ import { Video, VideoService } from '../shared'; -import { AuthService } from '../../core'; -import { AuthUser, RestPagination, Search, SearchField } from '../../shared'; +import { AuthService, AuthUser } from '../../core'; +import { RestPagination, Search, SearchField } from '../../shared'; import { SearchService } from '../../shared'; @Component({ -- cgit v1.2.3