Skip to content

Commit

Permalink
separate idplogin helper into separate file
Browse files Browse the repository at this point in the history
  • Loading branch information
yunakim714 committed Sep 12, 2024
1 parent 9189eb0 commit 31dd0a6
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 85 deletions.
87 changes: 87 additions & 0 deletions src/static/helpers/IDPLoginHelper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* @jest-environment node
*/
/* eslint header/header: "off", max-lines:"off" */
/*
* Copyright (c) 2022, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import nock from 'nock';
import {ShopperLogin, TokenResponse} from '../../lib/shopperLogin';
import loginIDPUser from './IDPLoginHelper';
import ResponseError from '../responseError';

const credentialsPublic = {};

const expectedTokenResponse: TokenResponse = {
access_token: 'access_token',
id_token: 'id_token',
refresh_token: 'refresh_token',
expires_in: 0,
refresh_token_expires_in: 0,
token_type: 'token_type',
usid: 'usid',
customer_id: 'customer_id',
enc_user_id: 'enc_user_id',
idp_access_token: 'idp',
};

const parameters = {
accessToken: 'access_token',
redirectURI: 'redirect_uri',
refreshToken: 'refresh_token',
usid: 'usid',
hint: 'hint',
dnt: false,
};

const url =
'https://localhost:3000/callback?usid=048adcfb-aa93-4978-be9e-09cb569fdcb9&code=J2lHm0cgXmnXpwDhjhLoyLJBoUAlBfxDY-AhjqGMC-o';

const authenticateCustomerMock = jest.fn(() => ({url}));

const getAccessTokenMock = jest.fn(() => expectedTokenResponse);
const logoutCustomerMock = jest.fn(() => expectedTokenResponse);
const generateCodeChallengeMock = jest.fn(() => 'code_challenge');

const createMockSlasClient = () =>
({
clientConfig: {
parameters: {
shortCode: 'short_code',
organizationId: 'organization_id',
clientId: 'client_id',
siteId: 'site_id',
},
},
authenticateCustomer: authenticateCustomerMock,
getAccessToken: getAccessTokenMock,
logoutCustomer: logoutCustomerMock,
generateCodeChallenge: generateCodeChallengeMock,
} as unknown as ShopperLogin<{
shortCode: string;
organizationId: string;
clientId: string;
siteId: string;
}>);

describe('Social login user flow', () => {
test('loginIDPUser does not stop when authorizeCustomer returns 303', async () => {
// slasClient is copied and tries to make an actual API call
const mockSlasClient = createMockSlasClient();
const {shortCode, organizationId} = mockSlasClient.clientConfig.parameters;

// Mock authorizeCustomer
nock(`https://${shortCode}.api.commercecloud.salesforce.com`)
.get(`/shopper/auth/v1/organizations/${organizationId}/oauth2/authorize`)
.query(true)
.reply(303, {message: 'Oh yes!'});

await expect(
loginIDPUser(mockSlasClient, credentialsPublic, parameters)
).resolves.not.toThrow(ResponseError);
});
});
81 changes: 81 additions & 0 deletions src/static/helpers/IDPLoginHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2022, Salesforce, Inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import {stringToBase64, createCodeVerifier, authorize} from './slasHelper';
import {
ShopperLogin,
TokenRequest,
TokenResponse,
} from '../../lib/shopperLogin';

/**
* A single function to execute the ShopperLogin External IDP Login with proof key for code exchange flow as described in the [API documentation](https://developer.salesforce.com/docs/commerce/commerce-api/references?meta=shopper-login:Summary).
* **Note**: this func can run on client side. Only use private slas when the slas client secret is secured.
* @param slasClient a configured instance of the ShopperLogin SDK client.
* @param credentials - the clientSecret (if applicable) to login with.
* @param credentials.clientSecret? - secret associated with client ID
* @param parameters - parameters to pass in the API calls.
* @param parameters.redirectURI - Per OAuth standard, a valid app route. Must be listed in your SLAS configuration. On server, this will not be actually called. On browser, this will be called, but ignored.
* @param parameters.hint - Name of an identity provider (IDP) to redirect to
* @param parameters.usid? - Unique Shopper Identifier to enable personalization.
* @param parameters.dnt? - Optional parameter to enable Do Not Track (DNT) for the user.
* @returns TokenResponse
*/
async function loginIDPUser(
slasClient: ShopperLogin<{
shortCode: string;
organizationId: string;
clientId: string;
siteId: string;
}>,
credentials: {
clientSecret?: string;
},
parameters: {
redirectURI: string;
hint: string;
usid?: string;
dnt?: boolean;
}
): Promise<TokenResponse> {
const codeVerifier = createCodeVerifier();

const authResponse = await authorize(slasClient, codeVerifier, {
redirectURI: parameters.redirectURI,
hint: parameters.hint,
...(parameters.usid && {usid: parameters.usid}),
});

const tokenBody: TokenRequest = {
client_id: slasClient.clientConfig.parameters.clientId,
channel_id: slasClient.clientConfig.parameters.siteId,
code: authResponse.code,
code_verifier: codeVerifier,
grant_type: 'authorization_code_pkce',
redirect_uri: parameters.redirectURI,
usid: authResponse.usid,
...(parameters.dnt !== undefined && {dnt: parameters.dnt.toString()}),
};

// Using private client
if (credentials.clientSecret) {
const authHeaderIdSecret = `Basic ${stringToBase64(
`${slasClient.clientConfig.parameters.clientId}:${credentials.clientSecret}`
)}`;

const optionsToken = {
headers: {
Authorization: authHeaderIdSecret,
},
body: tokenBody,
};
return slasClient.getAccessToken(optionsToken);
}

return slasClient.getAccessToken({body: tokenBody});
}

export default loginIDPUser;
18 changes: 0 additions & 18 deletions src/static/helpers/slasHelper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,24 +435,6 @@ describe('Registered B2C user flow', () => {
});
});

describe('Social login user flow', () => {
test('loginIDPUser does not stop when authorizeCustomer returns 303', async () => {
// slasClient is copied and tries to make an actual API call
const mockSlasClient = createMockSlasClient();
const {shortCode, organizationId} = mockSlasClient.clientConfig.parameters;

// Mock authorizeCustomer
nock(`https://${shortCode}.api.commercecloud.salesforce.com`)
.get(`/shopper/auth/v1/organizations/${organizationId}/oauth2/authorize`)
.query(true)
.reply(303, {message: 'Oh yes!'});

await expect(
slasHelper.loginIDPUser(mockSlasClient, parameters)
).resolves.not.toThrow(ResponseError);
});
});

describe('Refresh Token', () => {
test('refreshes the token with slas public client', () => {
const expectedBody = {
Expand Down
67 changes: 0 additions & 67 deletions src/static/helpers/slasHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,73 +359,6 @@ export async function loginRegisteredUserB2C(
return slasClient.getAccessToken({body: tokenBody});
}

/**
* A single function to execute the ShopperLogin External IDP Login with proof key for code exchange flow as described in the [API documentation](https://developer.salesforce.com/docs/commerce/commerce-api/references?meta=shopper-login:Summary).
* **Note**: this func can run on client side. Only use private slas when the slas client secret is secured.
* @param slasClient a configured instance of the ShopperLogin SDK client.
* @param credentials - the clientSecret (if applicable) to login with.
* @param credentials.clientSecret? - secret associated with client ID
* @param parameters - parameters to pass in the API calls.
* @param parameters.redirectURI - Per OAuth standard, a valid app route. Must be listed in your SLAS configuration. On server, this will not be actually called. On browser, this will be called, but ignored.
* @param parameters.hint - Name of an identity provider (IDP) to redirect to
* @param parameters.usid? - Unique Shopper Identifier to enable personalization.
* @param parameters.dnt? - Optional parameter to enable Do Not Track (DNT) for the user.
* @returns TokenResponse
*/
export async function loginIDPUser(
slasClient: ShopperLogin<{
shortCode: string;
organizationId: string;
clientId: string;
siteId: string;
}>,
credentials: {
clientSecret?: string;
},
parameters: {
redirectURI: string;
hint: string;
usid?: string;
dnt?: boolean;
}
): Promise<TokenResponse> {
const codeVerifier = createCodeVerifier();

const authResponse = await authorize(slasClient, codeVerifier, {
redirectURI: parameters.redirectURI,
hint: parameters.hint,
...(parameters.usid && {usid: parameters.usid}),
});

const tokenBody: TokenRequest = {
client_id: slasClient.clientConfig.parameters.clientId,
channel_id: slasClient.clientConfig.parameters.siteId,
code: authResponse.code,
code_verifier: codeVerifier,
grant_type: 'authorization_code_pkce',
redirect_uri: parameters.redirectURI,
usid: authResponse.usid,
...(parameters.dnt !== undefined && {dnt: parameters.dnt.toString()}),
};

// Using private client
if (credentials.clientSecret) {
const authHeaderIdSecret = `Basic ${stringToBase64(
`${slasClient.clientConfig.parameters.clientId}:${credentials.clientSecret}`
)}`;

const optionsToken = {
headers: {
Authorization: authHeaderIdSecret,
},
body: tokenBody,
};
return slasClient.getAccessToken(optionsToken);
}

return slasClient.getAccessToken({body: tokenBody});
}

/**
* Exchange a refresh token for a new access token.
* **Note**: this func can run on client side. Only use private slas when the slas client secret is secured.
Expand Down

0 comments on commit 31dd0a6

Please sign in to comment.