Implement login mechanism.

This commit is contained in:
Alexis Lahouze 2017-08-04 08:32:49 +02:00
parent 16bbc67850
commit 58f1abce21
6 changed files with 223 additions and 23 deletions

View File

@ -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<Token>;
constructor(
private logger: Logger,
private loginService: LoginService,
private injector: Injector,
) {}
injectAuthorizationHeader(request: HttpRequest<any>, accessToken: string) {
this.logger.log('Injecting Authorization header');
return request;
}
intercept(
req: HttpRequest<any>,
next: HttpHandler
request: HttpRequest<any>,
next: HttpHandler,
pass?: number
): Observable<HttpEvent<any>> {
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<any>) => 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<any> = next.handle(request);
return observable.catch(
(error, caught): Observable<any> => {
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<HttpEvent<any>> => {
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);
});
}
);
}

View File

@ -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 {};

View File

@ -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<Token> {
let modal = this.ngbModal.open(LoginModalComponent);
sessionStorage.clear();
let observable: Observable<any> = 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<any> {
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);
}
};

View File

@ -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: `
<div class="form-group">
<label for="email" class="col-sm-4 control-label">Adresse email</label>
<div class="col-sm-8">
<input type="text" class="form-control" id="email"
[(ngModel)]="login.email" placeholder="Nom d'utilisateur">
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-4 control-label">Mot de passe</label>
<div class="col-sm-8">
<input type="password" class="form-control" id="password"
[(ngModel)]="login.password" placeholder="Mot de passe">
</div>
</div>
`
})
export class LoginForm {
@Input('login-form') private login: Login
}

View File

@ -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: `
<div class="modal-header">
<h3 class="modal-title" id="modal-title">Authentification requise</h3>
</div>
<div class="modal-body" id="modal-body">
<form [(login-form)]="login" class="form-horizontal"
(ngSubmit)="submit()" #form="ngForm">
</form>
</div>
<div class="modal-footer">
<button class="btn btn-primary" [disabled]="!form.valid" (click)="submit()">
Login
</button>
<button class="btn btn-default" (click)="cancel()">
Cancel
</button>
</div>
`
})
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");
}
}

6
src/login/token.ts Normal file
View File

@ -0,0 +1,6 @@
// vim: set tw=80 ts=2 sw=2 sts=2 :
export class Token {
access_token: string;
refresh_token: string;
}