diff --git a/packages/next/src/server/app-render/action-handler.ts b/packages/next/src/server/app-render/action-handler.ts index b596798897083..4ae10bfc52ce3 100644 --- a/packages/next/src/server/app-render/action-handler.ts +++ b/packages/next/src/server/app-render/action-handler.ts @@ -41,6 +41,7 @@ import { getServerActionRequestMetadata, } from '../lib/server-action-request-meta' import { isCsrfOriginAllowed } from './csrf-protection' +import { warn } from '../../build/output/log' function formDataFromSearchQueryString(query: string) { const searchParams = new URLSearchParams(query) @@ -323,14 +324,19 @@ export async function handleAction({ } : undefined + let warning: string | undefined = undefined + + function warnBadServerActionRequest() { + if (warning) { + warn(warning) + } + } // This is to prevent CSRF attacks. If `x-forwarded-host` is set, we need to // ensure that the request is coming from the same host. if (!originDomain) { // This might be an old browser that doesn't send `host` header. We ignore // this case. - console.warn( - 'Missing `origin` header from a forwarded Server Actions request.' - ) + warning = 'Missing `origin` header from a forwarded Server Actions request.' } else if (!host || originDomain !== host.value) { // If the customer sets a list of allowed origins, we'll allow the request. // These are considered safe but might be different from forwarded host set @@ -420,8 +426,12 @@ export async function handleAction({ bound = await decodeReply(formData, serverModuleMap) } else { const action = await decodeAction(formData, serverModuleMap) - const actionReturnedState = await action() - formState = decodeFormState(actionReturnedState, formData) + if (typeof action === 'function') { + // Only warn if it's a server action, otherwise skip for other post requests + warnBadServerActionRequest() + const actionReturnedState = await action() + formState = decodeFormState(actionReturnedState, formData) + } // Skip the fetch path return @@ -498,8 +508,12 @@ export async function handleAction({ }) const formData = await fakeRequest.formData() const action = await decodeAction(formData, serverModuleMap) - const actionReturnedState = await action() - formState = await decodeFormState(actionReturnedState, formData) + if (typeof action === 'function') { + // Only warn if it's a server action, otherwise skip for other post requests + warnBadServerActionRequest() + const actionReturnedState = await action() + formState = await decodeFormState(actionReturnedState, formData) + } // Skip the fetch path return diff --git a/test/e2e/app-dir/actions/app-action.test.ts b/test/e2e/app-dir/actions/app-action.test.ts index 620aabf344345..d04b4830320d7 100644 --- a/test/e2e/app-dir/actions/app-action.test.ts +++ b/test/e2e/app-dir/actions/app-action.test.ts @@ -410,6 +410,7 @@ createNextDescribe( }) it('should 404 when POSTing an invalid server action', async () => { + const cliOutputPosition = next.cliOutput.length const res = await next.fetch('/non-existent-route', { method: 'POST', headers: { @@ -418,6 +419,12 @@ createNextDescribe( body: 'foo=bar', }) + const cliOutput = next.cliOutput.slice(cliOutputPosition) + + expect(cliOutput).not.toContain('TypeError') + expect(cliOutput).not.toContain( + 'Missing `origin` header from a forwarded Server Actions request' + ) expect(res.status).toBe(404) })