diff --git a/src/abpHttpInterceptor.ts b/src/abpHttpInterceptor.ts index 8a54da7..131a202 100644 --- a/src/abpHttpInterceptor.ts +++ b/src/abpHttpInterceptor.ts @@ -1,11 +1,12 @@ -import { Injectable } from '@angular/core'; -import { Observable, Subject, of } from 'rxjs'; +import { Injectable, Injector } from '@angular/core'; +import { Observable, Subject, of, BehaviorSubject } from 'rxjs'; import { MessageService } from './message/message.service'; import { LogService } from './log/log.service'; import { TokenService } from './auth/token.service'; import { UtilsService } from './utils/utils.service'; - import { HttpClient, HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http'; +import { switchMap, filter, take, catchError } from 'rxjs/operators'; +import { throwError } from 'rxjs/internal/observable/throwError'; export interface IValidationErrorInfo { @@ -43,6 +44,14 @@ export interface IAjaxResponse { } +@Injectable() +export abstract class RefreshTokenService { + /** + * Try to authenticate with refresh token and return if auth succeed + */ + abstract tryAuthWithRefreshToken(): Observable; +} + @Injectable() export class AbpHttpConfiguration { @@ -128,9 +137,9 @@ export class AbpHttpConfiguration { handleAbpResponse(response: HttpResponse, ajaxResponse: IAjaxResponse): HttpResponse { var newResponse: HttpResponse; - + if (ajaxResponse.success) { - + newResponse = response.clone({ body: ajaxResponse.result }); @@ -160,7 +169,7 @@ export class AbpHttpConfiguration { } getAbpAjaxResponseOrNull(response: HttpResponse): IAjaxResponse | null { - if(!response || !response.headers) { + if (!response || !response.headers) { return null; } @@ -174,7 +183,7 @@ export class AbpHttpConfiguration { this._logService.warn('Content-Type is not application/json: ' + contentType); return null; } - + var responseObj = JSON.parse(JSON.stringify(response.body)); if (!responseObj.__abp) { return null; @@ -198,12 +207,12 @@ export class AbpHttpConfiguration { observer.next(""); observer.complete(); } else { - let reader = new FileReader(); - reader.onload = function() { + let reader = new FileReader(); + reader.onload = function () { observer.next(this.result); observer.complete(); } - reader.readAsText(blob); + reader.readAsText(blob); } }); } @@ -211,37 +220,91 @@ export class AbpHttpConfiguration { @Injectable() export class AbpHttpInterceptor implements HttpInterceptor { - + protected configuration: AbpHttpConfiguration; private _tokenService: TokenService = new TokenService(); private _utilsService: UtilsService = new UtilsService(); private _logService: LogService = new LogService(); - constructor(configuration: AbpHttpConfiguration) { + constructor(configuration: AbpHttpConfiguration, + private _injector: Injector) { this.configuration = configuration; } - intercept(request: HttpRequest, next: HttpHandler): Observable> { - - var interceptObservable = new Subject>(); - var modifiedRequest = this.normalizeRequestHeaders(request); - - next.handle(modifiedRequest) - .subscribe((event: HttpEvent) => { - this.handleSuccessResponse(event, interceptObservable ); - }, (error: any) => { - return this.handleErrorResponse(error, interceptObservable); - }); + intercept(request: HttpRequest, next: HttpHandler): Observable> { - return interceptObservable; - } + var interceptObservable = new Subject>(); + var modifiedRequest = this.normalizeRequestHeaders(request); + next.handle(modifiedRequest) + .pipe( + catchError(error => { + if (error instanceof HttpErrorResponse && error.status === 401) { + return this.tryAuthWithRefreshToken(request, next, error); + } else { + return throwError(error); + } + }) + ).subscribe( + (event: HttpEvent) => { + this.handleSuccessResponse(event, interceptObservable); + }, + (error: any) => { + return this.handleErrorResponse(error, interceptObservable); + } + ); - protected normalizeRequestHeaders(request: HttpRequest):HttpRequest { + return interceptObservable; + } + + protected tryGetRefreshTokenService(): Observable { + try { + var _refreshTokenService = this._injector.get(RefreshTokenService); + + if (_refreshTokenService) { + return _refreshTokenService.tryAuthWithRefreshToken(); + } + } catch (error) { + return of(false); + } + return of(false); + } + + private isRefreshing = false; + private refreshTokenSubject: BehaviorSubject = new BehaviorSubject(null); + + private tryAuthWithRefreshToken(request: HttpRequest, next: HttpHandler, error: any) { + if (!this.isRefreshing) { + this.isRefreshing = true; + this.refreshTokenSubject.next(null); + + return this.tryGetRefreshTokenService().pipe( + switchMap((authResult: boolean) => { + this.isRefreshing = false; + if (authResult) { + this.refreshTokenSubject.next(authResult); + let modifiedRequest = this.normalizeRequestHeaders(request); + return next.handle(modifiedRequest); + } else { + return throwError(error); + } + })); + } else { + return this.refreshTokenSubject.pipe( + filter(token => token != null), + take(1), + switchMap(authResult => { + let modifiedRequest = this.normalizeRequestHeaders(request); + return next.handle(modifiedRequest); + })); + } + } + + protected normalizeRequestHeaders(request: HttpRequest): HttpRequest { var modifiedHeaders = new HttpHeaders(); - modifiedHeaders = request.headers.set("Pragma","no-cache") - .set("Cache-Control","no-cache") - .set("Expires", "Sat, 01 Jan 2000 00:00:00 GMT"); - + modifiedHeaders = request.headers.set("Pragma", "no-cache") + .set("Cache-Control", "no-cache") + .set("Expires", "Sat, 01 Jan 2000 00:00:00 GMT"); + modifiedHeaders = this.addXRequestedWithHeader(modifiedHeaders); modifiedHeaders = this.addAuthorizationHeaders(modifiedHeaders); modifiedHeaders = this.addAspNetCoreCultureHeader(modifiedHeaders); @@ -253,7 +316,7 @@ export class AbpHttpInterceptor implements HttpInterceptor { }); } - protected addXRequestedWithHeader(headers:HttpHeaders):HttpHeaders { + protected addXRequestedWithHeader(headers: HttpHeaders): HttpHeaders { if (headers) { headers = headers.set('X-Requested-With', 'XMLHttpRequest'); } @@ -261,7 +324,7 @@ export class AbpHttpInterceptor implements HttpInterceptor { return headers; } - protected addAspNetCoreCultureHeader(headers:HttpHeaders):HttpHeaders { + protected addAspNetCoreCultureHeader(headers: HttpHeaders): HttpHeaders { let cookieLangValue = this._utilsService.getCookieValue("Abp.Localization.CultureName"); if (cookieLangValue && headers && !headers.has('.AspNetCore.Culture')) { headers = headers.set('.AspNetCore.Culture', cookieLangValue); @@ -270,7 +333,7 @@ export class AbpHttpInterceptor implements HttpInterceptor { return headers; } - protected addAcceptLanguageHeader(headers:HttpHeaders):HttpHeaders { + protected addAcceptLanguageHeader(headers: HttpHeaders): HttpHeaders { let cookieLangValue = this._utilsService.getCookieValue("Abp.Localization.CultureName"); if (cookieLangValue && headers && !headers.has('Accept-Language')) { headers = headers.set('Accept-Language', cookieLangValue); @@ -279,7 +342,7 @@ export class AbpHttpInterceptor implements HttpInterceptor { return headers; } - protected addTenantIdHeader(headers:HttpHeaders):HttpHeaders { + protected addTenantIdHeader(headers: HttpHeaders): HttpHeaders { let cookieTenantIdValue = this._utilsService.getCookieValue(abp.multiTenancy.tenantIdCookieName); if (cookieTenantIdValue && headers && !headers.has(abp.multiTenancy.tenantIdCookieName)) { headers = headers.set(abp.multiTenancy.tenantIdCookieName, cookieTenantIdValue); @@ -288,8 +351,8 @@ export class AbpHttpInterceptor implements HttpInterceptor { return headers; } - protected addAuthorizationHeaders(headers:HttpHeaders): HttpHeaders { - let authorizationHeaders = headers ? headers.getAll('Authorization'): null; + protected addAuthorizationHeaders(headers: HttpHeaders): HttpHeaders { + let authorizationHeaders = headers ? headers.getAll('Authorization') : null; if (!authorizationHeaders) { authorizationHeaders = []; } @@ -304,22 +367,22 @@ export class AbpHttpInterceptor implements HttpInterceptor { return headers; } - protected handleSuccessResponse(event: HttpEvent, interceptObservable: Subject>): void{ + protected handleSuccessResponse(event: HttpEvent, interceptObservable: Subject>): void { var self = this; if (event instanceof HttpResponse) { - if (event.body instanceof Blob && event.body.type && event.body.type.indexOf("application/json") >= 0){ + if (event.body instanceof Blob && event.body.type && event.body.type.indexOf("application/json") >= 0) { var clonedResponse = event.clone(); - + self.configuration.blobToText(event.body).subscribe(json => { - const responseBody = json == "null" ? {}: JSON.parse(json); - + const responseBody = json == "null" ? {} : JSON.parse(json); + var modifiedResponse = self.configuration.handleResponse(event.clone({ body: responseBody })); - + interceptObservable.next(modifiedResponse.clone({ - body: new Blob([JSON.stringify(modifiedResponse.body)], {type : 'application/json'}) + body: new Blob([JSON.stringify(modifiedResponse.body)], { type: 'application/json' }) })); interceptObservable.complete(); @@ -336,14 +399,14 @@ export class AbpHttpInterceptor implements HttpInterceptor { protected handleErrorResponse(error: any, interceptObservable: Subject>): Observable { var errorObservable = new Subject(); - if(!(error.error instanceof Blob)){ + if (!(error.error instanceof Blob)) { interceptObservable.error(error); interceptObservable.complete(); return of({}); } this.configuration.blobToText(error.error).subscribe(json => { - const errorBody = (json == "" || json == "null") ? {}: JSON.parse(json); + const errorBody = (json == "" || json == "null") ? {} : JSON.parse(json); const errorResponse = new HttpResponse({ headers: error.headers, status: error.status, @@ -351,7 +414,7 @@ export class AbpHttpInterceptor implements HttpInterceptor { }); var ajaxResponse = this.configuration.getAbpAjaxResponseOrNull(errorResponse); - + if (ajaxResponse != null) { this.configuration.handleAbpResponse(errorResponse, ajaxResponse); } else { @@ -359,11 +422,11 @@ export class AbpHttpInterceptor implements HttpInterceptor { } errorObservable.complete(); - + interceptObservable.error(error); interceptObservable.complete(); }); - + return errorObservable; } diff --git a/src/auth/token.service.ts b/src/auth/token.service.ts index 3ca2404..3c37a3a 100644 --- a/src/auth/token.service.ts +++ b/src/auth/token.service.ts @@ -19,4 +19,20 @@ export class TokenService { abp.auth.setToken(authToken, expireDate); } + //refresh token + getRefreshToken(): string { + return abp.auth.getRefreshToken(); + } + + getRefreshTokenCookieName(): string { + return abp.auth.refreshTokenCookieName; + } + + clearRefreshToken(): void { + abp.auth.clearRefreshToken(); + } + + setRefreshToken(refreshToken: string, expireDate?: Date): void { + abp.auth.setRefreshToken(refreshToken, expireDate); + } } \ No newline at end of file