Skip to content

Commit e58d852

Browse files
chanceaclarkclaude
authored andcommitted
fix(core): TRAC-293 pass customer token through routes and webpages (#3013)
Customer-only webpages were inaccessible and missing from navigation when logged in. The with-routes proxy and the normal/contact webpage page-data queries resolved routes anonymously, so the Storefront API hid customer-restricted content from authenticated users. - Wrap the with-routes proxy handler in auth() and thread the customer access token into getRoute and getRawWebPageContent - Bypass the KV route cache when a customer access token is present; the cache key isn't namespaced by customer identity, so reading or writing it for authenticated requests could leak customer-specific route resolutions across users - Pass the customer access token into normal and contact webpage page-data queries, switching to a no-store fetch when authenticated so cached anonymous responses are not served to logged-in customers - Leave getStoreStatus unchanged (does not depend on customer identity) Refs TRAC-293 Co-authored-by: Claude <noreply@anthropic.com>
1 parent c8a7dae commit e58d852

6 files changed

Lines changed: 181 additions & 133 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@bigcommerce/catalyst-core": patch
3+
---
4+
5+
Pass the customer access token through route resolution and the normal/contact webpage queries so customer-restricted web pages are accessible (and appear in navigation) for authenticated customers. The `with-routes` proxy is now wrapped in `auth()`, and webpage `page-data` queries switch to an uncached fetch when a customer token is present.

core/app/[locale]/(default)/webpages/[id]/contact/page-data.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ const ContactPageQuery = graphql(
3131

3232
type Variables = VariablesOf<typeof ContactPageQuery>;
3333

34-
export const getWebpageData = cache(async (variables: Variables) => {
34+
export const getWebpageData = cache(async (variables: Variables, customerAccessToken?: string) => {
3535
const { data } = await client.fetch({
3636
document: ContactPageQuery,
3737
variables,
38-
fetchOptions: { next: { revalidate } },
38+
customerAccessToken,
39+
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
3940
});
4041

4142
return data;

core/app/[locale]/(default)/webpages/[id]/contact/page.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { Field, FieldGroup } from '@/vibes/soul/form/dynamic-form/schema';
88
import { Streamable } from '@/vibes/soul/lib/streamable';
99
import { ButtonLink } from '@/vibes/soul/primitives/button-link';
1010
import { Breadcrumb } from '@/vibes/soul/sections/breadcrumbs';
11+
import { getSessionCustomerAccessToken } from '~/auth';
1112
import {
1213
breadcrumbsTransformer,
1314
truncateBreadcrumbs,
@@ -41,8 +42,8 @@ const fieldMapping = {
4142

4243
type ContactField = keyof typeof fieldMapping;
4344

44-
const getWebPage = cache(async (id: string): Promise<ContactPage> => {
45-
const data = await getWebpageData({ id: decodeURIComponent(id) });
45+
const getWebPage = cache(async (id: string, customerAccessToken?: string): Promise<ContactPage> => {
46+
const data = await getWebpageData({ id: decodeURIComponent(id) }, customerAccessToken);
4647
const webpage = data.node?.__typename === 'ContactPage' ? data.node : null;
4748

4849
if (!webpage) {
@@ -62,10 +63,13 @@ const getWebPage = cache(async (id: string): Promise<ContactPage> => {
6263
};
6364
});
6465

65-
async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
66+
async function getWebPageBreadcrumbs(
67+
id: string,
68+
customerAccessToken?: string,
69+
): Promise<Breadcrumb[]> {
6670
const t = await getTranslations('WebPages.ContactUs');
6771

68-
const webpage = await getWebPage(id);
72+
const webpage = await getWebPage(id, customerAccessToken);
6973
const [, ...rest] = webpage.breadcrumbs.reverse();
7074
const breadcrumbs = [
7175
{
@@ -82,18 +86,22 @@ async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
8286
return truncateBreadcrumbs(breadcrumbs, 5);
8387
}
8488

85-
async function getWebPageWithSuccessContent(id: string, message: string) {
86-
const webpage = await getWebPage(id);
89+
async function getWebPageWithSuccessContent(
90+
id: string,
91+
message: string,
92+
customerAccessToken?: string,
93+
) {
94+
const webpage = await getWebPage(id, customerAccessToken);
8795

8896
return {
8997
...webpage,
9098
content: message,
9199
};
92100
}
93101

94-
async function getContactFields(id: string) {
102+
async function getContactFields(id: string, customerAccessToken?: string) {
95103
const t = await getTranslations('WebPages.ContactUs.Form');
96-
const { entityId, path, contactFields } = await getWebPage(id);
104+
const { entityId, path, contactFields } = await getWebPage(id, customerAccessToken);
97105
const toGroupsOfTwo = (fields: Field[]) =>
98106
fields.reduce<Array<FieldGroup<Field>>>((acc, _, i) => {
99107
if (i % 2 === 0) {
@@ -156,7 +164,8 @@ async function getContactFields(id: string) {
156164

157165
export async function generateMetadata({ params }: Props): Promise<Metadata> {
158166
const { id, locale } = await params;
159-
const webpage = await getWebPage(id);
167+
const customerAccessToken = await getSessionCustomerAccessToken();
168+
const webpage = await getWebPage(id, customerAccessToken);
160169
const { pageTitle, metaDescription, metaKeywords } = webpage.seo;
161170

162171
return {
@@ -172,6 +181,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
172181
export default async function ContactPage({ params, searchParams }: Props) {
173182
const { id, locale } = await params;
174183
const { success } = await searchParams;
184+
const customerAccessToken = await getSessionCustomerAccessToken();
175185

176186
setRequestLocale(locale);
177187

@@ -180,8 +190,10 @@ export default async function ContactPage({ params, searchParams }: Props) {
180190
if (success === 'true') {
181191
return (
182192
<WebPageContent
183-
breadcrumbs={Streamable.from(() => getWebPageBreadcrumbs(id))}
184-
webPage={Streamable.from(() => getWebPageWithSuccessContent(id, t('success')))}
193+
breadcrumbs={Streamable.from(() => getWebPageBreadcrumbs(id, customerAccessToken))}
194+
webPage={Streamable.from(() =>
195+
getWebPageWithSuccessContent(id, t('success'), customerAccessToken),
196+
)}
185197
>
186198
<ButtonLink
187199
className="mt-8 @2xl:mt-12 @4xl:mt-16"
@@ -200,13 +212,13 @@ export default async function ContactPage({ params, searchParams }: Props) {
200212

201213
return (
202214
<WebPageContent
203-
breadcrumbs={Streamable.from(() => getWebPageBreadcrumbs(id))}
204-
webPage={Streamable.from(() => getWebPage(id))}
215+
breadcrumbs={Streamable.from(() => getWebPageBreadcrumbs(id, customerAccessToken))}
216+
webPage={Streamable.from(() => getWebPage(id, customerAccessToken))}
205217
>
206218
<div className="mt-8 @2xl:mt-12 @4xl:mt-16">
207219
<DynamicForm
208220
action={submitContactForm}
209-
fields={await getContactFields(id)}
221+
fields={await getContactFields(id, customerAccessToken)}
210222
recaptchaSiteKey={recaptchaSiteKey}
211223
submitLabel={t('cta')}
212224
/>

core/app/[locale]/(default)/webpages/[id]/normal/page-data.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ const NormalPageQuery = graphql(
2929

3030
type Variables = VariablesOf<typeof NormalPageQuery>;
3131

32-
export const getWebpageData = cache(async (variables: Variables) => {
32+
export const getWebpageData = cache(async (variables: Variables, customerAccessToken?: string) => {
3333
const { data } = await client.fetch({
3434
document: NormalPageQuery,
3535
variables,
36-
fetchOptions: { next: { revalidate } },
36+
customerAccessToken,
37+
fetchOptions: customerAccessToken ? { cache: 'no-store' } : { next: { revalidate } },
3738
});
3839

3940
return data;

core/app/[locale]/(default)/webpages/[id]/normal/page.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { cache } from 'react';
55

66
import { Streamable } from '@/vibes/soul/lib/streamable';
77
import { Breadcrumb } from '@/vibes/soul/sections/breadcrumbs';
8+
import { getSessionCustomerAccessToken } from '~/auth';
89
import {
910
breadcrumbsTransformer,
1011
truncateBreadcrumbs,
@@ -19,8 +20,8 @@ interface Props {
1920
params: Promise<{ locale: string; id: string }>;
2021
}
2122

22-
const getWebPage = cache(async (id: string): Promise<WebPageData> => {
23-
const data = await getWebpageData({ id: decodeURIComponent(id) });
23+
const getWebPage = cache(async (id: string, customerAccessToken?: string): Promise<WebPageData> => {
24+
const data = await getWebpageData({ id: decodeURIComponent(id) }, customerAccessToken);
2425
const webpage = data.node?.__typename === 'NormalPage' ? data.node : null;
2526

2627
if (!webpage) {
@@ -37,10 +38,13 @@ const getWebPage = cache(async (id: string): Promise<WebPageData> => {
3738
};
3839
});
3940

40-
async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
41+
async function getWebPageBreadcrumbs(
42+
id: string,
43+
customerAccessToken?: string,
44+
): Promise<Breadcrumb[]> {
4145
const t = await getTranslations('WebPages.Normal');
4246

43-
const webpage = await getWebPage(id);
47+
const webpage = await getWebPage(id, customerAccessToken);
4448
const [, ...rest] = webpage.breadcrumbs.reverse();
4549
const breadcrumbs = [
4650
{
@@ -59,7 +63,8 @@ async function getWebPageBreadcrumbs(id: string): Promise<Breadcrumb[]> {
5963

6064
export async function generateMetadata({ params }: Props): Promise<Metadata> {
6165
const { id, locale } = await params;
62-
const webpage = await getWebPage(id);
66+
const customerAccessToken = await getSessionCustomerAccessToken();
67+
const webpage = await getWebPage(id, customerAccessToken);
6368
const { pageTitle, metaDescription, metaKeywords } = webpage.seo;
6469

6570
// Get the path from the last breadcrumb
@@ -75,13 +80,14 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
7580

7681
export default async function WebPage({ params }: Props) {
7782
const { locale, id } = await params;
83+
const customerAccessToken = await getSessionCustomerAccessToken();
7884

7985
setRequestLocale(locale);
8086

8187
return (
8288
<WebPageContent
83-
breadcrumbs={Streamable.from(() => getWebPageBreadcrumbs(id))}
84-
webPage={Streamable.from(() => getWebPage(id))}
89+
breadcrumbs={Streamable.from(() => getWebPageBreadcrumbs(id, customerAccessToken))}
90+
webPage={Streamable.from(() => getWebPage(id, customerAccessToken))}
8591
/>
8692
);
8793
}

0 commit comments

Comments
 (0)