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
36 changes: 36 additions & 0 deletions apps/storefront/src/utils/currentCustomerJwt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as bcService from '@/shared/service/bc';

import b2bLogger from './b3Logger';
import { fetchCurrentCustomerJwt, isCustomerJwtValid } from './currentCustomerJwt';

describe('currentCustomerJwt helpers', () => {
beforeEach(() => {
vi.spyOn(b2bLogger, 'error').mockImplementation(() => undefined);
});

afterEach(() => {
vi.restoreAllMocks();
});

describe('fetchCurrentCustomerJwt', () => {
it('returns the fetched JWT', async () => {
vi.spyOn(bcService, 'getCurrentCustomerJWT').mockResolvedValue('jwt-token');

await expect(fetchCurrentCustomerJwt()).resolves.toBe('jwt-token');
});

it('returns an empty string and logs when the fetch fails', async () => {
vi.spyOn(bcService, 'getCurrentCustomerJWT').mockRejectedValue(new Error('network'));

await expect(fetchCurrentCustomerJwt()).resolves.toBe('');
expect(b2bLogger.error).toHaveBeenCalled();
});
});

describe('isCustomerJwtValid', () => {
it('treats a present token as valid and an empty token as invalid', () => {
expect(isCustomerJwtValid('jwt-token')).toBe(true);
expect(isCustomerJwtValid('')).toBe(false);
});
});
});
20 changes: 20 additions & 0 deletions apps/storefront/src/utils/currentCustomerJwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { getCurrentCustomerJWT } from '@/shared/service/bc';
import { getAppClientId } from '@/shared/service/request/base';

import b2bLogger from './b3Logger';

// Fetch the BC customer JWT from the storefront. Returns '' on failure (logged).
// Store-free on purpose: callers persist with the dispatch available in their own
// context (useAppDispatch in components, the store singleton in non-React utils).
export const fetchCurrentCustomerJwt = async (): Promise<string> => {
const jwt = await getCurrentCustomerJWT(getAppClientId()).catch((error) => {
b2bLogger.error(error);
return '';
});

return jwt ?? '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Change]

return jwt ?? isCustomerJwtValid ('');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's address isCustomerJwtValid in a separate PR. Keeping PRs small and focused makes us easier to understand and review.

};

// Single chokepoint for JWT validity so callers don't repeat the check.
// TODO(B2B-4930): decode and verify the token (e.g. exp) instead of a presence check.
export const isCustomerJwtValid = (jwt: string): boolean => Boolean(jwt);
14 changes: 5 additions & 9 deletions apps/storefront/src/utils/loginInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import {
getUserCompany,
getUserMasqueradingCompany,
} from '@/shared/service/b2b';
import { getCurrentCustomerJWT, getCustomerInfo } from '@/shared/service/bc';
import { getAppClientId } from '@/shared/service/request/base';
import { getCustomerInfo } from '@/shared/service/bc';
import {
clearMasqueradeCompany,
MasqueradeCompany,
Expand All @@ -36,6 +35,7 @@ import { CompanyStatus, CustomerRole, CustomerRoleName, LoginTypes, UserTypes }
import b2bLogger from './b3Logger';
import { B3LStorage, B3SStorage } from './b3Storage';
import { channelId, storeHash } from './basicConfig';
import { fetchCurrentCustomerJwt } from './currentCustomerJwt';
import { getAccountHierarchyIsEnabled } from './storefrontConfig';

const getLoginTokenInfo = () => {
Expand Down Expand Up @@ -187,11 +187,7 @@ const getCompanyUserInfo = async (useBcLoginAndAuthorisation = false) => {

const loginWithCurrentCustomerJWT = async () => {
const prevCurrentCustomerJWT = store.getState().company.tokens.currentCustomerJWT;
const currentCustomerJWT = await getCurrentCustomerJWT(getAppClientId()).catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
return undefined;
});
const currentCustomerJWT = await fetchCurrentCustomerJwt();

if (!currentCustomerJWT || prevCurrentCustomerJWT === currentCustomerJWT) return undefined;

Expand Down Expand Up @@ -346,8 +342,8 @@ export const getCurrentCustomerInfo = async (
if (useBcLoginAndAuthorisation) {
let { currentCustomerJWT } = store.getState().company.tokens;
if (!currentCustomerJWT) {
currentCustomerJWT =
(await getCurrentCustomerJWT(getAppClientId()).catch(() => '')) ?? '';
currentCustomerJWT = await fetchCurrentCustomerJwt();
if (currentCustomerJWT) store.dispatch(setCurrentCustomerJWT(currentCustomerJWT));
}
if (currentCustomerJWT) {
const authorizationData = await b2bAuthorization({
Expand Down