diff --git a/webapp/Component.ts b/webapp/Component.ts index a255309..7acbc07 100644 --- a/webapp/Component.ts +++ b/webapp/Component.ts @@ -10,7 +10,7 @@ import ResourceModel from 'sap/ui/model/resource/ResourceModel'; import Core from 'sap/ui/core/Core'; import Auth from './services/Auth.service'; import { initLanguageConfig } from 'kms/common/Language.Helpers'; -import { RoleBasedAccessData, TenantsList, UserData } from './common/Types'; +import { RoleBasedAccessData, TenantsResponse, TenantsList, UserData } from './common/Types'; import { UserRoles } from './common/Enums'; import SplashScreen from './utils/SplashScreen'; import ForbiddenStateService from './utils/ForbiddenState'; @@ -67,10 +67,37 @@ export default class Component extends UIComponent { let tenantsResponse, userInfo; try { - [tenantsResponse, userInfo] = await Promise.all([ + // Use Promise.allSettled to avoid race conditions: + // If one call returns 500 and another returns 403, Promise.all would reject + // on the first error, potentially missing the 403 forbidden state. + // Promise.allSettled waits for both to complete, so we can detect 403 reliably. + const results = await Promise.allSettled([ api.getTenantsForTenant(), api.get('userInfo') ]); + + const [tenantsResult, userInfoResult] = results; + + // If forbidden state was set by either promise call (e.g. 403), prioritize it + if (this.forbiddenService.isForbidden()) { + const forbiddenRejection = results.find( + r => r.status === 'rejected' && r.reason instanceof ApiAccessError + ) as PromiseRejectedResult | undefined; + throw forbiddenRejection?.reason ?? new ApiAccessError( + this.forbiddenService.getForbiddenErrorMessage(), + this.forbiddenService.getForbiddenErrorCode() + ); + } + + // If either call failed (non-403), throw the first error + const firstFailure = results.find((r): r is PromiseRejectedResult => r.status === 'rejected'); + if (firstFailure) { + + throw firstFailure.reason; + } + + tenantsResponse = (tenantsResult as PromiseFulfilledResult).value; + userInfo = (userInfoResult as PromiseFulfilledResult).value; } catch (error) { // the error is thrown to and handled in the outer catch block @@ -103,12 +130,12 @@ export default class Component extends UIComponent { throw error; } - if (error instanceof ApiAccessError) { - this.showSplashScreenError(error.message); - } - else { - this.showSplashScreenError('errorAppInitializationFail'); - } + // if (error instanceof ApiAccessError) { + // this.showSplashScreenError(error.message); + // } + // else { + // this.showSplashScreenError('errorAppInitializationFail'); + // } throw error; } } diff --git a/webapp/common/Helpers.ts b/webapp/common/Helpers.ts index 741754c..ca74b7f 100644 --- a/webapp/common/Helpers.ts +++ b/webapp/common/Helpers.ts @@ -142,11 +142,11 @@ export function showErrorMessage(error: AxiosError, userMessage: string | undefi MessageBox.error(errorMessage, { title: 'Error', details: '

' + 'Error Details:' + '

' - + '', + + '', styleClass: 'sapUiUserSelectable' }); } diff --git a/webapp/services/Auth.service.ts b/webapp/services/Auth.service.ts index c9f3e04..cbfc4c3 100644 --- a/webapp/services/Auth.service.ts +++ b/webapp/services/Auth.service.ts @@ -52,8 +52,9 @@ export default class Auth { const timeWindow = 30000; // 30 seconds in milliseconds const loginAttemptsData = sessionStorage.getItem(Auth.loginSessionStorageKey); const now = Date.now(); - const defaultData = { count: 0, lastAttemptTime: 0 }; - let tracker = (loginAttemptsData ? JSON.parse(loginAttemptsData) : defaultData) as ILoginTracker; + const defaultData: ILoginTracker = { count: 0, lastAttemptTime: 0 }; + const parsed: unknown = loginAttemptsData ? JSON.parse(loginAttemptsData) : undefined; + let tracker: ILoginTracker = parsed ? parsed as ILoginTracker : defaultData; // Reset tracker if the last attempt was a long time ago if (now - tracker.lastAttemptTime > timeWindow) { diff --git a/webapp/utils/ForbiddenState.ts b/webapp/utils/ForbiddenState.ts index cda005c..ebc71f8 100644 --- a/webapp/utils/ForbiddenState.ts +++ b/webapp/utils/ForbiddenState.ts @@ -101,4 +101,4 @@ export default class ForbiddenStateService { return resourceBundle?.getText('forbiddenDescription') || 'Access forbidden'; } } -} \ No newline at end of file +}