Skip to content
Merged
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
1 change: 1 addition & 0 deletions projects/assets/src/translations/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@
"httpHandlers": {
"badRequest": {
"bad_credentials": "{{ errorMessage }}. Please login again.",
"password_expired": "{{ errorMessage }}. Please change your password.",
"user_is_disabled": "{{ errorMessage }}. Please contact administration."
},
"badGateway": "A server error occurred. Please try again later.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,13 @@ export interface FeatureTogglesInterface {
*/
navigationMenuCloseOnSameLinkClick?: boolean;

/**
* When enabled, translates the "Password expired" error message
* to the user's selected language using Spartacus i18n.
* Affects: `LoginComponent`
*/
enablePasswordExpiredErrorTranslation?: boolean;

/**
* shows the Quote Purchase Order Number input field in the Quote Request form
* and in the Quote Details page
Expand All @@ -606,6 +613,7 @@ export interface FeatureTogglesInterface {
* when requesting a quote and see it in the quote details
*/
enableQuotePurchaseOrderNumber?: boolean;

/**
* When enabled, fixes the issue with return order returnable quantity not being displayed correctly
* on the `ReturnOrderComponent` when navigating to the return request details page.
Expand Down Expand Up @@ -690,6 +698,7 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
dispatchLoginActionOnlyWhenTokenReceived: false,
defaultLayoutConfigWithoutPageFold: false,
navigationMenuCloseOnSameLinkClick: false,
enablePasswordExpiredErrorTranslation: false,
enableQuotePurchaseOrderNumber: false,
enableReturnOrderReturnableQuantityConsigmentFallback: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GlobalMessageService } from '../../../facade';
import { GlobalMessageType } from '../../../models/global-message.model';
import { HttpResponseStatus } from '../../../models/response-status.model';
import { BadRequestHandler } from './bad-request.handler';
import { FeatureConfigService } from '../../../../features-config/services/feature-config.service';

const MockRequest = {
url: 'https://electronics-spa/occ/user/password',
Expand Down Expand Up @@ -31,6 +32,14 @@ const MockBadPasswordResponse = {
},
} as HttpErrorResponse;

const MockExpiredPasswordResponse = {
url: 'https://server.com/authorizationserver/oauth/token',
error: {
error: 'invalid_grant',
error_description: 'Password expired for the user: John Doe',
},
} as HttpErrorResponse;

const MockDisabledUserResponse = {
url: 'https://server.com/authorizationserver/oauth/token',
error: {
Expand Down Expand Up @@ -87,6 +96,12 @@ class MockGlobalMessageService {
remove() {}
}

class MockFeatureConfigService {
isEnabled(_feature: string): boolean {
return true;
}
}

const MockBadGuestDuplicateEmailResponse = {
error: {
errors: [
Expand All @@ -110,6 +125,10 @@ describe('BadRequestHandler', () => {
provide: GlobalMessageService,
useClass: MockGlobalMessageService,
},
{
provide: FeatureConfigService,
useClass: MockFeatureConfigService,
},
],
});
service = TestBed.inject(BadRequestHandler);
Expand Down Expand Up @@ -149,6 +168,30 @@ describe('BadRequestHandler', () => {
MockBadPasswordResponse.error.error_description
);
expect(globalMessageService.add).toHaveBeenCalled();
expect(globalMessageService.add).toHaveBeenCalledWith(
{
key: 'httpHandlers.badRequest.bad_password',
params: { errorMessage: 'Bad password' },
},
GlobalMessageType.MSG_TYPE_ERROR
);
expect(globalMessageService.remove).toHaveBeenCalled();
});

it('should handle expired password message', () => {
spyOn(service, 'getErrorTranslationKey').and.callThrough();
service.handleError(MockBadPasswordRequest, MockExpiredPasswordResponse);
expect(service.getErrorTranslationKey).toHaveBeenCalledWith(
MockExpiredPasswordResponse.error.error_description
);
expect(globalMessageService.add).toHaveBeenCalled();
expect(globalMessageService.add).toHaveBeenCalledWith(
{
key: 'httpHandlers.badRequest.password_expired',
params: { errorMessage: 'Password expired for the user: John Doe' },
},
GlobalMessageType.MSG_TYPE_ERROR
);
expect(globalMessageService.remove).toHaveBeenCalled();
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
*/

import { HttpErrorResponse, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { ErrorModel } from '../../../../model/misc.model';
import { Priority } from '../../../../util/applicable';
import { GlobalMessageType } from '../../../models/global-message.model';
import { HttpResponseStatus } from '../../../models/response-status.model';
import { HttpErrorHandler } from '../http-error.handler';
import { Translatable } from '../../../../i18n/translatable';
import { FeatureConfigService } from '../../../../features-config/services/feature-config.service';

const OAUTH_ENDPOINT = '/authorizationserver/oauth/token';

Expand All @@ -20,6 +21,7 @@ const OAUTH_ENDPOINT = '/authorizationserver/oauth/token';
})
export class BadRequestHandler extends HttpErrorHandler {
responseStatus = HttpResponseStatus.BAD_REQUEST;
private featureConfigService = inject(FeatureConfigService);

handleError(request: HttpRequest<any>, response: HttpErrorResponse): void {
this.handleBadPassword(request, response);
Expand All @@ -39,13 +41,28 @@ export class BadRequestHandler extends HttpErrorHandler {
response.error?.error === 'invalid_grant' &&
request.body?.get('grant_type') === 'password'
) {
let key = this.getErrorTranslationKey(response.error?.error_description);
const translationPrefix = `httpHandlers.badRequest`;
const params: Translatable['params'] = {
errorMessage:
response.error.error_description || response.message || '',
};
if (
this.featureConfigService.isEnabled(
'enablePasswordExpiredErrorTranslation'
)
) {
const isPasswordExpiredError = key.startsWith(
`${translationPrefix}.password_expired_for_the_user`
);
if (isPasswordExpiredError) {
key = `${translationPrefix}.password_expired`;
}
}
this.globalMessageService.add(
{
key: this.getErrorTranslationKey(response.error?.error_description),
params: {
errorMessage:
response.error.error_description || response.message || '',
},
key,
params,
},
GlobalMessageType.MSG_TYPE_ERROR
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ if (environment.cpq) {
authorizationCodeFlowByDefault: false,
defaultLayoutConfigWithoutPageFold: true,
navigationMenuCloseOnSameLinkClick: true,
enablePasswordExpiredErrorTranslation: true,
enableQuotePurchaseOrderNumber: false,
enableReturnOrderReturnableQuantityConsigmentFallback: true,
};
Expand Down
Loading