diff --git a/.changeset/gorgeous-poets-scream.md b/.changeset/gorgeous-poets-scream.md new file mode 100644 index 0000000000..13967e51af --- /dev/null +++ b/.changeset/gorgeous-poets-scream.md @@ -0,0 +1,6 @@ +--- +"@bigcommerce/catalyst-client": minor +"@bigcommerce/catalyst-core": minor +--- + +Removes all usages of the customer impersonation token. Also updates the docs to correspond with the Storefront API Token. diff --git a/.changeset/itchy-rice-kiss.md b/.changeset/itchy-rice-kiss.md new file mode 100644 index 0000000000..9279a9e94d --- /dev/null +++ b/.changeset/itchy-rice-kiss.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/create-catalyst": minor +--- + +Generates a storefront token when using the CLI to init or create a Catalyst storefront. diff --git a/.changeset/new-icons-arrive.md b/.changeset/new-icons-arrive.md new file mode 100644 index 0000000000..6f47904e92 --- /dev/null +++ b/.changeset/new-icons-arrive.md @@ -0,0 +1,6 @@ +--- +"@bigcommerce/catalyst-client": minor +"@bigcommerce/catalyst-core": minor +--- + +Allows the ability to consume a [storefront token](https://developer.bigcommerce.com/docs/rest-authentication/tokens#storefront-tokens). This new token will allow Catalyst to create `customerAccessToken`'s whenever a user logs into their account. This change doesn't include consuming the either token, only adding the ability to pass it in. diff --git a/.changeset/twelve-mangos-pay.md b/.changeset/twelve-mangos-pay.md new file mode 100644 index 0000000000..5fa24e5466 --- /dev/null +++ b/.changeset/twelve-mangos-pay.md @@ -0,0 +1,5 @@ +--- +"@bigcommerce/create-catalyst": minor +--- + +Remove generating a customer impersonation token as we are using the API Token + Customer Access Token approach" diff --git a/.env.example b/.env.example index 25a1548379..bacc5581f5 100644 --- a/.env.example +++ b/.env.example @@ -2,9 +2,9 @@ # The control panel URL is of the form `https://store-{hash}.mybigcommerce.com`. BIGCOMMERCE_STORE_HASH= -# A bearer token that authorizes server-to-server requests to the GraphQL Storefront API -# See https://developer.bigcommerce.com/docs/rest-authentication/tokens/customer-impersonation-token -BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN= +# A JWT Token for accessing the Storefront API. Enables server-to-server requests if allowed_cors_origins is omitted. +# See https://developer.bigcommerce.com/docs/rest-authentication/tokens#storefront-tokens +BIGCOMMERCE_STOREFRONT_TOKEN= # The Channel ID for the selling channel being serviced by this Catalyst storefront. # Channel ID 1 will allow you to load the same data being used on the default Stencil storefront on your store, diff --git a/.github/workflows/basic.yml b/.github/workflows/basic.yml index 53b9739640..965c030e6e 100644 --- a/.github/workflows/basic.yml +++ b/.github/workflows/basic.yml @@ -13,7 +13,7 @@ env: TURBO_TEAM: ${{ vars.TURBO_TEAM }} TURBO_REMOTE_CACHE_SIGNATURE_KEY: ${{ secrets.TURBO_REMOTE_CACHE_SIGNATURE_KEY }} BIGCOMMERCE_STORE_HASH: ${{ secrets.BIGCOMMERCE_STORE_HASH }} - BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN: ${{ secrets.BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN }} + BIGCOMMERCE_STOREFRONT_TOKEN: ${{ secrets.BIGCOMMERCE_STOREFRONT_TOKEN }} BIGCOMMERCE_CHANNEL_ID: ${{ secrets.BIGCOMMERCE_CHANNEL_ID }} jobs: diff --git a/core/.env.example b/core/.env.example index 25a1548379..bacc5581f5 100644 --- a/core/.env.example +++ b/core/.env.example @@ -2,9 +2,9 @@ # The control panel URL is of the form `https://store-{hash}.mybigcommerce.com`. BIGCOMMERCE_STORE_HASH= -# A bearer token that authorizes server-to-server requests to the GraphQL Storefront API -# See https://developer.bigcommerce.com/docs/rest-authentication/tokens/customer-impersonation-token -BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN= +# A JWT Token for accessing the Storefront API. Enables server-to-server requests if allowed_cors_origins is omitted. +# See https://developer.bigcommerce.com/docs/rest-authentication/tokens#storefront-tokens +BIGCOMMERCE_STOREFRONT_TOKEN= # The Channel ID for the selling channel being serviced by this Catalyst storefront. # Channel ID 1 will allow you to load the same data being used on the default Stencil storefront on your store, diff --git a/core/app/[locale]/(default)/(auth)/register/page-data.ts b/core/app/[locale]/(default)/(auth)/register/page-data.ts index 977aa45959..25bf2569dd 100644 --- a/core/app/[locale]/(default)/(auth)/register/page-data.ts +++ b/core/app/[locale]/(default)/(auth)/register/page-data.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { FormFieldsFragment } from '~/components/form-fields/fragment'; @@ -69,7 +69,7 @@ interface Props { } export const getRegisterCustomerQuery = cache(async ({ address, customer }: Props = {}) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: RegisterCustomerQuery, @@ -80,7 +80,7 @@ export const getRegisterCustomerQuery = cache(async ({ address, customer }: Prop customerSortBy: customer?.sortBy, }, fetchOptions: { cache: 'no-store' }, - customerId, + customerAccessToken, }); const addressFields = response.data.site.settings?.formFields.shippingAddress; diff --git a/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts b/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts index c8639e7824..3aa7210220 100644 --- a/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts +++ b/core/app/[locale]/(default)/(faceted)/category/[slug]/page-data.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -31,13 +31,13 @@ const CategoryPageQuery = graphql( type Variables = VariablesOf; export const getCategoryPageData = cache(async (variables: Variables) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: CategoryPageQuery, variables, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); return response.data.site; diff --git a/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts b/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts index 39670b1b44..b885cb4ec6 100644 --- a/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts +++ b/core/app/[locale]/(default)/(faceted)/fetch-faceted-search.ts @@ -2,7 +2,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { PaginationFragment } from '~/client/fragments/pagination'; import { graphql, VariablesOf } from '~/client/graphql'; @@ -167,15 +167,15 @@ interface ProductSearch { const getProductSearchResults = cache( async ({ limit = 9, after, before, sort, filters }: ProductSearch) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const filterArgs = { filters, sort }; const paginationArgs = before ? { last: limit, before } : { first: limit, after }; const response = await client.fetch({ document: GetProductSearchResultsQuery, variables: { ...filterArgs, ...paginationArgs }, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate: 300 } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate: 300 } }, }); const { site } = response.data; diff --git a/core/app/[locale]/(default)/account/(tabs)/addresses/_actions/delete-address.ts b/core/app/[locale]/(default)/account/(tabs)/addresses/_actions/delete-address.ts index 3f84c60aff..6891daad19 100644 --- a/core/app/[locale]/(default)/account/(tabs)/addresses/_actions/delete-address.ts +++ b/core/app/[locale]/(default)/account/(tabs)/addresses/_actions/delete-address.ts @@ -3,7 +3,7 @@ import { revalidatePath } from 'next/cache'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; @@ -34,13 +34,12 @@ const DeleteCustomerAddressMutation = graphql(` export const deleteAddress = async (addressId: number): Promise => { const t = await getTranslations('Account.Addresses.Delete'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const response = await client.fetch({ document: DeleteCustomerAddressMutation, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, variables: { input: { diff --git a/core/app/[locale]/(default)/account/(tabs)/addresses/add/_actions/add-address.ts b/core/app/[locale]/(default)/account/(tabs)/addresses/add/_actions/add-address.ts index 30bd6a590e..1d97dcf8ea 100644 --- a/core/app/[locale]/(default)/account/(tabs)/addresses/add/_actions/add-address.ts +++ b/core/app/[locale]/(default)/account/(tabs)/addresses/add/_actions/add-address.ts @@ -3,7 +3,7 @@ import { revalidatePath } from 'next/cache'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { parseAccountFormData } from '~/components/form-fields/shared/parse-fields'; @@ -56,8 +56,7 @@ export const addAddress = async ({ reCaptchaToken?: string; }) => { const t = await getTranslations('Account.Addresses.Add.Form'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsed = parseAccountFormData(formData); @@ -71,7 +70,7 @@ export const addAddress = async ({ const response = await client.fetch({ document: AddCustomerAddressMutation, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, variables: { input: parsed, diff --git a/core/app/[locale]/(default)/account/(tabs)/addresses/add/page.tsx b/core/app/[locale]/(default)/account/(tabs)/addresses/add/page.tsx index 26fce41db1..c2998530f3 100644 --- a/core/app/[locale]/(default)/account/(tabs)/addresses/add/page.tsx +++ b/core/app/[locale]/(default)/account/(tabs)/addresses/add/page.tsx @@ -1,6 +1,6 @@ import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, ResultOf } from '~/client/graphql'; import { FormFieldsFragment } from '~/components/form-fields/fragment'; @@ -69,12 +69,11 @@ export async function generateMetadata() { export default async function AddPage() { const t = await getTranslations('Account.Addresses.Add'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: CustomerNewAdressQuery, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, variables: { shippingSorting: 'SORT_ORDER', diff --git a/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/_actions/update-address.ts b/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/_actions/update-address.ts index 02a40fc05b..b9b90ae4f0 100644 --- a/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/_actions/update-address.ts +++ b/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/_actions/update-address.ts @@ -3,7 +3,7 @@ import { revalidatePath } from 'next/cache'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { parseAccountFormData } from '~/components/form-fields/shared/parse-fields'; @@ -61,8 +61,7 @@ export const updateAddress = async ({ reCaptchaToken?: string; }) => { const t = await getTranslations('Account.Addresses.Edit.Form'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsed = parseAccountFormData(formData); @@ -76,7 +75,7 @@ export const updateAddress = async ({ const response = await client.fetch({ document: UpdateCustomerAddressMutation, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, variables: { input: { diff --git a/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/page.tsx b/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/page.tsx index cc1695d4e7..18a917d6f8 100644 --- a/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/page.tsx +++ b/core/app/[locale]/(default)/account/(tabs)/addresses/edit/[slug]/page.tsx @@ -2,7 +2,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { notFound } from 'next/navigation'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { FormFieldValuesFragment } from '~/client/fragments/form-fields-values'; import { PaginationFragment } from '~/client/fragments/pagination'; @@ -104,12 +104,11 @@ interface Props { export default async function Edit({ params: { slug } }: Props) { const t = await getTranslations('Account.Addresses.Edit'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: CustomerEditAddressQuery, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, variables: { countryCode: null, diff --git a/core/app/[locale]/(default)/account/(tabs)/addresses/page-data.ts b/core/app/[locale]/(default)/account/(tabs)/addresses/page-data.ts index 25a4eec08c..a484dac738 100644 --- a/core/app/[locale]/(default)/account/(tabs)/addresses/page-data.ts +++ b/core/app/[locale]/(default)/account/(tabs)/addresses/page-data.ts @@ -1,7 +1,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { FormFieldValuesFragment } from '~/client/fragments/form-fields-values'; import { PaginationFragment } from '~/client/fragments/pagination'; @@ -52,13 +52,13 @@ interface Pagination { export const getCustomerAddresses = cache( async ({ before = '', after = '', limit = 9 }: Pagination) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const paginationArgs = before ? { last: limit, before } : { first: limit, after }; const response = await client.fetch({ document: GetCustomerAddressesQuery, variables: { ...paginationArgs }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/account/(tabs)/settings/_actions/update-customer.ts b/core/app/[locale]/(default)/account/(tabs)/settings/_actions/update-customer.ts index 80352e4f15..1148a953a5 100644 --- a/core/app/[locale]/(default)/account/(tabs)/settings/_actions/update-customer.ts +++ b/core/app/[locale]/(default)/account/(tabs)/settings/_actions/update-customer.ts @@ -3,7 +3,7 @@ import { revalidatePath } from 'next/cache'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { parseAccountFormData } from '~/components/form-fields/shared/parse-fields'; @@ -65,8 +65,7 @@ interface UpdateCustomerForm { export const updateCustomer = async ({ formData, reCaptchaToken }: UpdateCustomerForm) => { const t = await getTranslations('Account.Settings.UpdateCustomer'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); formData.delete('g-recaptcha-response'); @@ -81,7 +80,7 @@ export const updateCustomer = async ({ formData, reCaptchaToken }: UpdateCustome const response = await client.fetch({ document: UpdateCustomerMutation, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, variables: { input: parsed, diff --git a/core/app/[locale]/(default)/account/(tabs)/settings/change-password/_actions/change-password.ts b/core/app/[locale]/(default)/account/(tabs)/settings/change-password/_actions/change-password.ts index deaec89f47..30691df16e 100644 --- a/core/app/[locale]/(default)/account/(tabs)/settings/change-password/_actions/change-password.ts +++ b/core/app/[locale]/(default)/account/(tabs)/settings/change-password/_actions/change-password.ts @@ -3,7 +3,7 @@ import { getTranslations } from 'next-intl/server'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; @@ -51,8 +51,7 @@ export interface State { export const changePassword = async (_previousState: unknown, formData: FormData) => { const t = await getTranslations('Account.Settings.ChangePassword'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsedData = CustomerChangePasswordSchema.parse({ @@ -69,7 +68,7 @@ export const changePassword = async (_previousState: unknown, formData: FormData newPassword: parsedData.newPassword, }, }, - customerId, + customerAccessToken, }); const result = response.data.customer.changePassword; diff --git a/core/app/[locale]/(default)/account/(tabs)/settings/page-data.tsx b/core/app/[locale]/(default)/account/(tabs)/settings/page-data.tsx index c7b42ff44c..0056319548 100644 --- a/core/app/[locale]/(default)/account/(tabs)/settings/page-data.tsx +++ b/core/app/[locale]/(default)/account/(tabs)/settings/page-data.tsx @@ -1,7 +1,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { FormFieldValuesFragment } from '~/client/fragments/form-fields-values'; import { PaginationFragment } from '~/client/fragments/pagination'; @@ -91,7 +91,7 @@ interface Props { } export const getCustomerSettingsQuery = cache(async ({ address, customer }: Props = {}) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: CustomerSettingsQuery, @@ -102,7 +102,7 @@ export const getCustomerSettingsQuery = cache(async ({ address, customer }: Prop customerSortBy: customer?.sortBy, }, fetchOptions: { cache: 'no-store' }, - customerId, + customerAccessToken, }); const addressFields = response.data.site.settings?.formFields.shippingAddress; @@ -168,13 +168,13 @@ export interface CustomerAddressesArgs { export const getCustomerAddresses = cache( async ({ before = '', after = '', limit = 9 }: CustomerAddressesArgs) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const paginationArgs = before ? { last: limit, before } : { first: limit, after }; const response = await client.fetch({ document: GetCustomerAddressesQuery, variables: { ...paginationArgs }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts b/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts index 85be76e95a..a1362c5c95 100644 --- a/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts +++ b/core/app/[locale]/(default)/cart/_actions/redirect-to-checkout.ts @@ -3,7 +3,7 @@ import { getLocale } from 'next-intl/server'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { redirect } from '~/i18n/routing'; @@ -23,13 +23,13 @@ const CheckoutRedirectMutation = graphql(` export const redirectToCheckout = async (formData: FormData) => { const locale = await getLocale(); const cartId = z.string().parse(formData.get('cartId')); - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: CheckoutRedirectMutation, variables: { cartId }, fetchOptions: { cache: 'no-store' }, - customerId, + customerAccessToken, }); const url = data.cart.createCartRedirectUrls.redirectUrls?.redirectedCheckoutUrl; diff --git a/core/app/[locale]/(default)/cart/_actions/remove-item.ts b/core/app/[locale]/(default)/cart/_actions/remove-item.ts index 326848b15d..085ecdc157 100644 --- a/core/app/[locale]/(default)/cart/_actions/remove-item.ts +++ b/core/app/[locale]/(default)/cart/_actions/remove-item.ts @@ -3,7 +3,7 @@ import { revalidateTag } from 'next/cache'; import { cookies } from 'next/headers'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -26,7 +26,7 @@ type DeleteCartLineItemInput = Variables['input']; export async function removeItem({ lineItemEntityId, }: Omit) { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const cartId = cookies().get('cartId')?.value; @@ -47,7 +47,7 @@ export async function removeItem({ lineItemEntityId, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/_components/coupon-code/apply-coupon-code.ts b/core/app/[locale]/(default)/cart/_components/coupon-code/apply-coupon-code.ts index 9afcd20a02..0443593a0a 100644 --- a/core/app/[locale]/(default)/cart/_components/coupon-code/apply-coupon-code.ts +++ b/core/app/[locale]/(default)/cart/_components/coupon-code/apply-coupon-code.ts @@ -3,7 +3,7 @@ import { revalidateTag } from 'next/cache'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -26,7 +26,7 @@ const ApplyCheckoutCouponMutation = graphql(` `); export const applyCouponCode = async (formData: FormData) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsedData = ApplyCouponCodeSchema.parse({ @@ -44,7 +44,7 @@ export const applyCouponCode = async (formData: FormData) => { }, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/_components/coupon-code/remove-coupon-code.ts b/core/app/[locale]/(default)/cart/_components/coupon-code/remove-coupon-code.ts index a055f2b15b..2a3621db51 100644 --- a/core/app/[locale]/(default)/cart/_components/coupon-code/remove-coupon-code.ts +++ b/core/app/[locale]/(default)/cart/_components/coupon-code/remove-coupon-code.ts @@ -3,7 +3,7 @@ import { revalidateTag } from 'next/cache'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -26,7 +26,7 @@ const UnapplyCheckoutCouponMutation = graphql(` `); export const removeCouponCode = async (formData: FormData) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsedData = RemoveCouponCodeSchema.parse({ @@ -44,7 +44,7 @@ export const removeCouponCode = async (formData: FormData) => { }, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/_components/item-quantity/update-item-quantity.ts b/core/app/[locale]/(default)/cart/_components/item-quantity/update-item-quantity.ts index 4ab616f964..369f7ec81b 100644 --- a/core/app/[locale]/(default)/cart/_components/item-quantity/update-item-quantity.ts +++ b/core/app/[locale]/(default)/cart/_components/item-quantity/update-item-quantity.ts @@ -3,7 +3,7 @@ import { revalidatePath } from 'next/cache'; import { cookies } from 'next/headers'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql, VariablesOf } from '~/client/graphql'; @@ -36,7 +36,7 @@ export async function updateItemQuantity({ variantEntityId, selectedOptions, }: UpdateProductQuantityParams) { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const cartId = cookies().get('cartId')?.value; @@ -72,7 +72,7 @@ export async function updateItemQuantity({ }, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/_components/shipping-info/submit-shipping-info.ts b/core/app/[locale]/(default)/cart/_components/shipping-info/submit-shipping-info.ts index 1e863f24fa..e34099b949 100644 --- a/core/app/[locale]/(default)/cart/_components/shipping-info/submit-shipping-info.ts +++ b/core/app/[locale]/(default)/cart/_components/shipping-info/submit-shipping-info.ts @@ -3,7 +3,7 @@ import { revalidateTag } from 'next/cache'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -47,7 +47,7 @@ export const submitShippingInfo = async ( lineItems: Array<{ quantity: number; lineItemEntityId: string }>; }, ) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsedData = ShippingInfoSchema.parse({ @@ -81,7 +81,7 @@ export const submitShippingInfo = async ( }, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); @@ -108,7 +108,7 @@ export const submitShippingInfo = async ( }, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/_components/shipping-options/submit-shipping-costs.ts b/core/app/[locale]/(default)/cart/_components/shipping-options/submit-shipping-costs.ts index 479f79ba15..0dc7719b1d 100644 --- a/core/app/[locale]/(default)/cart/_components/shipping-options/submit-shipping-costs.ts +++ b/core/app/[locale]/(default)/cart/_components/shipping-options/submit-shipping-costs.ts @@ -3,7 +3,7 @@ import { revalidateTag } from 'next/cache'; import { z } from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -29,7 +29,7 @@ export const submitShippingCosts = async ( checkoutEntityId: string, consignmentEntityId: string, ) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const parsedData = ShippingCostSchema.parse({ @@ -47,7 +47,7 @@ export const submitShippingCosts = async ( }, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/app/[locale]/(default)/cart/page.tsx b/core/app/[locale]/(default)/cart/page.tsx index 8a25d5a004..5807a48fb3 100644 --- a/core/app/[locale]/(default)/cart/page.tsx +++ b/core/app/[locale]/(default)/cart/page.tsx @@ -1,7 +1,7 @@ import { cookies } from 'next/headers'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { TAGS } from '~/client/tags'; @@ -53,12 +53,12 @@ export default async function Cart() { const t = await getTranslations('Cart'); - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: CartPageQuery, variables: { cartId }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store', next: { diff --git a/core/app/[locale]/(default)/compare/page.tsx b/core/app/[locale]/(default)/compare/page.tsx index 506e380252..39959f46dc 100644 --- a/core/app/[locale]/(default)/compare/page.tsx +++ b/core/app/[locale]/(default)/compare/page.tsx @@ -2,7 +2,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { getFormatter, getTranslations } from 'next-intl/server'; import * as z from 'zod'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { PricingFragment } from '~/client/fragments/pricing'; import { graphql } from '~/client/graphql'; @@ -96,8 +96,7 @@ interface Props { export default async function Compare({ searchParams }: Props) { const t = await getTranslations('Compare'); const format = await getFormatter(); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const parsed = CompareParamsSchema.parse(searchParams); const productIds = parsed.ids?.filter((id) => !Number.isNaN(id)); @@ -108,8 +107,8 @@ export default async function Compare({ searchParams }: Props) { entityIds: productIds ?? [], first: productIds?.length ? MAX_COMPARE_LIMIT : 0, }, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); const products = removeEdgesAndNodes(data.site.products).map((product) => ({ diff --git a/core/app/[locale]/(default)/page.tsx b/core/app/[locale]/(default)/page.tsx index 13395b54bf..59a15ca5de 100644 --- a/core/app/[locale]/(default)/page.tsx +++ b/core/app/[locale]/(default)/page.tsx @@ -1,7 +1,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { getTranslations, setRequestLocale } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -44,13 +44,12 @@ export default async function Home({ params: { locale } }: Props) { setRequestLocale(locale); const t = await getTranslations('Home'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: HomePageQuery, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); const featuredProducts = removeEdgesAndNodes(data.site.featuredProducts); diff --git a/core/app/[locale]/(default)/product/[slug]/_components/related-products.tsx b/core/app/[locale]/(default)/product/[slug]/_components/related-products.tsx index 0863367f94..7561f0e993 100644 --- a/core/app/[locale]/(default)/product/[slug]/_components/related-products.tsx +++ b/core/app/[locale]/(default)/product/[slug]/_components/related-products.tsx @@ -1,7 +1,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { getTranslations } from 'next-intl/server'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -33,14 +33,13 @@ interface Props { export const RelatedProducts = async ({ productId }: Props) => { const t = await getTranslations('Product.Carousel'); - - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: RelatedProductsQuery, variables: { entityId: productId }, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); const product = data.site.product; diff --git a/core/app/[locale]/(default)/product/[slug]/page-data.ts b/core/app/[locale]/(default)/product/[slug]/page-data.ts index 2d16108c15..f61ac31c59 100644 --- a/core/app/[locale]/(default)/product/[slug]/page-data.ts +++ b/core/app/[locale]/(default)/product/[slug]/page-data.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { ProductItemFragment } from '~/client/fragments/product-item'; import { graphql, VariablesOf } from '~/client/graphql'; @@ -65,13 +65,13 @@ const ProductPageQuery = graphql( type Variables = VariablesOf; export const getProduct = cache(async (variables: Variables) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data } = await client.fetch({ document: ProductPageQuery, variables, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); return data.site.product; diff --git a/core/app/[locale]/(default)/product/[slug]/static/page.tsx b/core/app/[locale]/(default)/product/[slug]/static/page.tsx index 94bea74ae3..9819400223 100644 --- a/core/app/[locale]/(default)/product/[slug]/static/page.tsx +++ b/core/app/[locale]/(default)/product/[slug]/static/page.tsx @@ -1,7 +1,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { getChannelIdFromLocale } from '~/channels.config'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; @@ -32,13 +32,15 @@ interface Options { } const getFeaturedProducts = cache(async ({ first = 12 }: Options = {}) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: FeaturedProductsQuery, variables: { first }, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate: revalidateTarget } }, + customerAccessToken, + fetchOptions: customerAccessToken + ? { cache: 'no-store' } + : { next: { revalidate: revalidateTarget } }, channelId: getChannelIdFromLocale(), // Using default channel id }); diff --git a/core/app/api/internal-auth.ts b/core/app/api/internal-auth.ts deleted file mode 100644 index 15277c8d1d..0000000000 --- a/core/app/api/internal-auth.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; - -const token = process.env.BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN; - -if (!token) { - throw new Error('BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN is not defined'); -} - -type Handler = (request: NextRequest) => NextResponse | Promise; - -export const withInternalAuth = (handler: Handler) => { - return (request: NextRequest) => { - const requestHeaders = new Headers(request.headers); - const requestToken = requestHeaders.get('x-internal-token'); - - if (requestToken !== token) { - return Response.json({ error: 'Unauthorized' }, { status: 401 }); - } - - return handler(request); - }; -}; diff --git a/core/auth.ts b/core/auth.ts index 09b4d5abaa..3d5e776a71 100644 --- a/core/auth.ts +++ b/core/auth.ts @@ -10,6 +10,9 @@ import { graphql } from './client/graphql'; const LoginMutation = graphql(` mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { + customerAccessToken { + value + } customer { entityId firstName @@ -32,16 +35,10 @@ const AssignCartToCustomerMutation = graphql(` } `); -const UnassignCartFromCustomerMutation = graphql(` - mutation UnassignCartFromCustomer( - $unassignCartFromCustomerInput: UnassignCartFromCustomerInput! - ) { - cart { - unassignCartFromCustomer(input: $unassignCartFromCustomerInput) { - cart { - entityId - } - } +const LogoutMutation = graphql(` + mutation LogoutMutation { + logout { + result } } `); @@ -62,25 +59,25 @@ const config = { jwt: ({ token, user }) => { // user can actually be undefined // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (user?.id) { - token.id = user.id; + if (user?.customerAccessToken) { + token.customerAccessToken = user.customerAccessToken; } return token; }, session({ session, token }) { - if (token.id) { - session.user.id = token.id; + if (token.customerAccessToken) { + session.customerAccessToken = token.customerAccessToken; } return session; }, }, events: { - async signIn({ user }) { + async signIn({ user: { customerAccessToken } }) { const cookieCartId = cookies().get('cartId')?.value; - if (cookieCartId && user.id) { + if (cookieCartId) { try { await client.fetch({ document: AssignCartToCustomerMutation, @@ -89,7 +86,7 @@ const config = { cartEntityId: cookieCartId, }, }, - customerId: user.id, + customerAccessToken, fetchOptions: { cache: 'no-store', }, @@ -101,20 +98,14 @@ const config = { } }, async signOut(message) { - const cookieCartId = cookies().get('cartId')?.value; + const customerAccessToken = 'token' in message ? message.token?.customerAccessToken : null; - const customerId = 'token' in message ? message.token?.id : null; - - if (customerId && cookieCartId) { + if (customerAccessToken) { try { await client.fetch({ - document: UnassignCartFromCustomerMutation, - variables: { - unassignCartFromCustomerInput: { - cartEntityId: cookieCartId, - }, - }, - customerId, + document: LogoutMutation, + variables: {}, + customerAccessToken, fetchOptions: { cache: 'no-store', }, @@ -145,14 +136,14 @@ const config = { const result = response.data.login; - if (!result.customer) { + if (!result.customer || !result.customerAccessToken) { return null; } return { - id: result.customer.entityId.toString(), name: `${result.customer.firstName} ${result.customer.lastName}`, email: result.customer.email, + customerAccessToken: result.customerAccessToken.value, }; }, }), @@ -161,34 +152,34 @@ const config = { const { handlers, auth, signIn, signOut } = NextAuth(config); -const getSessionCustomerId = async () => { +const getSessionCustomerAccessToken = async () => { try { const session = await auth(); - return session?.user.id; + return session?.customerAccessToken; } catch { // No empty } }; -export { handlers, auth, signIn, signOut, getSessionCustomerId }; +export { handlers, auth, signIn, signOut, getSessionCustomerAccessToken }; declare module 'next-auth' { interface Session { - user: { - id: string; - } & DefaultSession['user']; + user?: DefaultSession['user']; + customerAccessToken?: string; } interface User { - id?: string; name?: string | null; email?: string | null; + customerAccessToken?: string; } } declare module 'next-auth/jwt' { interface JWT { id?: string; + customerAccessToken?: string; } } diff --git a/core/client/index.ts b/core/client/index.ts index 21e6d28aa5..a9f7cecde7 100644 --- a/core/client/index.ts +++ b/core/client/index.ts @@ -7,7 +7,7 @@ import { getChannelIdFromLocale } from '~/channels.config'; import { backendUserAgent } from '../userAgent'; export const client = createClient({ - customerImpersonationToken: process.env.BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN ?? '', + storefrontToken: process.env.BIGCOMMERCE_STOREFRONT_TOKEN ?? '', xAuthToken: process.env.BIGCOMMERCE_ACCESS_TOKEN ?? '', storeHash: process.env.BIGCOMMERCE_STORE_HASH ?? '', channelId: process.env.BIGCOMMERCE_CHANNEL_ID, diff --git a/core/client/mutations/add-cart-line-item.ts b/core/client/mutations/add-cart-line-item.ts index b03926278a..00f3aa530e 100644 --- a/core/client/mutations/add-cart-line-item.ts +++ b/core/client/mutations/add-cart-line-item.ts @@ -1,4 +1,4 @@ -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '..'; import { graphql, VariablesOf } from '../graphql'; @@ -22,12 +22,12 @@ export const addCartLineItem = async ( cartEntityId: AddCartLineItemsInput['cartEntityId'], data: AddCartLineItemsInput['data'], ) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: AddCartLineItemMutation, variables: { input: { cartEntityId, data } }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/client/mutations/create-cart.ts b/core/client/mutations/create-cart.ts index d46a4282a7..c244d75228 100644 --- a/core/client/mutations/create-cart.ts +++ b/core/client/mutations/create-cart.ts @@ -1,4 +1,4 @@ -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '..'; import { graphql, VariablesOf } from '../graphql'; @@ -20,7 +20,7 @@ type CreateCartInput = Variables['createCartInput']; type LineItems = CreateCartInput['lineItems']; export const createCart = async (cartItems: LineItems) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: CreateCartMutation, @@ -29,7 +29,7 @@ export const createCart = async (cartItems: LineItems) => { lineItems: cartItems, }, }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store' }, }); diff --git a/core/client/queries/get-cart.ts b/core/client/queries/get-cart.ts index 3f28930b69..1adbebb68a 100644 --- a/core/client/queries/get-cart.ts +++ b/core/client/queries/get-cart.ts @@ -1,6 +1,6 @@ import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '..'; import { graphql } from '../graphql'; @@ -122,12 +122,12 @@ const GetCartQuery = graphql( ); export const getCart = cache(async (cartId?: string, channelId?: string) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const response = await client.fetch({ document: GetCartQuery, variables: { cartId }, - customerId, + customerAccessToken, fetchOptions: { cache: 'no-store', next: { diff --git a/core/components/footer/footer.tsx b/core/components/footer/footer.tsx index baf9cf215b..aefb208d90 100644 --- a/core/components/footer/footer.tsx +++ b/core/components/footer/footer.tsx @@ -10,7 +10,7 @@ import { import { JSX } from 'react'; import { LayoutQuery } from '~/app/[locale]/(default)/query'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { readFragment } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -36,11 +36,11 @@ const socialIcons: Record = { }; export const Footer = async () => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data: response } = await client.fetch({ document: LayoutQuery, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); const data = readFragment(FooterFragment, response).site; diff --git a/core/components/header/_actions/get-search-results.ts b/core/components/header/_actions/get-search-results.ts index 920f48914b..e5b4ce531b 100644 --- a/core/components/header/_actions/get-search-results.ts +++ b/core/components/header/_actions/get-search-results.ts @@ -3,7 +3,7 @@ import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; import { cache } from 'react'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { graphql } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -39,14 +39,14 @@ const GetQuickSearchResultsQuery = graphql( ); export const getSearchResults = cache(async (searchTerm: string) => { - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); try { const response = await client.fetch({ document: GetQuickSearchResultsQuery, variables: { filters: { searchTerm } }, - customerId, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + customerAccessToken, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); const { products } = response.data.site.search.searchProducts; diff --git a/core/components/header/index.tsx b/core/components/header/index.tsx index 8f24088e41..37eaa93921 100644 --- a/core/components/header/index.tsx +++ b/core/components/header/index.tsx @@ -3,7 +3,7 @@ import { getLocale, getTranslations } from 'next-intl/server'; import { ReactNode, Suspense } from 'react'; import { LayoutQuery } from '~/app/[locale]/(default)/query'; -import { getSessionCustomerId } from '~/auth'; +import { getSessionCustomerAccessToken } from '~/auth'; import { client } from '~/client'; import { readFragment } from '~/client/graphql'; import { revalidate } from '~/client/revalidate-target'; @@ -27,11 +27,11 @@ interface Props { export const Header = async ({ cart }: Props) => { const locale = await getLocale(); const t = await getTranslations('Components.Header'); - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); const { data: response } = await client.fetch({ document: LayoutQuery, - fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, + fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, }); const data = readFragment(HeaderFragment, response).site; @@ -58,7 +58,7 @@ export const Header = async ({ cart }: Props) => { return ( { } } - const customerId = await getSessionCustomerId(); + const customerAccessToken = await getSessionCustomerAccessToken(); let postfix = ''; - if (!request.nextUrl.search && !customerId && request.method === 'GET') { + if (!request.nextUrl.search && !customerAccessToken && request.method === 'GET') { postfix = '/static'; } diff --git a/core/scripts/generate.cjs b/core/scripts/generate.cjs index 84c8be1a9e..0af8f38050 100644 --- a/core/scripts/generate.cjs +++ b/core/scripts/generate.cjs @@ -21,10 +21,10 @@ const getChannelId = () => { }; const getToken = () => { - const token = process.env.BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN; + const token = process.env.BIGCOMMERCE_STOREFRONT_TOKEN; if (!token) { - throw new Error('Missing customer impersonation token'); + throw new Error('Missing storefront token'); } return token; diff --git a/core/tests/ui/e2e/checkout.spec.ts b/core/tests/ui/e2e/checkout.spec.ts index 5c24269934..35c7d056eb 100644 --- a/core/tests/ui/e2e/checkout.spec.ts +++ b/core/tests/ui/e2e/checkout.spec.ts @@ -98,9 +98,8 @@ test.describe('desktop', () => { await page.getByRole('button', { name: 'Proceed to checkout' }).click(); await waitForShippingForm(page, isMobile); - await enterShopperDetails(page); - await page.getByRole('button', { name: 'Continue' }).click(); + await page.getByText(customer.email).isVisible(); await page.getByRole('heading', { name: 'Payment', exact: true }).waitFor(); await enterCreditCardDetails(page); diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 6769cbd4a0..c740c261d0 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -27,7 +27,7 @@ The store hash is visible as part of the store's canonical URL, and you can also https://{BIGCOMMERCE_STORE_HASH}.mybigcommerce.com/manage ``` -### BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN +### BIGCOMMERCE_STOREFRONT_TOKEN > [!CAUTION] > This token is a **sensitive secret**. Do not expose outside environment variables. @@ -41,11 +41,11 @@ https://{BIGCOMMERCE_STORE_HASH}.mybigcommerce.com/manage This bearer token authorizes access to the [GraphQL Storefront API](https://developer.bigcommerce.com/docs/storefront/graphql) and supports operations that request information specific to individual customers, such as getting wishlist items. **Without the CLI** -Create a store-level or app-level API account with the following token creation scope. Then use the API account access token to [Create a customer impersonation token](https://developer.bigcommerce.com/docs/rest-authentication/tokens/customer-impersonation-token#create-a-token). +Create a store-level or app-level API account with the following token creation scope. Then use the API account access token to [Create a storefront token](https://developer.bigcommerce.com/docs/rest-authentication/tokens#create-a-token). -| UI Name | Permission | Parameter | -| :------------------------------------------- | :--------- | :-------------------------------------------- | -| Storefront API Customer Impersonation Tokens | modify | `store_storefront_api_customer_impersonation` | +| UI Name | Permission | Parameter | +|:----------------------|:-----------|:-----------------------| +| Storefront API tokens | modify | `store_storefront_api` | ### BIGCOMMERCE_CHANNEL_ID diff --git a/integrations/algolia/integration.patch b/integrations/algolia/integration.patch index 9ccc3b483c..82c82ce4ba 100644 --- a/integrations/algolia/integration.patch +++ b/integrations/algolia/integration.patch @@ -26,7 +26,7 @@ index 344c40e3..4e218344 100644 +import { createFetchRequester } from '@algolia/requester-fetch'; +import { ResultOf } from 'gql.tada'; - import { getSessionCustomerId } from '~/auth'; + import { getSessionCustomerAccessToken } from '~/auth'; -import { ProductCardFragment } from '~/components/product-card'; +import { PricingFragment, ProductCardFragment } from '~/components/product-card'; @@ -108,7 +108,7 @@ index 344c40e3..4e218344 100644 +} -export const getQuickSearchResults = cache(async ({ searchTerm }: QuickSearch) => { -- const customerId = await getSessionCustomerId(); +- const customerAccessToken = await getSessionCustomerAccessToken(); +interface Product { + name: string; + brand_id: number; @@ -193,8 +193,8 @@ index 344c40e3..4e218344 100644 - const response = await client.fetch({ - document: GET_QUICK_SEARCH_RESULTS_QUERY, - variables: { filters: { searchTerm } }, -- customerId, -- fetchOptions: customerId ? { cache: 'no-store' } : { next: { revalidate } }, +- customerAccessToken, +- fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } }, - }); +interface HighlightResultItem { + value: string; @@ -244,7 +244,7 @@ index 344c40e3..4e218344 100644 + +export const getQuickSearchResults = cache(async ({ searchTerm }: QuickSearch) => { + const selectedCurrency = 'USD'; // TODO: use selected storefront currency -+ const customerId = await getSessionCustomerId(); // Customer specific product viz / pricing not implmented in Algolia's app integration yet ++ const customerAccessToken = await getSessionCustomerAccessToken(); // Customer specific product viz / pricing not implmented in Algolia's app integration yet + + try { + const { results } = await searchClient.search([ diff --git a/integrations/makeswift/integration.patch b/integrations/makeswift/integration.patch index 1d2b9b20e7..4abcffa73d 100644 --- a/integrations/makeswift/integration.patch +++ b/integrations/makeswift/integration.patch @@ -127,7 +127,7 @@ index 00000000..bea1b56f +import { removeEdgesAndNodes } from '@bigcommerce/catalyst-client'; +import { NextRequest, NextResponse } from 'next/server'; + -+import { getSessionCustomerId } from '~/auth'; ++import { getSessionCustomerAccessToken } from '~/auth'; +import { client } from '~/client'; +import { graphql } from '~/client/graphql'; +import { ProductCardCarouselFragment } from '~/components/product-card-carousel/fragment'; @@ -160,12 +160,12 @@ index 00000000..bea1b56f + _request: NextRequest, + { params }: { params: { type: 'newest' | 'featured' } }, +) => { -+ const customerId = await getSessionCustomerId(); ++ const customerAccessToken = await getSessionCustomerAccessToken(); + const { type } = params; + + const { data } = await client.fetch({ + document: GetProductCardCarousel, -+ customerId, ++ customerAccessToken, + }); + + if (type === 'newest') { diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts index aa584176f2..cd2d6c0451 100644 --- a/packages/client/src/client.ts +++ b/packages/client/src/client.ts @@ -12,7 +12,7 @@ export const adminApiHostname: string = interface Config { storeHash: string; - customerImpersonationToken: string; + storefrontToken: string; xAuthToken: string; channelId?: string; platform?: string; @@ -52,7 +52,7 @@ class Client { async fetch>(config: { document: DocumentDecoration; variables: TVariables; - customerId?: string; + customerAccessToken?: string; fetchOptions?: FetcherRequestInit; channelId?: string; }): Promise>; @@ -61,7 +61,7 @@ class Client { async fetch(config: { document: DocumentDecoration>; variables?: undefined; - customerId?: string; + customerAccessToken?: string; fetchOptions?: FetcherRequestInit; channelId?: string; }): Promise>; @@ -69,13 +69,13 @@ class Client { async fetch({ document, variables, - customerId, + customerAccessToken, fetchOptions = {} as FetcherRequestInit, channelId, }: { document: DocumentDecoration; variables?: TVariables; - customerId?: string; + customerAccessToken?: string; fetchOptions?: FetcherRequestInit; channelId?: string; }): Promise> { @@ -91,9 +91,9 @@ class Client { method: 'POST', headers: { 'Content-Type': 'application/json', - Authorization: `Bearer ${this.config.customerImpersonationToken}`, + Authorization: `Bearer ${this.config.storefrontToken}`, 'User-Agent': this.backendUserAgent, - ...(customerId && { 'X-Bc-Customer-Id': customerId }), + ...(customerAccessToken && { 'X-Bc-Customer-Access-Token': customerAccessToken }), ...(this.trustedProxySecret && { 'X-BC-Trusted-Proxy-Secret': this.trustedProxySecret }), ...additionalFetchHeaders, ...headers, diff --git a/packages/create-catalyst/src/commands/create.ts b/packages/create-catalyst/src/commands/create.ts index 362fe0ec5f..9e4c61af9f 100644 --- a/packages/create-catalyst/src/commands/create.ts +++ b/packages/create-catalyst/src/commands/create.ts @@ -24,7 +24,7 @@ export const create = new Command('create') .option('--store-hash ', 'BigCommerce store hash') .option('--access-token ', 'BigCommerce access token') .option('--channel-id ', 'BigCommerce channel ID') - .option('--customer-impersonation-token ', 'BigCommerce customer impersonation token') + .option('--storefront-token ', 'BigCommerce storefront token') .option('--gh-ref ', 'Clone a specific ref from the source repository') .option('--repository ', 'GitHub repository to clone from', 'bigcommerce/catalyst') .option('--env ', 'Arbitrary environment variables to set in .env.local') @@ -67,7 +67,7 @@ export const create = new Command('create') let storeHash = options.storeHash; let accessToken = options.accessToken; let channelId; - let customerImpersonationToken = options.customerImpersonationToken; + let storefrontToken = options.storefrontToken; if (options.channelId) { channelId = parseInt(options.channelId, 10); @@ -143,7 +143,7 @@ export const create = new Command('create') // At this point we should have a storeHash and can identify the account await telemetry.identify(storeHash); - if (!channelId || !customerImpersonationToken) { + if (!channelId || !storefrontToken) { const bc = new Https({ bigCommerceApiUrl: bigcommerceApiUrl, storeHash, accessToken }); const sampleDataApi = new Https({ sampleDataApiUrl, @@ -181,7 +181,7 @@ export const create = new Command('create') await bc.createChannelMenus(createdChannelId); channelId = createdChannelId; - customerImpersonationToken = storefrontApiToken; + storefrontToken = storefrontApiToken; /** * @todo prompt sample data API @@ -213,16 +213,15 @@ export const create = new Command('create') channelId = existingChannel.id; const { - data: { token }, - } = await bc.customerImpersonationToken(); + data: { token: sfToken }, + } = await bc.storefrontToken(); - customerImpersonationToken = token; + storefrontToken = sfToken; } } if (!channelId) throw new Error('Something went wrong, channelId is not defined'); - if (!customerImpersonationToken) - throw new Error('Something went wrong, customerImpersonationToken is not defined'); + if (!storefrontToken) throw new Error('Something went wrong, storefrontToken is not defined'); console.log(`\nCreating '${projectName}' at '${projectDir}'\n`); @@ -231,7 +230,7 @@ export const create = new Command('create') writeEnv(projectDir, { channelId: channelId.toString(), storeHash, - customerImpersonationToken, + storefrontToken, arbitraryEnv: options.env, }); diff --git a/packages/create-catalyst/src/commands/init.ts b/packages/create-catalyst/src/commands/init.ts index dcee54b6e6..fe66616d0c 100644 --- a/packages/create-catalyst/src/commands/init.ts +++ b/packages/create-catalyst/src/commands/init.ts @@ -36,7 +36,7 @@ export const init = new Command('init') let storeHash = options.storeHash; let accessToken = options.accessToken; let channelId; - let customerImpersonationToken; + let storefrontToken; if (!options.storeHash || !options.accessToken) { const credentials = await login(bigCommerceAuthUrl); @@ -90,7 +90,7 @@ export const init = new Command('init') } = await sampleDataApi.createChannel(newChannelName); channelId = createdChannelId; - customerImpersonationToken = storefrontApiToken; + storefrontToken = storefrontApiToken; /** * @todo prompt sample data API @@ -122,19 +122,18 @@ export const init = new Command('init') channelId = existingChannel.id; const { - data: { token }, - } = await bc.customerImpersonationToken(); + data: { token: sfToken }, + } = await bc.storefrontToken(); - customerImpersonationToken = token; + storefrontToken = sfToken; } if (!channelId) throw new Error('Something went wrong, channelId is not defined'); - if (!customerImpersonationToken) - throw new Error('Something went wrong, customerImpersonationToken is not defined'); + if (!storefrontToken) throw new Error('Something went wrong, storefrontToken is not defined'); writeEnv(projectDir, { channelId: channelId.toString(), storeHash, - customerImpersonationToken, + storefrontToken, }); }); diff --git a/packages/create-catalyst/src/utils/https.ts b/packages/create-catalyst/src/utils/https.ts index 77c7eba403..8e48f96536 100644 --- a/packages/create-catalyst/src/utils/https.ts +++ b/packages/create-catalyst/src/utils/https.ts @@ -61,8 +61,6 @@ const BigCommerceChannelsV3ResponseSchema = BigCommerceV3ApiResponseSchema( export type BigCommerceChannelsV3Response = z.infer; export class Https { - DEVICE_OAUTH_CLIENT_ID = 'acse0vvawm9r1n0evag4b8e1ea1fo90'; - bigCommerceApiUrl: string; bigCommerceAuthUrl: string; sampleDataApiUrl: string; @@ -70,6 +68,9 @@ export class Https { accessToken: string; userAgent: string; + private DEVICE_OAUTH_CLIENT_ID = 'acse0vvawm9r1n0evag4b8e1ea1fo90'; + private MAX_EPOC_EXPIRES_AT = 2147483647; + constructor({ bigCommerceApiUrl, storeHash, accessToken }: BigCommerceRestApiConfig); constructor({ sampleDataApiUrl, storeHash, accessToken }: SampleDataApiConfig); constructor({ bigCommerceAuthUrl }: DeviceOAuthConfig); @@ -116,10 +117,10 @@ export class Https { 'store_channel_settings', 'store_sites', 'store_storefront_api', - 'store_storefront_api_customer_impersonation', 'store_v2_content', 'store_v2_information', 'store_v2_products', + 'store_cart', ].join(' '), client_id: this.DEVICE_OAUTH_CLIENT_ID, }), @@ -233,29 +234,27 @@ export class Https { } } - async customerImpersonationToken() { - const res = await this.api('/v3/storefront/api-token-customer-impersonation', { + async storefrontToken() { + const res = await this.api('/v3/storefront/api-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ expires_at: 2147483647, channel_ids: [] }), + body: JSON.stringify({ expires_at: this.MAX_EPOC_EXPIRES_AT, channel_ids: [] }), }); if (!res.ok) { console.error( - chalk.red( - `\nPOST /v3/storefront/api-token-customer-impersonation failed: ${res.status} ${res.statusText}\n`, - ), + chalk.red(`\nPOST /v3/storefront/api-token failed: ${res.status} ${res.statusText}\n`), ); process.exit(1); } - const BigCommerceCustomerImpersonationTokenSchema = z.object({ + const BigCommerceStorefrontTokenSchema = z.object({ data: z.object({ token: z.string(), }), }); - return parse(await res.json(), BigCommerceCustomerImpersonationTokenSchema); + return parse(await res.json(), BigCommerceStorefrontTokenSchema); } sampleDataApi(path: string, opts: RequestInit = {}) { @@ -308,7 +307,7 @@ export class Https { async createChannel(channelName: string) { const res = await this.sampleDataApi('/v3/channels/catalyst', { - body: JSON.stringify({ name: channelName }), + body: JSON.stringify({ name: channelName, tokenType: 'normal' }), }); if (!res.ok) { diff --git a/packages/create-catalyst/src/utils/write-env.ts b/packages/create-catalyst/src/utils/write-env.ts index 275bfb66fd..85814d2530 100644 --- a/packages/create-catalyst/src/utils/write-env.ts +++ b/packages/create-catalyst/src/utils/write-env.ts @@ -7,12 +7,12 @@ export const writeEnv = ( { channelId, storeHash, - customerImpersonationToken, + storefrontToken, arbitraryEnv, }: { channelId: string; storeHash: string; - customerImpersonationToken: string; + storefrontToken?: string; arbitraryEnv?: string[]; }, ) => { @@ -21,7 +21,7 @@ export const writeEnv = ( [ `BIGCOMMERCE_STORE_HASH=${storeHash}`, `BIGCOMMERCE_CHANNEL_ID=${channelId}`, - `BIGCOMMERCE_CUSTOMER_IMPERSONATION_TOKEN=${customerImpersonationToken}`, + `BIGCOMMERCE_STOREFRONT_TOKEN=${storefrontToken}`, '', `AUTH_SECRET=${randomBytes(32).toString('hex')}`, `CLIENT_LOGGER=false`,