Skip to content

Commit ecb8a60

Browse files
author
Rajat
committed
separate auth instances for every origin
1 parent 4cc92b4 commit ecb8a60

File tree

4 files changed

+81
-32
lines changed

4 files changed

+81
-32
lines changed

apps/web/app/actions.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,7 @@ import { headers as headersType } from "next/headers";
33
export async function getBackendAddress(
44
headers: Headers,
55
): Promise<`${string}://${string}`> {
6-
const protocol = headers.get("x-forwarded-proto") || "http";
7-
const forwardedHost = headers
8-
.get("x-forwarded-host")
9-
?.split(",")[0]
10-
?.trim();
11-
const host = forwardedHost || headers.get("host");
12-
13-
return `${protocol}://${host}`;
6+
return `${headers.get("x-forwarded-proto")}://${headers.get("host")}`;
147
}
158

169
export async function getAddressFromHeaders(headers: typeof headersType) {

apps/web/app/api/auth/[...all]/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { als } from "@/async-local-storage";
22
import { getBackendAddress } from "@/app/actions";
3-
import { auth } from "@/auth";
3+
import { getAuth } from "@/auth";
44
import { toNextJsHandler } from "better-auth/next-js";
55

6-
const handlers = toNextJsHandler(auth);
6+
const getHandlers = (baseURL: string) => toNextJsHandler(getAuth(baseURL));
77

8+
// This is needed to prevent creating URLs like https://0.0.0.0:3000/api/auth/sign-in/sso
89
export const rewriteAuthRequestOrigin = async (req: Request) => {
910
const publicOrigin = await getBackendAddress(req.headers);
1011
const currentUrl = new URL(req.url);
@@ -23,6 +24,7 @@ export const rewriteAuthRequestOrigin = async (req: Request) => {
2324

2425
export const POST = async (req: Request) => {
2526
const rewrittenReq = await rewriteAuthRequestOrigin(req);
27+
const handlers = getHandlers(new URL(rewrittenReq.url).origin);
2628
const map = new Map();
2729
map.set("domain", req.headers.get("domain"));
2830
map.set("domainId", req.headers.get("domainId"));
@@ -31,6 +33,7 @@ export const POST = async (req: Request) => {
3133

3234
export const GET = async (req: Request) => {
3335
const rewrittenReq = await rewriteAuthRequestOrigin(req);
36+
const handlers = getHandlers(new URL(rewrittenReq.url).origin);
3437
const map = new Map();
3538
map.set("domain", req.headers.get("domain"));
3639
map.set("domainId", req.headers.get("domainId"));

apps/web/app/api/auth/__tests__/route.test.ts

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
* @jest-environment node
33
*/
44

5+
const mockGetAuth = jest.fn(() => ({}));
6+
const mockHandlerGet = jest.fn();
7+
const mockHandlerPost = jest.fn();
8+
59
jest.mock("@/app/actions", () => ({
610
getBackendAddress: jest.fn(),
711
}));
@@ -18,17 +22,19 @@ jest.mock(
1822

1923
jest.mock("@/auth", () => ({
2024
auth: {},
25+
getAuth: mockGetAuth,
2126
}));
2227

2328
jest.mock("better-auth/next-js", () => ({
2429
toNextJsHandler: () => ({
25-
GET: jest.fn(),
26-
POST: jest.fn(),
30+
GET: mockHandlerGet,
31+
POST: mockHandlerPost,
2732
}),
2833
}));
2934

3035
import { getBackendAddress } from "@/app/actions";
31-
import { rewriteAuthRequestOrigin } from "../[...all]/route";
36+
import { getAuth } from "@/auth";
37+
import { POST, rewriteAuthRequestOrigin } from "../[...all]/route";
3238

3339
describe("Auth Route Origin Rewrite", () => {
3440
beforeEach(() => {
@@ -110,4 +116,33 @@ describe("Auth Route Origin Rewrite", () => {
110116
}),
111117
);
112118
});
119+
120+
it("creates request-scoped auth handlers per rewritten origin", async () => {
121+
(getBackendAddress as jest.Mock).mockResolvedValue(
122+
"https://domain1.clqa.site",
123+
);
124+
mockHandlerPost.mockResolvedValue(new Response(null, { status: 200 }));
125+
126+
const req = new Request("https://0.0.0.0:3000/api/auth/sign-in/sso", {
127+
method: "POST",
128+
headers: {
129+
host: "domain1.clqa.site",
130+
domain: "domain1",
131+
domainId: "domain-id-1",
132+
},
133+
body: JSON.stringify({
134+
providerId: "google",
135+
callbackURL: "/dashboard",
136+
}),
137+
});
138+
139+
await POST(req);
140+
141+
expect(getAuth).toHaveBeenCalledWith("https://domain1.clqa.site");
142+
expect(mockHandlerPost).toHaveBeenCalledTimes(1);
143+
const rewrittenRequest = mockHandlerPost.mock.calls[0][0] as Request;
144+
expect(rewrittenRequest.url).toBe(
145+
"https://domain1.clqa.site/api/auth/sign-in/sso",
146+
);
147+
});
113148
});

apps/web/auth.ts

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,34 @@ const getSessionUserId = async (
8787
return (authUser as { userId?: string } | null)?.userId;
8888
};
8989

90-
const config: any = {
90+
const getSessionConfig = () => {
91+
if (process.env.SESSION_COOKIE_CACHE_MAX_AGE) {
92+
const configuredMaxAge = parseInt(
93+
process.env.SESSION_COOKIE_CACHE_MAX_AGE,
94+
);
95+
96+
if (configuredMaxAge > 0) {
97+
return {
98+
cookieCache: {
99+
enabled: true,
100+
maxAge: configuredMaxAge * 60,
101+
},
102+
};
103+
}
104+
}
105+
106+
return {
107+
cookieCache: {
108+
enabled: true,
109+
maxAge: 5 * 60, // 5 minutes
110+
},
111+
};
112+
};
113+
114+
const createAuthConfig = (baseURL = ""): any => ({
91115
appName: "CourseLit",
92116
secret: process.env.AUTH_SECRET,
117+
...(baseURL ? { baseURL } : {}),
93118
user: {
94119
additionalFields: {
95120
userId: {
@@ -259,24 +284,17 @@ const config: any = {
259284
}
260285
return origins;
261286
},
262-
};
287+
session: getSessionConfig(),
288+
});
263289

264-
if (process.env.SESSION_COOKIE_CACHE_MAX_AGE) {
265-
if (parseInt(process.env.SESSION_COOKIE_CACHE_MAX_AGE) > 0) {
266-
config.session = {
267-
cookieCache: {
268-
enabled: true,
269-
maxAge: parseInt(process.env.SESSION_COOKIE_CACHE_MAX_AGE) * 60,
270-
},
271-
};
290+
const authInstances = new Map<string, ReturnType<typeof betterAuth>>();
291+
292+
export const getAuth = (baseURL = "") => {
293+
if (!authInstances.has(baseURL)) {
294+
authInstances.set(baseURL, betterAuth(createAuthConfig(baseURL)));
272295
}
273-
} else {
274-
config.session = {
275-
cookieCache: {
276-
enabled: true,
277-
maxAge: 5 * 60, // 5 minutes
278-
},
279-
};
280-
}
281296

282-
export const auth = betterAuth(config);
297+
return authInstances.get(baseURL)!;
298+
};
299+
300+
export const auth = getAuth();

0 commit comments

Comments
 (0)