Skip to content

Commit

Permalink
refactor(commercetools)!: refactor token handling in commercetools (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
filipsobol committed Oct 4, 2021
1 parent 965c5e9 commit 3073946
Show file tree
Hide file tree
Showing 60 changed files with 1,956 additions and 1,715 deletions.
5 changes: 3 additions & 2 deletions .github/labeler.yml
Expand Up @@ -14,9 +14,10 @@ commercetools:
boilerplate:
- packages/boilerplate/**/*

# Add 'docs' label to any change to packages/core/docs files EXCEPT for the changelog
# Add 'docs' label to any change to packages/core/docs files
docs:
- any: ['packages/core/docs/**/*', '!packages/core/docs/changelog/**/*']
- packages/core/docs/**/*
- packages/core/docs/.vuepress/**/*

# Add 'ci' label to any change to continuous integration files inside .github folder
ci:
Expand Down
Expand Up @@ -23,23 +23,4 @@ describe('[commercetools-api-client] customerSignOut', () => {
customerSignOut(mockContext);
expect(onTokenRemove).toBeCalled();
});

it('calls "invalidateTokenInfo" if provided', () => {
const tokenProvider = { invalidateTokenInfo: jest.fn().mockImplementation(() => {}) };
mockContext.client.tokenProvider = tokenProvider;

customerSignOut(mockContext);
expect(tokenProvider.invalidateTokenInfo).toBeCalled();
});

it('calls "onTokenRemove" and "invalidateTokenInfo" if both are provided', () => {
const onTokenRemove = jest.fn().mockImplementation(() => {});
const tokenProvider = { invalidateTokenInfo: jest.fn().mockImplementation(() => {}) };
mockContext.config.auth.onTokenRemove = onTokenRemove;
mockContext.client.tokenProvider = tokenProvider;

customerSignOut(mockContext);
expect(onTokenRemove).toBeCalled();
expect(tokenProvider.invalidateTokenInfo).toBeCalled();
});
});
Expand Up @@ -17,11 +17,11 @@ describe('[commercetools-api-client] isGuest', () => {
jest.clearAllMocks();
});

it('defaults to false', () => {
it('defaults to true', () => {
const context = getMockContext();
context.client.tokenProvider = false;
context.config.auth.onTokenRead = jest.fn();

expect(isGuest(context)).toBeFalsy();
expect(isGuest(context)).toBeTruthy();
});

it('calls "handleIsGuest" from config', () => {
Expand Down
58 changes: 58 additions & 0 deletions packages/commercetools/api-client/__tests__/api/isLoggedIn.spec.ts
@@ -0,0 +1,58 @@
import isLoggedIn from '../../src/api/isLoggedIn';

const getMockContext = () => ({
client: {
tokenProvider: true
},
config: {
handleIsLoggedIn: null,
auth: {
onTokenRead: null
}
}
});

describe('[commercetools-api-client] isLoggedIn', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('defaults to false', () => {
const context = getMockContext();
context.config.auth.onTokenRead = jest.fn();

expect(isLoggedIn(context)).toBeFalsy();
});

it('calls "handleIsLoggedIn" from config', () => {
const context = getMockContext();
context.config.handleIsLoggedIn = jest.fn().mockImplementation(() => true);

expect(isLoggedIn(context)).toBeTruthy();
expect(context.config.handleIsLoggedIn).toBeCalled();
});

it('returns false if visitor is a guest', () => {
const context = getMockContext();
context.config.auth.onTokenRead = jest.fn().mockImplementation(() => ({ scope: '' }));

expect(isLoggedIn(context)).toBeFalsy();
expect(context.config.auth.onTokenRead).toBeCalled();
});

it('returns false if visitor has anonymous session', () => {
const context = getMockContext();
context.config.auth.onTokenRead = jest.fn().mockImplementation(() => ({ scope: 'anonymous_id' }));

expect(isLoggedIn(context)).toBeFalsy();
expect(context.config.auth.onTokenRead).toBeCalled();
});

it('returns true if visitor has user session', () => {
const context = getMockContext();
context.config.auth.onTokenRead = jest.fn().mockImplementation(() => ({ scope: 'customer_id' }));

expect(isLoggedIn(context)).toBeTruthy();
expect(context.config.auth.onTokenRead).toBeCalled();
});
});
@@ -1,4 +1,4 @@
import { setShippingMethodAction } from '../../src/helpers/cart/actions';
import { setShippingMethodAction } from '../../src/helpers/actions/cart';

describe('[commercetools-api-client] setShippingMethod', () => {
beforeEach(() => {
Expand Down
@@ -1,4 +1,4 @@
import { setBillingAddressAction } from './../../../src/helpers/cart/actions';
import { setBillingAddressAction } from './../../../src/helpers/actions/cart';

describe('[commercetools-api-client] setBillingAddressAction', () => {
beforeEach(() => {
Expand Down
@@ -1,4 +1,4 @@
import { handleBeforeAuth, handleAfterAuth, handleRetry } from '../../../src/helpers/commercetoolsLink/linkHandlers';
import { handleBeforeAuth, handleAfterAuth } from '../../src/links/authLinks';

const getSdkAuth = (scope) => ({
anonymousFlow: jest.fn().mockImplementation(() => ({ scope, access_token: 'GUEST_TOKEN' })),
Expand All @@ -19,11 +19,10 @@ describe('[commercetools-helpers] handleBeforeAuth', () => {
it('doesnt generate access token for guest on users related operations', async () => {
const scope = '';
const result = await handleBeforeAuth({
settings: {},
configuration: {},
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'customerSignMeIn' },
currentToken: { scope }
apolloReq: { operationName: 'customerSignMeIn' }
});

expect(result).toMatchObject({ access_token: 'ACCESS_TOKEN' });
Expand All @@ -32,11 +31,10 @@ describe('[commercetools-helpers] handleBeforeAuth', () => {
it('generates access token for guest on anonymous-session allowed operations', async () => {
const scope = '';
const result = await handleBeforeAuth({
settings: {},
configuration: {},
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'createCart' },
currentToken: { scope }
apolloReq: { operationName: 'createCart' }
});

expect(result).toMatchObject({ access_token: 'GUEST_TOKEN' });
Expand All @@ -45,11 +43,10 @@ describe('[commercetools-helpers] handleBeforeAuth', () => {
it('returns current token for anonymous user', async () => {
const scope = 'anonymous_id';
const result = await handleBeforeAuth({
settings: {},
configuration: {},
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'customerSignMeIn' },
currentToken: { scope }
apolloReq: { operationName: 'customerSignMeIn' }
});

expect(result).toMatchObject({ scope, access_token: 'ACCESS_TOKEN' });
Expand All @@ -58,17 +55,36 @@ describe('[commercetools-helpers] handleBeforeAuth', () => {
it('returns current token for logged in user', async () => {
const scope = 'customer_id';
const result = await handleBeforeAuth({
settings: {},
configuration: {},
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'customerSignMeIn' },
currentToken: { scope }
apolloReq: { operationName: 'customerSignMeIn' }
});

expect(result).toMatchObject({ scope, access_token: 'ACCESS_TOKEN' });
});
});

it('returns token from customToken handler', async () => {
const token = {
access_token: 'CUSTOM_TOKEN',
scope: 'CUSTOM_SCOPE'
};
const customToken = jest.fn().mockImplementation(() => token);

const result = await handleBeforeAuth({
configuration: {
customToken
},
sdkAuth: getSdkAuth(token.scope),
tokenProvider: getTokenProvider(token.scope),
apolloReq: { operationName: 'customerSignMeIn' }
});

expect(customToken).toBeCalled();
expect(result).toMatchObject(token);
});

describe('[commercetools-helpers] handleAfterAuth', () => {
beforeEach(() => {
jest.clearAllMocks();
Expand All @@ -80,7 +96,6 @@ describe('[commercetools-helpers] handleAfterAuth', () => {
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'createCart' },
currentToken: { scope },
response: { errors: [] }
});

Expand All @@ -93,7 +108,6 @@ describe('[commercetools-helpers] handleAfterAuth', () => {
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'customerSignMeIn' },
currentToken: { scope },
response: { errors: [] }
});

Expand All @@ -110,7 +124,6 @@ describe('[commercetools-helpers] handleAfterAuth', () => {
operationName: 'customerSignMeIn',
variables: { draft: { email: 'EMAIL', password: 'PASSWORD' } }
},
currentToken: { scope },
response: { errors: [] }
});

Expand All @@ -128,47 +141,10 @@ describe('[commercetools-helpers] handleAfterAuth', () => {
operationName: 'customerSignMeIn',
variables: { draft: { email: 'EMAIL', password: 'PASSWORD' } }
},
currentToken: { scope },
response: { errors: [] }
});

expect(result).toMatchObject({ scope, access_token: 'LOGIN_TOKEN' });
expect(sdkAuth.customerPasswordFlow).toBeCalledWith({ username: 'EMAIL', password: 'PASSWORD' });
});
});

describe('[commercetools-helpers] handleRetry', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('defaults to false', () => {
const tokenProvider = getTokenProvider('');
const handler = handleRetry({ settings: {}, tokenProvider });
const operation = { operationName: 'any' };
const error = { result: { message: '' } };

expect(handler(1, operation, error)).toBeFalsy();
expect(tokenProvider.invalidateTokenInfo).not.toBeCalled();
});

it('doesnt run more than 3 times', () => {
const tokenProvider = getTokenProvider('');
const handler = handleRetry({ settings: {}, tokenProvider });
const operation = { operationName: 'any' };
const error = { result: { message: 'invalid_token' } };

expect(handler(4, operation, error)).toBeFalsy();
expect(tokenProvider.invalidateTokenInfo).not.toBeCalled();
});

it('calls "invalidateTokenInfo" on "invalid_token" error', () => {
const tokenProvider = getTokenProvider('');
const handler = handleRetry({ settings: {}, tokenProvider });
const operation = { operationName: 'any' };
const error = { result: { message: 'invalid_token' } };

expect(handler(1, operation, error)).toBeTruthy();
expect(tokenProvider.invalidateTokenInfo).toBeCalled();
});
});
17 changes: 17 additions & 0 deletions packages/commercetools/api-client/src/api/accessToken/index.ts
@@ -0,0 +1,17 @@
import { Context } from '@vue-storefront/core';
import { createSdkHelpers } from '../../links/sdkHelpers';

const accessToken = (context: Context, isServer: boolean) => {
const configuration = context.config;

if (isServer) {
// Don't add a cookie to the response if the endpoint was called during SSR
configuration.auth.onTokenChange = () => {};
}

const { tokenProvider } = createSdkHelpers(context.config);

return tokenProvider.getTokenInfo();
};

export default accessToken;
Expand Up @@ -2,7 +2,7 @@ import { Context, CustomQuery } from '@vue-storefront/core';
import updateCart from './../updateCart';
import { CartDetails, CartResponse } from './../../types/Api';
import { ProductVariant } from './../../types/GraphQL';
import { createAddLineItemAction } from './../../helpers/cart/actions';
import { createAddLineItemAction } from '../../helpers/actions/cart';

const addToCart = async (
context: Context,
Expand Down
@@ -1,7 +1,7 @@
import { CustomQuery } from '@vue-storefront/core';
import updateCart from '../updateCart';
import { CartDetails, CartResponse } from '../../types/Api';
import { addDiscountCodeAction } from '../../helpers/cart/actions';
import { addDiscountCodeAction } from '../../helpers/actions/cart';

const applyCartCoupon = async (
settings,
Expand Down
@@ -1,11 +1,5 @@
const customerSignOut = async ({ config, client }) => {
if (config.auth.onTokenRemove) {
config.auth.onTokenRemove();
}

if (client.tokenProvider) {
client.tokenProvider.invalidateTokenInfo();
}
const customerSignOut = async ({ config }) => {
config.auth.onTokenRemove?.();
};

export default customerSignOut;
@@ -1,5 +1,5 @@
import gql from 'graphql-tag';
import { changeCustomerEmailAction, setCustomerFirstNameAction, setCustomerLastNameAction } from '../../helpers/customer';
import { changeCustomerEmailAction, setCustomerFirstNameAction, setCustomerLastNameAction } from '../../helpers/actions/customer';
import CustomerUpdateMeMutation from './defaultMutation';

const customerUpdateMe = async ({ client }, currentUser, updatedUserData) => {
Expand Down
2 changes: 2 additions & 0 deletions packages/commercetools/api-client/src/api/index.ts
@@ -1,3 +1,4 @@
export { default as accessToken } from './accessToken';
export { default as addToCart } from './addToCart';
export { default as applyCartCoupon } from './applyCartCoupon';
export { default as createCart } from './createCart';
Expand All @@ -16,6 +17,7 @@ export { default as getOrders } from './getOrders';
export { default as getProduct } from './getProduct';
export { default as getShippingMethods } from './getShippingMethods';
export { default as isGuest } from './isGuest';
export { default as isLoggedIn } from './isLoggedIn';
export { default as removeCartCoupon } from './removeCartCoupon';
export { default as removeFromCart } from './removeFromCart';
export { default as updateCart } from './updateCart';
Expand Down
12 changes: 4 additions & 8 deletions packages/commercetools/api-client/src/api/isGuest/index.ts
@@ -1,18 +1,14 @@
import { isAnonymousSession, isUserSession } from '../../helpers/utils';

const isGuest = (context) => {
const { client, config } = context;
const { config } = context;

if (config.handleIsGuest) {
if (typeof config.handleIsGuest === 'function') {
return config.handleIsGuest(context);
}

if (client.tokenProvider || context.isProxy) {
const token = config.auth.onTokenRead();
return !isAnonymousSession(token) && !isUserSession(token);
}

return false;
const token = context.config.auth.onTokenRead();
return !isAnonymousSession(token) && !isUserSession(token);
};

export default isGuest;
13 changes: 13 additions & 0 deletions packages/commercetools/api-client/src/api/isLoggedIn/index.ts
@@ -0,0 +1,13 @@
import { isUserSession } from '../../helpers/utils';

const isLoggedIn = (context) => {
const { config } = context;

if (typeof config.handleIsLoggedIn === 'function') {
return config.handleIsLoggedIn(context);
}

return isUserSession(context.config.auth.onTokenRead());
};

export default isLoggedIn;
Expand Up @@ -2,7 +2,7 @@ import { CustomQuery } from '@vue-storefront/core';
import updateCart from '../updateCart';
import { CartDetails, CartResponse } from '../../types/Api';
import { ReferenceInput } from '../../types/GraphQL';
import { removeDiscountCodeAction } from '../../helpers/cart/actions';
import { removeDiscountCodeAction } from '../../helpers/actions/cart';

const removeCartCoupon = async (
context,
Expand Down

0 comments on commit 3073946

Please sign in to comment.