Skip to content

Commit d1a9b07

Browse files
authored
feat: add i18n support for password expired errors (#20768)
Closes CXSPA-10815
1 parent 4712fa3 commit d1a9b07

File tree

5 files changed

+77
-6
lines changed

5 files changed

+77
-6
lines changed

projects/assets/src/translations/en/common.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
"httpHandlers": {
114114
"badRequest": {
115115
"bad_credentials": "{{ errorMessage }}. Please login again.",
116+
"password_expired": "{{ errorMessage }}. Please change your password.",
116117
"user_is_disabled": "{{ errorMessage }}. Please contact administration."
117118
},
118119
"badGateway": "A server error occurred. Please try again later.",

projects/core/src/features-config/feature-toggles/config/feature-toggles.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,13 @@ export interface FeatureTogglesInterface {
598598
*/
599599
navigationMenuCloseOnSameLinkClick?: boolean;
600600

601+
/**
602+
* When enabled, translates the "Password expired" error message
603+
* to the user's selected language using Spartacus i18n.
604+
* Affects: `LoginComponent`
605+
*/
606+
enablePasswordExpiredErrorTranslation?: boolean;
607+
601608
/**
602609
* shows the Quote Purchase Order Number input field in the Quote Request form
603610
* and in the Quote Details page
@@ -606,6 +613,7 @@ export interface FeatureTogglesInterface {
606613
* when requesting a quote and see it in the quote details
607614
*/
608615
enableQuotePurchaseOrderNumber?: boolean;
616+
609617
/**
610618
* When enabled, fixes the issue with return order returnable quantity not being displayed correctly
611619
* on the `ReturnOrderComponent` when navigating to the return request details page.
@@ -690,6 +698,7 @@ export const defaultFeatureToggles: Required<FeatureTogglesInterface> = {
690698
dispatchLoginActionOnlyWhenTokenReceived: false,
691699
defaultLayoutConfigWithoutPageFold: false,
692700
navigationMenuCloseOnSameLinkClick: false,
701+
enablePasswordExpiredErrorTranslation: false,
693702
enableQuotePurchaseOrderNumber: false,
694703
enableReturnOrderReturnableQuantityConsigmentFallback: false,
695704
};

projects/core/src/global-message/http-interceptors/handlers/bad-request/bad-request.handler.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GlobalMessageService } from '../../../facade';
44
import { GlobalMessageType } from '../../../models/global-message.model';
55
import { HttpResponseStatus } from '../../../models/response-status.model';
66
import { BadRequestHandler } from './bad-request.handler';
7+
import { FeatureConfigService } from '../../../../features-config/services/feature-config.service';
78

89
const MockRequest = {
910
url: 'https://electronics-spa/occ/user/password',
@@ -31,6 +32,14 @@ const MockBadPasswordResponse = {
3132
},
3233
} as HttpErrorResponse;
3334

35+
const MockExpiredPasswordResponse = {
36+
url: 'https://server.com/authorizationserver/oauth/token',
37+
error: {
38+
error: 'invalid_grant',
39+
error_description: 'Password expired for the user: John Doe',
40+
},
41+
} as HttpErrorResponse;
42+
3443
const MockDisabledUserResponse = {
3544
url: 'https://server.com/authorizationserver/oauth/token',
3645
error: {
@@ -87,6 +96,12 @@ class MockGlobalMessageService {
8796
remove() {}
8897
}
8998

99+
class MockFeatureConfigService {
100+
isEnabled(_feature: string): boolean {
101+
return true;
102+
}
103+
}
104+
90105
const MockBadGuestDuplicateEmailResponse = {
91106
error: {
92107
errors: [
@@ -110,6 +125,10 @@ describe('BadRequestHandler', () => {
110125
provide: GlobalMessageService,
111126
useClass: MockGlobalMessageService,
112127
},
128+
{
129+
provide: FeatureConfigService,
130+
useClass: MockFeatureConfigService,
131+
},
113132
],
114133
});
115134
service = TestBed.inject(BadRequestHandler);
@@ -149,6 +168,30 @@ describe('BadRequestHandler', () => {
149168
MockBadPasswordResponse.error.error_description
150169
);
151170
expect(globalMessageService.add).toHaveBeenCalled();
171+
expect(globalMessageService.add).toHaveBeenCalledWith(
172+
{
173+
key: 'httpHandlers.badRequest.bad_password',
174+
params: { errorMessage: 'Bad password' },
175+
},
176+
GlobalMessageType.MSG_TYPE_ERROR
177+
);
178+
expect(globalMessageService.remove).toHaveBeenCalled();
179+
});
180+
181+
it('should handle expired password message', () => {
182+
spyOn(service, 'getErrorTranslationKey').and.callThrough();
183+
service.handleError(MockBadPasswordRequest, MockExpiredPasswordResponse);
184+
expect(service.getErrorTranslationKey).toHaveBeenCalledWith(
185+
MockExpiredPasswordResponse.error.error_description
186+
);
187+
expect(globalMessageService.add).toHaveBeenCalled();
188+
expect(globalMessageService.add).toHaveBeenCalledWith(
189+
{
190+
key: 'httpHandlers.badRequest.password_expired',
191+
params: { errorMessage: 'Password expired for the user: John Doe' },
192+
},
193+
GlobalMessageType.MSG_TYPE_ERROR
194+
);
152195
expect(globalMessageService.remove).toHaveBeenCalled();
153196
});
154197

projects/core/src/global-message/http-interceptors/handlers/bad-request/bad-request.handler.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
*/
66

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

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

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

2426
handleError(request: HttpRequest<any>, response: HttpErrorResponse): void {
2527
this.handleBadPassword(request, response);
@@ -39,13 +41,28 @@ export class BadRequestHandler extends HttpErrorHandler {
3941
response.error?.error === 'invalid_grant' &&
4042
request.body?.get('grant_type') === 'password'
4143
) {
44+
let key = this.getErrorTranslationKey(response.error?.error_description);
45+
const translationPrefix = `httpHandlers.badRequest`;
46+
const params: Translatable['params'] = {
47+
errorMessage:
48+
response.error.error_description || response.message || '',
49+
};
50+
if (
51+
this.featureConfigService.isEnabled(
52+
'enablePasswordExpiredErrorTranslation'
53+
)
54+
) {
55+
const isPasswordExpiredError = key.startsWith(
56+
`${translationPrefix}.password_expired_for_the_user`
57+
);
58+
if (isPasswordExpiredError) {
59+
key = `${translationPrefix}.password_expired`;
60+
}
61+
}
4262
this.globalMessageService.add(
4363
{
44-
key: this.getErrorTranslationKey(response.error?.error_description),
45-
params: {
46-
errorMessage:
47-
response.error.error_description || response.message || '',
48-
},
64+
key,
65+
params,
4966
},
5067
GlobalMessageType.MSG_TYPE_ERROR
5168
);

projects/storefrontapp/src/app/spartacus/spartacus-features.module.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,7 @@ if (environment.cpq) {
375375
authorizationCodeFlowByDefault: false,
376376
defaultLayoutConfigWithoutPageFold: true,
377377
navigationMenuCloseOnSameLinkClick: true,
378+
enablePasswordExpiredErrorTranslation: true,
378379
enableQuotePurchaseOrderNumber: false,
379380
enableReturnOrderReturnableQuantityConsigmentFallback: true,
380381
};

0 commit comments

Comments
 (0)