Skip to content
Permalink
Browse files
refactor(commercetools)!: refactor token handling in commercetools (#…
  • Loading branch information
filipsobol committed Oct 4, 2021
1 parent 965c5e9 commit 30739460875bcc7003b132efee9a59353bbeafce
Showing with 1,956 additions and 1,715 deletions.
  1. +3 −2 .github/labeler.yml
  2. +0 −19 packages/commercetools/api-client/__tests__/api/customerSignOut.spec.ts
  3. +3 −3 packages/commercetools/api-client/__tests__/api/isGuest.spec.ts
  4. +58 −0 packages/commercetools/api-client/__tests__/api/isLoggedIn.spec.ts
  5. +1 −1 packages/commercetools/api-client/__tests__/api/setShippingMethod.spec.ts
  6. +1 −1 packages/commercetools/api-client/__tests__/helpers/actions/updateShippingDetails.spec.ts
  7. +29 −53 ...s/api-client/__tests__/{helpers/commercetoolsLink/linkHandlers.spec.ts → links/authLinks.spec.ts}
  8. +17 −0 packages/commercetools/api-client/src/api/accessToken/index.ts
  9. +1 −1 packages/commercetools/api-client/src/api/addToCart/index.ts
  10. +1 −1 packages/commercetools/api-client/src/api/applyCartCoupon/index.ts
  11. +2 −8 packages/commercetools/api-client/src/api/customerSignOut/index.ts
  12. +1 −1 packages/commercetools/api-client/src/api/customerUpdateMe/index.ts
  13. +2 −0 packages/commercetools/api-client/src/api/index.ts
  14. +4 −8 packages/commercetools/api-client/src/api/isGuest/index.ts
  15. +13 −0 packages/commercetools/api-client/src/api/isLoggedIn/index.ts
  16. +1 −1 packages/commercetools/api-client/src/api/removeCartCoupon/index.ts
  17. +1 −1 packages/commercetools/api-client/src/api/removeFromCart/index.ts
  18. +1 −1 packages/commercetools/api-client/src/api/updateCartQuantity/index.ts
  19. +1 −1 packages/commercetools/api-client/src/api/updateShippingDetails/index.ts
  20. +23 −0 packages/commercetools/api-client/src/extensions/emailExtension.ts
  21. +20 −0 packages/commercetools/api-client/src/extensions/index.ts
  22. +26 −0 packages/commercetools/api-client/src/extensions/internationalizationExtension.ts
  23. +64 −0 packages/commercetools/api-client/src/extensions/tokenExtension.ts
  24. +1 −1 packages/commercetools/api-client/src/helpers/{cart/actions.ts → actions/cart.ts}
  25. 0 packages/commercetools/api-client/src/helpers/{customer/index.ts → actions/customer.ts}
  26. +0 −35 packages/commercetools/api-client/src/helpers/commercetoolsLink/authHelpers.ts
  27. +0 −69 packages/commercetools/api-client/src/helpers/commercetoolsLink/createCommerceToolsConnection.ts
  28. +0 −25 packages/commercetools/api-client/src/helpers/commercetoolsLink/graphqlError.ts
  29. +16 −110 packages/commercetools/api-client/src/index.server.ts
  30. +2 −2 packages/commercetools/api-client/src/index.ts
  31. +24 −44 ...ges/commercetools/api-client/src/{helpers/commercetoolsLink/linkHandlers.ts → links/authLinks.ts}
  32. +128 −0 packages/commercetools/api-client/src/links/createLinks.ts
  33. +20 −0 packages/commercetools/api-client/src/links/index.ts
  34. +3 −3 packages/commercetools/api-client/src/{helpers/commercetoolsLink → links}/restrictedOperations.ts
  35. +63 −0 packages/commercetools/api-client/src/links/sdkHelpers.ts
  36. +17 −0 packages/commercetools/api-client/src/types/setup.ts
  37. +2 −1 packages/commercetools/api-client/tsconfig.json
  38. +0 −5 packages/commercetools/composables/__tests__/useUser/factoryParams.spec.ts
  39. +18 −0 packages/commercetools/composables/nuxt/accessToken.js
  40. +0 −2 packages/commercetools/composables/nuxt/helpers/index.js
  41. +4 −1 packages/commercetools/composables/nuxt/index.js
  42. +8 −36 packages/commercetools/composables/nuxt/plugin.js
  43. +0 −9 packages/commercetools/composables/src/useCart/index.ts
  44. +0 −9 packages/commercetools/composables/src/useUser/factoryParams.ts
  45. +6 −11 packages/commercetools/theme/components/AppHeader.vue
  46. +5 −1 packages/commercetools/theme/components/MyAccount/ProfileUpdateForm.vue
  47. +1 −7 packages/commercetools/theme/components/StoreLocaleSelector.vue
  48. +11 −2 packages/commercetools/theme/middleware/is-authenticated.js
  49. +8 −9 packages/commercetools/theme/pages/Checkout/Billing.vue
  50. +8 −9 packages/commercetools/theme/pages/Checkout/Shipping.vue
  51. +1 −1 packages/core/core/tsconfig.json
  52. +15 −0 packages/core/docs/changelog/6329.js
  53. +15 −0 packages/core/docs/commercetools/changelog/6329.js
  54. +2 −4 packages/core/nuxt-theme-module/theme/components/AppHeader.vue
  55. +2 −9 packages/core/nuxt-theme-module/theme/components/CartSidebar.vue
  56. +1 −3 packages/core/nuxt-theme-module/theme/components/WishlistSidebar.vue
  57. +24 −6 packages/core/nuxt-theme-module/theme/layouts/default.vue
  58. +4 −4 packages/core/nuxt-theme-module/theme/pages/MyAccount/BillingDetails.vue
  59. +4 −4 packages/core/nuxt-theme-module/theme/pages/MyAccount/ShippingDetails.vue
  60. +1,270 −1,191 yarn.lock
@@ -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:
@@ -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();
});
});
@@ -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', () => {
@@ -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(() => {
@@ -1,4 +1,4 @@
import { setBillingAddressAction } from './../../../src/helpers/cart/actions';
import { setBillingAddressAction } from './../../../src/helpers/actions/cart';

describe('[commercetools-api-client] setBillingAddressAction', () => {
beforeEach(() => {
@@ -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' })),
@@ -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' });
@@ -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' });
@@ -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' });
@@ -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();
@@ -80,7 +96,6 @@ describe('[commercetools-helpers] handleAfterAuth', () => {
sdkAuth: getSdkAuth(scope),
tokenProvider: getTokenProvider(scope),
apolloReq: { operationName: 'createCart' },
currentToken: { scope },
response: { errors: [] }
});

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

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

@@ -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();
});
});
@@ -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;
@@ -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,
@@ -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,
@@ -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) => {
@@ -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';
@@ -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';
@@ -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;
@@ -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;
@@ -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,

0 comments on commit 3073946

Please sign in to comment.