fix(auth) : keep Guide sessions beyond Sudloh token expiry
CI / Verify (push) Successful in 1m31s
CI / Build immutable images and deploy (push) Successful in 2m16s

This commit is contained in:
2026-10-06 18:00:42 +07:00 Unverified
parent 41014ef654
commit 4b16941e11
14 changed files with 74 additions and 236 deletions
+7 -12
View File
@@ -1,7 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const session = vi.fn();
const validate = vi.fn();
const handler = vi.fn(async () => Response.json({ passed: true }));
vi.mock("server-only", () => ({}));
@@ -10,7 +9,6 @@ vi.mock("@/lib/auth/server", () => ({
getAuth: () => ({ api: { getSession: session }, handler }),
isSudlohOidcEnabled: () => true,
}));
vi.mock("@/lib/auth/sudloh", () => ({ validateSudlohSession: validate }));
const { GET, POST } = await import("./route");
@@ -19,30 +17,27 @@ beforeEach(() => {
process.env.BETTER_AUTH_URL = "https://guide.sudloh.com";
process.env.SUDLOH_OIDC_ONLY = "true";
session.mockResolvedValue({ user: { id: "user-1" }, session: { id: "session-1" } });
validate.mockResolvedValue(false);
});
describe("Better Auth Sudloh boundary", () => {
it("returns no browser session after Sudloh revokes the bound token", async () => {
it("lets Better Auth serve the local browser session", async () => {
const response = await GET(new Request("https://guide.sudloh.com/api/auth/get-session"));
expect(response.status).toBe(200);
expect(await response.json()).toBeNull();
expect(handler).not.toHaveBeenCalled();
expect(await response.json()).toEqual({ passed: true });
expect(handler).toHaveBeenCalledOnce();
});
it("denies other account endpoints but permits the OIDC callback", async () => {
const denied = await GET(new Request("https://guide.sudloh.com/api/auth/list-sessions"));
expect(denied.status).toBe(401);
it("permits the OIDC callback", async () => {
const callback = await GET(new Request("https://guide.sudloh.com/api/auth/callback/sudloh?code=code"));
expect(callback.status).toBe(200);
});
it("denies Better Auth mutations with a revoked local session", async () => {
it("passes mutations to Better Auth for local session checks", async () => {
const response = await POST(new Request("https://guide.sudloh.com/api/auth/admin/create-user", {
method: "POST", headers: { Origin: "https://guide.sudloh.com", "Content-Type": "application/json" },
body: JSON.stringify({ password: "example-password" }),
}));
expect(response.status).toBe(401);
expect(handler).not.toHaveBeenCalled();
expect(response.status).toBe(200);
expect(handler).toHaveBeenCalledOnce();
});
});
-21
View File
@@ -2,32 +2,13 @@ import { toNextJsHandler } from "better-auth/next-js";
import { and, eq } from "drizzle-orm";
import { getAuth, isSudlohOidcEnabled } from "@/lib/auth/server";
import { validateSudlohSession } from "@/lib/auth/sudloh";
import { getDb } from "@/db";
import { accounts } from "@/db/schema";
import { errorResponse, HttpError, readJson, requireSameOrigin } from "@/lib/security/http";
const handlers = toNextJsHandler((request) => getAuth().handler(request));
async function hasActiveSudlohSession(request: Request): Promise<boolean | null> {
if (!isSudlohOidcEnabled() || process.env.SUDLOH_OIDC_ONLY !== "true") return null;
const session = await getAuth().api.getSession({ headers: request.headers });
if (!session) return null;
return validateSudlohSession(session.user.id, session.session.id);
}
export async function GET(request: Request) {
const path = new URL(request.url).pathname;
if (!path.endsWith("/callback/sudloh")) {
try {
const active = await hasActiveSudlohSession(request);
if (active === false) {
if (path.endsWith("/get-session"))
return Response.json(null, { headers: { "Cache-Control": "no-store" } });
throw new HttpError(401, "unauthorized");
}
} catch (cause) { return errorResponse(cause); }
}
return handlers.GET(request);
}
@@ -36,8 +17,6 @@ async function mutate(request: Request) {
requireSameOrigin(request);
const input = await readJson(request.clone());
const path = new URL(request.url).pathname;
if (!path.endsWith("/sign-in/social") && !path.endsWith("/sign-out") &&
await hasActiveSudlohSession(request) === false) throw new HttpError(401, "unauthorized");
if (["/update-user", "/change-email"].some((endpoint) => path.endsWith(endpoint))) {
if (isSudlohOidcEnabled() && process.env.SUDLOH_OIDC_ONLY === "true")
throw new HttpError(403, "manage-profile-at-sudloh");
+3 -6
View File
@@ -1,5 +1,5 @@
import { getCustomerSession, isSudlohOidcEnabled } from "@/lib/auth/server";
import { refreshLinkedSudlohProfile, validateSudlohSession } from "@/lib/auth/sudloh";
import { getCustomerSession } from "@/lib/auth/server";
import { refreshLinkedSudlohProfile } from "@/lib/auth/sudloh";
import { errorResponse, HttpError, requireSameOrigin } from "@/lib/security/http";
export async function POST(request: Request) {
@@ -7,10 +7,7 @@ export async function POST(request: Request) {
requireSameOrigin(request);
const session = await getCustomerSession();
if (!session) throw new HttpError(401, "unauthorized");
if (isSudlohOidcEnabled() && process.env.SUDLOH_OIDC_ONLY === "true") {
if (!await validateSudlohSession(session.user.id, session.session.id, true))
throw new HttpError(401, "sudloh-session-expired");
} else await refreshLinkedSudlohProfile(session.user.id);
await refreshLinkedSudlohProfile(session.user.id);
return Response.json({ ok: true }, { headers: { "Cache-Control": "no-store" } });
} catch (cause) { return errorResponse(cause); }
}