From b7429f8c87a0b5d0be716afda515e4e3c1f63256 Mon Sep 17 00:00:00 2001 From: Peter Ashwood Date: Wed, 15 Apr 2026 15:28:53 +0000 Subject: [PATCH 1/2] feat(auth): add SSR handling and redirect behavior to `DaffAuthResetPasswordGuard` --- libs/auth/routing/src/guards/public_api.ts | 14 ++++++- .../reset-password-guard-redirect.token.ts | 12 ++++++ .../src/guards/reset-password.guard.spec.ts | 28 ++++++------- .../src/guards/reset-password.guard.ts | 40 +++++++++++-------- 4 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts diff --git a/libs/auth/routing/src/guards/public_api.ts b/libs/auth/routing/src/guards/public_api.ts index f1f5ec2f14..acd36c9363 100644 --- a/libs/auth/routing/src/guards/public_api.ts +++ b/libs/auth/routing/src/guards/public_api.ts @@ -2,5 +2,15 @@ export { MemberOnlyGuard } from './member-only.guard'; export { GuestOnlyGuard } from './guest-only.guard'; export { DaffAuthResetPasswordGuard } from './reset-password.guard'; -export { DaffAuthGuestOnlyGuardRedirectUrl } from './guest-only-guard-redirect.token'; -export { DaffAuthMemberOnlyGuardRedirectUrl } from './member-only-guard-redirect.token'; +export { + DaffAuthGuestOnlyGuardRedirectUrl, + provideDaffAuthGuestOnlyGuardRedirectUrl, +} from './guest-only-guard-redirect.token'; +export { + DaffAuthMemberOnlyGuardRedirectUrl, + provideDaffAuthMemberOnlyGuardRedirectUrl, +} from './member-only-guard-redirect.token'; +export { + DaffAuthResetPasswordGuardRedirectUrl, + provideDaffAuthResetPasswordGuardRedirectUrl, +} from './reset-password-guard-redirect.token'; diff --git a/libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts b/libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts new file mode 100644 index 0000000000..7c2f5fb00d --- /dev/null +++ b/libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts @@ -0,0 +1,12 @@ +import { createSingleInjectionToken } from '@daffodil/core'; + +export const { + token: DaffAuthResetPasswordGuardRedirectUrl, + /** + * Provider function for {@link DaffAuthResetPasswordGuardRedirectUrl}. + */ + provider: provideDaffAuthResetPasswordGuardRedirectUrl, +} = createSingleInjectionToken( + 'DaffAuthResetPasswordGuardRedirectUrl', + { factory: () => '/' }, +); diff --git a/libs/auth/routing/src/guards/reset-password.guard.spec.ts b/libs/auth/routing/src/guards/reset-password.guard.spec.ts index 40470329de..b0c894f986 100644 --- a/libs/auth/routing/src/guards/reset-password.guard.spec.ts +++ b/libs/auth/routing/src/guards/reset-password.guard.spec.ts @@ -7,11 +7,14 @@ import { import { ActivatedRoute, Router, + UrlTree, } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; -import { Observable } from 'rxjs'; -import { DAFF_AUTH_ROUTING_CONFIG } from '@daffodil/auth/routing'; +import { + DAFF_AUTH_ROUTING_CONFIG, + provideDaffAuthResetPasswordGuardRedirectUrl, +} from '@daffodil/auth/routing'; import { DaffResetPasswordLanding } from '@daffodil/auth/state'; import { DaffAuthStateTestingModule, @@ -34,10 +37,12 @@ describe('@daffodil/auth/routing | DaffAuthResetPasswordGuard', () => { let param: string; let token: string; + let redirectUrl: string; beforeEach(() => { param = 'token'; token = '290384runfei9usnrg0ew9rgm'; + redirectUrl = 'redirectUrl'; TestBed.configureTestingModule({ imports: [ @@ -60,6 +65,7 @@ describe('@daffodil/auth/routing | DaffAuthResetPasswordGuard', () => { resetPasswordTokenParam: param, }, }, + provideDaffAuthResetPasswordGuardRedirectUrl(redirectUrl), ], }); @@ -71,7 +77,7 @@ describe('@daffodil/auth/routing | DaffAuthResetPasswordGuard', () => { }); describe('canActivate | checking if the route can be activated', () => { - let result: Observable; + let result: boolean | UrlTree; describe('when there is a token set to the param', () => { beforeEach(fakeAsync(() => { @@ -80,15 +86,11 @@ describe('@daffodil/auth/routing | DaffAuthResetPasswordGuard', () => { result = guard.canActivate(TestBed.inject(ActivatedRoute).snapshot); })); - it('should allow activation', done => { - result.subscribe(res => { - expect(res).toBeTrue(); - done(); - }); + it('should allow activation', () => { + expect(result).toBeTrue(); }); it('should dispatch DaffResetPasswordLanding with the token', () => { - result.subscribe(); expect(mockFacade.dispatch).toHaveBeenCalledWith(new DaffResetPasswordLanding(token)); }); }); @@ -100,15 +102,11 @@ describe('@daffodil/auth/routing | DaffAuthResetPasswordGuard', () => { result = guard.canActivate(TestBed.inject(ActivatedRoute).snapshot); })); - it('should not allow activation', done => { - result.subscribe(res => { - expect(res).toBeFalse(); - done(); - }); + it('should return the parsed redirect URL', () => { + expect(result.toString()).toEqual(`/${redirectUrl}`); }); it('should not dispatch DaffResetPasswordLanding with the token', () => { - result.subscribe(); expect(mockFacade.dispatch).not.toHaveBeenCalledWith(new DaffResetPasswordLanding(token)); }); }); diff --git a/libs/auth/routing/src/guards/reset-password.guard.ts b/libs/auth/routing/src/guards/reset-password.guard.ts index 51935b7d01..f6d49cf389 100644 --- a/libs/auth/routing/src/guards/reset-password.guard.ts +++ b/libs/auth/routing/src/guards/reset-password.guard.ts @@ -1,41 +1,47 @@ +import { isPlatformServer } from '@angular/common'; import { - Inject, + inject, Injectable, + PLATFORM_ID, } from '@angular/core'; -import { ActivatedRouteSnapshot } from '@angular/router'; import { - Observable, - of, -} from 'rxjs'; + ActivatedRouteSnapshot, + CanActivate, + Router, + UrlTree, +} from '@angular/router'; import { DaffAuthFacade, DaffResetPasswordLanding, } from '@daffodil/auth/state'; -import { - DaffAuthRoutingConfig, - DAFF_AUTH_ROUTING_CONFIG, -} from '../config/public_api'; +import { DaffAuthResetPasswordGuardRedirectUrl } from './reset-password-guard-redirect.token'; +import { DAFF_AUTH_ROUTING_CONFIG } from '../config/public_api'; @Injectable({ providedIn: 'any', }) -export class DaffAuthResetPasswordGuard { - constructor( - private facade: DaffAuthFacade, - @Inject(DAFF_AUTH_ROUTING_CONFIG) private config: DaffAuthRoutingConfig, - ) {} +export class DaffAuthResetPasswordGuard implements CanActivate { + readonly facade = inject(DaffAuthFacade); + readonly config = inject(DAFF_AUTH_ROUTING_CONFIG); + readonly platformId = inject(PLATFORM_ID); + readonly redirectUrl = inject(DaffAuthResetPasswordGuardRedirectUrl); + readonly router = inject(Router); + + canActivate(route: ActivatedRouteSnapshot): boolean | UrlTree { + if (isPlatformServer(this.platformId)) { + return true; + } - canActivate(route: ActivatedRouteSnapshot): Observable { const token = route.queryParamMap.get(this.config.resetPasswordTokenParam); if (!token) { - return of(false); + return this.router.parseUrl(this.redirectUrl); } this.facade.dispatch(new DaffResetPasswordLanding(token)); - return of(true); + return true; } } From dc7c9aeda6c16fcfb62f614c005dfc94fe6983e6 Mon Sep 17 00:00:00 2001 From: Peter Ashwood Date: Wed, 15 Apr 2026 16:07:17 +0000 Subject: [PATCH 2/2] move redirect url to config --- libs/auth/routing/src/config/default.ts | 1 + libs/auth/routing/src/config/interface.ts | 6 ++++++ libs/auth/routing/src/guards/public_api.ts | 4 ---- .../guards/reset-password-guard-redirect.token.ts | 12 ------------ .../routing/src/guards/reset-password.guard.spec.ts | 4 ++-- libs/auth/routing/src/guards/reset-password.guard.ts | 4 +--- 6 files changed, 10 insertions(+), 21 deletions(-) delete mode 100644 libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts diff --git a/libs/auth/routing/src/config/default.ts b/libs/auth/routing/src/config/default.ts index f3097b378c..b19d72ef36 100644 --- a/libs/auth/routing/src/config/default.ts +++ b/libs/auth/routing/src/config/default.ts @@ -6,4 +6,5 @@ export const DAFF_AUTH_ROUTING_CONFIG_DEFAULT: DaffAuthRoutingConfig = { authCompleteRedirectPath: '/', logoutRedirectPath: '/', tokenExpirationRedirectPath: '/', + resetPasswordRedirectPath: '/', }; diff --git a/libs/auth/routing/src/config/interface.ts b/libs/auth/routing/src/config/interface.ts index 9df9ae025a..794df2e396 100644 --- a/libs/auth/routing/src/config/interface.ts +++ b/libs/auth/routing/src/config/interface.ts @@ -30,4 +30,10 @@ export interface DaffAuthRoutingConfig { * Defaults to `'/'`. */ tokenExpirationRedirectPath: string; + + /** + * The path to which the user will be redirected when the reset password guard blocks activation. + * Defaults to `'/'`. + */ + resetPasswordRedirectPath: string; } diff --git a/libs/auth/routing/src/guards/public_api.ts b/libs/auth/routing/src/guards/public_api.ts index acd36c9363..f54d2c7a53 100644 --- a/libs/auth/routing/src/guards/public_api.ts +++ b/libs/auth/routing/src/guards/public_api.ts @@ -10,7 +10,3 @@ export { DaffAuthMemberOnlyGuardRedirectUrl, provideDaffAuthMemberOnlyGuardRedirectUrl, } from './member-only-guard-redirect.token'; -export { - DaffAuthResetPasswordGuardRedirectUrl, - provideDaffAuthResetPasswordGuardRedirectUrl, -} from './reset-password-guard-redirect.token'; diff --git a/libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts b/libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts deleted file mode 100644 index 7c2f5fb00d..0000000000 --- a/libs/auth/routing/src/guards/reset-password-guard-redirect.token.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { createSingleInjectionToken } from '@daffodil/core'; - -export const { - token: DaffAuthResetPasswordGuardRedirectUrl, - /** - * Provider function for {@link DaffAuthResetPasswordGuardRedirectUrl}. - */ - provider: provideDaffAuthResetPasswordGuardRedirectUrl, -} = createSingleInjectionToken( - 'DaffAuthResetPasswordGuardRedirectUrl', - { factory: () => '/' }, -); diff --git a/libs/auth/routing/src/guards/reset-password.guard.spec.ts b/libs/auth/routing/src/guards/reset-password.guard.spec.ts index b0c894f986..ca293cee0a 100644 --- a/libs/auth/routing/src/guards/reset-password.guard.spec.ts +++ b/libs/auth/routing/src/guards/reset-password.guard.spec.ts @@ -13,7 +13,7 @@ import { RouterTestingModule } from '@angular/router/testing'; import { DAFF_AUTH_ROUTING_CONFIG, - provideDaffAuthResetPasswordGuardRedirectUrl, + provideDaffAuthRoutingConfig, } from '@daffodil/auth/routing'; import { DaffResetPasswordLanding } from '@daffodil/auth/state'; import { @@ -65,7 +65,7 @@ describe('@daffodil/auth/routing | DaffAuthResetPasswordGuard', () => { resetPasswordTokenParam: param, }, }, - provideDaffAuthResetPasswordGuardRedirectUrl(redirectUrl), + provideDaffAuthRoutingConfig({ resetPasswordRedirectPath: redirectUrl }), ], }); diff --git a/libs/auth/routing/src/guards/reset-password.guard.ts b/libs/auth/routing/src/guards/reset-password.guard.ts index f6d49cf389..9e4accc8ff 100644 --- a/libs/auth/routing/src/guards/reset-password.guard.ts +++ b/libs/auth/routing/src/guards/reset-password.guard.ts @@ -16,7 +16,6 @@ import { DaffResetPasswordLanding, } from '@daffodil/auth/state'; -import { DaffAuthResetPasswordGuardRedirectUrl } from './reset-password-guard-redirect.token'; import { DAFF_AUTH_ROUTING_CONFIG } from '../config/public_api'; @Injectable({ @@ -26,7 +25,6 @@ export class DaffAuthResetPasswordGuard implements CanActivate { readonly facade = inject(DaffAuthFacade); readonly config = inject(DAFF_AUTH_ROUTING_CONFIG); readonly platformId = inject(PLATFORM_ID); - readonly redirectUrl = inject(DaffAuthResetPasswordGuardRedirectUrl); readonly router = inject(Router); canActivate(route: ActivatedRouteSnapshot): boolean | UrlTree { @@ -37,7 +35,7 @@ export class DaffAuthResetPasswordGuard implements CanActivate { const token = route.queryParamMap.get(this.config.resetPasswordTokenParam); if (!token) { - return this.router.parseUrl(this.redirectUrl); + return this.router.parseUrl(this.config.resetPasswordRedirectPath); } this.facade.dispatch(new DaffResetPasswordLanding(token));