Skip to content

Commit 4f8098d

Browse files
chrisbbreuerclaude
andcommitted
fix: support CloudFront-compatible dashboard auth
CloudFront strips cookies by default, causing dashboard auth to fail. Added support for Authorization header and query parameter auth as alternatives. Login now redirects with ?token= for CDN compatibility. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e1b0de5 commit 4f8098d

File tree

2 files changed

+34
-7
lines changed

2 files changed

+34
-7
lines changed

packages/registry/src/e2e.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ describe('e2e: binary proxy + analytics + dashboard', () => {
274274
expect(html).toContain('Sign In')
275275
})
276276

277-
it('POST /dashboard/login with valid token sets cookie and redirects', async () => {
277+
it('POST /dashboard/login with valid token sets cookie and redirects with token param', async () => {
278278
const formData = new FormData()
279279
formData.set('token', TEST_TOKEN)
280280

@@ -284,14 +284,28 @@ describe('e2e: binary proxy + analytics + dashboard', () => {
284284
redirect: 'manual',
285285
})
286286
expect(res.status).toBe(302)
287-
expect(res.headers.get('location')).toBe('/dashboard')
287+
expect(res.headers.get('location')).toContain('/dashboard?token=')
288288

289289
const cookie = res.headers.get('set-cookie')
290290
expect(cookie).toContain('pantry_token=')
291291
expect(cookie).toContain('HttpOnly')
292292
expect(cookie).toContain('SameSite=Strict')
293293
})
294294

295+
it('supports auth via query parameter (CloudFront compatible)', async () => {
296+
const res = await fetch(`${baseUrl}/dashboard?token=${TEST_TOKEN}`)
297+
expect(res.status).toBe(200)
298+
expect(res.headers.get('content-type')).toBe('text/html')
299+
})
300+
301+
it('supports auth via Authorization header', async () => {
302+
const res = await fetch(`${baseUrl}/dashboard`, {
303+
headers: { 'Authorization': `Bearer ${TEST_TOKEN}` },
304+
})
305+
expect(res.status).toBe(200)
306+
expect(res.headers.get('content-type')).toBe('text/html')
307+
})
308+
295309
it('POST /dashboard/login with invalid token shows error', async () => {
296310
const formData = new FormData()
297311
formData.set('token', 'wrong-token')

packages/registry/src/server.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,10 +1012,23 @@ async function handleBinaryProxy(
10121012
*/
10131013
const DASHBOARD_TOKEN = process.env.PANTRY_REGISTRY_TOKEN || process.env.PANTRY_TOKEN || 'ABCD1234'
10141014

1015-
function getDashboardAuth(req: Request): boolean {
1015+
function getDashboardAuth(req: Request, url?: URL): boolean {
1016+
// Check cookie first (direct access / cookie-forwarding CDN)
10161017
const cookieHeader = req.headers.get('cookie') || ''
1017-
const match = cookieHeader.match(/pantry_token=([^;]+)/)
1018-
return match ? match[1] === DASHBOARD_TOKEN : false
1018+
const cookieMatch = cookieHeader.match(/pantry_token=([^;]+)/)
1019+
if (cookieMatch && cookieMatch[1] === DASHBOARD_TOKEN) return true
1020+
1021+
// Check Authorization header (CloudFront forwards this)
1022+
const authHeader = req.headers.get('authorization') || ''
1023+
if (authHeader.startsWith('Bearer ') && authHeader.slice(7) === DASHBOARD_TOKEN) return true
1024+
1025+
// Check query parameter (CloudFront forwards query strings)
1026+
if (url) {
1027+
const tokenParam = url.searchParams.get('token')
1028+
if (tokenParam === DASHBOARD_TOKEN) return true
1029+
}
1030+
1031+
return false
10191032
}
10201033

10211034
async function handleDashboard(
@@ -1053,7 +1066,7 @@ async function handleDashboard(
10531066
status: 302,
10541067
headers: {
10551068
...noCacheHeaders,
1056-
'Location': '/dashboard',
1069+
'Location': `/dashboard?token=${encodeURIComponent(token)}`,
10571070
'Set-Cookie': `pantry_token=${token}; Path=/dashboard; HttpOnly; SameSite=Strict; Max-Age=86400`,
10581071
},
10591072
})
@@ -1066,7 +1079,7 @@ async function handleDashboard(
10661079
}
10671080

10681081
// Auth gate for all other dashboard routes
1069-
if (!getDashboardAuth(req)) {
1082+
if (!getDashboardAuth(req, url)) {
10701083
return new Response(null, {
10711084
status: 302,
10721085
headers: { ...noCacheHeaders, 'Location': '/dashboard/login' },

0 commit comments

Comments
 (0)