Skip to content

Commit b89f1ae

Browse files
fix: replace internal redirects with 404 responses across workflow pages and block .json path access
1 parent 064febf commit b89f1ae

5 files changed

Lines changed: 111 additions & 13 deletions

File tree

site/src/pages/[locale]/workflows/[slug].astro

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ Astro.response.headers.set('CDN-Cache-Control', 'public, max-age=60, stale-while
3535
3636
const { locale, slug } = Astro.params;
3737
38+
if (Astro.url.pathname.endsWith('.json/')) {
39+
return new Response(null, {
40+
status: 404,
41+
statusText: 'Not Found',
42+
});
43+
}
44+
3845
// Validate locale
3946
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
4047
return Astro.redirect('/workflows/');
@@ -60,8 +67,18 @@ if (detailShareId) {
6067
const is404 = err instanceof Error && err.message.includes('404');
6168
if (!is404) {
6269
console.error('Hub API error on ISR detail:', err);
70+
return new Response(null, {
71+
status: 502,
72+
statusText: 'Bad Gateway',
73+
headers: {
74+
'CDN-Cache-Control': 'no-store',
75+
},
76+
});
6377
}
64-
return Astro.redirect(`/${locale}/workflows/`);
78+
return new Response(null, {
79+
status: 404,
80+
statusText: 'Not Found',
81+
});
6582
}
6683
}
6784
@@ -116,21 +133,34 @@ try {
116133
// Fetch workflows for this creator from index
117134
const profiles = await getProfileCache();
118135
let serialized: SerializedTemplate[] = [];
136+
let indexFetchFailed = false;
119137
120138
try {
121139
const entries = await listWorkflowIndex();
122140
const filtered = entries.filter((e) => (e.profile?.username || e.username) === slug);
123141
serialized = filtered.map((e) => serializeIndexEntry(e, profiles));
124142
} catch (err) {
143+
indexFetchFailed = true;
125144
console.error('Hub API failed for creator grid:', err);
126-
// Index API failed — show empty grid
127145
}
128146
129147
// Slug is bogus (e.g. "undefined" or a malformed "<name>-" suffix from a broken
130148
// link): no profile and no workflows — redirect instead of rendering a page
131149
// that would title itself "<slug> (@<slug>)" and get indexed by Google.
132150
if (!isDetailPage && !profileFound && serialized.length === 0) {
133-
return Astro.redirect(`/${locale}/workflows/`);
151+
if (indexFetchFailed) {
152+
return new Response(null, {
153+
status: 502,
154+
statusText: 'Bad Gateway',
155+
headers: {
156+
'CDN-Cache-Control': 'no-store',
157+
},
158+
});
159+
}
160+
return new Response(null, {
161+
status: 404,
162+
statusText: 'Not Found',
163+
});
134164
}
135165
136166
const canonicalUrl = absoluteUrl(`/workflows/${slug}/`);

site/src/pages/[locale]/workflows/category/[type].astro

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { localizeUrl } from '../../../../i18n/utils';
1818
import { SITE_ORIGIN, absoluteUrl } from '../../../../config/site';
1919
import { categoryPath } from '../../../../lib/routes';
2020
import { badgeVariants } from '../../../../components/ui/badge';
21-
import { loadSerializedTemplates, type SerializedTemplate, type MediaType } from '../../../../lib/hub-api';
21+
import { loadSerializedTemplates, type MediaType } from '../../../../lib/hub-api';
2222
2323
const validTypes: MediaType[] = ['image', 'video', 'audio', '3d'];
2424
@@ -31,14 +31,24 @@ const categoryTranslationKeys: Record<MediaType, string> = {
3131
3232
const { locale, type } = Astro.params;
3333
34+
if (Astro.url.pathname.endsWith('.json/')) {
35+
return new Response(null, {
36+
status: 404,
37+
statusText: 'Not Found',
38+
});
39+
}
40+
3441
// Validate locale
3542
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
3643
return Astro.redirect(`/workflows/category/${type || ''}/`);
3744
}
3845
3946
// Validate type
4047
if (!type || !validTypes.includes(type as MediaType)) {
41-
return Astro.redirect(`/${locale}/workflows/`);
48+
return new Response(null, {
49+
status: 404,
50+
statusText: 'Not Found',
51+
});
4252
}
4353
4454
const typedLocale = locale as Locale;
@@ -51,6 +61,13 @@ const description = t('meta.description', typedLocale);
5161
const allSerialized = await loadSerializedTemplates(() => getCollection('templates'));
5262
const templates = allSerialized.filter((tmpl) => tmpl.mediaType === mediaType);
5363
64+
if (templates.length === 0) {
65+
return new Response(null, {
66+
status: 404,
67+
statusText: 'Not Found',
68+
});
69+
}
70+
5471
const basePath = categoryPath(mediaType);
5572
const canonicalUrl = absoluteUrl(localizeUrl(basePath, typedLocale));
5673

site/src/pages/[locale]/workflows/model/[name].astro

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,17 @@ import { slugify } from '../../../../lib/slugify';
1919
import { SITE_ORIGIN, absoluteUrl } from '../../../../config/site';
2020
import { modelPath } from '../../../../lib/routes';
2121
import { badgeVariants } from '../../../../components/ui/badge';
22-
import { loadSerializedTemplates, type SerializedTemplate } from '../../../../lib/hub-api';
22+
import { loadSerializedTemplates } from '../../../../lib/hub-api';
2323
2424
const { locale, name } = Astro.params;
2525
26+
if (Astro.url.pathname.endsWith('.json/')) {
27+
return new Response(null, {
28+
status: 404,
29+
statusText: 'Not Found',
30+
});
31+
}
32+
2633
// Validate locale
2734
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
2835
return Astro.redirect(`/workflows/model/${name || ''}/`);
@@ -38,7 +45,10 @@ const matchingTemplates = allTemplates.filter((tmpl) =>
3845
);
3946
4047
if (matchingTemplates.length === 0) {
41-
return Astro.redirect(`/${locale}/workflows/`);
48+
return new Response(null, {
49+
status: 404,
50+
statusText: 'Not Found',
51+
});
4252
}
4353
4454
// Get display name from the first matching model

site/src/pages/[locale]/workflows/tag/[tag].astro

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@ import { localizeUrl } from '../../../../i18n/utils';
1818
import { tagSlug, tagDisplayName } from '../../../../lib/tag-aliases';
1919
import { SITE_ORIGIN, absoluteUrl } from '../../../../config/site';
2020
import { badgeVariants } from '../../../../components/ui/badge';
21-
import { loadSerializedTemplates, type SerializedTemplate } from '../../../../lib/hub-api';
21+
import { loadSerializedTemplates } from '../../../../lib/hub-api';
2222
2323
const { locale, tag } = Astro.params;
2424
25+
if (Astro.url.pathname.endsWith('.json/')) {
26+
return new Response(null, {
27+
status: 404,
28+
statusText: 'Not Found',
29+
});
30+
}
31+
2532
// Validate locale
2633
if (!locale || !LOCALES.includes(locale as Locale) || locale === DEFAULT_LOCALE) {
2734
return Astro.redirect(`/workflows/tag/${tag || ''}/`);
@@ -37,7 +44,10 @@ const matchingTemplates = allTemplates.filter((tmpl) =>
3744
);
3845
3946
if (matchingTemplates.length === 0) {
40-
return Astro.redirect(`/${locale}/workflows/`);
47+
return new Response(null, {
48+
status: 404,
49+
statusText: 'Not Found',
50+
});
4151
}
4252
4353
// Get display name from the first matching tag

site/src/pages/workflows/[username].astro

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ Astro.response.headers.set(
3131
const { username: slugParam } = Astro.params;
3232
const locale = getLocaleFromPath(Astro.url.pathname);
3333
34+
if (Astro.url.pathname.endsWith('.json/')) {
35+
return new Response(null, {
36+
status: 404,
37+
statusText: 'Not Found',
38+
});
39+
}
40+
3441
if (!slugParam) {
3542
return Astro.redirect('/workflows/');
3643
}
@@ -49,8 +56,18 @@ if (detailShareId) {
4956
const is404 = err instanceof Error && err.message.includes('404');
5057
if (!is404) {
5158
console.error('Hub API error on ISR detail:', err);
59+
return new Response(null, {
60+
status: 502,
61+
statusText: 'Bad Gateway',
62+
headers: {
63+
'CDN-Cache-Control': 'no-store',
64+
},
65+
});
5266
}
53-
return Astro.redirect('/workflows/');
67+
return new Response(null, {
68+
status: 404,
69+
statusText: 'Not Found',
70+
});
5471
}
5572
}
5673
@@ -83,21 +100,35 @@ try {
83100
// Fetch workflows for this creator from index
84101
const profiles = await getProfileCache();
85102
let serialized: SerializedTemplate[] = [];
103+
let indexFetchFailed = false;
86104
87105
try {
88106
const entries = await listWorkflowIndex();
89107
const filtered = entries.filter((e) => (e.profile?.username || e.username) === slugParam);
90108
serialized = filtered.map((e) => serializeIndexEntry(e, profiles));
91-
} catch {
92-
// Index API failed — show empty grid
109+
} catch (err) {
110+
indexFetchFailed = true;
111+
console.error('Hub API failed for creator grid:', err);
93112
}
94113
95114
// Slug is bogus (e.g. "undefined" or a malformed "<name>-" suffix from a broken
96115
// link): not a workflow detail, no profile, and no workflows — redirect instead
97116
// of rendering a page that would title itself "<slug> (@<slug>)" and get
98117
// indexed by Google.
99118
if (!isDetailPage && !profileFound && serialized.length === 0) {
100-
return Astro.redirect('/workflows/');
119+
if (indexFetchFailed) {
120+
return new Response(null, {
121+
status: 502,
122+
statusText: 'Bad Gateway',
123+
headers: {
124+
'CDN-Cache-Control': 'no-store',
125+
},
126+
});
127+
}
128+
return new Response(null, {
129+
status: 404,
130+
statusText: 'Not Found',
131+
});
101132
}
102133
103134
const canonicalUrl = absoluteUrl(`/workflows/${slugParam}/`);

0 commit comments

Comments
 (0)