diff --git a/src/login/authInterceptor.ts b/src/login/authInterceptor.ts index 7dce326..bd3c984 100644 --- a/src/login/authInterceptor.ts +++ b/src/login/authInterceptor.ts @@ -1,48 +1,91 @@ // vim: set tw=80 ts=2 sw=2 sts=2 : -import { Injectable } from '@angular/core'; +import { Injectable, Injector } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse } from '@angular/common/http'; import { Observable} from 'rxjs/Rx'; -import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/catch'; import { Logger } from '@nsalaun/ng-logger'; import { LoginService } from './login.service'; +import { Token } from './token'; @Injectable() export class AuthInterceptor implements HttpInterceptor { + private observable: Observable; + constructor( private logger: Logger, - private loginService: LoginService, + private injector: Injector, ) {} + injectAuthorizationHeader(request: HttpRequest, accessToken: string) { + this.logger.log('Injecting Authorization header'); + + return request; + } + intercept( - req: HttpRequest, - next: HttpHandler + request: HttpRequest, + next: HttpHandler, + pass?: number ): Observable> { - this.logger.log('Intercepted request', req, next); - - if(!req.headers.has('Authorization')) { - let accessToken: string = this.loginService.accessToken(); - - if(accessToken){ - req = req.clone({ - headers: req.headers.set('Authorization', `Bearer ${accessToken}`) - }); - } + if(!pass) { + pass = 1; } - this.logger.log('Request', req); + let loginService = this.injector.get(LoginService); - return next.handle(req).map( - (event: HttpEvent) => event, - (error) => { - if(error instanceof HttpErrorResponse && error.status === 401) { - this.logger.error('Unauthorized', error); + if(request.url == loginService.url) { + this.logger.log("Login URL, do not handle."); + + return next.handle(request); + } + + this.logger.log(`Intercepted request, pass #${pass}`, request, next); + + let accessToken = loginService.accessToken; + + if(accessToken){ + request = request.clone({ + headers: request.headers.set('Authorization', `Bearer ${accessToken}`) + }); + } + + this.logger.log('Request', request); + + let observable: Observable = next.handle(request); + + return observable.catch( + (error, caught): Observable => { + this.logger.error("Error", error, caught); + + if(!(error instanceof HttpErrorResponse) || error.status != 401) { + return Observable.throw(error); } + + this.logger.log('Unauthorized', error); + + if(pass === 3) { + return Observable.throw(error); + } + + if(!this.observable) { + this.logger.log("No current login observable.") + this.observable = loginService.login(); + } + + return this.observable.flatMap((token: Token): Observable> => { + this.logger.log("Logged in, access_token:", token.access_token); + this.observable = null; + return this.intercept(request, next, ++pass); + }).catch((error) => { + this.observable = null; + return Observable.throw(error); + }); } ); } diff --git a/src/login/login.module.ts b/src/login/login.module.ts index d84e07c..cb1a735 100644 --- a/src/login/login.module.ts +++ b/src/login/login.module.ts @@ -1,15 +1,21 @@ // vim: set tw=80 ts=2 sw=2 sts=2 : import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; import { HTTP_INTERCEPTORS } from '@angular/common/http'; import { NgLoggerModule } from '@nsalaun/ng-logger'; -import { LoginService } from './login.service'; import { AuthInterceptor } from './authInterceptor'; +import { LoginService } from './login.service'; +import { LoginForm } from './loginForm.component'; +import { LoginModalComponent } from './loginModal.component'; @NgModule({ imports: [ + HttpClientModule, + FormsModule, NgLoggerModule, ], providers: [ @@ -19,6 +25,14 @@ import { AuthInterceptor } from './authInterceptor'; useClass: AuthInterceptor, multi: true } + ], + declarations: [ + LoginModalComponent, + LoginForm, + ], + entryComponents: [ + LoginModalComponent, + LoginForm, ] }) export class LoginModule {}; diff --git a/src/login/login.service.ts b/src/login/login.service.ts index 14e6994..7418807 100644 --- a/src/login/login.service.ts +++ b/src/login/login.service.ts @@ -1,10 +1,67 @@ import { Injectable } from '@angular/core'; +import { HttpClient, HttpHeaders } from '@angular/common/http'; + +import { Observable} from 'rxjs/Rx'; import * as base64 from 'base64util'; +import { Logger } from '@nsalaun/ng-logger'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { Token } from './token'; +import { LoginModalComponent } from './loginModal.component'; +import { Login } from './login'; + @Injectable() export class LoginService { - accessToken() { + + constructor( + private httpClient: HttpClient, + private logger: Logger, + private ngbModal: NgbModal, + ) {} + + public readonly url: string = '/api/user/login'; + + login(): Observable { + let modal = this.ngbModal.open(LoginModalComponent); + + sessionStorage.clear(); + + let observable: Observable = Observable.fromPromise(modal.result); + + return observable.flatMap((login: Login) => + this.doLogin(login) + ).map((token: Token): Token => { + this.accessToken = token.access_token; + return token; + }); + } + + logout() { + sessionStorage.clear(); + } + + doLogin(login: Login): Observable { + var authdata = base64.encode( + `${login.email}:${login.password}` + ); + + let headers = new HttpHeaders() + headers = headers.set('Authorization', `Basic ${authdata}`); + + this.logger.log("Headers", headers); + + return this.httpClient.post(this.url, {}, { + headers: headers + }); + } + + get accessToken(): string { return sessionStorage.getItem('access_token'); } + + set accessToken(token: string) { + sessionStorage.setItem('access_token', token); + } }; diff --git a/src/login/loginForm.component.ts b/src/login/loginForm.component.ts new file mode 100644 index 0000000..078f940 --- /dev/null +++ b/src/login/loginForm.component.ts @@ -0,0 +1,33 @@ +// vim: set tw=80 ts=2 sw=2 sts=2 : + +import { Component, Input } from '@angular/core'; +import { NgForm } from '@angular/forms'; + +import { Login } from './login'; + +@Component({ + selector: 'form[login-form]', + template: ` +
+ + +
+ +
+
+ +
+ + +
+ +
+
+ ` +}) + +export class LoginForm { + @Input('login-form') private login: Login +} diff --git a/src/login/loginModal.component.ts b/src/login/loginModal.component.ts new file mode 100644 index 0000000..7d4650e --- /dev/null +++ b/src/login/loginModal.component.ts @@ -0,0 +1,47 @@ +// vim: set tw=80 ts=2 sw=2 sts=2: + +import { Component } from '@angular/core' + +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { Login } from './login'; + +@Component({ + selector: 'login-modal', + template: ` + + + + + + ` +}) +export class LoginModalComponent { + private login: Login = new Login(); + + constructor(private activeModal: NgbActiveModal) { + + } + + submit(): void { + this.activeModal.close(this.login); + } + + cancel(): void { + this.activeModal.dismiss("closed"); + } +} diff --git a/src/login/token.ts b/src/login/token.ts new file mode 100644 index 0000000..bd6902d --- /dev/null +++ b/src/login/token.ts @@ -0,0 +1,6 @@ +// vim: set tw=80 ts=2 sw=2 sts=2 : + +export class Token { + access_token: string; + refresh_token: string; +}