Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 35 additions & 8 deletions webapp/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<UserData>('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<TenantsResponse | undefined>).value;
userInfo = (userInfoResult as PromiseFulfilledResult<UserData | undefined>).value;
}
catch (error) {
// the error is thrown to and handled in the outer catch block
Expand Down Expand Up @@ -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;
}
}
Expand Down
10 changes: 5 additions & 5 deletions webapp/common/Helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,11 @@ export function showErrorMessage(error: AxiosError, userMessage: string | undefi
MessageBox.error(errorMessage, {
title: 'Error',
details: '<p><strong>' + 'Error Details:' + '</strong></p>'
+ '<ul>'
+ '<li><strong>' + 'Request ID: ' + '</strong>' + ' ' + requestID + '</li>'
+ '<li><strong>' + 'Timestamp (UTC): ' + '</strong>' + datetime + '</li>'
+ '<li><strong>' + 'Support Page: ' + '</strong>' + "<a href='https://support.sap.com/'>https://support.sap.com<a/>" + '</li>'
+ '</ul>',
+ '<ul>'
+ '<li><strong>' + 'Request ID: ' + '</strong>' + ' ' + requestID + '</li>'
+ '<li><strong>' + 'Timestamp (UTC): ' + '</strong>' + datetime + '</li>'
+ '<li><strong>' + 'Support Page: ' + '</strong>' + "<a href='https://support.sap.com/'>https://support.sap.com<a/>" + '</li>'
+ '</ul>',
styleClass: 'sapUiUserSelectable'
});
}
5 changes: 3 additions & 2 deletions webapp/services/Auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
2 changes: 1 addition & 1 deletion webapp/utils/ForbiddenState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,4 @@ export default class ForbiddenStateService {
return resourceBundle?.getText('forbiddenDescription') || 'Access forbidden';
}
}
}
}
Loading