fix(share): return 403 instead of 401 on wrong share password

Dev (vite + miniflare + undici) returns 500 "fetch failed" when the
Worker responds 401 to a POST with a body. undici follows the Fetch
spec's HTTP-auth retry branch, which needs to re-extract the request
body from its source — but the body comes from Node's IncomingMessage
stream, so `body.source` is null and undici throws
`expected non-null body source`. Production (direct CF edge) is
unaffected, this only breaks local dev.

403 is also semantically more accurate: the client isn't performing
HTTP authentication, just supplying a shared secret.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
saltbo
2026-04-20 15:10:21 -04:00
co-authored by Claude Opus 4.7
parent f215439a89
commit 6bf8ecc8d5
4 changed files with 6 additions and 6 deletions
+1 -1
View File
@@ -85,7 +85,7 @@ const app = new Hono<Env>()
const { share } = resolved
if (share.kind !== 'landing') return c.json({ error: 'Share not found or revoked' }, 404)
if (!verifyPassword(share, password)) return c.json({ error: 'Invalid password' }, 401)
if (!verifyPassword(share, password)) return c.json({ error: 'Invalid password' }, 403)
const now = new Date()
const oneDayMs = 24 * 60 * 60 * 1000
@@ -214,7 +214,7 @@ describe('POST /s/:token/verify', () => {
expect(cookieHeader).toContain('HttpOnly')
})
it('returns 401 on wrong password', async () => {
it('returns 403 on wrong password', async () => {
const { app, db } = await createTestApp()
await authedHeaders(app)
await insertStorage(db)
@@ -234,7 +234,7 @@ describe('POST /s/:token/verify', () => {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password: 'wrongpassword' }),
})
expect(res.status).toBe(401)
expect(res.status).toBe(403)
})
})
+1 -1
View File
@@ -25,7 +25,7 @@ export function PasswordPrompt({ token, fileName, onUnlocked }: PasswordPromptPr
await verifySharePassword(token, password)
onUnlocked()
} catch (err) {
if (err instanceof ApiError && err.status === 401) {
if (err instanceof ApiError && err.status === 403) {
setError(t('share.passwordWrong'))
} else {
setError(t('share.loadError'))
+2 -2
View File
@@ -1152,8 +1152,8 @@ describe('api', () => {
expect(JSON.parse(init.body as string)).toEqual({ password: 'secret' })
})
it('throws ApiError on 401 (wrong password)', async () => {
vi.mocked(fetch).mockResolvedValueOnce(makeResponse({ error: 'Invalid password' }, false, 401))
it('throws ApiError on 403 (wrong password)', async () => {
vi.mocked(fetch).mockResolvedValueOnce(makeResponse({ error: 'Invalid password' }, false, 403))
await expect(verifySharePassword('tok123', 'wrong')).rejects.toThrow('Invalid password')
})