Compare commits
202 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbe3bc707a | |||
| eaab6b14a0 | |||
| 845e5981c3 | |||
| 94f8f316af | |||
| e46fc4ff0e | |||
| d84e95fc0e | |||
| 8bb66f546d | |||
| 5dba701ae5 | |||
| b6a7d1646a | |||
| 7ed8d29aa8 | |||
| 91fcde5a2e | |||
| b7be86970b | |||
| 8e85009ad1 | |||
| b23360f0dd | |||
| bac39d75f4 | |||
| 1b1da2a0d4 | |||
| 838923cb14 | |||
| 9f2c42867f | |||
| 0e116b9953 | |||
| 8b3edb2f69 | |||
| fb78462eeb | |||
| 112fe9c247 | |||
| 951c254bea | |||
| 17ee1fd8ac | |||
| 5dd5b05700 | |||
| 5d1ab876e1 | |||
| ccf602ce13 | |||
| 384701326b | |||
| 12aeba2854 | |||
| 095aa0b298 | |||
| af532289c6 | |||
| 078ef933e4 | |||
| f20d8c36f3 | |||
| 27dd78e36d | |||
| 4e1b7e61c1 | |||
| bf3eebf267 | |||
| 0600cf5611 | |||
| 2a5ad784aa | |||
| ba29ddc9c0 | |||
| 2a77c8b2c8 | |||
| 6efcdaac16 | |||
| 54cc5991b9 | |||
| d2281ef4b3 | |||
| 9dc504d3ee | |||
| 2d2d410e31 | |||
| b4a4fa223a | |||
| 9eca40ca7c | |||
| 2c89f1ac4a | |||
| 1b5a4f4881 | |||
| b412371a15 | |||
| 0caef2a4b2 | |||
| d00bd44653 | |||
| 7e2e1b253c | |||
| 33a0148e51 | |||
| 88b6e804ce | |||
| 901d269050 | |||
| b8c1030095 | |||
| f4a7a7a4c0 | |||
| 496ccb7496 | |||
| 5e4a1ba4c9 | |||
| c9fdcfcdda | |||
| 90272ab2c3 | |||
| 7f3032b4ee | |||
| 4235f9cff6 | |||
| 414b71ae0b | |||
| df9a3413d9 | |||
| af4cec26fb | |||
| 3180b786f8 | |||
| 955ca36cb4 | |||
| 66b9e0bd9e | |||
| 555fcbc3fe | |||
| cdf3b252d8 | |||
| 5748aeb23a | |||
| 1f911b8bdf | |||
| 451ec5b63f | |||
| 21c700acc9 | |||
| 4508f9575c | |||
| 0d75ea6995 | |||
| 861a5cb20b | |||
| 392e209153 | |||
| 2224524146 | |||
| 07cb5c2e9c | |||
| 266ae098c7 | |||
| f0e0c0efbd | |||
| 128192dff7 | |||
| 7d04d0cead | |||
| b623bdbbfb | |||
| a274b1831c | |||
| a0169b2af9 | |||
| b24b65893b | |||
| 450f5d274a | |||
| 479b607726 | |||
| 49d4d89d23 | |||
| a023e114d5 | |||
| e4435c3fb0 | |||
| 7affa4e4c0 | |||
| e17072878d | |||
| c989304868 | |||
| ba2a3265ae | |||
| 57b27327c2 | |||
| fc06885541 | |||
| cfeeb61d51 | |||
| 1f16ad3dbd | |||
| 5678aa4586 | |||
| 50c4a5a1e7 | |||
| d1fdf222d9 | |||
| 1926094861 | |||
| b67516fb08 | |||
| 8fb5c86021 | |||
| 069b39ac35 | |||
| 43668b3aa5 | |||
| 623024a1f8 | |||
| 5dc319a5ec | |||
| c1081e2a87 | |||
| cf48b03d88 | |||
| 6ed3ba4910 | |||
| bc05cb7175 | |||
| cbd014bdd7 | |||
| dc8a06edf8 | |||
| 92defaf111 | |||
| 99514cdbaf | |||
| cd4894a002 | |||
| 7eea347638 | |||
| 29ee983651 | |||
| 7068c512fc | |||
| eb43a23641 | |||
| 9e50487605 | |||
| a2648cc198 | |||
| 63fe8e647a | |||
| d6e63b0b1f | |||
| dfe4fb271e | |||
| 7a583d77fd | |||
| aa58dd4f6b | |||
| fb22290c07 | |||
| 5ef85ea7f4 | |||
| 300a045ef6 | |||
| 2dd936b48e | |||
| 3cfa103d43 | |||
| 465be358f6 | |||
| c5d1f6f143 | |||
| c60e4d0cec | |||
| 450c71c946 | |||
| e9649445ad | |||
| edff650f46 | |||
| 55e52317ae | |||
| fbc9b78a3a | |||
| fa5b387993 | |||
| 0b203286dd | |||
| e66527ea64 | |||
| 857ed8304b | |||
| a389c2b057 | |||
| ecb85c0edd | |||
| 106d56ec88 | |||
| d321214fa3 | |||
| fa2a1bc5ac | |||
| 15a33b4ba6 | |||
| 380f4debc9 | |||
| 750576bd77 | |||
| e650f93fdc | |||
| 4b39c48abc | |||
| 2fac6b73f0 | |||
| 76ec700def | |||
| 199a607dac | |||
| 690402df4c | |||
| 5d075fb3fd | |||
| 7bc358e458 | |||
| 5652acc9e6 | |||
| 23ae8eb7d5 | |||
| b3e8cdefec | |||
| e8d9faa5e1 | |||
| 57b7e4e85e | |||
| 6a324fa85b | |||
| 50d06a54a1 | |||
| ffb23b3b75 | |||
| 19d276b5ee | |||
| 516ae5d574 | |||
| dab6227074 | |||
| 68b73f99fb | |||
| 0dd6df1d88 | |||
| 8b22931c33 | |||
| 59bdb4b35c | |||
| a5f67df0f5 | |||
| 5dd79322c0 | |||
| 10d54a6c8b | |||
| 69f21f4ea6 | |||
| c53a4f1e28 | |||
| 0df0776a4c | |||
| 197057f2f5 | |||
| 89873f6a1b | |||
| 75d9a7c77a | |||
| 411c817989 | |||
| 508b602d6e | |||
| fc82c54298 | |||
| 616d379f7d | |||
| ba1f39c70d | |||
| 0409f91f04 | |||
| bc2c84def4 | |||
| 6f22791329 | |||
| 1590feb25e | |||
| 5d3ef708e1 | |||
| 5906c55874 | |||
| b4bc0a327e |
+2648
-57
File diff suppressed because it is too large
Load Diff
+2298
-55
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,110 @@
|
|||||||
|
// @vitest-environment node
|
||||||
|
|
||||||
|
import { Auth, type AuthConfig } from "@auth/core";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
import { githubPlugin } from "@/lib/plugins/auth/github";
|
||||||
|
|
||||||
|
const ORIGIN = "https://prompts.test";
|
||||||
|
const GITHUB_ISSUER = "https://github.com/login/oauth";
|
||||||
|
|
||||||
|
function responseCookies(response: Response): string {
|
||||||
|
return response.headers.getSetCookie().map((cookie) => cookie.split(";")[0]).join("; ");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("GitHub OAuth callback", () => {
|
||||||
|
const fetchMock = vi.fn<typeof fetch>();
|
||||||
|
const signIn = vi.fn(() => true);
|
||||||
|
const logError = vi.fn();
|
||||||
|
let config: AuthConfig;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.stubEnv("GITHUB_CLIENT_ID", "test-github-client");
|
||||||
|
vi.stubEnv("GITHUB_CLIENT_SECRET", "test-github-secret");
|
||||||
|
vi.stubGlobal("fetch", fetchMock);
|
||||||
|
fetchMock.mockImplementation(async (input) => {
|
||||||
|
const url = String(input);
|
||||||
|
if (url === "https://github.com/login/oauth/access_token") {
|
||||||
|
return Response.json({ access_token: "test-access-token", token_type: "bearer" });
|
||||||
|
}
|
||||||
|
if (url === "https://api.github.com/user") {
|
||||||
|
return Response.json({
|
||||||
|
id: 123,
|
||||||
|
login: "testuser",
|
||||||
|
name: "Test User",
|
||||||
|
email: "test@example.com",
|
||||||
|
avatar_url: "https://avatars.githubusercontent.com/u/123",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error(`Unexpected fetch: ${url}`);
|
||||||
|
});
|
||||||
|
config = {
|
||||||
|
providers: [githubPlugin.getProvider()],
|
||||||
|
secret: "github-callback-test-secret",
|
||||||
|
trustHost: true,
|
||||||
|
basePath: "/api/auth",
|
||||||
|
callbacks: { signIn },
|
||||||
|
logger: { error: logError, warn: vi.fn(), debug: vi.fn() },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.unstubAllEnvs();
|
||||||
|
vi.unstubAllGlobals();
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function startSignIn(): Promise<string> {
|
||||||
|
const csrfResponse = await Auth(new Request(`${ORIGIN}/api/auth/csrf`), config);
|
||||||
|
const { csrfToken } = await csrfResponse.json() as { csrfToken: string };
|
||||||
|
const response = await Auth(new Request(`${ORIGIN}/api/auth/signin/github`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
|
Cookie: responseCookies(csrfResponse),
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({ csrfToken, callbackUrl: ORIGIN }),
|
||||||
|
}), config);
|
||||||
|
|
||||||
|
const authorizationUrl = new URL(response.headers.get("location")!);
|
||||||
|
expect(authorizationUrl.origin).toBe("https://github.com");
|
||||||
|
expect(authorizationUrl.searchParams.get("code_challenge_method")).toBe("S256");
|
||||||
|
expect(authorizationUrl.searchParams.get("code_challenge")).toBeTruthy();
|
||||||
|
return responseCookies(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
it.each([GITHUB_ISSUER, undefined])("accepts GitHub callbacks with issuer %s", async (issuer) => {
|
||||||
|
const cookies = await startSignIn();
|
||||||
|
const callbackUrl = new URL(`${ORIGIN}/api/auth/callback/github?code=test-code`);
|
||||||
|
if (issuer) callbackUrl.searchParams.set("iss", issuer);
|
||||||
|
|
||||||
|
const response = await Auth(new Request(callbackUrl, { headers: { Cookie: cookies } }), config);
|
||||||
|
|
||||||
|
expect(logError).not.toHaveBeenCalled();
|
||||||
|
expect(response.headers.get("location")).toBe(ORIGIN);
|
||||||
|
expect(signIn).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
user: expect.objectContaining({ email: "test@example.com", username: "testuser" }),
|
||||||
|
account: expect.objectContaining({ provider: "github", providerAccountId: "123" }),
|
||||||
|
}));
|
||||||
|
expect(responseCookies(response)).toContain("__Secure-authjs.session-token=");
|
||||||
|
const tokenRequest = fetchMock.mock.calls.find(([url]) => String(url).endsWith("/access_token"));
|
||||||
|
expect((tokenRequest?.[1]?.body as URLSearchParams).get("code_verifier")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("rejects a callback from an unexpected issuer before exchanging the code", async () => {
|
||||||
|
const cookies = await startSignIn();
|
||||||
|
const callbackUrl = new URL(`${ORIGIN}/api/auth/callback/github?code=test-code`);
|
||||||
|
callbackUrl.searchParams.set("iss", "https://unexpected.example.com");
|
||||||
|
|
||||||
|
const response = await Auth(new Request(callbackUrl, { headers: { Cookie: cookies } }), config);
|
||||||
|
|
||||||
|
expect(new URL(response.headers.get("location")!).searchParams.get("error")).toBe("Configuration");
|
||||||
|
expect(logError).toHaveBeenCalledWith(expect.objectContaining({
|
||||||
|
cause: expect.objectContaining({
|
||||||
|
err: expect.objectContaining({ message: 'unexpected "iss" (issuer) response parameter value' }),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
expect(fetchMock).not.toHaveBeenCalled();
|
||||||
|
expect(signIn).not.toHaveBeenCalled();
|
||||||
|
expect(responseCookies(response)).not.toContain("authjs.session-token");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -6,6 +6,8 @@ export const githubPlugin: AuthPlugin = {
|
|||||||
name: "GitHub",
|
name: "GitHub",
|
||||||
getProvider: () =>
|
getProvider: () =>
|
||||||
GitHub({
|
GitHub({
|
||||||
|
// GitHub includes this issuer in OAuth authorization responses.
|
||||||
|
issuer: "https://github.com/login/oauth",
|
||||||
clientId: process.env.GITHUB_CLIENT_ID!,
|
clientId: process.env.GITHUB_CLIENT_ID!,
|
||||||
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
||||||
profile(profile) {
|
profile(profile) {
|
||||||
|
|||||||
Reference in New Issue
Block a user