chore: misc update

This commit is contained in:
Bereket Engida
2025-09-28 15:12:16 -07:00
committed by Alex Yang
parent ba916a6669
commit 9818fb85ec
28 changed files with 1423 additions and 1299 deletions

View File

@@ -1,6 +1,6 @@
{ {
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"lib": "@/lib" "lib": "@/lib"
} }
} }

View File

@@ -103,19 +103,20 @@ export default function SignIn() {
toast.success("Successfully signed in"); toast.success("Successfully signed in");
router.push(getCallbackURL(params)); router.push(getCallbackURL(params));
}, },
onError(context) {
toast.error(context.error.message);
},
}, },
); );
}); });
}} }}
> >
<div className="flex items-center justify-between w-full"> <div className="flex items-center justify-center w-full relative">
<span className="flex-1"> {loading ? (
{loading ? ( <Loader2 size={16} className="animate-spin" />
<Loader2 size={16} className="animate-spin" /> ) : (
) : ( "Login"
"Login" )}
)}
</span>
{client.isLastUsedLoginMethod("email") && <LastUsedIndicator />} {client.isLastUsedLoginMethod("email") && <LastUsedIndicator />}
</div> </div>
</Button> </Button>

View File

@@ -1,7 +1,6 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
import { DayPicker } from "react-day-picker"; import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -59,10 +58,6 @@ function Calendar({
day_hidden: "invisible", day_hidden: "invisible",
...classNames, ...classNames,
}} }}
components={{
IconLeft: ({ ...props }) => <ChevronLeftIcon className="h-4 w-4" />,
IconRight: ({ ...props }) => <ChevronRightIcon className="h-4 w-4" />,
}}
{...props} {...props}
/> />
); );

View File

@@ -19,11 +19,9 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Callout } from "@/components/ui/callout";
import * as React from "react"; import * as React from "react";
import { import {
Card, Card,
CardContent,
CardDescription, CardDescription,
CardFooter, CardFooter,
CardHeader, CardHeader,

View File

@@ -1,7 +1,6 @@
"use client"; "use client";
import * as React from "react"; import * as React from "react";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { DayPicker } from "react-day-picker"; import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -59,14 +58,6 @@ function Calendar({
day_hidden: "invisible", day_hidden: "invisible",
...classNames, ...classNames,
}} }}
components={{
IconLeft: ({ className, ...props }) => (
<ChevronLeft className={cn("size-4", className)} {...props} />
),
IconRight: ({ className, ...props }) => (
<ChevronRight className={cn("size-4", className)} {...props} />
),
}}
{...props} {...props}
/> />
); );

View File

@@ -2,12 +2,12 @@
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "esnext", "target": "esnext",
"module": "esnext",
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"strict": true, "strict": true,
"skipLibCheck": true, "skipLibCheck": true,
"moduleResolution": "node16" "moduleResolution": "node16",
"module": "Node16"
}, },
"include": ["./test/**/*"] "include": ["./test/**/*"]
} }

View File

@@ -19,15 +19,17 @@
"test": "turbo --filter \"./packages/*\" test", "test": "turbo --filter \"./packages/*\" test",
"e2e:smoke": "turbo --filter \"./e2e/*\" e2e:smoke", "e2e:smoke": "turbo --filter \"./e2e/*\" e2e:smoke",
"e2e:integration": "turbo --filter \"./e2e/*\" e2e:integration", "e2e:integration": "turbo --filter \"./e2e/*\" e2e:integration",
"typecheck": "tsc -b --verbose --diagnostics" "typecheck": "turbo --filter \"./packages/*\" typecheck"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "2.2.4", "@biomejs/biome": "2.2.4",
"@types/bun": "^1.2.20",
"@types/node": "^24.4.0", "@types/node": "^24.4.0",
"bumpp": "^10.2.3", "bumpp": "^10.2.3",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"turbo": "^2.5.6", "turbo": "^2.5.6",
"typescript": "catalog:", "typescript": "catalog:",
"unbuild": "catalog:",
"vitest": "catalog:" "vitest": "catalog:"
}, },
"resolutions": { "resolutions": {

View File

@@ -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

View File

@@ -69,42 +69,7 @@ export const createInternalAdapter = (
session: parsed.session, session: parsed.session,
user, user,
}), }),
sessionTTL, Math.floor(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,
); );
}), }),
); );

View File

@@ -1,6 +1,5 @@
import { describe, expect, it, vi } from "vitest"; import { describe, expect, it, vi } from "vitest";
import { getTestInstance } from "../test-utils/test-instance"; import { getTestInstance } from "../test-utils/test-instance";
import { parseSetCookieHeader } from "../cookies";
import type { GoogleProfile } from "../social-providers"; import type { GoogleProfile } from "../social-providers";
import { DEFAULT_SECRET } from "../utils/constants"; import { DEFAULT_SECRET } from "../utils/constants";
import { getOAuth2Tokens } from "../oauth2"; import { getOAuth2Tokens } from "../oauth2";
@@ -43,7 +42,7 @@ vi.mock("../oauth2", async (importOriginal) => {
}); });
describe("oauth2 - email verification on link", async () => { describe("oauth2 - email verification on link", async () => {
const { auth, client } = await getTestInstance({ const { auth, client, cookieSetter } = await getTestInstance({
socialProviders: { socialProviders: {
google: { google: {
clientId: "test", clientId: "test",
@@ -66,25 +65,19 @@ describe("oauth2 - email verification on link", async () => {
const ctx = await auth.$context; const ctx = await auth.$context;
async function linkGoogleAccount() { async function linkGoogleAccount() {
const oAuthHeaders = new Headers();
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/", callbackURL: "/",
fetchOptions: {
onSuccess: cookieSetter(oAuthHeaders),
},
}); });
const state = new URL(signInRes.data!.url!).searchParams.get("state") || ""; 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", { await client.$fetch("/callback/google", {
query: { state, code: "test_code" }, query: { state, code: "test_code" },
method: "GET", method: "GET",
headers, headers: oAuthHeaders,
onError(context) { onError(context) {
expect(context.response.status).toBe(302); expect(context.response.status).toBe(302);
}, },

View File

@@ -461,12 +461,14 @@ describe("Admin plugin", async () => {
}); });
it("should not allow banned user to sign in with social provider", async () => { it("should not allow banned user to sign in with social provider", async () => {
const headers = new Headers();
const res = await client.signIn.social( const res = await client.signIn.social(
{ {
provider: "google", provider: "google",
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(headers),
}, },
); );
const state = new URL(res.url!).searchParams.get("state"); const state = new URL(res.url!).searchParams.get("state");
@@ -476,6 +478,7 @@ describe("Admin plugin", async () => {
state, state,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError(context) { onError(context) {
expect(context.response.status).toBe(302); expect(context.response.status).toBe(302);

View File

@@ -45,7 +45,7 @@ vi.mock("../../oauth2", async (importOriginal) => {
describe("anonymous", async () => { describe("anonymous", async () => {
const linkAccountFn = vi.fn(); const linkAccountFn = vi.fn();
const { customFetchImpl, auth, sessionSetter, testUser } = const { customFetchImpl, auth, sessionSetter, testUser, cookieSetter } =
await getTestInstance({ await getTestInstance({
plugins: [ plugins: [
anonymous({ anonymous({
@@ -114,9 +114,13 @@ describe("anonymous", async () => {
headers, headers,
}, },
}); });
const singInRes = await client.signIn.social({ const singInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
const state = new URL(singInRes.data?.url || "").searchParams.get("state"); const state = new URL(singInRes.data?.url || "").searchParams.get("state");
await client.$fetch("/callback/google", { await client.$fetch("/callback/google", {

View File

@@ -225,7 +225,7 @@ describe("email-otp", async () => {
type: "email-verification", type: "email-verification",
}); });
vi.useFakeTimers(); vi.useFakeTimers();
await vi.advanceTimersByTimeAsync(1000 * 60 * 5); await vi.advanceTimersByTimeAsync(1000 * 60 * 6);
const res = await client.emailOtp.verifyEmail({ const res = await client.emailOtp.verifyEmail({
email: testUser.email, email: testUser.email,
otp, otp,

View File

@@ -20,7 +20,7 @@ describe("oauth2", async () => {
await server.stop(); await server.stop();
}); });
const { customFetchImpl, auth } = await getTestInstance({ const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -88,10 +88,7 @@ describe("oauth2", async () => {
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
newHeaders.set( cookieSetter(newHeaders)(context);
"cookie",
context.response.headers.get("Set-Cookie") || "",
);
}, },
}); });
@@ -104,6 +101,9 @@ describe("oauth2", async () => {
providerId: "test", providerId: "test",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining(`http://localhost:${port}/authorize`), url: expect.stringContaining(`http://localhost:${port}/authorize`),
@@ -133,6 +133,9 @@ describe("oauth2", async () => {
providerId: "test", providerId: "test",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining(`http://localhost:${port}/authorize`), url: expect.stringContaining(`http://localhost:${port}/authorize`),
@@ -148,7 +151,7 @@ describe("oauth2", async () => {
headers: newHeaders, headers: newHeaders,
}, },
}); });
console.log(session.data, newHeaders);
const ctx = await auth.$context; const ctx = await auth.$context;
const accounts = await ctx.internalAdapter.findAccounts( const accounts = await ctx.internalAdapter.findAccounts(
session.data?.user.id!, session.data?.user.id!,
@@ -173,6 +176,9 @@ describe("oauth2", async () => {
providerId: "test", providerId: "test",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
const { callbackURL } = await simulateOAuthFlow( const { callbackURL } = await simulateOAuthFlow(
res.data?.url || "", res.data?.url || "",
@@ -250,12 +256,13 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
@@ -263,9 +270,11 @@ describe("oauth2", async () => {
providerId: "test2", providerId: "test2",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`); expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
const headers = new Headers();
const { callbackURL } = await simulateOAuthFlow( const { callbackURL } = await simulateOAuthFlow(
res.data?.url || "", res.data?.url || "",
headers, headers,
@@ -286,7 +295,7 @@ describe("oauth2", async () => {
userInfoResponse.statusCode = 200; userInfoResponse.statusCode = 200;
}); });
const { customFetchImpl } = await getTestInstance({ const { customFetchImpl, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -302,7 +311,6 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
@@ -310,14 +318,16 @@ describe("oauth2", async () => {
customFetchImpl, customFetchImpl,
}, },
}); });
const headers = new Headers();
const res = await authClient.signIn.oauth2({ const res = await authClient.signIn.oauth2({
providerId: "test2", providerId: "test2",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
errorCallbackURL: "http://localhost:3000/error", errorCallbackURL: "http://localhost:3000/error",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`); expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
const headers = new Headers();
const { callbackURL } = await simulateOAuthFlow( const { callbackURL } = await simulateOAuthFlow(
res.data?.url || "", res.data?.url || "",
headers, headers,
@@ -340,7 +350,7 @@ describe("oauth2", async () => {
userInfoResponse.statusCode = 200; userInfoResponse.statusCode = 200;
}); });
const { customFetchImpl } = await getTestInstance({ const { customFetchImpl, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -364,15 +374,17 @@ describe("oauth2", async () => {
customFetchImpl, customFetchImpl,
}, },
}); });
const headers = new Headers();
const res = await authClient.signIn.oauth2({ const res = await authClient.signIn.oauth2({
providerId: "test2", providerId: "test2",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
errorCallbackURL: "http://localhost:3000/error", errorCallbackURL: "http://localhost:3000/error",
requestSignUp: true, requestSignUp: true,
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`); expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
const headers = new Headers();
const { callbackURL } = await simulateOAuthFlow( const { callbackURL } = await simulateOAuthFlow(
res.data?.url || "", res.data?.url || "",
headers, headers,
@@ -391,7 +403,7 @@ describe("oauth2", async () => {
receivedHeaders = req.headers as Record<string, string>; receivedHeaders = req.headers as Record<string, string>;
}); });
const { customFetchImpl } = await getTestInstance({ const { customFetchImpl, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -407,12 +419,13 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
@@ -420,10 +433,12 @@ describe("oauth2", async () => {
providerId: "test3", providerId: "test3",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(res.data?.url).toContain(`http://localhost:${port}/authorize`); expect(res.data?.url).toContain(`http://localhost:${port}/authorize`);
const headers = new Headers();
await simulateOAuthFlow(res.data?.url || "", headers, customFetchImpl); await simulateOAuthFlow(res.data?.url || "", headers, customFetchImpl);
expect(receivedHeaders).toHaveProperty("x-custom-header"); expect(receivedHeaders).toHaveProperty("x-custom-header");
@@ -432,7 +447,7 @@ describe("oauth2", async () => {
it("should delete oauth user with verification flow without password", async () => { it("should delete oauth user with verification flow without password", async () => {
let token = ""; let token = "";
const { customFetchImpl } = await getTestInstance({ const { customFetchImpl, cookieSetter } = await getTestInstance({
user: { user: {
deleteUser: { deleteUser: {
enabled: true, enabled: true,
@@ -454,18 +469,22 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const client = createAuthClient({ const client = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
const signInRes = await client.signIn.oauth2({ const signInRes = await client.signIn.oauth2({
providerId: "test", providerId: "test",
callbackURL: "http://localhost:3000/dashboard", callbackURL: "http://localhost:3000/dashboard",
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
@@ -473,22 +492,22 @@ describe("oauth2", async () => {
redirect: true, redirect: true,
}); });
const { headers } = await simulateOAuthFlow( const { headers: newHeaders } = await simulateOAuthFlow(
signInRes.data?.url || "", signInRes.data?.url || "",
new Headers(), headers,
customFetchImpl, customFetchImpl,
); );
const session = await client.getSession({ const session = await client.getSession({
fetchOptions: { fetchOptions: {
headers, headers: newHeaders,
}, },
}); });
expect(session.data).not.toBeNull(); expect(session.data).not.toBeNull();
const deleteRes = await client.deleteUser({ const deleteRes = await client.deleteUser({
fetchOptions: { fetchOptions: {
headers, headers: newHeaders,
}, },
}); });
@@ -501,7 +520,7 @@ describe("oauth2", async () => {
const deleteCallbackRes = await client.deleteUser({ const deleteCallbackRes = await client.deleteUser({
token, token,
fetchOptions: { fetchOptions: {
headers, headers: newHeaders,
}, },
}); });
expect(deleteCallbackRes.data).toMatchObject({ expect(deleteCallbackRes.data).toMatchObject({
@@ -530,7 +549,7 @@ describe("oauth2", async () => {
userInfoResponse.statusCode = 200; userInfoResponse.statusCode = 200;
}); });
const { customFetchImpl, auth } = await getTestInstance({ const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -545,12 +564,13 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
@@ -563,7 +583,7 @@ describe("oauth2", async () => {
const { callbackURL: firstCallbackURL, headers: firstHeaders } = const { callbackURL: firstCallbackURL, headers: firstHeaders } =
await simulateOAuthFlow( await simulateOAuthFlow(
firstSignIn.data?.url || "", firstSignIn.data?.url || "",
new Headers(), headers,
customFetchImpl, customFetchImpl,
); );
@@ -606,7 +626,7 @@ describe("oauth2", async () => {
const { callbackURL: secondCallbackURL, headers: secondHeaders } = const { callbackURL: secondCallbackURL, headers: secondHeaders } =
await simulateOAuthFlow( await simulateOAuthFlow(
secondSignIn.data?.url || "", secondSignIn.data?.url || "",
new Headers(), headers,
customFetchImpl, customFetchImpl,
); );
@@ -629,7 +649,7 @@ describe("oauth2", async () => {
it("should handle custom getUserInfo returning numeric ID", async () => { it("should handle custom getUserInfo returning numeric ID", async () => {
const numericId = 987654321; const numericId = 987654321;
const { customFetchImpl, auth } = await getTestInstance({ const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -654,12 +674,13 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
@@ -669,9 +690,9 @@ describe("oauth2", async () => {
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
}); });
const { callbackURL, headers } = await simulateOAuthFlow( const { callbackURL, headers: newHeaders } = await simulateOAuthFlow(
signInRes.data?.url || "", signInRes.data?.url || "",
new Headers(), headers,
customFetchImpl, customFetchImpl,
); );
@@ -679,7 +700,7 @@ describe("oauth2", async () => {
const session = await authClient.getSession({ const session = await authClient.getSession({
fetchOptions: { fetchOptions: {
headers, headers: newHeaders,
}, },
}); });
@@ -706,7 +727,7 @@ describe("oauth2", async () => {
userInfoResponse.statusCode = 200; userInfoResponse.statusCode = 200;
}); });
const { customFetchImpl, auth } = await getTestInstance({ const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -729,12 +750,13 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
@@ -744,9 +766,9 @@ describe("oauth2", async () => {
newUserCallbackURL: "http://localhost:3000/new_user", newUserCallbackURL: "http://localhost:3000/new_user",
}); });
const { callbackURL, headers } = await simulateOAuthFlow( const { callbackURL, headers: newHeaders } = await simulateOAuthFlow(
signInRes.data?.url || "", signInRes.data?.url || "",
new Headers(), headers,
customFetchImpl, customFetchImpl,
); );
@@ -754,7 +776,7 @@ describe("oauth2", async () => {
const session = await authClient.getSession({ const session = await authClient.getSession({
fetchOptions: { fetchOptions: {
headers, headers: newHeaders,
}, },
}); });
@@ -781,7 +803,7 @@ describe("oauth2", async () => {
userInfoResponse.statusCode = 200; userInfoResponse.statusCode = 200;
}); });
const { customFetchImpl, auth } = await getTestInstance({ const { customFetchImpl, auth, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
genericOAuth({ genericOAuth({
config: [ config: [
@@ -809,12 +831,13 @@ describe("oauth2", async () => {
}), }),
], ],
}); });
const headers = new Headers();
const authClient = createAuthClient({ const authClient = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
fetchOptions: { fetchOptions: {
customFetchImpl, customFetchImpl,
onSuccess: cookieSetter(headers),
}, },
}); });
@@ -828,9 +851,9 @@ describe("oauth2", async () => {
// we missed the `activity:read_all` // we missed the `activity:read_all`
expect(signInRes.data?.url).toContain("scope=read+activity"); expect(signInRes.data?.url).toContain("scope=read+activity");
const { callbackURL, headers } = await simulateOAuthFlow( const { callbackURL, headers: newHeaders } = await simulateOAuthFlow(
signInRes.data?.url || "", signInRes.data?.url || "",
new Headers(), headers,
customFetchImpl, customFetchImpl,
); );
@@ -838,7 +861,7 @@ describe("oauth2", async () => {
const session = await authClient.getSession({ const session = await authClient.getSession({
fetchOptions: { fetchOptions: {
headers, headers: newHeaders,
}, },
}); });

View File

@@ -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 () => { 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 })], plugins: [lastLoginMethod({ storeInDatabase: true })],
account: { account: {
accountLinking: { accountLinking: {
@@ -216,9 +216,13 @@ describe("lastLoginMethod", async () => {
await client.signOut(); await client.signOut();
const oAuthHeaders = new Headers();
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
fetchOptions: {
onSuccess: cookieSetter(oAuthHeaders),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -232,6 +236,7 @@ describe("lastLoginMethod", async () => {
state, state,
code: "test", code: "test",
}, },
headers: oAuthHeaders,
method: "GET", method: "GET",
onError(context) { onError(context) {
expect(context.response.status).toBe(302); expect(context.response.status).toBe(302);

View File

@@ -21,7 +21,7 @@ describe("mcp", async () => {
const baseURL = `http://localhost:${port}`; const baseURL = `http://localhost:${port}`;
await tempServer.close(); await tempServer.close();
const { auth, signInWithTestUser, customFetchImpl, testUser } = const { auth, signInWithTestUser, customFetchImpl, testUser, cookieSetter } =
await getTestInstance({ await getTestInstance({
baseURL, baseURL,
plugins: [ plugins: [
@@ -161,28 +161,29 @@ describe("mcp", async () => {
}); });
it("should authenticate public client with PKCE only", async ({ expect }) => { it("should authenticate public client with PKCE only", async ({ expect }) => {
const { customFetchImpl: customFetchImplRP } = await getTestInstance({ const { customFetchImpl: customFetchImplRP, cookieSetter } =
account: { await getTestInstance({
accountLinking: { account: {
trustedProviders: ["test-public"], accountLinking: {
trustedProviders: ["test-public"],
},
}, },
}, plugins: [
plugins: [ genericOAuth({
genericOAuth({ config: [
config: [ {
{ providerId: "test-public",
providerId: "test-public", clientId: publicClient.clientId,
clientId: publicClient.clientId, clientSecret: "", // Public client has no secret
clientSecret: "", // Public client has no secret authorizationUrl: `${baseURL}/api/auth/mcp/authorize`,
authorizationUrl: `${baseURL}/api/auth/mcp/authorize`, tokenUrl: `${baseURL}/api/auth/mcp/token`,
tokenUrl: `${baseURL}/api/auth/mcp/token`, scopes: ["openid", "profile", "email"],
scopes: ["openid", "profile", "email"], pkce: true,
pkce: true, },
}, ],
], }),
}), ],
], });
});
const client = createAuthClient({ const client = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
@@ -191,7 +192,7 @@ describe("mcp", async () => {
customFetchImpl: customFetchImplRP, customFetchImpl: customFetchImplRP,
}, },
}); });
const oAuthHeaders = new Headers();
const data = await client.signIn.oauth2( const data = await client.signIn.oauth2(
{ {
providerId: "test-public", providerId: "test-public",
@@ -199,6 +200,7 @@ describe("mcp", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(oAuthHeaders),
}, },
); );
@@ -220,6 +222,7 @@ describe("mcp", async () => {
let callbackURL = ""; let callbackURL = "";
await client.$fetch(redirectURI, { await client.$fetch(redirectURI, {
headers: oAuthHeaders,
onError(context: any) { onError(context: any) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
}, },
@@ -254,29 +257,30 @@ describe("mcp", async () => {
it("should still support confidential clients in MCP context", async ({ it("should still support confidential clients in MCP context", async ({
expect, expect,
}) => { }) => {
const { customFetchImpl: customFetchImplRP } = await getTestInstance({ const { customFetchImpl: customFetchImplRP, cookieSetter } =
account: { await getTestInstance({
accountLinking: { account: {
trustedProviders: ["test-confidential"], accountLinking: {
trustedProviders: ["test-confidential"],
},
}, },
}, plugins: [
plugins: [ genericOAuth({
genericOAuth({ config: [
config: [ {
{ providerId: "test-confidential",
providerId: "test-confidential", clientId: confidentialClient.clientId,
clientId: confidentialClient.clientId, clientSecret: confidentialClient.clientSecret || "",
clientSecret: confidentialClient.clientSecret || "", authorizationUrl: `${baseURL}/api/auth/mcp/authorize`,
authorizationUrl: `${baseURL}/api/auth/mcp/authorize`, tokenUrl: `${baseURL}/api/auth/mcp/token`,
tokenUrl: `${baseURL}/api/auth/mcp/token`, scopes: ["openid", "profile", "email"],
scopes: ["openid", "profile", "email"], pkce: true,
pkce: true, },
}, ],
], }),
}), ],
], });
}); const oAuthHeaders = new Headers();
const client = createAuthClient({ const client = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
baseURL: "http://localhost:5001", baseURL: "http://localhost:5001",
@@ -292,6 +296,7 @@ describe("mcp", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(oAuthHeaders),
}, },
); );
@@ -311,6 +316,7 @@ describe("mcp", async () => {
let callbackURL = ""; let callbackURL = "";
await client.$fetch(redirectURI, { await client.$fetch(redirectURI, {
headers: oAuthHeaders,
onError(context: any) { onError(context: any) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
}, },

View File

@@ -42,7 +42,7 @@ vi.mock("../../oauth2", async (importOriginal) => {
}); });
describe("oauth-proxy", async () => { describe("oauth-proxy", async () => {
const { client } = await getTestInstance({ const { client, cookieSetter } = await getTestInstance({
plugins: [ plugins: [
oAuthProxy({ oAuthProxy({
currentURL: "http://preview-localhost:3000", currentURL: "http://preview-localhost:3000",
@@ -57,6 +57,7 @@ describe("oauth-proxy", async () => {
}); });
it("should redirect to proxy url", async () => { it("should redirect to proxy url", async () => {
const headers = new Headers();
const res = await client.signIn.social( const res = await client.signIn.social(
{ {
provider: "google", provider: "google",
@@ -64,10 +65,12 @@ describe("oauth-proxy", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(headers),
}, },
); );
const state = new URL(res.url!).searchParams.get("state"); const state = new URL(res.url!).searchParams.get("state");
await client.$fetch(`/callback/google?code=test&state=${state}`, { await client.$fetch(`/callback/google?code=test&state=${state}`, {
headers,
onError(context) { onError(context) {
const location = context.response.headers.get("location") ?? ""; const location = context.response.headers.get("location") ?? "";
if (!location) { if (!location) {
@@ -83,7 +86,7 @@ describe("oauth-proxy", async () => {
}); });
it("shouldn't redirect to proxy url on same origin", async () => { it("shouldn't redirect to proxy url on same origin", async () => {
const { client } = await getTestInstance({ const { client, cookieSetter } = await getTestInstance({
plugins: [oAuthProxy()], plugins: [oAuthProxy()],
socialProviders: { socialProviders: {
google: { google: {
@@ -92,6 +95,7 @@ describe("oauth-proxy", async () => {
}, },
}, },
}); });
const headers = new Headers();
const res = await client.signIn.social( const res = await client.signIn.social(
{ {
provider: "google", provider: "google",
@@ -99,10 +103,12 @@ describe("oauth-proxy", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(headers),
}, },
); );
const state = new URL(res.url!).searchParams.get("state"); const state = new URL(res.url!).searchParams.get("state");
await client.$fetch(`/callback/google?code=test&state=${state}`, { await client.$fetch(`/callback/google?code=test&state=${state}`, {
headers,
onError(context) { onError(context) {
const location = context.response.headers.get("location"); const location = context.response.headers.get("location");
if (!location) { if (!location) {
@@ -115,7 +121,7 @@ describe("oauth-proxy", async () => {
}); });
it("should proxy to the original request url", async () => { it("should proxy to the original request url", async () => {
const { client } = await getTestInstance({ const { client, cookieSetter } = await getTestInstance({
baseURL: "https://myapp.com", baseURL: "https://myapp.com",
plugins: [ plugins: [
oAuthProxy({ oAuthProxy({
@@ -129,6 +135,7 @@ describe("oauth-proxy", async () => {
}, },
}, },
}); });
const headers = new Headers();
const res = await client.signIn.social( const res = await client.signIn.social(
{ {
provider: "google", provider: "google",
@@ -136,10 +143,12 @@ describe("oauth-proxy", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(headers),
}, },
); );
const state = new URL(res.url!).searchParams.get("state"); const state = new URL(res.url!).searchParams.get("state");
await client.$fetch(`/callback/google?code=test&state=${state}`, { await client.$fetch(`/callback/google?code=test&state=${state}`, {
headers,
onError(context) { onError(context) {
const location = context.response.headers.get("location"); const location = context.response.headers.get("location");
if (!location) { if (!location) {
@@ -155,7 +164,7 @@ describe("oauth-proxy", async () => {
}); });
it("shouldn't redirect to proxy url on same origin", 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", baseURL: "https://myapp.com",
plugins: [oAuthProxy()], plugins: [oAuthProxy()],
socialProviders: { socialProviders: {
@@ -165,6 +174,7 @@ describe("oauth-proxy", async () => {
}, },
}, },
}); });
const headers = new Headers();
const res = await client.signIn.social( const res = await client.signIn.social(
{ {
provider: "google", provider: "google",
@@ -172,10 +182,12 @@ describe("oauth-proxy", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(headers),
}, },
); );
const state = new URL(res.url!).searchParams.get("state"); const state = new URL(res.url!).searchParams.get("state");
await client.$fetch(`/callback/google?code=test&state=${state}`, { await client.$fetch(`/callback/google?code=test&state=${state}`, {
headers,
onError(context) { onError(context) {
const location = context.response.headers.get("location"); const location = context.response.headers.get("location");
if (!location) { if (!location) {

View File

@@ -24,6 +24,7 @@ describe("oidc", async () => {
auth: authorizationServer, auth: authorizationServer,
signInWithTestUser, signInWithTestUser,
customFetchImpl, customFetchImpl,
cookieSetter,
testUser, testUser,
} = await getTestInstance({ } = await getTestInstance({
baseURL: "http://localhost:3000", baseURL: "http://localhost:3000",
@@ -140,6 +141,7 @@ describe("oidc", async () => {
customFetchImpl: customFetchImplRP, customFetchImpl: customFetchImplRP,
}, },
}); });
const headers = new Headers();
const data = await client.signIn.oauth2( const data = await client.signIn.oauth2(
{ {
providerId: "test", providerId: "test",
@@ -147,6 +149,7 @@ describe("oidc", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(headers),
}, },
); );
expect(data.url).toContain( expect(data.url).toContain(
@@ -167,6 +170,7 @@ describe("oidc", async () => {
let callbackURL = ""; let callbackURL = "";
await client.$fetch(redirectURI, { await client.$fetch(redirectURI, {
headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
}, },
@@ -209,6 +213,7 @@ describe("oidc", async () => {
customFetchImpl: customFetchImplRP, customFetchImpl: customFetchImplRP,
}, },
}); });
const oAuthHeaders = new Headers();
const data = await client.signIn.oauth2( const data = await client.signIn.oauth2(
{ {
providerId: "test", providerId: "test",
@@ -216,6 +221,7 @@ describe("oidc", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(oAuthHeaders),
}, },
); );
expect(data.url).toContain( expect(data.url).toContain(
@@ -236,6 +242,8 @@ describe("oidc", async () => {
expect(redirectURI).toContain("/oauth2/authorize?"); expect(redirectURI).toContain("/oauth2/authorize?");
expect(redirectURI).toContain("consent_code="); expect(redirectURI).toContain("consent_code=");
expect(redirectURI).toContain("client_id="); expect(redirectURI).toContain("client_id=");
console.log({ newHeaders });
const res = await serverClient.oauth2.consent( const res = await serverClient.oauth2.consent(
{ {
accept: true, accept: true,
@@ -251,6 +259,7 @@ describe("oidc", async () => {
let callbackURL = ""; let callbackURL = "";
await client.$fetch(res.redirectURI, { await client.$fetch(res.redirectURI, {
headers: oAuthHeaders,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
}, },
@@ -293,6 +302,7 @@ describe("oidc", async () => {
customFetchImpl: customFetchImplRP, customFetchImpl: customFetchImplRP,
}, },
}); });
const oAuthHeaders = new Headers();
const data = await client.signIn.oauth2( const data = await client.signIn.oauth2(
{ {
providerId: "test", providerId: "test",
@@ -300,6 +310,7 @@ describe("oidc", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(oAuthHeaders),
}, },
); );
expect(data.url).toContain( expect(data.url).toContain(
@@ -338,6 +349,7 @@ describe("oidc", async () => {
); );
let callbackURL = ""; let callbackURL = "";
await client.$fetch(redirectURI, { await client.$fetch(redirectURI, {
headers: oAuthHeaders,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
}, },
@@ -443,29 +455,30 @@ describe("oidc storage", async () => {
}; };
} }
// The RP (Relying Party) - the client application // The RP (Relying Party) - the client application
const { customFetchImpl: customFetchImplRP } = await getTestInstance({ const { customFetchImpl: customFetchImplRP, cookieSetter } =
account: { await getTestInstance({
accountLinking: { account: {
trustedProviders: ["test"], accountLinking: {
trustedProviders: ["test"],
},
}, },
}, plugins: [
plugins: [ genericOAuth({
genericOAuth({ config: [
config: [ {
{ providerId: "test",
providerId: "test", clientId: application.clientId,
clientId: application.clientId, clientSecret: application.clientSecret || "",
clientSecret: application.clientSecret || "", authorizationUrl:
authorizationUrl: "http://localhost:3000/api/auth/oauth2/authorize",
"http://localhost:3000/api/auth/oauth2/authorize", tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
tokenUrl: "http://localhost:3000/api/auth/oauth2/token", scopes: ["openid", "profile", "email"],
scopes: ["openid", "profile", "email"], pkce: true,
pkce: true, },
}, ],
], }),
}), ],
], });
});
const client = createAuthClient({ const client = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
@@ -474,6 +487,7 @@ describe("oidc storage", async () => {
customFetchImpl: customFetchImplRP, customFetchImpl: customFetchImplRP,
}, },
}); });
const oAuthHeaders = new Headers();
const data = await client.signIn.oauth2( const data = await client.signIn.oauth2(
{ {
providerId: "test", providerId: "test",
@@ -481,6 +495,7 @@ describe("oidc storage", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(oAuthHeaders),
}, },
); );
expect(data.url).toContain( expect(data.url).toContain(
@@ -501,6 +516,7 @@ describe("oidc storage", async () => {
let callbackURL = ""; let callbackURL = "";
await client.$fetch(redirectURI, { await client.$fetch(redirectURI, {
headers: oAuthHeaders,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
}, },
@@ -601,29 +617,30 @@ describe("oidc-jwt", async () => {
} }
// The RP (Relying Party) - the client application // The RP (Relying Party) - the client application
const { customFetchImpl: customFetchImplRP } = await getTestInstance({ const { customFetchImpl: customFetchImplRP, cookieSetter } =
account: { await getTestInstance({
accountLinking: { account: {
trustedProviders: ["test"], accountLinking: {
trustedProviders: ["test"],
},
}, },
}, plugins: [
plugins: [ genericOAuth({
genericOAuth({ config: [
config: [ {
{ providerId: "test",
providerId: "test", clientId: application.clientId,
clientId: application.clientId, clientSecret: application.clientSecret || "",
clientSecret: application.clientSecret || "", authorizationUrl:
authorizationUrl: "http://localhost:3000/api/auth/oauth2/authorize",
"http://localhost:3000/api/auth/oauth2/authorize", tokenUrl: "http://localhost:3000/api/auth/oauth2/token",
tokenUrl: "http://localhost:3000/api/auth/oauth2/token", scopes: ["openid", "profile", "email"],
scopes: ["openid", "profile", "email"], pkce: true,
pkce: true, },
}, ],
], }),
}), ],
], });
});
const client = createAuthClient({ const client = createAuthClient({
plugins: [genericOAuthClient()], plugins: [genericOAuthClient()],
@@ -632,6 +649,7 @@ describe("oidc-jwt", async () => {
customFetchImpl: customFetchImplRP, customFetchImpl: customFetchImplRP,
}, },
}); });
const oAuthHeaders = new Headers();
const data = await client.signIn.oauth2( const data = await client.signIn.oauth2(
{ {
providerId: "test", providerId: "test",
@@ -639,6 +657,7 @@ describe("oidc-jwt", async () => {
}, },
{ {
throw: true, throw: true,
onSuccess: cookieSetter(oAuthHeaders),
}, },
); );
expect(data.url).toContain( expect(data.url).toContain(
@@ -659,6 +678,7 @@ describe("oidc-jwt", async () => {
let authToken = undefined; let authToken = undefined;
let callbackURL = ""; let callbackURL = "";
await client.$fetch(redirectURI, { await client.$fetch(redirectURI, {
headers: oAuthHeaders,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("Location") || ""; callbackURL = context.response.headers.get("Location") || "";
authToken = context.response.headers.get("set-auth-token")!; authToken = context.response.headers.get("set-auth-token")!;

View File

@@ -4,12 +4,23 @@ import { sso } from ".";
import { OAuth2Server } from "oauth2-mock-server"; import { OAuth2Server } from "oauth2-mock-server";
import { betterFetch } from "@better-fetch/fetch"; import { betterFetch } from "@better-fetch/fetch";
import { organization } from "../organization"; import { organization } from "../organization";
import { createAuthClient } from "../../client";
import { ssoClient } from "./client";
let server = new OAuth2Server(); let server = new OAuth2Server();
describe("SSO", async () => { describe("SSO", async () => {
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
plugins: [sso(), organization()], await getTestInstance({
plugins: [sso(), organization()],
});
const authClient = createAuthClient({
plugins: [ssoClient()],
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
}); });
beforeAll(async () => { beforeAll(async () => {
@@ -56,7 +67,7 @@ describe("SSO", async () => {
}); });
if (!location) throw new Error("No redirect location found"); if (!location) throw new Error("No redirect location found");
const newHeaders = new Headers();
let callbackURL = ""; let callbackURL = "";
await betterFetch(location, { await betterFetch(location, {
method: "GET", method: "GET",
@@ -64,10 +75,11 @@ describe("SSO", async () => {
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
cookieSetter(newHeaders)(context);
}, },
}); });
return callbackURL; return { callbackURL, headers: newHeaders };
} }
it("should register a new SSO provider", async () => { 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 () => { it("should sign in with SSO provider with email matching", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@localhost.com", email: "my-email@localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
it("should sign in with SSO provider with domain", async () => { it("should sign in with SSO provider with domain", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@test.com", email: "my-email@test.com",
domain: "localhost.com", domain: "localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
it("should sign in with SSO provider with providerId", async () => { it("should sign in with SSO provider with providerId", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
providerId: "test", providerId: "test",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
}); });
describe("SSO disable implicit sign in", async () => { describe("SSO disable implicit sign in", async () => {
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
plugins: [sso({ disableImplicitSignUp: true }), organization()], await getTestInstance({
plugins: [sso({ disableImplicitSignUp: true }), organization()],
});
const authClient = createAuthClient({
plugins: [ssoClient()],
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
}); });
beforeAll(async () => { beforeAll(async () => {
@@ -239,7 +267,7 @@ describe("SSO disable implicit sign in", async () => {
}); });
if (!location) throw new Error("No redirect location found"); if (!location) throw new Error("No redirect location found");
const newHeaders = new Headers(headers);
let callbackURL = ""; let callbackURL = "";
await betterFetch(location, { await betterFetch(location, {
method: "GET", method: "GET",
@@ -247,10 +275,11 @@ describe("SSO disable implicit sign in", async () => {
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
cookieSetter(newHeaders)(context);
}, },
}); });
return callbackURL; return { callbackURL, headers: newHeaders };
} }
it("should register a new SSO provider", async () => { 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 () => { it("should not create user with SSO provider when sign ups are disabled", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@localhost.com", email: "my-email@localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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( expect(callbackURL).toContain(
"/api/auth/error/error?error=signup disabled", "/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 () => { it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@localhost.com", email: "my-email@localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
requestSignUp: true, requestSignUp: true,
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
}); });
describe("provisioning", async (ctx) => { describe("provisioning", async (ctx) => {
const { auth, signInWithTestUser, customFetchImpl } = await getTestInstance({ const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
plugins: [sso(), organization()], await getTestInstance({
plugins: [sso(), organization()],
});
const authClient = createAuthClient({
plugins: [ssoClient()],
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
}); });
beforeAll(async () => { beforeAll(async () => {
@@ -367,12 +409,14 @@ describe("provisioning", async (ctx) => {
if (!location) throw new Error("No redirect location found"); if (!location) throw new Error("No redirect location found");
let callbackURL = ""; let callbackURL = "";
const newHeaders = new Headers();
await betterFetch(location, { await betterFetch(location, {
method: "GET", method: "GET",
customFetchImpl: fetchImpl || customFetchImpl, customFetchImpl: fetchImpl || customFetchImpl,
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
cookieSetter(newHeaders)(context);
}, },
}); });
@@ -429,18 +473,20 @@ describe("provisioning", async (ctx) => {
expect(provider).toMatchObject({ expect(provider).toMatchObject({
organizationId: organization?.id, organizationId: organization?.id,
}); });
const newHeaders = new Headers();
const res = await auth.api.signInSSO({ const res = await authClient.signIn.sso({
body: { email: "my-email@localhost.com",
email: "my-email@localhost.com", callbackURL: "/dashboard",
callbackURL: "/dashboard", fetchOptions: {
onSuccess: cookieSetter(newHeaders),
throw: true,
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
); );
const newHeaders = new Headers();
const callbackURL = await simulateOAuthFlow(res.url, newHeaders); const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
expect(callbackURL).toContain("/dashboard"); expect(callbackURL).toContain("/dashboard");
const org = await auth.api.getFullOrganization({ const org = await auth.api.getFullOrganization({

View File

@@ -181,216 +181,235 @@ describe("Social Providers", async (c) => {
token.payload.name = "Test User"; token.payload.name = "Test User";
token.payload.picture = "https://test.com/picture.png"; token.payload.picture = "https://test.com/picture.png";
}); });
let state = "";
const headers = new Headers(); const headers = new Headers();
describe("signin", async () => { async function simulateOAuthFlowRefresh(
async function simulateOAuthFlowRefresh( authUrl: string,
authUrl: string, headers: Headers,
headers: Headers, fetchImpl?: (...args: any) => any,
fetchImpl?: (...args: any) => any, ) {
) { let location: string | null = null;
let location: string | null = null; await betterFetch(authUrl, {
await betterFetch(authUrl, { method: "GET",
method: "GET", redirect: "manual",
redirect: "manual", onError(context) {
onError(context) { location = context.response.headers.get("location");
location = context.response.headers.get("location"); },
}, });
}); if (!location) throw new Error("No redirect location found");
if (!location) throw new Error("No redirect location found");
const tokens = await refreshAccessToken({ const tokens = await refreshAccessToken({
refreshToken: "mock-refresh-token", refreshToken: "mock-refresh-token",
options: { options: {
clientId: "test-client-id", clientId: "test-client-id",
clientKey: "test-client-key", clientKey: "test-client-key",
clientSecret: "test-client-secret", clientSecret: "test-client-secret",
}, },
tokenEndpoint: `http://localhost:${port}/token`, tokenEndpoint: `http://localhost:${port}/token`,
}); });
return tokens; return tokens;
} }
it("should be able to add social providers", async () => { it("should be able to add social providers", async () => {
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
newUserCallbackURL: "/welcome", newUserCallbackURL: "/welcome",
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
redirect: true, redirect: true,
}); });
state = new URL(signInRes.data!.url!).searchParams.get("state") || ""; });
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", {
await client.$fetch("/callback/google", { query: {
query: { state,
state, code: "test",
code: "test", },
}, headers,
method: "GET", method: "GET",
onError(context) { onError(context) {
expect(context.response.status).toBe(302); expect(context.response.status).toBe(302);
const location = context.response.headers.get("location"); const location = context.response.headers.get("location");
expect(location).toBeDefined(); expect(location).toBeDefined();
expect(location).toContain("/welcome"); expect(location).toContain("/callback");
const cookies = parseSetCookieHeader( const cookies = parseSetCookieHeader(
context.response.headers.get("set-cookie") || "", context.response.headers.get("set-cookie") || "",
); );
expect(cookies.get("better-auth.session_token")?.value).toBeDefined(); expect(cookies.get("better-auth.session_token")?.value).toBeDefined();
}, },
});
}); });
});
it("Should use callback URL if the user is already registered", async () => { it("should be able to map profile to user", async () => {
const signInRes = await client.signIn.social({ const headers = new Headers();
provider: "google", const signInRes = await client.signIn.social({
callbackURL: "/callback", provider: "google",
newUserCallbackURL: "/welcome", callbackURL: "/callback",
}); fetchOptions: {
expect(signInRes.data).toMatchObject({ onSuccess: cookieSetter(headers),
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();
},
});
}); });
expect(signInRes.data).toMatchObject({
it("should be able to map profile to user", async () => { url: expect.stringContaining("google.com"),
const signInRes = await client.signIn.social({ redirect: true,
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",
});
}); });
const state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
it("should be protected from callback URL attacks", async () => { await client.$fetch("/callback/google", {
const signInRes = await client.signIn.social( query: {
{ state,
provider: "google", code: "test",
callbackURL: "https://evil.com/callback", },
}, headers,
{ method: "GET",
onSuccess(context) { onError: (c) => {
const cookies = parseSetCookieHeader( //TODO: fix this
context.response.headers.get("set-cookie") || "", cookieSetter(headers)(c as any);
); },
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 session = await client.getSession({
it("should refresh the access token", async () => { fetchOptions: {
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",
},
headers, 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; it("should be protected from callback URL attacks", async () => {
if (!authUrl) throw new Error("No auth url found"); const signInRes = await client.signIn.social(
const mockEndpoint = authUrl.replace( {
"https://accounts.google.com/o/oauth2/auth", provider: "google",
`http://localhost:${port}/authorize`, callbackURL: "https://evil.com/callback",
); },
const result = await simulateOAuthFlowRefresh(mockEndpoint, headers); {
const { accessToken, refreshToken } = result; onSuccess(context) {
expect({ accessToken, refreshToken }).toEqual({ const cookies = parseSetCookieHeader(
accessToken: "new-access-token", context.response.headers.get("set-cookie") || "",
refreshToken: "new-refresh-token", );
}); 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 () => { describe("Disable implicit signup", async () => {
it("Should not create user when implicit sign up is disabled", async () => { it("Should not create user when implicit sign up is disabled", async () => {
const { client } = await getTestInstance({ const { client, cookieSetter } = await getTestInstance({
socialProviders: { socialProviders: {
google: { google: {
clientId: "test", clientId: "test",
@@ -464,11 +483,14 @@ describe("Disable implicit signup", async () => {
}, },
}, },
}); });
const headers = new Headers();
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
newUserCallbackURL: "/welcome", newUserCallbackURL: "/welcome",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -481,6 +503,7 @@ describe("Disable implicit signup", async () => {
state, state,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError(context) { onError(context) {
expect(context.response.status).toBe(302); 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 () => { 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: { socialProviders: {
google: { google: {
clientId: "test", clientId: "test",
@@ -505,11 +528,15 @@ describe("Disable implicit signup", async () => {
}, },
}); });
const headers = new Headers();
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
newUserCallbackURL: "/welcome", newUserCallbackURL: "/welcome",
requestSignUp: true, requestSignUp: true,
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -522,6 +549,7 @@ describe("Disable implicit signup", async () => {
state, state,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError(context) { onError(context) {
expect(context.response.status).toBe(302); expect(context.response.status).toBe(302);
@@ -539,7 +567,8 @@ describe("Disable implicit signup", async () => {
describe("Disable signup", async () => { describe("Disable signup", async () => {
it("Should not create user when sign up is disabled", 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: { socialProviders: {
google: { google: {
clientId: "test", clientId: "test",
@@ -554,6 +583,9 @@ describe("Disable signup", async () => {
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
newUserCallbackURL: "/welcome", newUserCallbackURL: "/welcome",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -566,6 +598,7 @@ describe("Disable signup", async () => {
state, state,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError(context) { onError(context) {
expect(context.response.status).toBe(302); expect(context.response.status).toBe(302);
@@ -590,6 +623,7 @@ describe("signin", async () => {
}); });
it("should allow user info override during sign in", async () => { it("should allow user info override during sign in", async () => {
let state = ""; let state = "";
const headers = new Headers();
const { client, cookieSetter } = await getTestInstance({ const { client, cookieSetter } = await getTestInstance({
database, database,
socialProviders: { socialProviders: {
@@ -603,6 +637,9 @@ describe("signin", async () => {
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -610,13 +647,12 @@ describe("signin", async () => {
}); });
state = new URL(signInRes.data!.url!).searchParams.get("state") || ""; state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
const headers = new Headers();
await client.$fetch("/callback/google", { await client.$fetch("/callback/google", {
query: { query: {
state, state,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError: (c) => { onError: (c) => {
cookieSetter(headers)(c as any); cookieSetter(headers)(c as any);
@@ -634,6 +670,7 @@ describe("signin", async () => {
}); });
it("should allow user info override during sign in", async () => { it("should allow user info override during sign in", async () => {
const headers = new Headers();
let state = ""; let state = "";
const { client, cookieSetter } = await getTestInstance( const { client, cookieSetter } = await getTestInstance(
{ {
@@ -654,6 +691,9 @@ describe("signin", async () => {
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -661,13 +701,12 @@ describe("signin", async () => {
}); });
state = new URL(signInRes.data!.url!).searchParams.get("state") || ""; state = new URL(signInRes.data!.url!).searchParams.get("state") || "";
const headers = new Headers();
await client.$fetch("/callback/google", { await client.$fetch("/callback/google", {
query: { query: {
state, state,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError: (c) => { onError: (c) => {
cookieSetter(headers)(c as any); cookieSetter(headers)(c as any);
@@ -697,6 +736,9 @@ describe("updateAccountOnSignIn", async () => {
const signInRes = await client.signIn.social({ const signInRes = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes.data).toMatchObject({ expect(signInRes.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -710,6 +752,7 @@ describe("updateAccountOnSignIn", async () => {
code: "test", code: "test",
}, },
method: "GET", method: "GET",
headers,
onError(context) { onError(context) {
cookieSetter(headers)(context as any); cookieSetter(headers)(context as any);
}, },
@@ -730,6 +773,9 @@ describe("updateAccountOnSignIn", async () => {
const signInRes2 = await client.signIn.social({ const signInRes2 = await client.signIn.social({
provider: "google", provider: "google",
callbackURL: "/callback", callbackURL: "/callback",
fetchOptions: {
onSuccess: cookieSetter(headers),
},
}); });
expect(signInRes2.data).toMatchObject({ expect(signInRes2.data).toMatchObject({
url: expect.stringContaining("google.com"), url: expect.stringContaining("google.com"),
@@ -743,6 +789,7 @@ describe("updateAccountOnSignIn", async () => {
state: state2, state: state2,
code: "test", code: "test",
}, },
headers,
method: "GET", method: "GET",
onError(context) { onError(context) {
cookieSetter(headers)(context as any); cookieSetter(headers)(context as any);

View File

@@ -1,5 +1,4 @@
{ {
"extends": "../../tsconfig.json",
"extends": "../../tsconfig.json", "extends": "../../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"rootDir": "./src", "rootDir": "./src",
@@ -7,6 +6,5 @@
"lib": ["esnext", "dom", "dom.iterable"], "lib": ["esnext", "dom", "dom.iterable"],
"types": ["node", "bun"] "types": ["node", "bun"]
}, },
"include": ["src"], "include": ["src"]
"references": [{ "path": "../core" }]
} }

View File

@@ -5,10 +5,5 @@
"outDir": "./dist", "outDir": "./dist",
"lib": ["esnext", "dom", "dom.iterable"] "lib": ["esnext", "dom", "dom.iterable"]
}, },
"references": [
{
"path": "../better-auth/tsconfig.json"
}
],
"include": ["src"] "include": ["src"]
} }

View File

@@ -5,10 +5,5 @@
"outDir": "./dist", "outDir": "./dist",
"lib": ["esnext", "dom", "dom.iterable"] "lib": ["esnext", "dom", "dom.iterable"]
}, },
"references": [
{
"path": "../better-auth/tsconfig.json"
}
],
"include": ["src"] "include": ["src"]
} }

View File

@@ -1,18 +1,28 @@
import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { getTestInstanceMemory as getTestInstance } from "better-auth/test";
import { sso } from "."; import { sso } from ".";
import { OAuth2Server } from "oauth2-mock-server"; import { OAuth2Server } from "oauth2-mock-server";
import { betterFetch } from "@better-fetch/fetch"; import { betterFetch } from "@better-fetch/fetch";
import { organization } from "better-auth/plugins/organization"; import { organization } from "better-auth/plugins";
import { getTestInstanceMemory } from "better-auth/test"; import { createAuthClient } from "better-auth/client";
import { ssoClient } from "./client";
let server = new OAuth2Server(); let server = new OAuth2Server();
describe("SSO", async () => { describe("SSO", async () => {
const { auth, signInWithTestUser, customFetchImpl } = const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
await getTestInstanceMemory({ await getTestInstance({
plugins: [sso(), organization()], plugins: [sso(), organization()],
}); });
const authClient = createAuthClient({
plugins: [ssoClient()],
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
});
beforeAll(async () => { beforeAll(async () => {
await server.issuer.keys.generate("RS256"); await server.issuer.keys.generate("RS256");
server.issuer.on; server.issuer.on;
@@ -57,7 +67,7 @@ describe("SSO", async () => {
}); });
if (!location) throw new Error("No redirect location found"); if (!location) throw new Error("No redirect location found");
const newHeaders = new Headers();
let callbackURL = ""; let callbackURL = "";
await betterFetch(location, { await betterFetch(location, {
method: "GET", method: "GET",
@@ -65,10 +75,11 @@ describe("SSO", async () => {
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
cookieSetter(newHeaders)(context);
}, },
}); });
return callbackURL; return { callbackURL, headers: newHeaders };
} }
it("should register a new SSO provider", async () => { 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 () => { it("should sign in with SSO provider with email matching", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@localhost.com", email: "my-email@localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
it("should sign in with SSO provider with domain", async () => { it("should sign in with SSO provider with domain", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@test.com", email: "my-email@test.com",
domain: "localhost.com", domain: "localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
it("should sign in with SSO provider with providerId", async () => { it("should sign in with SSO provider with providerId", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
providerId: "test", providerId: "test",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); 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 () => { describe("SSO disable implicit sign in", async () => {
const { auth, signInWithTestUser, customFetchImpl } = const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
await getTestInstanceMemory({ await getTestInstance({
plugins: [sso({ disableImplicitSignUp: true }), organization()], plugins: [sso({ disableImplicitSignUp: true }), organization()],
}); });
const authClient = createAuthClient({
plugins: [ssoClient()],
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
});
beforeAll(async () => { beforeAll(async () => {
await server.issuer.keys.generate("RS256"); await server.issuer.keys.generate("RS256");
server.issuer.on; server.issuer.on;
@@ -307,7 +272,7 @@ describe("SSO disable implicit sign in", async () => {
}); });
if (!location) throw new Error("No redirect location found"); if (!location) throw new Error("No redirect location found");
const newHeaders = new Headers(headers);
let callbackURL = ""; let callbackURL = "";
await betterFetch(location, { await betterFetch(location, {
method: "GET", method: "GET",
@@ -315,10 +280,11 @@ describe("SSO disable implicit sign in", async () => {
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
cookieSetter(newHeaders)(context);
}, },
}); });
return callbackURL; return { callbackURL, headers: newHeaders };
} }
it("should register a new SSO provider", async () => { it("should register a new SSO provider", async () => {
@@ -369,150 +335,61 @@ describe("SSO disable implicit sign in", async () => {
userId: expect.any(String), 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 () => { it("should not create user with SSO provider when sign ups are disabled", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@localhost.com", email: "my-email@localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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( expect(callbackURL).toContain(
"/api/auth/error/error?error=signup disabled", "/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 () => { it("should create user with SSO provider when sign ups are disabled but sign up is requested", async () => {
const res = await auth.api.signInSSO({ const headers = new Headers();
body: { const res = await authClient.signIn.sso({
email: "my-email@localhost.com", email: "my-email@localhost.com",
callbackURL: "/dashboard", callbackURL: "/dashboard",
requestSignUp: true, requestSignUp: true,
fetchOptions: {
throw: true,
onSuccess: cookieSetter(headers),
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "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"); expect(callbackURL).toContain("/dashboard");
}); });
}); });
describe("provisioning", async (ctx) => { describe("provisioning", async (ctx) => {
const { auth, signInWithTestUser, customFetchImpl } = const { auth, signInWithTestUser, customFetchImpl, cookieSetter } =
await getTestInstanceMemory({ await getTestInstance({
plugins: [sso(), organization()], plugins: [sso(), organization()],
}); });
const authClient = createAuthClient({
plugins: [ssoClient()],
baseURL: "http://localhost:3000",
fetchOptions: {
customFetchImpl,
},
});
beforeAll(async () => { beforeAll(async () => {
await server.issuer.keys.generate("RS256"); await server.issuer.keys.generate("RS256");
server.issuer.on; server.issuer.on;
@@ -540,12 +417,14 @@ describe("provisioning", async (ctx) => {
if (!location) throw new Error("No redirect location found"); if (!location) throw new Error("No redirect location found");
let callbackURL = ""; let callbackURL = "";
const newHeaders = new Headers();
await betterFetch(location, { await betterFetch(location, {
method: "GET", method: "GET",
customFetchImpl: fetchImpl || customFetchImpl, customFetchImpl: fetchImpl || customFetchImpl,
headers, headers,
onError(context) { onError(context) {
callbackURL = context.response.headers.get("location") || ""; callbackURL = context.response.headers.get("location") || "";
cookieSetter(newHeaders)(context);
}, },
}); });
@@ -605,18 +484,20 @@ describe("provisioning", async (ctx) => {
expect(provider).toMatchObject({ expect(provider).toMatchObject({
organizationId: organization?.id, organizationId: organization?.id,
}); });
const newHeaders = new Headers();
const res = await auth.api.signInSSO({ const res = await authClient.signIn.sso({
body: { email: "my-email@localhost.com",
email: "my-email@localhost.com", callbackURL: "/dashboard",
callbackURL: "/dashboard", fetchOptions: {
onSuccess: cookieSetter(newHeaders),
throw: true,
}, },
}); });
expect(res.url).toContain("http://localhost:8080/authorize"); expect(res.url).toContain("http://localhost:8080/authorize");
expect(res.url).toContain( expect(res.url).toContain(
"redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest", "redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fapi%2Fauth%2Fsso%2Fcallback%2Ftest",
); );
const newHeaders = new Headers();
const callbackURL = await simulateOAuthFlow(res.url, newHeaders); const callbackURL = await simulateOAuthFlow(res.url, newHeaders);
expect(callbackURL).toContain("/dashboard"); expect(callbackURL).toContain("/dashboard");
const org = await auth.api.getFullOrganization({ const org = await auth.api.getFullOrganization({

View File

@@ -5,10 +5,5 @@
"outDir": "./dist", "outDir": "./dist",
"lib": ["esnext", "dom", "dom.iterable"] "lib": ["esnext", "dom", "dom.iterable"]
}, },
"references": [
{
"path": "../better-auth/tsconfig.json"
}
],
"include": ["src"] "include": ["src"]
} }

View File

@@ -5,10 +5,5 @@
"outDir": "./dist", "outDir": "./dist",
"lib": ["esnext", "dom", "dom.iterable"] "lib": ["esnext", "dom", "dom.iterable"]
}, },
"references": [
{
"path": "../better-auth/tsconfig.json"
}
],
"include": ["src"] "include": ["src"]
} }

1244
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,83 +2,18 @@
"compilerOptions": { "compilerOptions": {
"strict": true, "strict": true,
"target": "esnext", "target": "esnext",
"module": "esnext",
"moduleResolution": "bundler",
"downlevelIteration": true, "downlevelIteration": true,
"baseUrl": ".", "baseUrl": ".",
"strict": true,
"target": "esnext",
"downlevelIteration": true,
"esModuleInterop": true, "esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"module": "esnext",
"moduleResolution": "bundler",
"skipLibCheck": true, "skipLibCheck": true,
"verbatimModuleSyntax": true, "verbatimModuleSyntax": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": false, "exactOptionalPropertyTypes": false,
"declaration": true,
"emitDeclarationOnly": true,
"composite": true,
"incremental": true, "incremental": true,
"noErrorTruncation": true, "noErrorTruncation": true,
"types": ["node"] "types": ["node", "bun"]
}, },
"references": [ "exclude": ["**/dist/**", "**/node_modules/**"]
{
"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/**"]
} }