fix: multi session conflicting with nextjs plugin on clearing cookies

This commit is contained in:
Bereket Engida
2024-11-20 01:07:35 +03:00
parent 991d3b9ef7
commit a5fa1e975b
4 changed files with 33 additions and 41 deletions

View File

@@ -19,7 +19,7 @@ describe("session", async () => {
const cookies = parseSetCookieHeader(header || "");
expect(cookies.get("better-auth.session_token")).toMatchObject({
value: expect.any(String),
"max-age": (60 * 60 * 24 * 7).toString(),
"max-age": 60 * 60 * 24 * 7,
path: "/",
httponly: true,
samesite: "lax",
@@ -141,7 +141,7 @@ describe("session", async () => {
const cookies = parseSetCookieHeader(header || "");
expect(cookies.get("better-auth.session_token")).toMatchObject({
value: expect.any(String),
"max-age": (60 * 60 * 24 * 7).toString(),
"max-age": 60 * 60 * 24 * 7,
path: "/",
httponly: true,
samesite: "lax",

View File

@@ -1,6 +1,6 @@
interface CookieAttributes {
value: string;
"max-age"?: string;
"max-age"?: number;
expires?: Date;
domain?: string;
path?: string;
@@ -34,12 +34,13 @@ export function parseSetCookieHeader(
const [attrName, ...attrValueParts] = attribute.split("=");
const attrValue = attrValueParts.join("=");
// Normalize the attribute name to camelCase
const normalizedAttrName = attrName.trim().toLowerCase();
switch (normalizedAttrName) {
case "max-age":
attrObj["max-age"] = attrValue;
attrObj["max-age"] = attrValue
? parseInt(attrValue.trim(), 10)
: undefined;
break;
case "expires":
attrObj.expires = attrValue ? new Date(attrValue.trim()) : undefined;

View File

@@ -24,24 +24,23 @@ export const nextCookies = () => {
hooks: {
after: [
{
matcher() {
matcher(ctx) {
return true;
},
handler: async (ctx) => {
const returned = ctx.context.endpoint?.headers;
const returned = ctx.responseHeader;
if (returned instanceof Headers) {
const setCookies = returned?.get("set-cookie");
if (!setCookies) return;
const parsed = parseSetCookieHeader(setCookies);
const cookieHelper = await cookies();
parsed.forEach((value, key) => {
if (!value) return;
if (!key) return;
const opts = {
samesite: value.samesite,
sameSite: value.samesite,
secure: value.secure,
"max-age": value["max-age"],
httponly: value.httponly,
maxAge: value["max-age"],
httpOnly: value.httponly,
domain: value.domain,
path: value.path,
};

View File

@@ -6,11 +6,13 @@ import {
sessionMiddleware,
} from "../../api";
import {
deleteSessionCookie,
parseCookies,
parseSetCookieHeader,
setSessionCookie,
} from "../../cookies";
import type { BetterAuthPlugin } from "../../types";
import { returnHookResponse } from "../../utils/plugin-helper";
interface MultiSessionConfig {
/**
@@ -98,12 +100,7 @@ export const multiSession = (options?: MultiSessionConfig) => {
message: "Invalid session id",
});
}
await ctx.setSignedCookie(
ctx.context.authCookies.sessionToken.name,
sessionId,
ctx.context.secret,
ctx.context.authCookies.sessionToken.options,
);
await setSessionCookie(ctx, session);
return ctx.json(session);
},
),
@@ -139,7 +136,6 @@ export const multiSession = (options?: MultiSessionConfig) => {
if (!isActive) return ctx.json({ success: true });
const cookieHeader = ctx.headers?.get("cookie");
const authCookies = ctx.context.authCookies;
if (cookieHeader) {
const cookies = Object.fromEntries(parseCookies(cookieHeader));
@@ -165,22 +161,13 @@ export const multiSession = (options?: MultiSessionConfig) => {
const nextSession = validSessions[0];
await setSessionCookie(ctx, nextSession);
} else {
ctx.setCookie(authCookies.sessionToken.name, "", {
...authCookies.sessionToken.options,
maxAge: 0,
});
deleteSessionCookie(ctx);
}
} else {
ctx.setCookie(authCookies.sessionToken.name, "", {
...authCookies.sessionToken.options,
maxAge: 0,
});
deleteSessionCookie(ctx);
}
} else {
ctx.setCookie(authCookies.sessionToken.name, "", {
...authCookies.sessionToken.options,
maxAge: 0,
});
deleteSessionCookie(ctx);
}
return ctx.json({
success: true,
@@ -244,22 +231,27 @@ export const multiSession = (options?: MultiSessionConfig) => {
handler: createAuthMiddleware(async (ctx) => {
const cookieHeader = ctx.headers?.get("cookie");
if (!cookieHeader) return;
const cookies = Object.fromEntries(parseCookies(cookieHeader));
await Promise.all(
Object.entries(cookies).map(async ([key, value]) => {
const ids = Object.keys(cookies)
.map((key) => {
if (isMultiSessionCookie(key)) {
ctx.setCookie(key, "", { maxAge: 0 });
const id = key.split("_multi-")[1];
await ctx.context.internalAdapter.deleteSession(id);
return id;
}
}),
);
return {
responseHeader: ctx.responseHeader,
};
return null;
})
.filter((v): v is string => v !== null);
await ctx.context.internalAdapter.deleteSessions(ids);
const response = ctx.context.returned;
if (response instanceof Response) {
console.log("response", ctx.responseHeader.get("set-cookie"));
response.headers.append(
"Set-Cookie",
ctx.responseHeader.get("set-cookie")!,
);
return { response };
}
}),
},
],