mirror of
https://github.com/LukeHagar/better-auth.git
synced 2025-12-06 04:19:20 +00:00
chore: misc update
This commit is contained in:
committed by
Alex Yang
parent
ba916a6669
commit
9818fb85ec
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"lib": "@/lib"
|
||||
}
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"lib": "@/lib"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,19 +103,20 @@ export default function SignIn() {
|
||||
toast.success("Successfully signed in");
|
||||
router.push(getCallbackURL(params));
|
||||
},
|
||||
onError(context) {
|
||||
toast.error(context.error.message);
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<span className="flex-1">
|
||||
{loading ? (
|
||||
<Loader2 size={16} className="animate-spin" />
|
||||
) : (
|
||||
"Login"
|
||||
)}
|
||||
</span>
|
||||
<div className="flex items-center justify-center w-full relative">
|
||||
{loading ? (
|
||||
<Loader2 size={16} className="animate-spin" />
|
||||
) : (
|
||||
"Login"
|
||||
)}
|
||||
{client.isLastUsedLoginMethod("email") && <LastUsedIndicator />}
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
|
||||
import { DayPicker } from "react-day-picker";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -59,10 +58,6 @@ function Calendar({
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ ...props }) => <ChevronLeftIcon className="h-4 w-4" />,
|
||||
IconRight: ({ ...props }) => <ChevronRightIcon className="h-4 w-4" />,
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -19,11 +19,9 @@ import {
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Callout } from "@/components/ui/callout";
|
||||
import * as React from "react";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import { DayPicker } from "react-day-picker";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -59,14 +58,6 @@ function Calendar({
|
||||
day_hidden: "invisible",
|
||||
...classNames,
|
||||
}}
|
||||
components={{
|
||||
IconLeft: ({ className, ...props }) => (
|
||||
<ChevronLeft className={cn("size-4", className)} {...props} />
|
||||
),
|
||||
IconRight: ({ className, ...props }) => (
|
||||
<ChevronRight className={cn("size-4", className)} {...props} />
|
||||
),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node16"
|
||||
"moduleResolution": "node16",
|
||||
"module": "Node16"
|
||||
},
|
||||
"include": ["./test/**/*"]
|
||||
}
|
||||
|
||||
@@ -19,15 +19,17 @@
|
||||
"test": "turbo --filter \"./packages/*\" test",
|
||||
"e2e:smoke": "turbo --filter \"./e2e/*\" e2e:smoke",
|
||||
"e2e:integration": "turbo --filter \"./e2e/*\" e2e:integration",
|
||||
"typecheck": "tsc -b --verbose --diagnostics"
|
||||
"typecheck": "turbo --filter \"./packages/*\" typecheck"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.2.4",
|
||||
"@types/bun": "^1.2.20",
|
||||
"@types/node": "^24.4.0",
|
||||
"bumpp": "^10.2.3",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"turbo": "^2.5.6",
|
||||
"typescript": "catalog:",
|
||||
"unbuild": "catalog:",
|
||||
"vitest": "catalog:"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# better-auth
|
||||
|
||||
## 1.3.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2bd2fa9: Added support for listing organization members with pagination, sorting, and filtering, and improved client inference for additional organization fields. Also fixed date handling in rate limits and tokens, improved Notion OAuth user extraction, and ensured session is always set in context.
|
||||
|
||||
Organization
|
||||
|
||||
- Added listMembers API with pagination, sorting, and filtering.
|
||||
- Added membersLimit param to getFullOrganization.
|
||||
- Improved client inference for additional fields in organization schemas.
|
||||
- Bug Fixes
|
||||
- Fixed date handling by casting DB values to Date objects before using date methods.
|
||||
- Fixed Notion OAuth to extract user info correctly.
|
||||
- Ensured session is set in context when reading from cookie cach
|
||||
@@ -69,42 +69,7 @@ export const createInternalAdapter = (
|
||||
session: parsed.session,
|
||||
user,
|
||||
}),
|
||||
sessionTTL,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async function refreshUserSessions(user: User) {
|
||||
if (!secondaryStorage) return;
|
||||
|
||||
const listRaw = await secondaryStorage.get(`active-sessions-${user.id}`);
|
||||
if (!listRaw) return;
|
||||
|
||||
const now = Date.now();
|
||||
const list =
|
||||
safeJSONParse<{ token: string; expiresAt: number }[]>(listRaw) || [];
|
||||
const validSessions = list.filter((s) => s.expiresAt > now);
|
||||
|
||||
await Promise.all(
|
||||
validSessions.map(async ({ token }) => {
|
||||
const cached = await secondaryStorage.get(token);
|
||||
if (!cached) return;
|
||||
const parsed = safeJSONParse<{ session: Session; user: User }>(cached);
|
||||
if (!parsed) return;
|
||||
|
||||
const sessionTTL = Math.max(
|
||||
Math.floor(new Date(parsed.session.expiresAt).getTime() - now) / 1000,
|
||||
0,
|
||||
);
|
||||
|
||||
await secondaryStorage.set(
|
||||
token,
|
||||
JSON.stringify({
|
||||
session: parsed.session,
|
||||
user,
|
||||
}),
|
||||
sessionTTL,
|
||||
Math.floor(sessionTTL),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { getTestInstance } from "../test-utils/test-instance";
|
||||
import { parseSetCookieHeader } from "../cookies";
|
||||
import type { GoogleProfile } from "../social-providers";
|
||||
import { DEFAULT_SECRET } from "../utils/constants";
|
||||
import { getOAuth2Tokens } from "../oauth2";
|
||||
@@ -43,7 +42,7 @@ vi.mock("../oauth2", async (importOriginal) => {
|
||||
});
|
||||
|
||||
describe("oauth2 - email verification on link", async () => {
|
||||
const { auth, client } = await getTestInstance({
|
||||
const { auth, client, cookieSetter } = await getTestInstance({
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: "test",
|
||||
@@ -66,25 +65,19 @@ describe("oauth2 - email verification on link", async () => {
|
||||
const ctx = await auth.$context;
|
||||
|
||||
async function linkGoogleAccount() {
|
||||
const oAuthHeaders = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
});
|
||||
|
||||
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
const headers = new Headers();
|
||||
const cookies = parseSetCookieHeader(
|
||||
(signInRes as any).headers?.["set-cookie"] || "",
|
||||
);
|
||||
headers.set(
|
||||
"cookie",
|
||||
`better-auth.state=${cookies.get("better-auth.state")?.value}`,
|
||||
);
|
||||
|
||||
await client.$fetch("/callback/google", {
|
||||
query: { state, code: "test_code" },
|
||||
method: "GET",
|
||||
headers,
|
||||
headers: oAuthHeaders,
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
},
|
||||
|
||||
@@ -461,12 +461,14 @@ describe("Admin plugin", async () => {
|
||||
});
|
||||
|
||||
it("should not allow banned user to sign in with social provider", async () => {
|
||||
const headers = new Headers();
|
||||
const res = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
const state = new URL(res.url!).searchParams.get("state");
|
||||
@@ -476,6 +478,7 @@ describe("Admin plugin", async () => {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
|
||||
@@ -45,7 +45,7 @@ vi.mock("../../oauth2", async (importOriginal) => {
|
||||
|
||||
describe("anonymous", async () => {
|
||||
const linkAccountFn = vi.fn();
|
||||
const { customFetchImpl, auth, sessionSetter, testUser } =
|
||||
const { customFetchImpl, auth, sessionSetter, testUser, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [
|
||||
anonymous({
|
||||
@@ -114,9 +114,13 @@ describe("anonymous", async () => {
|
||||
headers,
|
||||
},
|
||||
});
|
||||
|
||||
const singInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
const state = new URL(singInRes.data?.url || "").searchParams.get("state");
|
||||
await client.$fetch("/callback/google", {
|
||||
|
||||
@@ -225,7 +225,7 @@ describe("email-otp", async () => {
|
||||
type: "email-verification",
|
||||
});
|
||||
vi.useFakeTimers();
|
||||
await vi.advanceTimersByTimeAsync(1000 * 60 * 5);
|
||||
await vi.advanceTimersByTimeAsync(1000 * 60 * 6);
|
||||
const res = await client.emailOtp.verifyEmail({
|
||||
email: testUser.email,
|
||||
otp,
|
||||
|
||||
@@ -20,7 +20,7 @@ describe("oauth2", async () => {
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
const { customFetchImpl, auth } = await getTestInstance({
|
||||
const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -88,10 +88,7 @@ describe("oauth2", async () => {
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
newHeaders.set(
|
||||
"cookie",
|
||||
context.response.headers.get("Set-Cookie") || "",
|
||||
);
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -104,6 +101,9 @@ describe("oauth2", async () => {
|
||||
providerId: "test",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining(`http://localhost:${port}/authorize`),
|
||||
@@ -133,6 +133,9 @@ describe("oauth2", async () => {
|
||||
providerId: "test",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining(`http://localhost:${port}/authorize`),
|
||||
@@ -148,7 +151,7 @@ describe("oauth2", async () => {
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(session.data, newHeaders);
|
||||
const ctx = await auth.$context;
|
||||
const accounts = await ctx.internalAdapter.findAccounts(
|
||||
session.data?.user.id!,
|
||||
@@ -173,6 +176,9 @@ describe("oauth2", async () => {
|
||||
providerId: "test",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
const { callbackURL } = await simulateOAuthFlow(
|
||||
res.data?.url || "",
|
||||
@@ -250,12 +256,13 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -263,9 +270,11 @@ describe("oauth2", async () => {
|
||||
providerId: "test2",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
|
||||
const headers = new Headers();
|
||||
const { callbackURL } = await simulateOAuthFlow(
|
||||
res.data?.url || "",
|
||||
headers,
|
||||
@@ -286,7 +295,7 @@ describe("oauth2", async () => {
|
||||
userInfoResponse.statusCode = 200;
|
||||
});
|
||||
|
||||
const { customFetchImpl } = await getTestInstance({
|
||||
const { customFetchImpl, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -302,7 +311,6 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
@@ -310,14 +318,16 @@ describe("oauth2", async () => {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.oauth2({
|
||||
providerId: "test2",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
errorCallbackURL: "http://localhost:3000/error",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
|
||||
const headers = new Headers();
|
||||
const { callbackURL } = await simulateOAuthFlow(
|
||||
res.data?.url || "",
|
||||
headers,
|
||||
@@ -340,7 +350,7 @@ describe("oauth2", async () => {
|
||||
userInfoResponse.statusCode = 200;
|
||||
});
|
||||
|
||||
const { customFetchImpl } = await getTestInstance({
|
||||
const { customFetchImpl, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -364,15 +374,17 @@ describe("oauth2", async () => {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.oauth2({
|
||||
providerId: "test2",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
errorCallbackURL: "http://localhost:3000/error",
|
||||
requestSignUp: true,
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
|
||||
const headers = new Headers();
|
||||
const { callbackURL } = await simulateOAuthFlow(
|
||||
res.data?.url || "",
|
||||
headers,
|
||||
@@ -391,7 +403,7 @@ describe("oauth2", async () => {
|
||||
receivedHeaders = req.headers as Record<string, string>;
|
||||
});
|
||||
|
||||
const { customFetchImpl } = await getTestInstance({
|
||||
const { customFetchImpl, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -407,12 +419,13 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -420,10 +433,12 @@ describe("oauth2", async () => {
|
||||
providerId: "test3",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
|
||||
const headers = new Headers();
|
||||
await simulateOAuthFlow(res.data?.url || "", headers, customFetchImpl);
|
||||
|
||||
expect(receivedHeaders).toHaveProperty("x-custom-header");
|
||||
@@ -432,7 +447,7 @@ describe("oauth2", async () => {
|
||||
|
||||
it("should delete oauth user with verification flow without password", async () => {
|
||||
let token = "";
|
||||
const { customFetchImpl } = await getTestInstance({
|
||||
const { customFetchImpl, cookieSetter } = await getTestInstance({
|
||||
user: {
|
||||
deleteUser: {
|
||||
enabled: true,
|
||||
@@ -454,18 +469,22 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const client = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
const signInRes = await client.signIn.oauth2({
|
||||
providerId: "test",
|
||||
callbackURL: "http://localhost:3000/dashboard",
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
expect(signInRes.data).toMatchObject({
|
||||
@@ -473,22 +492,22 @@ describe("oauth2", async () => {
|
||||
redirect: true,
|
||||
});
|
||||
|
||||
const { headers } = await simulateOAuthFlow(
|
||||
const { headers: newHeaders } = await simulateOAuthFlow(
|
||||
signInRes.data?.url || "",
|
||||
new Headers(),
|
||||
headers,
|
||||
customFetchImpl,
|
||||
);
|
||||
|
||||
const session = await client.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
expect(session.data).not.toBeNull();
|
||||
|
||||
const deleteRes = await client.deleteUser({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -501,7 +520,7 @@ describe("oauth2", async () => {
|
||||
const deleteCallbackRes = await client.deleteUser({
|
||||
token,
|
||||
fetchOptions: {
|
||||
headers,
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
expect(deleteCallbackRes.data).toMatchObject({
|
||||
@@ -530,7 +549,7 @@ describe("oauth2", async () => {
|
||||
userInfoResponse.statusCode = 200;
|
||||
});
|
||||
|
||||
const { customFetchImpl, auth } = await getTestInstance({
|
||||
const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -545,12 +564,13 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -563,7 +583,7 @@ describe("oauth2", async () => {
|
||||
const { callbackURL: firstCallbackURL, headers: firstHeaders } =
|
||||
await simulateOAuthFlow(
|
||||
firstSignIn.data?.url || "",
|
||||
new Headers(),
|
||||
headers,
|
||||
customFetchImpl,
|
||||
);
|
||||
|
||||
@@ -606,7 +626,7 @@ describe("oauth2", async () => {
|
||||
const { callbackURL: secondCallbackURL, headers: secondHeaders } =
|
||||
await simulateOAuthFlow(
|
||||
secondSignIn.data?.url || "",
|
||||
new Headers(),
|
||||
headers,
|
||||
customFetchImpl,
|
||||
);
|
||||
|
||||
@@ -629,7 +649,7 @@ describe("oauth2", async () => {
|
||||
it("should handle custom getUserInfo returning numeric ID", async () => {
|
||||
const numericId = 987654321;
|
||||
|
||||
const { customFetchImpl, auth } = await getTestInstance({
|
||||
const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -654,12 +674,13 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -669,9 +690,9 @@ describe("oauth2", async () => {
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
});
|
||||
|
||||
const { callbackURL, headers } = await simulateOAuthFlow(
|
||||
const { callbackURL, headers: newHeaders } = await simulateOAuthFlow(
|
||||
signInRes.data?.url || "",
|
||||
new Headers(),
|
||||
headers,
|
||||
customFetchImpl,
|
||||
);
|
||||
|
||||
@@ -679,7 +700,7 @@ describe("oauth2", async () => {
|
||||
|
||||
const session = await authClient.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -706,7 +727,7 @@ describe("oauth2", async () => {
|
||||
userInfoResponse.statusCode = 200;
|
||||
});
|
||||
|
||||
const { customFetchImpl, auth } = await getTestInstance({
|
||||
const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -729,12 +750,13 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -744,9 +766,9 @@ describe("oauth2", async () => {
|
||||
newUserCallbackURL: "http://localhost:3000/new_user",
|
||||
});
|
||||
|
||||
const { callbackURL, headers } = await simulateOAuthFlow(
|
||||
const { callbackURL, headers: newHeaders } = await simulateOAuthFlow(
|
||||
signInRes.data?.url || "",
|
||||
new Headers(),
|
||||
headers,
|
||||
customFetchImpl,
|
||||
);
|
||||
|
||||
@@ -754,7 +776,7 @@ describe("oauth2", async () => {
|
||||
|
||||
const session = await authClient.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -781,7 +803,7 @@ describe("oauth2", async () => {
|
||||
userInfoResponse.statusCode = 200;
|
||||
});
|
||||
|
||||
const { customFetchImpl, auth } = await getTestInstance({
|
||||
const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
@@ -809,12 +831,13 @@ describe("oauth2", async () => {
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const authClient = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -828,9 +851,9 @@ describe("oauth2", async () => {
|
||||
// we missed the `activity:read_all`
|
||||
expect(signInRes.data?.url).toContain("scope=read+activity");
|
||||
|
||||
const { callbackURL, headers } = await simulateOAuthFlow(
|
||||
const { callbackURL, headers: newHeaders } = await simulateOAuthFlow(
|
||||
signInRes.data?.url || "",
|
||||
new Headers(),
|
||||
headers,
|
||||
customFetchImpl,
|
||||
);
|
||||
|
||||
@@ -838,7 +861,7 @@ describe("oauth2", async () => {
|
||||
|
||||
const session = await authClient.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
headers: newHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ describe("lastLoginMethod", async () => {
|
||||
});
|
||||
|
||||
it("should update the last login method in the database on subsequent logins with email and OAuth", async () => {
|
||||
const { client, auth } = await getTestInstance({
|
||||
const { client, auth, cookieSetter } = await getTestInstance({
|
||||
plugins: [lastLoginMethod({ storeInDatabase: true })],
|
||||
account: {
|
||||
accountLinking: {
|
||||
@@ -216,9 +216,13 @@ describe("lastLoginMethod", async () => {
|
||||
|
||||
await client.signOut();
|
||||
|
||||
const oAuthHeaders = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -232,6 +236,7 @@ describe("lastLoginMethod", async () => {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers: oAuthHeaders,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
|
||||
@@ -21,7 +21,7 @@ describe("mcp", async () => {
|
||||
const baseURL = `http://localhost:${port}`;
|
||||
await tempServer.close();
|
||||
|
||||
const { auth, signInWithTestUser, customFetchImpl, testUser } =
|
||||
const { auth, signInWithTestUser, customFetchImpl, testUser, cookieSetter } =
|
||||
await getTestInstance({
|
||||
baseURL,
|
||||
plugins: [
|
||||
@@ -161,28 +161,29 @@ describe("mcp", async () => {
|
||||
});
|
||||
|
||||
it("should authenticate public client with PKCE only", async ({ expect }) => {
|
||||
const { customFetchImpl: customFetchImplRP } = await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test-public"],
|
||||
const { customFetchImpl: customFetchImplRP, cookieSetter } =
|
||||
await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test-public"],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test-public",
|
||||
clientId: publicClient.clientId,
|
||||
clientSecret: "", // Public client has no secret
|
||||
authorizationUrl: `${baseURL}/api/auth/mcp/authorize`,
|
||||
tokenUrl: `${baseURL}/api/auth/mcp/token`,
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test-public",
|
||||
clientId: publicClient.clientId,
|
||||
clientSecret: "", // Public client has no secret
|
||||
authorizationUrl: `${baseURL}/api/auth/mcp/authorize`,
|
||||
tokenUrl: `${baseURL}/api/auth/mcp/token`,
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
@@ -191,7 +192,7 @@ describe("mcp", async () => {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
|
||||
const oAuthHeaders = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId: "test-public",
|
||||
@@ -199,6 +200,7 @@ describe("mcp", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -220,6 +222,7 @@ describe("mcp", async () => {
|
||||
|
||||
let callbackURL = "";
|
||||
await client.$fetch(redirectURI, {
|
||||
headers: oAuthHeaders,
|
||||
onError(context: any) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
},
|
||||
@@ -254,29 +257,30 @@ describe("mcp", async () => {
|
||||
it("should still support confidential clients in MCP context", async ({
|
||||
expect,
|
||||
}) => {
|
||||
const { customFetchImpl: customFetchImplRP } = await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test-confidential"],
|
||||
const { customFetchImpl: customFetchImplRP, cookieSetter } =
|
||||
await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test-confidential"],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test-confidential",
|
||||
clientId: confidentialClient.clientId,
|
||||
clientSecret: confidentialClient.clientSecret || "",
|
||||
authorizationUrl: `${baseURL}/api/auth/mcp/authorize`,
|
||||
tokenUrl: `${baseURL}/api/auth/mcp/token`,
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test-confidential",
|
||||
clientId: confidentialClient.clientId,
|
||||
clientSecret: confidentialClient.clientSecret || "",
|
||||
authorizationUrl: `${baseURL}/api/auth/mcp/authorize`,
|
||||
tokenUrl: `${baseURL}/api/auth/mcp/token`,
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
const oAuthHeaders = new Headers();
|
||||
const client = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
baseURL: "http://localhost:5001",
|
||||
@@ -292,6 +296,7 @@ describe("mcp", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -311,6 +316,7 @@ describe("mcp", async () => {
|
||||
|
||||
let callbackURL = "";
|
||||
await client.$fetch(redirectURI, {
|
||||
headers: oAuthHeaders,
|
||||
onError(context: any) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@ vi.mock("../../oauth2", async (importOriginal) => {
|
||||
});
|
||||
|
||||
describe("oauth-proxy", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
plugins: [
|
||||
oAuthProxy({
|
||||
currentURL: "http://preview-localhost:3000",
|
||||
@@ -57,6 +57,7 @@ describe("oauth-proxy", async () => {
|
||||
});
|
||||
|
||||
it("should redirect to proxy url", async () => {
|
||||
const headers = new Headers();
|
||||
const res = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
@@ -64,10 +65,12 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
const state = new URL(res.url!).searchParams.get("state");
|
||||
await client.$fetch(`/callback/google?code=test&state=${state}`, {
|
||||
headers,
|
||||
onError(context) {
|
||||
const location = context.response.headers.get("location") ?? "";
|
||||
if (!location) {
|
||||
@@ -83,7 +86,7 @@ describe("oauth-proxy", async () => {
|
||||
});
|
||||
|
||||
it("shouldn't redirect to proxy url on same origin", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
plugins: [oAuthProxy()],
|
||||
socialProviders: {
|
||||
google: {
|
||||
@@ -92,6 +95,7 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const headers = new Headers();
|
||||
const res = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
@@ -99,10 +103,12 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
const state = new URL(res.url!).searchParams.get("state");
|
||||
await client.$fetch(`/callback/google?code=test&state=${state}`, {
|
||||
headers,
|
||||
onError(context) {
|
||||
const location = context.response.headers.get("location");
|
||||
if (!location) {
|
||||
@@ -115,7 +121,7 @@ describe("oauth-proxy", async () => {
|
||||
});
|
||||
|
||||
it("should proxy to the original request url", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
baseURL: "https://myapp.com",
|
||||
plugins: [
|
||||
oAuthProxy({
|
||||
@@ -129,6 +135,7 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const headers = new Headers();
|
||||
const res = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
@@ -136,10 +143,12 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
const state = new URL(res.url!).searchParams.get("state");
|
||||
await client.$fetch(`/callback/google?code=test&state=${state}`, {
|
||||
headers,
|
||||
onError(context) {
|
||||
const location = context.response.headers.get("location");
|
||||
if (!location) {
|
||||
@@ -155,7 +164,7 @@ describe("oauth-proxy", async () => {
|
||||
});
|
||||
|
||||
it("shouldn't redirect to proxy url on same origin", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
baseURL: "https://myapp.com",
|
||||
plugins: [oAuthProxy()],
|
||||
socialProviders: {
|
||||
@@ -165,6 +174,7 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const headers = new Headers();
|
||||
const res = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
@@ -172,10 +182,12 @@ describe("oauth-proxy", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
const state = new URL(res.url!).searchParams.get("state");
|
||||
await client.$fetch(`/callback/google?code=test&state=${state}`, {
|
||||
headers,
|
||||
onError(context) {
|
||||
const location = context.response.headers.get("location");
|
||||
if (!location) {
|
||||
|
||||
@@ -24,6 +24,7 @@ describe("oidc", async () => {
|
||||
auth: authorizationServer,
|
||||
signInWithTestUser,
|
||||
customFetchImpl,
|
||||
cookieSetter,
|
||||
testUser,
|
||||
} = await getTestInstance({
|
||||
baseURL: "http://localhost:3000",
|
||||
@@ -140,6 +141,7 @@ describe("oidc", async () => {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
const headers = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId: "test",
|
||||
@@ -147,6 +149,7 @@ describe("oidc", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
);
|
||||
expect(data.url).toContain(
|
||||
@@ -167,6 +170,7 @@ describe("oidc", async () => {
|
||||
|
||||
let callbackURL = "";
|
||||
await client.$fetch(redirectURI, {
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
},
|
||||
@@ -209,6 +213,7 @@ describe("oidc", async () => {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
const oAuthHeaders = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId: "test",
|
||||
@@ -216,6 +221,7 @@ describe("oidc", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
);
|
||||
expect(data.url).toContain(
|
||||
@@ -236,6 +242,8 @@ describe("oidc", async () => {
|
||||
expect(redirectURI).toContain("/oauth2/authorize?");
|
||||
expect(redirectURI).toContain("consent_code=");
|
||||
expect(redirectURI).toContain("client_id=");
|
||||
|
||||
console.log({ newHeaders });
|
||||
const res = await serverClient.oauth2.consent(
|
||||
{
|
||||
accept: true,
|
||||
@@ -251,6 +259,7 @@ describe("oidc", async () => {
|
||||
|
||||
let callbackURL = "";
|
||||
await client.$fetch(res.redirectURI, {
|
||||
headers: oAuthHeaders,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
},
|
||||
@@ -293,6 +302,7 @@ describe("oidc", async () => {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
const oAuthHeaders = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId: "test",
|
||||
@@ -300,6 +310,7 @@ describe("oidc", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
);
|
||||
expect(data.url).toContain(
|
||||
@@ -338,6 +349,7 @@ describe("oidc", async () => {
|
||||
);
|
||||
let callbackURL = "";
|
||||
await client.$fetch(redirectURI, {
|
||||
headers: oAuthHeaders,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
},
|
||||
@@ -443,29 +455,30 @@ describe("oidc storage", async () => {
|
||||
};
|
||||
}
|
||||
// The RP (Relying Party) - the client application
|
||||
const { customFetchImpl: customFetchImplRP } = await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test"],
|
||||
const { customFetchImpl: customFetchImplRP, cookieSetter } =
|
||||
await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test"],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test",
|
||||
clientId: application.clientId,
|
||||
clientSecret: application.clientSecret || "",
|
||||
authorizationUrl:
|
||||
"http://localhost:3000/api/auth/oauth2/authorize",
|
||||
tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test",
|
||||
clientId: application.clientId,
|
||||
clientSecret: application.clientSecret || "",
|
||||
authorizationUrl:
|
||||
"http://localhost:3000/api/auth/oauth2/authorize",
|
||||
tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
@@ -474,6 +487,7 @@ describe("oidc storage", async () => {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
const oAuthHeaders = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId: "test",
|
||||
@@ -481,6 +495,7 @@ describe("oidc storage", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
);
|
||||
expect(data.url).toContain(
|
||||
@@ -501,6 +516,7 @@ describe("oidc storage", async () => {
|
||||
|
||||
let callbackURL = "";
|
||||
await client.$fetch(redirectURI, {
|
||||
headers: oAuthHeaders,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
},
|
||||
@@ -601,29 +617,30 @@ describe("oidc-jwt", async () => {
|
||||
}
|
||||
|
||||
// The RP (Relying Party) - the client application
|
||||
const { customFetchImpl: customFetchImplRP } = await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test"],
|
||||
const { customFetchImpl: customFetchImplRP, cookieSetter } =
|
||||
await getTestInstance({
|
||||
account: {
|
||||
accountLinking: {
|
||||
trustedProviders: ["test"],
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test",
|
||||
clientId: application.clientId,
|
||||
clientSecret: application.clientSecret || "",
|
||||
authorizationUrl:
|
||||
"http://localhost:3000/api/auth/oauth2/authorize",
|
||||
tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
plugins: [
|
||||
genericOAuth({
|
||||
config: [
|
||||
{
|
||||
providerId: "test",
|
||||
clientId: application.clientId,
|
||||
clientSecret: application.clientSecret || "",
|
||||
authorizationUrl:
|
||||
"http://localhost:3000/api/auth/oauth2/authorize",
|
||||
tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
|
||||
scopes: ["openid", "profile", "email"],
|
||||
pkce: true,
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const client = createAuthClient({
|
||||
plugins: [genericOAuthClient()],
|
||||
@@ -632,6 +649,7 @@ describe("oidc-jwt", async () => {
|
||||
customFetchImpl: customFetchImplRP,
|
||||
},
|
||||
});
|
||||
const oAuthHeaders = new Headers();
|
||||
const data = await client.signIn.oauth2(
|
||||
{
|
||||
providerId: "test",
|
||||
@@ -639,6 +657,7 @@ describe("oidc-jwt", async () => {
|
||||
},
|
||||
{
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(oAuthHeaders),
|
||||
},
|
||||
);
|
||||
expect(data.url).toContain(
|
||||
@@ -659,6 +678,7 @@ describe("oidc-jwt", async () => {
|
||||
let authToken = undefined;
|
||||
let callbackURL = "";
|
||||
await client.$fetch(redirectURI, {
|
||||
headers: oAuthHeaders,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("Location") || "";
|
||||
authToken = context.response.headers.get("set-auth-token")!;
|
||||
|
||||
@@ -4,12 +4,23 @@ import { sso } from ".";
|
||||
import { OAuth2Server } from "oauth2-mock-server";
|
||||
import { betterFetch } from "@better-fetch/fetch";
|
||||
import { organization } from "../organization";
|
||||
import { createAuthClient } from "../../client";
|
||||
import { ssoClient } from "./client";
|
||||
|
||||
let server = new OAuth2Server();
|
||||
|
||||
describe("SSO", async () => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({
|
||||
plugins: [sso(), organization()],
|
||||
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [sso(), organization()],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [ssoClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -56,7 +67,7 @@ describe("SSO", async () => {
|
||||
});
|
||||
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
const newHeaders = new Headers();
|
||||
let callbackURL = "";
|
||||
await betterFetch(location, {
|
||||
method: "GET",
|
||||
@@ -64,10 +75,11 @@ describe("SSO", async () => {
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
return callbackURL;
|
||||
return { callbackURL, headers: newHeaders };
|
||||
}
|
||||
|
||||
it("should register a new SSO provider", async () => {
|
||||
@@ -141,58 +153,74 @@ describe("SSO", async () => {
|
||||
});
|
||||
|
||||
it("should sign in with SSO provider with email matching", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
|
||||
it("should sign in with SSO provider with domain", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@test.com",
|
||||
domain: "localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@test.com",
|
||||
domain: "localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
|
||||
it("should sign in with SSO provider with providerId", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
providerId: "test",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
providerId: "test",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSO disable implicit sign in", async () => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({
|
||||
plugins: [sso({ disableImplicitSignUp: true }), organization()],
|
||||
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [sso({ disableImplicitSignUp: true }), organization()],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [ssoClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -239,7 +267,7 @@ describe("SSO disable implicit sign in", async () => {
|
||||
});
|
||||
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
const newHeaders = new Headers(headers);
|
||||
let callbackURL = "";
|
||||
await betterFetch(location, {
|
||||
method: "GET",
|
||||
@@ -247,10 +275,11 @@ describe("SSO disable implicit sign in", async () => {
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
return callbackURL;
|
||||
return { callbackURL, headers: newHeaders };
|
||||
}
|
||||
|
||||
it("should register a new SSO provider", async () => {
|
||||
@@ -300,44 +329,57 @@ describe("SSO disable implicit sign in", async () => {
|
||||
});
|
||||
|
||||
it("should not create user with SSO provider when sign ups are disabled", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain(
|
||||
"/api/auth/error/error?error=signup disabled",
|
||||
);
|
||||
});
|
||||
|
||||
it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
requestSignUp: true,
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
requestSignUp: true,
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
});
|
||||
|
||||
describe("provisioning", async (ctx) => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({
|
||||
plugins: [sso(), organization()],
|
||||
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [sso(), organization()],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [ssoClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -367,12 +409,14 @@ describe("provisioning", async (ctx) => {
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
let callbackURL = "";
|
||||
const newHeaders = new Headers();
|
||||
await betterFetch(location, {
|
||||
method: "GET",
|
||||
customFetchImpl: fetchImpl || customFetchImpl,
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -429,18 +473,20 @@ describe("provisioning", async (ctx) => {
|
||||
expect(provider).toMatchObject({
|
||||
organizationId: organization?.id,
|
||||
});
|
||||
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const newHeaders = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(newHeaders),
|
||||
throw: true,
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const newHeaders = new Headers();
|
||||
|
||||
const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
const org = await auth.api.getFullOrganization({
|
||||
|
||||
@@ -181,216 +181,235 @@ describe("Social Providers", async (c) => {
|
||||
token.payload.name = "Test User";
|
||||
token.payload.picture = "https://test.com/picture.png";
|
||||
});
|
||||
let state = "";
|
||||
|
||||
const headers = new Headers();
|
||||
describe("signin", async () => {
|
||||
async function simulateOAuthFlowRefresh(
|
||||
authUrl: string,
|
||||
headers: Headers,
|
||||
fetchImpl?: (...args: any) => any,
|
||||
) {
|
||||
let location: string | null = null;
|
||||
await betterFetch(authUrl, {
|
||||
method: "GET",
|
||||
redirect: "manual",
|
||||
onError(context) {
|
||||
location = context.response.headers.get("location");
|
||||
},
|
||||
});
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
async function simulateOAuthFlowRefresh(
|
||||
authUrl: string,
|
||||
headers: Headers,
|
||||
fetchImpl?: (...args: any) => any,
|
||||
) {
|
||||
let location: string | null = null;
|
||||
await betterFetch(authUrl, {
|
||||
method: "GET",
|
||||
redirect: "manual",
|
||||
onError(context) {
|
||||
location = context.response.headers.get("location");
|
||||
},
|
||||
});
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
const tokens = await refreshAccessToken({
|
||||
refreshToken: "mock-refresh-token",
|
||||
options: {
|
||||
clientId: "test-client-id",
|
||||
clientKey: "test-client-key",
|
||||
clientSecret: "test-client-secret",
|
||||
},
|
||||
tokenEndpoint: `http://localhost:${port}/token`,
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
it("should be able to add social providers", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
const tokens = await refreshAccessToken({
|
||||
refreshToken: "mock-refresh-token",
|
||||
options: {
|
||||
clientId: "test-client-id",
|
||||
clientKey: "test-client-key",
|
||||
clientSecret: "test-client-secret",
|
||||
},
|
||||
tokenEndpoint: `http://localhost:${port}/token`,
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
it("should be able to add social providers", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should be able to sign in with social providers", async () => {
|
||||
const headers = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
const location = context.response.headers.get("location");
|
||||
expect(location).toBeDefined();
|
||||
expect(location).toContain("/welcome");
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Should use callback URL if the user is already registered", async () => {
|
||||
const headers = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
|
||||
it("should be able to sign in with social providers", async () => {
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
const location = context.response.headers.get("location");
|
||||
expect(location).toBeDefined();
|
||||
expect(location).toContain("/welcome");
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
|
||||
},
|
||||
});
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
const location = context.response.headers.get("location");
|
||||
expect(location).toBeDefined();
|
||||
expect(location).toContain("/callback");
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Should use callback URL if the user is already registered", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
const location = context.response.headers.get("location");
|
||||
expect(location).toBeDefined();
|
||||
expect(location).toContain("/callback");
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
|
||||
},
|
||||
});
|
||||
it("should be able to map profile to user", async () => {
|
||||
const headers = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
it("should be able to map profile to user", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
|
||||
const headers = new Headers();
|
||||
|
||||
const profile = await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
method: "GET",
|
||||
onError: (c) => {
|
||||
//TODO: fix this
|
||||
cookieSetter(headers)(c as any);
|
||||
},
|
||||
});
|
||||
const session = await client.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
},
|
||||
});
|
||||
expect(session.data?.user).toMatchObject({
|
||||
isOAuth: true,
|
||||
firstName: "First",
|
||||
lastName: "Last",
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
|
||||
it("should be protected from callback URL attacks", async () => {
|
||||
const signInRes = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
callbackURL: "https://evil.com/callback",
|
||||
},
|
||||
{
|
||||
onSuccess(context) {
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
headers.set(
|
||||
"cookie",
|
||||
`better-auth.state=${cookies.get("better-auth.state")?.value}`,
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(signInRes.error?.status).toBe(403);
|
||||
expect(signInRes.error?.message).toBe("Invalid callbackURL");
|
||||
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError: (c) => {
|
||||
//TODO: fix this
|
||||
cookieSetter(headers)(c as any);
|
||||
},
|
||||
});
|
||||
|
||||
it("should refresh the access token", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
});
|
||||
const headers = new Headers();
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
const location = context.response.headers.get("location");
|
||||
expect(location).toBeDefined();
|
||||
expect(location).toContain("/callback");
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
cookieSetter(headers)(context as any);
|
||||
expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
|
||||
},
|
||||
});
|
||||
const accounts = await client.listAccounts({
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
await client.$fetch("/refresh-token", {
|
||||
body: {
|
||||
accountId: "test-id",
|
||||
providerId: "google",
|
||||
},
|
||||
const session = await client.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
method: "POST",
|
||||
onError(context) {
|
||||
cookieSetter(headers)(context as any);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
expect(session.data?.user).toMatchObject({
|
||||
isOAuth: true,
|
||||
firstName: "First",
|
||||
lastName: "Last",
|
||||
});
|
||||
});
|
||||
|
||||
const authUrl = signInRes.data?.url;
|
||||
if (!authUrl) throw new Error("No auth url found");
|
||||
const mockEndpoint = authUrl.replace(
|
||||
"https://accounts.google.com/o/oauth2/auth",
|
||||
`http://localhost:${port}/authorize`,
|
||||
);
|
||||
const result = await simulateOAuthFlowRefresh(mockEndpoint, headers);
|
||||
const { accessToken, refreshToken } = result;
|
||||
expect({ accessToken, refreshToken }).toEqual({
|
||||
accessToken: "new-access-token",
|
||||
refreshToken: "new-refresh-token",
|
||||
});
|
||||
it("should be protected from callback URL attacks", async () => {
|
||||
const signInRes = await client.signIn.social(
|
||||
{
|
||||
provider: "google",
|
||||
callbackURL: "https://evil.com/callback",
|
||||
},
|
||||
{
|
||||
onSuccess(context) {
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
headers.set(
|
||||
"cookie",
|
||||
`better-auth.state=${cookies.get("better-auth.state")?.value}`,
|
||||
);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
expect(signInRes.error?.status).toBe(403);
|
||||
expect(signInRes.error?.message).toBe("Invalid callbackURL");
|
||||
});
|
||||
|
||||
it("should refresh the access token", async () => {
|
||||
const headers = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
redirect: true,
|
||||
});
|
||||
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
const location = context.response.headers.get("location");
|
||||
expect(location).toBeDefined();
|
||||
expect(location).toContain("/callback");
|
||||
const cookies = parseSetCookieHeader(
|
||||
context.response.headers.get("set-cookie") || "",
|
||||
);
|
||||
cookieSetter(headers)(context as any);
|
||||
expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
|
||||
},
|
||||
});
|
||||
const accounts = await client.listAccounts({
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
await client.$fetch("/refresh-token", {
|
||||
body: {
|
||||
accountId: "test-id",
|
||||
providerId: "google",
|
||||
},
|
||||
headers,
|
||||
method: "POST",
|
||||
onError(context) {
|
||||
cookieSetter(headers)(context as any);
|
||||
},
|
||||
});
|
||||
|
||||
const authUrl = signInRes.data?.url;
|
||||
if (!authUrl) throw new Error("No auth url found");
|
||||
const mockEndpoint = authUrl.replace(
|
||||
"https://accounts.google.com/o/oauth2/auth",
|
||||
`http://localhost:${port}/authorize`,
|
||||
);
|
||||
const result = await simulateOAuthFlowRefresh(mockEndpoint, headers);
|
||||
const { accessToken, refreshToken } = result;
|
||||
expect({ accessToken, refreshToken }).toEqual({
|
||||
accessToken: "new-access-token",
|
||||
refreshToken: "new-refresh-token",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -454,7 +473,7 @@ describe("Redirect URI", async () => {
|
||||
|
||||
describe("Disable implicit signup", async () => {
|
||||
it("Should not create user when implicit sign up is disabled", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: "test",
|
||||
@@ -464,11 +483,14 @@ describe("Disable implicit signup", async () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -481,6 +503,7 @@ describe("Disable implicit signup", async () => {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
@@ -494,7 +517,7 @@ describe("Disable implicit signup", async () => {
|
||||
});
|
||||
|
||||
it("Should create user when implicit sign up is disabled but it is requested", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: "test",
|
||||
@@ -505,11 +528,15 @@ describe("Disable implicit signup", async () => {
|
||||
},
|
||||
});
|
||||
|
||||
const headers = new Headers();
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
requestSignUp: true,
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -522,6 +549,7 @@ describe("Disable implicit signup", async () => {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
@@ -539,7 +567,8 @@ describe("Disable implicit signup", async () => {
|
||||
|
||||
describe("Disable signup", async () => {
|
||||
it("Should not create user when sign up is disabled", async () => {
|
||||
const { client } = await getTestInstance({
|
||||
const headers = new Headers();
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
socialProviders: {
|
||||
google: {
|
||||
clientId: "test",
|
||||
@@ -554,6 +583,9 @@ describe("Disable signup", async () => {
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
newUserCallbackURL: "/welcome",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -566,6 +598,7 @@ describe("Disable signup", async () => {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
expect(context.response.status).toBe(302);
|
||||
@@ -590,6 +623,7 @@ describe("signin", async () => {
|
||||
});
|
||||
it("should allow user info override during sign in", async () => {
|
||||
let state = "";
|
||||
const headers = new Headers();
|
||||
const { client, cookieSetter } = await getTestInstance({
|
||||
database,
|
||||
socialProviders: {
|
||||
@@ -603,6 +637,9 @@ describe("signin", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -610,13 +647,12 @@ describe("signin", async () => {
|
||||
});
|
||||
state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
|
||||
const headers = new Headers();
|
||||
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError: (c) => {
|
||||
cookieSetter(headers)(c as any);
|
||||
@@ -634,6 +670,7 @@ describe("signin", async () => {
|
||||
});
|
||||
|
||||
it("should allow user info override during sign in", async () => {
|
||||
const headers = new Headers();
|
||||
let state = "";
|
||||
const { client, cookieSetter } = await getTestInstance(
|
||||
{
|
||||
@@ -654,6 +691,9 @@ describe("signin", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -661,13 +701,12 @@ describe("signin", async () => {
|
||||
});
|
||||
state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
|
||||
|
||||
const headers = new Headers();
|
||||
|
||||
await client.$fetch("/callback/google", {
|
||||
query: {
|
||||
state,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError: (c) => {
|
||||
cookieSetter(headers)(c as any);
|
||||
@@ -697,6 +736,9 @@ describe("updateAccountOnSignIn", async () => {
|
||||
const signInRes = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -710,6 +752,7 @@ describe("updateAccountOnSignIn", async () => {
|
||||
code: "test",
|
||||
},
|
||||
method: "GET",
|
||||
headers,
|
||||
onError(context) {
|
||||
cookieSetter(headers)(context as any);
|
||||
},
|
||||
@@ -730,6 +773,9 @@ describe("updateAccountOnSignIn", async () => {
|
||||
const signInRes2 = await client.signIn.social({
|
||||
provider: "google",
|
||||
callbackURL: "/callback",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(signInRes2.data).toMatchObject({
|
||||
url: expect.stringContaining("google.com"),
|
||||
@@ -743,6 +789,7 @@ describe("updateAccountOnSignIn", async () => {
|
||||
state: state2,
|
||||
code: "test",
|
||||
},
|
||||
headers,
|
||||
method: "GET",
|
||||
onError(context) {
|
||||
cookieSetter(headers)(context as any);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
@@ -7,6 +6,5 @@
|
||||
"lib": ["esnext", "dom", "dom.iterable"],
|
||||
"types": ["node", "bun"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "../core" }]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -5,10 +5,5 @@
|
||||
"outDir": "./dist",
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../better-auth/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -5,10 +5,5 @@
|
||||
"outDir": "./dist",
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../better-auth/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -1,18 +1,28 @@
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { getTestInstanceMemory as getTestInstance } from "better-auth/test";
|
||||
import { sso } from ".";
|
||||
import { OAuth2Server } from "oauth2-mock-server";
|
||||
import { betterFetch } from "@better-fetch/fetch";
|
||||
import { organization } from "better-auth/plugins/organization";
|
||||
import { getTestInstanceMemory } from "better-auth/test";
|
||||
import { organization } from "better-auth/plugins";
|
||||
import { createAuthClient } from "better-auth/client";
|
||||
import { ssoClient } from "./client";
|
||||
|
||||
let server = new OAuth2Server();
|
||||
|
||||
describe("SSO", async () => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } =
|
||||
await getTestInstanceMemory({
|
||||
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [sso(), organization()],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [ssoClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await server.issuer.keys.generate("RS256");
|
||||
server.issuer.on;
|
||||
@@ -57,7 +67,7 @@ describe("SSO", async () => {
|
||||
});
|
||||
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
const newHeaders = new Headers();
|
||||
let callbackURL = "";
|
||||
await betterFetch(location, {
|
||||
method: "GET",
|
||||
@@ -65,10 +75,11 @@ describe("SSO", async () => {
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
return callbackURL;
|
||||
return { callbackURL, headers: newHeaders };
|
||||
}
|
||||
|
||||
it("should register a new SSO provider", async () => {
|
||||
@@ -147,122 +158,76 @@ describe("SSO", async () => {
|
||||
});
|
||||
|
||||
it("should sign in with SSO provider with email matching", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
|
||||
it("should sign in with SSO provider with domain", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@test.com",
|
||||
domain: "localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@test.com",
|
||||
domain: "localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
|
||||
it("should sign in with SSO provider with providerId", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
providerId: "test",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
providerId: "test",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSO with defaultSSO array", async () => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } =
|
||||
await getTestInstanceMemory({
|
||||
plugins: [
|
||||
sso({
|
||||
defaultSSO: [
|
||||
{
|
||||
domain: "localhost.com",
|
||||
providerId: "default-test",
|
||||
oidcConfig: {
|
||||
issuer: "http://localhost:8080",
|
||||
clientId: "test",
|
||||
clientSecret: "test",
|
||||
authorizationEndpoint: "http://localhost:8080/authorize",
|
||||
tokenEndpoint: "http://localhost:8080/token",
|
||||
jwksEndpoint: "http://localhost:8080/jwks",
|
||||
discoveryEndpoint:
|
||||
"http://localhost:8080/.well-known/openid-configuration",
|
||||
pkce: true,
|
||||
mapping: {
|
||||
id: "sub",
|
||||
email: "email",
|
||||
emailVerified: "email_verified",
|
||||
name: "name",
|
||||
image: "picture",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
organization(),
|
||||
],
|
||||
});
|
||||
|
||||
it("should use default SSO provider from array when no provider found in database using providerId", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
providerId: "default-test",
|
||||
callbackURL: "/dashboard",
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Fdefault-test",
|
||||
);
|
||||
});
|
||||
|
||||
it("should use default SSO provider from array when no provider found in database using domain fallback", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "test@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Fdefault-test",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("SSO disable implicit sign in", async () => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } =
|
||||
await getTestInstanceMemory({
|
||||
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [sso({ disableImplicitSignUp: true }), organization()],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [ssoClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await server.issuer.keys.generate("RS256");
|
||||
server.issuer.on;
|
||||
@@ -307,7 +272,7 @@ describe("SSO disable implicit sign in", async () => {
|
||||
});
|
||||
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
const newHeaders = new Headers(headers);
|
||||
let callbackURL = "";
|
||||
await betterFetch(location, {
|
||||
method: "GET",
|
||||
@@ -315,10 +280,11 @@ describe("SSO disable implicit sign in", async () => {
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
return callbackURL;
|
||||
return { callbackURL, headers: newHeaders };
|
||||
}
|
||||
|
||||
it("should register a new SSO provider", async () => {
|
||||
@@ -369,150 +335,61 @@ describe("SSO disable implicit sign in", async () => {
|
||||
userId: expect.any(String),
|
||||
});
|
||||
});
|
||||
it("should not allow creating a provider if limit is set to 0", async () => {
|
||||
const { auth, signInWithTestUser } = await getTestInstanceMemory({
|
||||
plugins: [sso({ providersLimit: 0 })],
|
||||
});
|
||||
const { headers } = await signInWithTestUser();
|
||||
await expect(
|
||||
auth.api.registerSSOProvider({
|
||||
body: {
|
||||
issuer: server.issuer.url!,
|
||||
domain: "localhost.com",
|
||||
oidcConfig: {
|
||||
clientId: "test",
|
||||
clientSecret: "test",
|
||||
},
|
||||
providerId: "test",
|
||||
},
|
||||
headers,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
status: "FORBIDDEN",
|
||||
body: { message: "SSO provider registration is disabled" },
|
||||
});
|
||||
});
|
||||
it("should not allow creating a provider if limit is reached", async () => {
|
||||
const { auth, signInWithTestUser } = await getTestInstanceMemory({
|
||||
plugins: [sso({ providersLimit: 1 })],
|
||||
});
|
||||
const { headers } = await signInWithTestUser();
|
||||
|
||||
await auth.api.registerSSOProvider({
|
||||
body: {
|
||||
issuer: server.issuer.url!,
|
||||
domain: "localhost.com",
|
||||
oidcConfig: {
|
||||
clientId: "test",
|
||||
clientSecret: "test",
|
||||
},
|
||||
providerId: "test-1",
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
await expect(
|
||||
auth.api.registerSSOProvider({
|
||||
body: {
|
||||
issuer: server.issuer.url!,
|
||||
domain: "localhost.com",
|
||||
oidcConfig: {
|
||||
clientId: "test",
|
||||
clientSecret: "test",
|
||||
},
|
||||
providerId: "test-2",
|
||||
},
|
||||
headers,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
status: "FORBIDDEN",
|
||||
body: {
|
||||
message: "You have reached the maximum number of SSO providers",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("should not allow creating a provider if limit from function is reached", async () => {
|
||||
const { auth, signInWithTestUser } = await getTestInstanceMemory({
|
||||
plugins: [sso({ providersLimit: async () => 1 })],
|
||||
});
|
||||
const { headers } = await signInWithTestUser();
|
||||
|
||||
await auth.api.registerSSOProvider({
|
||||
body: {
|
||||
issuer: server.issuer.url!,
|
||||
domain: "localhost.com",
|
||||
oidcConfig: {
|
||||
clientId: "test",
|
||||
clientSecret: "test",
|
||||
},
|
||||
providerId: "test-1",
|
||||
},
|
||||
headers,
|
||||
});
|
||||
|
||||
await expect(
|
||||
auth.api.registerSSOProvider({
|
||||
body: {
|
||||
issuer: server.issuer.url!,
|
||||
domain: "localhost.com",
|
||||
oidcConfig: {
|
||||
clientId: "test",
|
||||
clientSecret: "test",
|
||||
},
|
||||
providerId: "test-2",
|
||||
},
|
||||
headers,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
status: "FORBIDDEN",
|
||||
body: {
|
||||
message: "You have reached the maximum number of SSO providers",
|
||||
},
|
||||
});
|
||||
});
|
||||
it("should not create user with SSO provider when sign ups are disabled", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain(
|
||||
"/api/auth/error/error?error=signup disabled",
|
||||
);
|
||||
});
|
||||
|
||||
it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
requestSignUp: true,
|
||||
const headers = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
requestSignUp: true,
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: cookieSetter(headers),
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const headers = new Headers();
|
||||
const callbackURL = await simulateOAuthFlow(res.url, headers);
|
||||
const { callbackURL } = await simulateOAuthFlow(res.url, headers);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
});
|
||||
});
|
||||
|
||||
describe("provisioning", async (ctx) => {
|
||||
const { auth, signInWithTestUser, customFetchImpl } =
|
||||
await getTestInstanceMemory({
|
||||
const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
|
||||
await getTestInstance({
|
||||
plugins: [sso(), organization()],
|
||||
});
|
||||
|
||||
const authClient = createAuthClient({
|
||||
plugins: [ssoClient()],
|
||||
baseURL: "http://localhost:3000",
|
||||
fetchOptions: {
|
||||
customFetchImpl,
|
||||
},
|
||||
});
|
||||
|
||||
beforeAll(async () => {
|
||||
await server.issuer.keys.generate("RS256");
|
||||
server.issuer.on;
|
||||
@@ -540,12 +417,14 @@ describe("provisioning", async (ctx) => {
|
||||
if (!location) throw new Error("No redirect location found");
|
||||
|
||||
let callbackURL = "";
|
||||
const newHeaders = new Headers();
|
||||
await betterFetch(location, {
|
||||
method: "GET",
|
||||
customFetchImpl: fetchImpl || customFetchImpl,
|
||||
headers,
|
||||
onError(context) {
|
||||
callbackURL = context.response.headers.get("location") || "";
|
||||
cookieSetter(newHeaders)(context);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -605,18 +484,20 @@ describe("provisioning", async (ctx) => {
|
||||
expect(provider).toMatchObject({
|
||||
organizationId: organization?.id,
|
||||
});
|
||||
|
||||
const res = await auth.api.signInSSO({
|
||||
body: {
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
const newHeaders = new Headers();
|
||||
const res = await authClient.signIn.sso({
|
||||
email: "my-email@localhost.com",
|
||||
callbackURL: "/dashboard",
|
||||
fetchOptions: {
|
||||
onSuccess: cookieSetter(newHeaders),
|
||||
throw: true,
|
||||
},
|
||||
});
|
||||
expect(res.url).toContain("http://localhost:8080/authorize");
|
||||
expect(res.url).toContain(
|
||||
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
|
||||
);
|
||||
const newHeaders = new Headers();
|
||||
|
||||
const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
|
||||
expect(callbackURL).toContain("/dashboard");
|
||||
const org = await auth.api.getFullOrganization({
|
||||
|
||||
@@ -5,10 +5,5 @@
|
||||
"outDir": "./dist",
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../better-auth/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -5,10 +5,5 @@
|
||||
"outDir": "./dist",
|
||||
"lib": ["esnext", "dom", "dom.iterable"]
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../better-auth/tsconfig.json"
|
||||
}
|
||||
],
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
1244
pnpm-lock.yaml
generated
1244
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -2,83 +2,18 @@
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"downlevelIteration": true,
|
||||
"baseUrl": ".",
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"downlevelIteration": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"skipLibCheck": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"noErrorTruncation": true,
|
||||
"types": ["node"]
|
||||
"types": ["node", "bun"]
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./packages/better-auth"
|
||||
},
|
||||
{
|
||||
"path": "./packages/cli"
|
||||
},
|
||||
{
|
||||
"path": "./packages/expo"
|
||||
},
|
||||
{
|
||||
"path": "./packages/sso"
|
||||
},
|
||||
{
|
||||
"path": "./packages/stripe"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"include": [],
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"noErrorTruncation": true,
|
||||
"types": ["node"],
|
||||
"paths": {
|
||||
"better-auth": ["./packages/better-auth/src"],
|
||||
"better-auth/*": ["./packages/better-auth/src/*"]
|
||||
}
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./packages/better-auth"
|
||||
},
|
||||
{
|
||||
"path": "./packages/cli"
|
||||
},
|
||||
{
|
||||
"path": "./packages/cli/tsconfig.test.json"
|
||||
},
|
||||
{
|
||||
"path": "./packages/expo"
|
||||
},
|
||||
{
|
||||
"path": "./packages/sso"
|
||||
},
|
||||
{
|
||||
"path": "./packages/stripe"
|
||||
}
|
||||
],
|
||||
"files": [],
|
||||
"include": [],
|
||||
"exclude": ["**/dist/**", "**/node_modules/**", "**/examples/**"]
|
||||
"exclude": ["**/dist/**", "**/node_modules/**"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user