From 6eebd4f935a99f402fa03874f4fc6a88f2794a83 Mon Sep 17 00:00:00 2001 From: naman-shukla-ksolves Date: Mon, 29 Dec 2025 15:25:44 +0530 Subject: [PATCH 1/2] The authentication flow was not preserving the original requested URL before redirecting to the login page, causing users to lose their intended destination after successful authentication --- .../src/app/pages/login/state/access/access.effects.ts | 8 +++++++- .../nifi/src/app/service/guard/authentication.guard.ts | 10 +++++++++- .../src/app/state/current-user/current-user.effects.ts | 4 ++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts index 46f87732bb2d..ba5b66aeddb3 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts @@ -59,7 +59,13 @@ export class AccessEffects { this.actions$.pipe( ofType(AccessActions.loginSuccess), tap(() => { - this.router.navigate(['/']); + const returnUrl = sessionStorage.getItem('returnUrl'); + if (returnUrl) { + sessionStorage.removeItem('returnUrl'); + this.router.navigateByUrl(returnUrl); + } else { + this.router.navigate(['/']); + } }) ), { dispatch: false } diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts index 33c54a59fee7..79694a968ee3 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { CanMatchFn } from '@angular/router'; +import { CanMatchFn, Router } from '@angular/router'; import { inject } from '@angular/core'; import { AuthService } from '../auth.service'; import { catchError, from, map, of, switchMap, take, tap } from 'rxjs'; @@ -35,6 +35,7 @@ export const authenticationGuard: CanMatchFn = () => { const userService: CurrentUserService = inject(CurrentUserService); const errorHelper: ErrorHelper = inject(ErrorHelper); const store: Store = inject(Store); + const router: Router = inject(Router); const getAuthenticationConfig = store.select(selectLoginConfiguration).pipe( take(1), @@ -77,6 +78,13 @@ export const authenticationGuard: CanMatchFn = () => { }), map(() => true), catchError((errorResponse: HttpErrorResponse) => { + if (errorResponse.status === 401) { + const currentUrl = router.url; + if (currentUrl && currentUrl !== '/login') { + sessionStorage.setItem('returnUrl', currentUrl); + } + } + if (errorResponse.status !== 401 || authConfigResponse.loginSupported) { store.dispatch(errorHelper.fullScreenError(errorResponse)); } diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts index 8bb50a541603..1f180bb0b138 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts @@ -75,6 +75,10 @@ export class CurrentUserEffects { this.actions$.pipe( ofType(UserActions.navigateToLogIn), tap(() => { + const currentUrl = window.location.hash.substring(1) || this.router.url; + if (currentUrl && currentUrl !== '/login') { + sessionStorage.setItem('returnUrl', currentUrl); + } this.router.navigate(['/login']); }) ), From 31b39e851bca6c79bb0efc4d9859d13a7210db62 Mon Sep 17 00:00:00 2001 From: naman-shukla-ksolves Date: Tue, 30 Dec 2025 13:02:35 +0530 Subject: [PATCH 2/2] Replace direct sessionStorage access with shared service abstraction --- .../login/state/access/access.effects.ts | 6 ++- .../nifi/src/app/service/client.service.ts | 8 +-- .../app/service/guard/authentication.guard.ts | 4 +- .../nifi/src/app/service/storage.service.ts | 49 +++++++++++++++++++ .../current-user/current-user.effects.ts | 4 +- 5 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 nifi-frontend/src/main/frontend/apps/nifi/src/app/service/storage.service.ts diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts index ba5b66aeddb3..632e524b8ac0 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/pages/login/state/access/access.effects.ts @@ -29,6 +29,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { Store } from '@ngrx/store'; import { NiFiState } from '../../../../state'; import { resetLoginFailure } from './access.actions'; +import { StorageService } from '../../../../service/storage.service'; @Injectable() export class AccessEffects { @@ -38,6 +39,7 @@ export class AccessEffects { private router = inject(Router); private dialog = inject(MatDialog); private errorHelper = inject(ErrorHelper); + private storageService = inject(StorageService); login$ = createEffect(() => this.actions$.pipe( @@ -59,9 +61,9 @@ export class AccessEffects { this.actions$.pipe( ofType(AccessActions.loginSuccess), tap(() => { - const returnUrl = sessionStorage.getItem('returnUrl'); + const returnUrl = this.storageService.getReturnUrl(); if (returnUrl) { - sessionStorage.removeItem('returnUrl'); + this.storageService.removeReturnUrl(); this.router.navigateByUrl(returnUrl); } else { this.router.navigate(['/']); diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/client.service.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/client.service.ts index d9565d01f00d..3006acba4882 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/client.service.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/client.service.ts @@ -17,22 +17,22 @@ import { Injectable, inject } from '@angular/core'; import { v4 as uuidv4 } from 'uuid'; -import { SessionStorageService } from '@nifi/shared'; +import { StorageService } from './storage.service'; @Injectable({ providedIn: 'root' }) export class Client { - private sessionStorage = inject(SessionStorageService); + private storageService = inject(StorageService); private clientId: string; constructor() { - let clientId = this.sessionStorage.getItem('clientId'); + let clientId = this.storageService.getClientId(); if (clientId === null) { clientId = uuidv4(); - this.sessionStorage.setItem('clientId', clientId); + this.storageService.setClientId(clientId); } this.clientId = clientId; diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts index 79694a968ee3..616df912f6d0 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/guard/authentication.guard.ts @@ -29,6 +29,7 @@ import { fullScreenError } from '../../state/error/error.actions'; import { ErrorHelper } from '../error-helper.service'; import { selectLoginConfiguration } from '../../state/login-configuration/login-configuration.selectors'; import { loadLoginConfigurationSuccess } from '../../state/login-configuration/login-configuration.actions'; +import { StorageService } from '../storage.service'; export const authenticationGuard: CanMatchFn = () => { const authService: AuthService = inject(AuthService); @@ -36,6 +37,7 @@ export const authenticationGuard: CanMatchFn = () => { const errorHelper: ErrorHelper = inject(ErrorHelper); const store: Store = inject(Store); const router: Router = inject(Router); + const storageService: StorageService = inject(StorageService); const getAuthenticationConfig = store.select(selectLoginConfiguration).pipe( take(1), @@ -81,7 +83,7 @@ export const authenticationGuard: CanMatchFn = () => { if (errorResponse.status === 401) { const currentUrl = router.url; if (currentUrl && currentUrl !== '/login') { - sessionStorage.setItem('returnUrl', currentUrl); + storageService.setReturnUrl(currentUrl); } } diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/storage.service.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/storage.service.ts new file mode 100644 index 000000000000..da695974ea65 --- /dev/null +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/service/storage.service.ts @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Injectable, inject } from '@angular/core'; +import { SessionStorageService } from '@nifi/shared'; + +@Injectable({ + providedIn: 'root' +}) +export class StorageService { + private sessionStorageService = inject(SessionStorageService); + + private static readonly RETURN_URL = 'returnUrl'; + private static readonly CLIENT_ID = 'clientId'; + + public setReturnUrl(url: string): void { + this.sessionStorageService.setItem(StorageService.RETURN_URL, url); + } + + public getReturnUrl(): string | null { + return this.sessionStorageService.getItem(StorageService.RETURN_URL); + } + + public removeReturnUrl(): void { + this.sessionStorageService.removeItem(StorageService.RETURN_URL); + } + + public getClientId(): string | null { + return this.sessionStorageService.getItem(StorageService.CLIENT_ID); + } + + public setClientId(clientId: string): void { + this.sessionStorageService.setItem(StorageService.CLIENT_ID, clientId); + } +} diff --git a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts index 1f180bb0b138..b446db127a9c 100644 --- a/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts +++ b/nifi-frontend/src/main/frontend/apps/nifi/src/app/state/current-user/current-user.effects.ts @@ -28,6 +28,7 @@ import { selectLogoutUri } from '../login-configuration/login-configuration.sele import { Router } from '@angular/router'; import { AuthService } from '../../service/auth.service'; import { HttpErrorResponse } from '@angular/common/http'; +import { StorageService } from '../../service/storage.service'; @Injectable() export class CurrentUserEffects { @@ -37,6 +38,7 @@ export class CurrentUserEffects { private userService = inject(CurrentUserService); private authService = inject(AuthService); private errorHelper = inject(ErrorHelper); + private storageService = inject(StorageService); loadCurrentUser$ = createEffect(() => this.actions$.pipe( @@ -77,7 +79,7 @@ export class CurrentUserEffects { tap(() => { const currentUrl = window.location.hash.substring(1) || this.router.url; if (currentUrl && currentUrl !== '/login') { - sessionStorage.setItem('returnUrl', currentUrl); + this.storageService.setReturnUrl(currentUrl); } this.router.navigate(['/login']); })