Skip to content

Commit

Permalink
feat(auth): add new isAuthenticatedOrRefresh method, update `NbAuth…
Browse files Browse the repository at this point in the history
…JWTInterceptor` to refresh the token (#649)

BREAKING CHANGE:
`NbAuthJWTInterceptor` now always tries to refresh the token.
Urls for token sending can be filtered using a filter function:
```
{ provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: (req) => filter(req)},
```
  • Loading branch information
alain-charles authored and nnixaa committed Aug 22, 2018
1 parent efa16ef commit c8e8964
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 36 deletions.
8 changes: 7 additions & 1 deletion src/framework/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injector, ModuleWithProviders, NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { HttpClientModule, HttpRequest } from '@angular/common/http';

import {
NbAlertModule,
Expand Down Expand Up @@ -36,6 +36,7 @@ import {
NB_AUTH_INTERCEPTOR_HEADER,
NB_AUTH_OPTIONS,
NB_AUTH_STRATEGIES,
NB_AUTH_TOKEN_INTERCEPTOR_FILTER,
NB_AUTH_TOKENS,
NB_AUTH_USER_OPTIONS,
NbAuthOptions,
Expand Down Expand Up @@ -79,6 +80,10 @@ export function nbOptionsFactory(options) {
return deepExtend(defaultAuthOptions, options);
}

export function nbNoOpInterceptorFilter(req: HttpRequest<any>): boolean {
return true;
}

@NgModule({
imports: [
CommonModule,
Expand Down Expand Up @@ -122,6 +127,7 @@ export class NbAuthModule {
{ provide: NB_AUTH_TOKENS, useFactory: nbTokensFactory, deps: [NB_AUTH_STRATEGIES] },
{ provide: NB_AUTH_FALLBACK_TOKEN, useValue: NbAuthSimpleToken },
{ provide: NB_AUTH_INTERCEPTOR_HEADER, useValue: 'Authorization' },
{ provide: NB_AUTH_TOKEN_INTERCEPTOR_FILTER, useValue: nbNoOpInterceptorFilter },
{ provide: NbTokenStorage, useClass: NbTokenLocalStorage },
NbAuthTokenParceler,
NbAuthService,
Expand Down
6 changes: 5 additions & 1 deletion src/framework/auth/auth.options.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InjectionToken } from '@angular/core';
import { HttpRequest } from '@angular/common/http';
import { NbAuthStrategy, NbAuthStrategyOptions } from './strategies';
import { NbAuthToken, NbAuthTokenClass } from './services/token/token';

Expand Down Expand Up @@ -88,4 +89,7 @@ export const NB_AUTH_OPTIONS = new InjectionToken<NbAuthOptions>('Nebular Auth O
export const NB_AUTH_USER_OPTIONS = new InjectionToken<NbAuthOptions>('Nebular User Auth Options');
export const NB_AUTH_STRATEGIES = new InjectionToken<NbAuthStrategies>('Nebular Auth Strategies');
export const NB_AUTH_TOKENS = new InjectionToken<NbAuthTokenClass<NbAuthToken>[]>('Nebular Auth Tokens');
export const NB_AUTH_INTERCEPTOR_HEADER = new InjectionToken<NbAuthStrategies>('Nebular Simple Interceptor Header');
export const NB_AUTH_INTERCEPTOR_HEADER = new InjectionToken<string>('Nebular Simple Interceptor Header');
export const NB_AUTH_TOKEN_INTERCEPTOR_FILTER =
new InjectionToken<(req: HttpRequest<any>) => boolean>('Nebular Interceptor Filter');

30 changes: 28 additions & 2 deletions src/framework/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,40 @@ export class NbAuthService {
}

/**
* Returns true if auth token is presented in the token storage
* @returns {Observable<any>}
* Returns true if auth token is present in the token storage
* @returns {Observable<boolean>}
*/
isAuthenticated(): Observable<boolean> {
return this.getToken()
.pipe(map((token: NbAuthToken) => token.isValid()));
}

/**
* Returns true if valid auth token is present in the token storage.
* If not, calls the strategy refreshToken, and returns isAuthenticated() if success, false otherwise
* @returns {Observable<boolean>}
*/
isAuthenticatedOrRefresh(): Observable<boolean> {
return this.getToken()
.pipe(
switchMap(token => {
if (!token.isValid()) {
return this.refreshToken(token.getOwnerStrategyName(), token)
.pipe(
switchMap(res => {
if (res.isSuccess()) {
return this.isAuthenticated();
} else {
return observableOf(false);
}
}),
)
} else {
return observableOf(token.isValid());
}
}));
}

/**
* Returns tokens stream
* @returns {Observable<NbAuthSimpleToken>}
Expand Down
82 changes: 69 additions & 13 deletions src/framework/auth/services/auth.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('auth-service', () => {
let tokenService: NbTokenService;
let dummyAuthStrategy: NbDummyAuthStrategy;
const testTokenValue = 'test-token';
const ownerStrategyName = 'strategy';
const ownerStrategyName = 'dummy';


const resp401 = new HttpResponse<Object>({body: {}, status: 401});
Expand Down Expand Up @@ -143,6 +143,62 @@ describe('auth-service', () => {
},
);

it('isAuthenticatedOrRefresh, token valid, strategy refreshToken not called, returns true', (done) => {
const spy = spyOn(dummyAuthStrategy, 'refreshToken')

spyOn(tokenService, 'get')
.and
.returnValue(observableOf(testToken));

authService.isAuthenticatedOrRefresh()
.pipe(first())
.subscribe((isAuth: boolean) => {
expect(spy).not.toHaveBeenCalled();
expect(isAuth).toBeTruthy();
done();
});
},
);

it('isAuthenticatedOrRefresh, token invalid, strategy refreshToken called, returns true', (done) => {

const spy = spyOn(dummyAuthStrategy, 'refreshToken')
.and
.returnValue(observableOf(successResult));

spyOn(tokenService, 'get')
.and
.returnValues(observableOf(emptyToken), observableOf(testToken));

authService.isAuthenticatedOrRefresh()
.pipe(first())
.subscribe((isAuth: boolean) => {
expect(spy).toHaveBeenCalled();
expect(isAuth).toBeTruthy();
done();
});
},
);

it('isAuthenticatedOrRefresh, token invalid, strategy refreshToken called, returns false', (done) => {
const spy = spyOn(dummyAuthStrategy, 'refreshToken')
.and
.returnValue(observableOf(failResult));

spyOn(tokenService, 'get')
.and
.returnValues(observableOf(emptyToken), observableOf(emptyToken));

authService.isAuthenticatedOrRefresh()
.pipe(first())
.subscribe((isAuth: boolean) => {
expect(spy).toHaveBeenCalled();
expect(isAuth).toBeFalsy();
done();
});
},
);

it('onTokenChange return correct stream and gets test token', (done) => {
const spy = spyOn(tokenService, 'tokenChange')
.and
Expand All @@ -166,7 +222,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.authenticate('dummy').subscribe((authRes: NbAuthResult) => {
authService.authenticate(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();
expect(authRes.isFailure()).toBeTruthy();
expect(authRes.isSuccess()).toBeFalsy();
Expand All @@ -193,7 +249,7 @@ describe('auth-service', () => {
.returnValue(observableOf(null));


authService.authenticate('dummy').subscribe((authRes: NbAuthResult) => {
authService.authenticate(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategySpy).toHaveBeenCalled();
expect(tokenServiceSetSpy).toHaveBeenCalled();

Expand All @@ -218,7 +274,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.register('dummy').subscribe((authRes: NbAuthResult) => {
authService.register(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();
expect(authRes.isFailure()).toBeTruthy();
expect(authRes.isSuccess()).toBeFalsy();
Expand All @@ -244,7 +300,7 @@ describe('auth-service', () => {
.and
.returnValue(observableOf(null));

authService.register('dummy').subscribe((authRes: NbAuthResult) => {
authService.register(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategySpy).toHaveBeenCalled();
expect(tokenServiceSetSpy).toHaveBeenCalled();

Expand All @@ -268,7 +324,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.logout('dummy').subscribe((authRes: NbAuthResult) => {
authService.logout(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeTruthy();
Expand All @@ -292,7 +348,7 @@ describe('auth-service', () => {
));
const tokenServiceClearSpy = spyOn(tokenService, 'clear').and.returnValue(observableOf('STUB'));

authService.logout('dummy').subscribe((authRes: NbAuthResult) => {
authService.logout(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategyLogoutSpy).toHaveBeenCalled();
expect(tokenServiceClearSpy).toHaveBeenCalled();

Expand All @@ -316,7 +372,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.requestPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.requestPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeTruthy();
Expand All @@ -339,7 +395,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.requestPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.requestPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategyLogoutSpy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeFalsy();
Expand All @@ -362,7 +418,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.resetPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.resetPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeTruthy();
Expand All @@ -385,7 +441,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.resetPassword('dummy').subscribe((authRes: NbAuthResult) => {
authService.resetPassword(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategyLogoutSpy).toHaveBeenCalled();

expect(authRes.isFailure()).toBeFalsy();
Expand All @@ -408,7 +464,7 @@ describe('auth-service', () => {
delay(1000),
));

authService.refreshToken('dummy').subscribe((authRes: NbAuthResult) => {
authService.refreshToken(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(spy).toHaveBeenCalled();
expect(authRes.isFailure()).toBeTruthy();
expect(authRes.isSuccess()).toBeFalsy();
Expand All @@ -434,7 +490,7 @@ describe('auth-service', () => {
.and
.returnValue(observableOf(null));

authService.refreshToken('dummy').subscribe((authRes: NbAuthResult) => {
authService.refreshToken(ownerStrategyName).subscribe((authRes: NbAuthResult) => {
expect(strategySpy).toHaveBeenCalled();
expect(tokenServiceSetSpy).toHaveBeenCalled();

Expand Down
Loading

0 comments on commit c8e8964

Please sign in to comment.