Skip to content

Commit

Permalink
feat(auth): refresh token
Browse files Browse the repository at this point in the history
  • Loading branch information
mbarbeau committed Nov 21, 2019
1 parent cc4e918 commit fa94a3f
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 564 deletions.
2 changes: 1 addition & 1 deletion demo/src/environments/environment.prod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const environment: Environment = {
}
},
icherchereverse: {
searchUrl: 'https://geoegl.msp.gouv.qc.ca/apis/territoires',
searchUrl: 'https://geoegl.msp.gouv.qc.ca/apis/terrapi',
order: 3,
enabled: true
},
Expand Down
4 changes: 3 additions & 1 deletion demo/src/environments/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const environment: Environment = {
}
],
auth: {
url: '/apis/users',
tokenKey: 'testIgo2Lib',
intern: {
enabled: true
},
Expand Down Expand Up @@ -80,7 +82,7 @@ export const environment: Environment = {
}
},
icherchereverse: {
searchUrl: '/apis/territoires',
searchUrl: '/apis/terrapi',
order: 3,
enabled: true
},
Expand Down
694 changes: 170 additions & 524 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"typy": "^2.0.1",
"unorm": "^1.4.1",
"web-animations-js": "^2.3.1",
"webpack": "^4.41.2",
"zone.js": "^0.8.29"
},
"devDependencies": {
Expand Down
31 changes: 30 additions & 1 deletion packages/auth/src/lib/shared/auth.interceptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ import {
HttpRequest
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { finalize } from 'rxjs/operators';

import { ConfigService } from '@igo2/core';
import { TokenService } from './token.service';
import { AuthService } from './auth.service';

@Injectable({
providedIn: 'root'
})
export class AuthInterceptor implements HttpInterceptor {
private trustHosts: string[] = [];
private refreshInProgress = false;

constructor(
private config: ConfigService,
private tokenService: TokenService
private tokenService: TokenService,
private authService: AuthService
) {
this.trustHosts = this.config.getConfig('auth.trustHosts') || [];
this.trustHosts.push(window.location.hostname);
Expand All @@ -28,6 +32,7 @@ export class AuthInterceptor implements HttpInterceptor {
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
this.refreshToken();
const token = this.tokenService.get();
const element = document.createElement('a');
element.href = req.url;
Expand All @@ -42,4 +47,28 @@ export class AuthInterceptor implements HttpInterceptor {
});
return next.handle(authReq);
}

interceptXhr(xhr, url: string) {
this.refreshToken();
const element = document.createElement('a');
element.href = url;

const token = this.tokenService.get();
if (!token && this.trustHosts.indexOf(element.hostname) === -1) {
return;
}
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
}

refreshToken() {
const jwt = this.tokenService.decode();
const currentTime = (new Date().getTime() / 1000);

if (!this.refreshInProgress && jwt && currentTime < jwt.exp && currentTime > jwt.exp - 1800) {
this.refreshInProgress = true;
this.authService.refresh().pipe(
finalize(() => { this.refreshInProgress = false;})
).subscribe();
}
}
}
40 changes: 27 additions & 13 deletions packages/auth/src/lib/shared/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { tap, catchError } from 'rxjs/operators';
import { ConfigService, LanguageService, MessageService } from '@igo2/core';
import { Base64 } from '@igo2/utils';

import { AuthOptions, User } from './auth.interface';
import { User } from './auth.interface';
import { TokenService } from './token.service';

@Injectable({
Expand All @@ -17,7 +17,6 @@ import { TokenService } from './token.service';
export class AuthService {
public authenticate$ = new BehaviorSubject<boolean>(undefined);
public redirectUrl: string;
private options: AuthOptions;
private anonymous = false;

constructor(
Expand All @@ -28,11 +27,10 @@ export class AuthService {
private messageService: MessageService,
@Optional() private router: Router
) {
this.options = this.config.getConfig('auth') || {};
this.authenticate$.next(this.authenticated);
}

login(username: string, password: string): any {
login(username: string, password: string): Observable<void> {
const myHeader = new HttpHeaders();
myHeader.append('Content-Type', 'application/json');

Expand All @@ -44,7 +42,7 @@ export class AuthService {
return this.loginCall(body, myHeader);
}

loginWithToken(token: string, type: string): any {
loginWithToken(token: string, type: string): Observable<void> {
const myHeader = new HttpHeaders();
myHeader.append('Content-Type', 'application/json');

Expand All @@ -56,12 +54,25 @@ export class AuthService {
return this.loginCall(body, myHeader);
}

loginAnonymous() {
loginAnonymous(): Observable<boolean> {
this.anonymous = true;
return of(true);
}

logout() {
refresh(): Observable<void> {
const url = this.config.getConfig('auth.url');
return this.http.post(`${url}/refresh`, {}).pipe(
tap((data: any) => {
this.tokenService.set(data.token);
}),
catchError(err => {
err.error.caught = true;
throw err;
})
);
}

logout(): Observable<boolean>{
this.anonymous = false;
this.tokenService.remove();
this.authenticate$.next(false);
Expand Down Expand Up @@ -89,25 +100,27 @@ export class AuthService {
}
const redirectUrl = this.redirectUrl || this.router.url;

if (redirectUrl === this.options.loginRoute) {
const homeRoute = this.options.homeRoute || '/';
const options = this.config.getConfig('auth') || {};
if (redirectUrl === options.loginRoute) {
const homeRoute = options.homeRoute || '/';
this.router.navigateByUrl(homeRoute);
} else if (redirectUrl) {
this.router.navigateByUrl(redirectUrl);
}
}

getUserInfo(): Observable<User> {
const url = this.options.url + '/info';
const url = this.config.getConfig('auth.url') + '/info';
return this.http.get<User>(url);
}

getProfils() {
return this.http.get(`${this.options.url}/profils`);
const url = this.config.getConfig('auth.url');
return this.http.get(`${url}/profils`);
}

updateUser(user: User): Observable<User> {
const url = this.options.url;
const url = this.config.getConfig('auth.url');
return this.http.patch<User>(url, JSON.stringify(user));
}

Expand All @@ -129,7 +142,8 @@ export class AuthService {
}

private loginCall(body, headers) {
return this.http.post(`${this.options.url}/login`, body, { headers }).pipe(
const url = this.config.getConfig('auth.url');
return this.http.post(`${url}/login`, body, { headers }).pipe(
tap((data: any) => {
this.tokenService.set(data.token);
const tokenDecoded = this.decodeToken();
Expand Down
21 changes: 6 additions & 15 deletions packages/geo/src/lib/layer/shared/layer.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable, Optional } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, Injector, Optional } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import stylefunction from 'ol-mapbox-style/stylefunction';
import { AuthInterceptor } from '@igo2/auth';
import { ObjectUtils } from '@igo2/utils';
import { ConfigService } from '@igo2/core';

import {
OSMDataSource,
Expand Down Expand Up @@ -42,18 +42,13 @@ import { StyleService } from './style.service';
providedIn: 'root'
})
export class LayerService {
private tokenKey: string;

constructor(
private http: HttpClient,
private styleService: StyleService,
private dataSourceService: DataSourceService,
@Optional() private config: ConfigService
) {
if (this.config) {
this.tokenKey = this.config.getConfig('auth.tokenKey');
}
}
@Optional() private authInterceptor: AuthInterceptor
) {}

createLayer(layerOptions: AnyLayerOptions): Layer {
if (!layerOptions.source) {
Expand Down Expand Up @@ -120,11 +115,7 @@ export class LayerService {
}

private createImageLayer(layerOptions: ImageLayerOptions): ImageLayer {
if (this.tokenKey) {
layerOptions.tokenKey = this.tokenKey;
}

return new ImageLayer(layerOptions);
return new ImageLayer(layerOptions, this.authInterceptor);
}

private createTileLayer(layerOptions: TileLayerOptions): TileLayer {
Expand Down
14 changes: 7 additions & 7 deletions packages/geo/src/lib/layer/shared/layers/image-layer.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import olLayerImage from 'ol/layer/Image';
import olSourceImage from 'ol/source/Image';

import { AuthInterceptor } from '@igo2/auth';

import { ImageWatcher } from '../../utils';
import { IgoMap } from '../../../map';

Expand All @@ -16,9 +18,8 @@ export class ImageLayer extends Layer {

private watcher: ImageWatcher;

constructor(options: ImageLayerOptions) {
super(options);

constructor(options: ImageLayerOptions, public authInterceptor?: AuthInterceptor) {
super(options, authInterceptor);
this.watcher = new ImageWatcher(this);
this.status$ = this.watcher.status$;
}
Expand All @@ -29,7 +30,7 @@ export class ImageLayer extends Layer {
});

const image = new olLayerImage(olOptions);
if (this.options.tokenKey) {
if (this.authInterceptor) {
(image.getSource() as any).setImageLoadFunction((tile, src) => {
this.customLoader(tile, src);
});
Expand All @@ -48,11 +49,10 @@ export class ImageLayer extends Layer {
}

private customLoader(tile, src) {
const xhr = new XMLHttpRequest();
let xhr = new XMLHttpRequest();
xhr.open('GET', src);

const token = localStorage.getItem(this.options.tokenKey);
xhr.setRequestHeader('Authorization', 'Bearer ' + token);
this.authInterceptor.interceptXhr(xhr, src);
xhr.responseType = 'arraybuffer';

xhr.onload = function() {
Expand Down
3 changes: 2 additions & 1 deletion packages/geo/src/lib/layer/shared/layers/layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import olLayer from 'ol/layer/Layer';
import { DataSource, Legend } from '../../../datasource';
import { IgoMap } from '../../../map';

import { AuthInterceptor } from '@igo2/auth';
import { SubjectStatus } from '@igo2/utils';
import { LayerOptions } from './layer.interface';

Expand Down Expand Up @@ -102,7 +103,7 @@ export abstract class Layer {
return this.options.showInLayerList !== false;
}

constructor(public options: LayerOptions) {
constructor(public options: LayerOptions, protected authInterceptor?: AuthInterceptor) {
this.dataSource = options.source;

this.ol = this.createOlLayer();
Expand Down
2 changes: 1 addition & 1 deletion proxy.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"changeOrigin": true
},
"/apis/": {
"target": "https://testgeoegl.msp.gouv.qc.ca",
"target": "https://geoegl.msp.gouv.qc.ca",
"secure": false,
"changeOrigin": true
},
Expand Down

0 comments on commit fa94a3f

Please sign in to comment.