Skip to content

Commit f33a0ac

Browse files
fix: filter client error response, error on missing host (#9837)
1 parent ec50cf0 commit f33a0ac

File tree

4 files changed

+262
-5
lines changed

4 files changed

+262
-5
lines changed

packages/core/src/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,12 @@ export async function Auth(
114114
!htmlPages.includes(internalRequest.action) ||
115115
internalRequest.method !== "GET"
116116
) {
117-
return new Response(
118-
JSON.stringify({
117+
return Response.json(
118+
{
119119
message:
120120
"There was a problem with the server configuration. Check the server logs for more information.",
121-
code: assertionResult.name,
122-
}),
123-
{ status: 500, headers: { "Content-Type": "application/json" } }
121+
},
122+
{ status: 500 }
124123
)
125124
}
126125

packages/core/src/lib/utils/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function createActionURL(
5353
let url = envObject.AUTH_URL ?? envObject.NEXTAUTH_URL
5454
if (!url) {
5555
const host = headers.get("x-forwarded-host") ?? headers.get("host")
56+
if (!host) throw new TypeError("Missing host")
5657
const proto = headers.get("x-forwarded-proto") ?? protocol
5758
url = `${proto === "http" ? "http" : "https"}://${host}${basePath}`
5859
}
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import { vi, expect, it, describe, beforeEach } from "vitest"
2+
import { makeAuthRequest } from "./utils"
3+
import {
4+
InvalidCallbackUrl,
5+
InvalidEndpoints,
6+
MissingAuthorize,
7+
MissingSecret,
8+
UnsupportedStrategy,
9+
UntrustedHost,
10+
} from "../src/errors"
11+
import { Provider } from "../src/providers"
12+
import { AuthConfig } from "../src"
13+
import Credentials from "../src/providers/credentials"
14+
15+
describe("Assert user config correctness", () => {
16+
beforeEach(() => {
17+
vi.resetAllMocks()
18+
})
19+
20+
it("missing secret", async () => {
21+
const { response, logger } = await makeAuthRequest({
22+
action: "session",
23+
config: { secret: undefined },
24+
})
25+
26+
expect(response.status).toBe(500)
27+
expect(await response.json()).toEqual({
28+
message:
29+
"There was a problem with the server configuration. Check the server logs for more information.",
30+
})
31+
expect(logger?.error).toHaveBeenCalledWith(
32+
new MissingSecret("Please define a `secret`.")
33+
)
34+
})
35+
36+
it("trust host", async () => {
37+
const { response, logger } = await makeAuthRequest({
38+
action: "session",
39+
config: { trustHost: false },
40+
})
41+
42+
expect(response.status).toBe(500)
43+
expect(await response.json()).toEqual({
44+
message:
45+
"There was a problem with the server configuration. Check the server logs for more information.",
46+
})
47+
expect(logger?.error).toHaveBeenCalledWith(
48+
new UntrustedHost(
49+
"Host must be trusted. URL was: http://authjs.test/auth/session"
50+
)
51+
)
52+
})
53+
54+
describe.each(["non-relative", "a"])(
55+
'invalid callbackUrl (url: "%s")',
56+
async (callbackUrl) => {
57+
it.each(["search param", "cookie", "cookie-custom"])(
58+
"from %s",
59+
async (type) => {
60+
const cookieName =
61+
type === "cookie" ? "authjs.callback-url" : "custom-name"
62+
63+
const { response, logger } = await makeAuthRequest({
64+
action: "session",
65+
...(type.startsWith("cookie")
66+
? { cookies: { [cookieName]: callbackUrl } }
67+
: { query: { callbackUrl } }),
68+
config: {
69+
cookies:
70+
type === "cookie-custom"
71+
? { callbackUrl: { name: cookieName } }
72+
: undefined,
73+
},
74+
})
75+
76+
expect(response.status).toBe(500)
77+
expect(await response.json()).toEqual({
78+
message:
79+
"There was a problem with the server configuration. Check the server logs for more information.",
80+
})
81+
expect(logger?.error).toHaveBeenCalledWith(
82+
new InvalidCallbackUrl(
83+
`Invalid callback URL. Received: ${callbackUrl}`
84+
)
85+
)
86+
}
87+
)
88+
}
89+
)
90+
91+
describe("providers", () => {
92+
it.each<[Provider, string]>([
93+
[
94+
() => ({ type: "oidc", id: "provider-id", name: "" }),
95+
'Provider "provider-id" is missing both `issuer` and `authorization` endpoint config. At least one of them is required.',
96+
],
97+
[
98+
{ type: "oidc", id: "provider-id", name: "" },
99+
'Provider "provider-id" is missing both `issuer` and `authorization` endpoint config. At least one of them is required.',
100+
],
101+
[
102+
{
103+
authorization: { url: "http://a" },
104+
type: "oauth",
105+
id: "provider-id",
106+
name: "",
107+
},
108+
'Provider "provider-id" is missing both `issuer` and `token` endpoint config. At least one of them is required.',
109+
],
110+
[
111+
{
112+
authorization: "http://a",
113+
token: { url: "http://a" },
114+
type: "oauth",
115+
id: "provider-id",
116+
name: "",
117+
},
118+
'Provider "provider-id" is missing both `issuer` and `userinfo` endpoint config. At least one of them is required.',
119+
],
120+
[
121+
{
122+
authorization: "http://a",
123+
token: "http://a",
124+
// @ts-expect-error Purposefully testing invalid config
125+
userinfo: { foo: "http://a" },
126+
type: "oauth",
127+
id: "oauth-provider",
128+
name: "",
129+
},
130+
'Provider "oauth-provider" is missing both `issuer` and `userinfo` endpoint config. At least one of them is required.',
131+
],
132+
])("OAuth/OIDC: invalid endpoints %j", async (provider, error) => {
133+
const { response, logger } = await makeAuthRequest({
134+
action: "providers",
135+
config: { providers: [provider] },
136+
})
137+
138+
expect(response.status).toBe(500)
139+
expect(await response.json()).toEqual({
140+
message:
141+
"There was a problem with the server configuration. Check the server logs for more information.",
142+
})
143+
expect(logger?.error).toHaveBeenCalledWith(new InvalidEndpoints(error))
144+
})
145+
146+
it.each<[string, Provider, Error, Partial<AuthConfig>]>([
147+
[
148+
"missing authorize() (function)",
149+
() => ({ type: "credentials", id: "provider-id", name: "" }),
150+
new MissingAuthorize(
151+
"Must define an authorize() handler to use credentials authentication provider"
152+
),
153+
],
154+
[
155+
"missing authorize() (object)",
156+
{ type: "credentials", id: "provider-id", name: "" },
157+
new MissingAuthorize(
158+
"Must define an authorize() handler to use credentials authentication provider"
159+
),
160+
],
161+
[
162+
"trying to use database strategy",
163+
Credentials({}),
164+
new UnsupportedStrategy(
165+
"Signing in with credentials only supported if JWT strategy is enabled"
166+
),
167+
{ session: { strategy: "database" } },
168+
],
169+
] as any)("credentials: %s", async (_, provider, error, config) => {
170+
const { response, logger } = await makeAuthRequest({
171+
action: "providers",
172+
config: { ...config, providers: [provider] },
173+
})
174+
175+
expect(response.status).toBe(500)
176+
expect(await response.json()).toEqual({
177+
message:
178+
"There was a problem with the server configuration. Check the server logs for more information.",
179+
})
180+
expect(logger?.error).toHaveBeenCalledWith(error)
181+
})
182+
})
183+
})

packages/core/test/utils.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { vi } from "vitest"
2+
import { Auth, createActionURL } from "../src"
3+
4+
import type { Adapter } from "../src/adapters"
5+
import type { AuthAction, AuthConfig, LoggerInstance } from "../src/types"
6+
7+
export function TestAdapter(): Adapter {
8+
return {
9+
createUser: vi.fn(),
10+
getUser: vi.fn(),
11+
getUserByEmail: vi.fn(),
12+
getUserByAccount: vi.fn(),
13+
updateUser: vi.fn(),
14+
deleteUser: vi.fn(),
15+
linkAccount: vi.fn(),
16+
unlinkAccount: vi.fn(),
17+
createSession: vi.fn(),
18+
getSessionAndUser: vi.fn(),
19+
updateSession: vi.fn(),
20+
deleteSession: vi.fn(),
21+
createVerificationToken: vi.fn(),
22+
useVerificationToken: vi.fn(),
23+
}
24+
}
25+
26+
export const logger: LoggerInstance = {
27+
debug: vi.fn(),
28+
warn: vi.fn(),
29+
error: vi.fn(),
30+
}
31+
32+
export function testConfig(overrides?: Partial<AuthConfig>): AuthConfig {
33+
return {
34+
secret: "secret",
35+
trustHost: true,
36+
logger,
37+
basePath: "/auth",
38+
providers: [],
39+
...overrides,
40+
}
41+
}
42+
43+
export async function makeAuthRequest(params: {
44+
action: AuthAction
45+
cookies?: Record<string, string>
46+
query?: Record<string, string>
47+
body?: any
48+
config?: Partial<AuthConfig>
49+
}) {
50+
const { action, body, cookies = {} } = params
51+
const config = testConfig(params.config)
52+
const headers = new Headers({ host: "authjs.test" })
53+
for (const [name, value] of Object.entries(cookies))
54+
headers.append("cookie", `${name}=${value}`)
55+
56+
let url: string | URL = createActionURL(
57+
action,
58+
"http",
59+
headers,
60+
{},
61+
config.basePath
62+
)
63+
if (params.query) url = `${url}?${new URLSearchParams(params.query)}`
64+
const request = new Request(url, {
65+
method: body ? "POST" : "GET",
66+
headers,
67+
body,
68+
})
69+
const response = (await Auth(request, config)) as Response
70+
return {
71+
response,
72+
logger: config.logger,
73+
}
74+
}

0 commit comments

Comments
 (0)