Skip to content

Commit

Permalink
feat(auth)!: support asynchronous unauthenticated hooks (#2513)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `DAFF_AUTH_UNAUTHENTICATED_HOOKS` contains functions that now must return an observable
  • Loading branch information
griest024 committed Jul 26, 2023
1 parent 49f6cf6 commit 2100b24
Show file tree
Hide file tree
Showing 17 changed files with 283 additions and 138 deletions.
81 changes: 0 additions & 81 deletions libs/auth/routing/src/effects/redirect.effects.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,7 @@ import { Observable } from 'rxjs';

import { provideDaffAuthRoutingConfig } from '@daffodil/auth/routing';
import {
DaffAuthCheckFailure,
DaffAuthGuardLogout,
DaffAuthLoginSuccess,
DaffAuthLogoutSuccess,
DaffAuthRegisterSuccess,
DaffResetPasswordSuccess,
} from '@daffodil/auth/state';
Expand Down Expand Up @@ -155,82 +152,4 @@ describe('@daffodil/auth/routing | DaffAuthRedirectEffects', () => {
});
});
});

describe('when DaffAuthLogoutSuccess is dispatched', () => {
beforeEach(() => {
actions$ = hot('--a', { a: new DaffAuthLogoutSuccess() });
});

it('should navigate to the login page', () => {
const expected = cold('---');

expect(effects.redirectAfterLogout$).toBeObservable(expected);
expect(routerNavigateSpy).toHaveBeenCalledWith(logoutRedirectUrl);
});

describe('and when the redirect QP is set', () => {
beforeEach(() => {
qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl);
});

it('should navigate to the redirect URL', () => {
const expected = cold('---');

expect(effects.redirectAfterLogout$).toBeObservable(expected);
expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl);
});
});
});

describe('when DaffAuthCheckFailure is dispatched', () => {
beforeEach(() => {
actions$ = hot('--a', { a: new DaffAuthCheckFailure(null) });
});

it('should navigate to the home page', () => {
const expected = cold('---');

expect(effects.redirectAfterExpiration$).toBeObservable(expected);
expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl);
});

describe('and when the redirect QP is set', () => {
beforeEach(() => {
qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl);
});

it('should navigate to the redirect URL', () => {
const expected = cold('---');

expect(effects.redirectAfterExpiration$).toBeObservable(expected);
expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl);
});
});
});

describe('when DaffAuthGuardLogout is dispatched', () => {
beforeEach(() => {
actions$ = hot('--a', { a: new DaffAuthGuardLogout(null) });
});

it('should navigate to the home page', () => {
const expected = cold('---');

expect(effects.redirectAfterExpiration$).toBeObservable(expected);
expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl);
});

describe('and when the redirect QP is set', () => {
beforeEach(() => {
qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl);
});

it('should navigate to the redirect URL', () => {
const expected = cold('---');

expect(effects.redirectAfterExpiration$).toBeObservable(expected);
expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl);
});
});
});
});
19 changes: 0 additions & 19 deletions libs/auth/routing/src/effects/redirect.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,4 @@ export class DaffAuthRedirectEffects {
}),
switchMap(() => EMPTY),
), { dispatch: false });

redirectAfterLogout$ = createEffect(() => this.actions$.pipe(
ofType(DaffAuthLoginActionTypes.LogoutSuccessAction),
tap((action) => {
this.router.navigateByUrl(this.route.snapshot.queryParamMap.get(this.config.redirectUrlParam) || this.config.logoutRedirectPath);
}),
switchMap(() => EMPTY),
), { dispatch: false });

redirectAfterExpiration$ = createEffect(() => this.actions$.pipe(
ofType(
DaffAuthActionTypes.AuthCheckFailureAction,
DaffAuthActionTypes.AuthGuardLogoutAction,
),
tap(() => {
this.router.navigateByUrl(this.route.snapshot.queryParamMap.get(this.config.redirectUrlParam) || this.config.tokenExpirationRedirectPath);
}),
switchMap(() => EMPTY),
), { dispatch: false });
}
1 change: 1 addition & 0 deletions libs/auth/routing/src/helpers/public_api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { daffAuthRoutingRedirectUnauthenticatedHookFactory } from './redirect-unauthenticated-hook-factory';
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { TestBed } from '@angular/core/testing';
import {
ActivatedRoute,
ParamMap,
Router,
} from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable } from 'rxjs';

import { DAFF_AUTH_ROUTING_CONFIG_DEFAULT } from '@daffodil/auth/routing';
import {
DaffAuthActionTypes,
DaffAuthLoginActionTypes,
DaffAuthUnauthenticatedHook,
} from '@daffodil/auth/state';

import { daffAuthRoutingRedirectUnauthenticatedHookFactory } from './redirect-unauthenticated-hook-factory';


describe('@daffodil/auth/routing | daffAuthRoutingRedirectUnauthenticatedHookFactory', () => {
let router: Router;
let route: ActivatedRoute;

let hook: DaffAuthUnauthenticatedHook;
let result: Observable<unknown>;

let routerNavigateSpy: jasmine.Spy<Router['navigateByUrl']>;
let qpSpy: jasmine.SpyObj<ParamMap>;
let logoutRedirectUrl: string;
let expirationRedirectUrl: string;
let redirectUrl: string;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule,
],
});

router = TestBed.inject(Router);
route = TestBed.inject(ActivatedRoute);

logoutRedirectUrl = '/login';
expirationRedirectUrl = '/';
redirectUrl = '/redirect';

qpSpy = jasmine.createSpyObj('ParamMap', ['get']);
routerNavigateSpy = spyOn(router, 'navigateByUrl');
routerNavigateSpy.and.returnValue(new Promise((resolve) => resolve(true)));

hook = daffAuthRoutingRedirectUnauthenticatedHookFactory(
router,
<any>{
...route,
snapshot: {
...route.snapshot,
queryParamMap: qpSpy,
},
},
{
...DAFF_AUTH_ROUTING_CONFIG_DEFAULT,
logoutRedirectPath: logoutRedirectUrl,
tokenExpirationRedirectPath: expirationRedirectUrl,
},
);
});

describe('when the hook is triggered with DaffAuthLogoutSuccess', () => {
beforeEach(() => {
result = hook(DaffAuthLoginActionTypes.LogoutSuccessAction);
});

it('should navigate to the login page', (done) => {
result.subscribe(() => {
expect(routerNavigateSpy).toHaveBeenCalledWith(logoutRedirectUrl);
done();
});
});

describe('and when the redirect QP is set', () => {
beforeEach(() => {
qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl);
result = hook(DaffAuthLoginActionTypes.LogoutSuccessAction);
});

it('should navigate to the redirect URL', (done) => {
result.subscribe(() => {
expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl);
done();
});
});
});
});

describe('when the hook is triggered with DaffAuthCheckFailure', () => {
beforeEach(() => {
result = hook(DaffAuthActionTypes.AuthCheckFailureAction);
});

it('should navigate to the home page', (done) => {
result.subscribe(() => {
expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl);
done();
});
});

describe('and when the redirect QP is set', () => {
beforeEach(() => {
qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl);
result = hook(DaffAuthActionTypes.AuthCheckFailureAction);
});

it('should navigate to the redirect URL', (done) => {
result.subscribe(() => {
expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl);
done();
});
});
});
});

describe('when the hook is triggered with DaffAuthGuardLogout', () => {
beforeEach(() => {
result = hook(DaffAuthActionTypes.AuthGuardLogoutAction);
});

it('should navigate to the home page', (done) => {
result.subscribe(() => {
expect(routerNavigateSpy).toHaveBeenCalledWith(expirationRedirectUrl);
done();
});
});

describe('and when the redirect QP is set', () => {
beforeEach(() => {
qpSpy.get.withArgs('redirect').and.returnValue(redirectUrl);
result = hook(DaffAuthActionTypes.AuthGuardLogoutAction);
});

it('should navigate to the redirect URL', (done) => {
result.subscribe(() => {
expect(routerNavigateSpy).toHaveBeenCalledWith(redirectUrl);
done();
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
ActivatedRoute,
Router,
} from '@angular/router';
import {
from,
of,
} from 'rxjs';

import {
DaffAuthActionTypes,
DaffAuthLoginActionTypes,
DaffAuthUnauthenticatedHook,
} from '@daffodil/auth/state';

import { DaffAuthRoutingConfig } from '../config/public_api';

export function daffAuthRoutingRedirectUnauthenticatedHookFactory(router: Router, route: ActivatedRoute, config: DaffAuthRoutingConfig): DaffAuthUnauthenticatedHook {
return (trigger) => {
switch (trigger) {
case DaffAuthLoginActionTypes.LogoutSuccessAction:
return from(router.navigateByUrl(route.snapshot.queryParamMap.get(config.redirectUrlParam) || config.logoutRedirectPath));

case DaffAuthActionTypes.AuthCheckFailureAction:
case DaffAuthActionTypes.AuthGuardLogoutAction:
return from(router.navigateByUrl(route.snapshot.queryParamMap.get(config.redirectUrlParam) || config.tokenExpirationRedirectPath));

default:
return of(null);
}
};
}
1 change: 1 addition & 0 deletions libs/auth/routing/src/public_api.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './guards/public_api';
export * from './config/public_api';
export * from './helpers/public_api';

export * from './module';
export * from './redirect.module';
35 changes: 34 additions & 1 deletion libs/auth/routing/src/redirect.module.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
import { NgModule } from '@angular/core';
import {
NgModule,
inject,
} from '@angular/core';
import {
ActivatedRoute,
Router,
} from '@angular/router';
import { EffectsModule } from '@ngrx/effects';
import {
from,
of,
} from 'rxjs';

import {
DAFF_AUTH_UNAUTHENTICATED_HOOKS,
DaffAuthActionTypes,
DaffAuthLoginActionTypes,
DaffAuthUnauthenticatedHook,
} from '@daffodil/auth/state';

import { DAFF_AUTH_ROUTING_CONFIG } from './config/public_api';
import { DaffAuthRedirectEffects } from './effects/redirect.effects';
import { daffAuthRoutingRedirectUnauthenticatedHookFactory } from './helpers/public_api';


@NgModule({
imports: [
EffectsModule.forFeature([
DaffAuthRedirectEffects,
]),
],
providers: [
{
provide: DAFF_AUTH_UNAUTHENTICATED_HOOKS,
multi: true,
useFactory: () =>
daffAuthRoutingRedirectUnauthenticatedHookFactory(
inject(Router),
inject(ActivatedRoute),
inject(DAFF_AUTH_ROUTING_CONFIG),
),
},
],
})
export class DaffAuthRoutingRedirectModule {}
4 changes: 4 additions & 0 deletions libs/auth/state/src/actions/auth.actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export enum DaffAuthActionTypes {
*/
export class DaffAuthResetToUnauthenticated implements Action {
readonly type = DaffAuthActionTypes.ResetToUnauthenticatedAction;

constructor(
public reason: Action['type'],
) {}
}

/*
Expand Down
Loading

0 comments on commit 2100b24

Please sign in to comment.