mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-24 08:01:56 -05:00
fix: prevent double encoded cookie (#8133)
Co-authored-by: Alex Yang <himself65@outlook.com>
This commit is contained in:
committed by
GitHub
parent
47bba48f28
commit
49921100ae
@@ -1,3 +1,11 @@
|
||||
function tryDecode(str: string): string {
|
||||
try {
|
||||
return decodeURIComponent(str);
|
||||
} catch {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CookieAttributes {
|
||||
value: string;
|
||||
"max-age"?: number | undefined;
|
||||
@@ -85,7 +93,8 @@ export function parseSetCookieHeader(
|
||||
return;
|
||||
}
|
||||
|
||||
const attrObj: CookieAttributes = { value };
|
||||
const decodedValue = value.includes("%") ? tryDecode(value) : value;
|
||||
const attrObj: CookieAttributes = { value: decodedValue };
|
||||
|
||||
attributes.forEach((attribute) => {
|
||||
const [attrName, ...attrValueParts] = attribute!.split("=");
|
||||
|
||||
@@ -226,6 +226,12 @@ describe("cookie-utils parseSetCookieHeader", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("decodes URI-encoded cookie values", () => {
|
||||
const header = "token=hello%20world%3Dfoo; Path=/";
|
||||
const map = parseSetCookieHeader(header);
|
||||
expect(map.get("token")?.value).toBe("hello world=foo");
|
||||
});
|
||||
|
||||
it("handles cookie with Expires followed by cookie without Expires", () => {
|
||||
const map = parseSetCookieHeader(
|
||||
"session=xyz; Expires=Mon, 01 Jan 2026 00:00:00 GMT, token=abc",
|
||||
|
||||
@@ -100,7 +100,7 @@ export const nextCookies = () => {
|
||||
path: value.path,
|
||||
} as const;
|
||||
try {
|
||||
cookieHelper.set(key, decodeURIComponent(value.value), opts);
|
||||
cookieHelper.set(key, value.value, opts);
|
||||
} catch {
|
||||
// this will fail if the cookie is being set on server component
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export const sveltekitCookies = (
|
||||
|
||||
for (const [name, { value, ...ops }] of parsed) {
|
||||
try {
|
||||
event.cookies.set(name, decodeURIComponent(value), {
|
||||
event.cookies.set(name, value, {
|
||||
sameSite: ops.samesite,
|
||||
path: ops.path || "/",
|
||||
expires: ops.expires,
|
||||
|
||||
@@ -51,7 +51,7 @@ export const tanstackStartCookies = () => {
|
||||
path: value.path,
|
||||
} as const;
|
||||
try {
|
||||
setCookie(key, decodeURIComponent(value.value), opts);
|
||||
setCookie(key, value.value, opts);
|
||||
} catch {
|
||||
// this will fail if the cookie is being set on server component
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export const tanstackStartCookies = () => {
|
||||
path: value.path,
|
||||
} as const;
|
||||
try {
|
||||
setCookie(key, decodeURIComponent(value.value), opts);
|
||||
setCookie(key, value.value, opts);
|
||||
} catch {
|
||||
// this will fail if the cookie is being set on server component
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, expect, expectTypeOf, it } from "vitest";
|
||||
import { createAuthClient } from "../../client";
|
||||
import { parseSetCookieHeader } from "../../cookies";
|
||||
import { getTestInstance } from "../../test-utils/test-instance";
|
||||
import type { BetterAuthOptions } from "../../types";
|
||||
import { admin } from "../admin";
|
||||
@@ -81,6 +82,37 @@ describe("Custom Session Plugin Tests", async () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should not double-encode session cookie during get-session refresh", async () => {
|
||||
const { headers } = await signInWithTestUser();
|
||||
const signedInCookie = headers.get("cookie");
|
||||
const signedInSessionToken = signedInCookie?.match(
|
||||
/better-auth\.session_token=([^;]+)/,
|
||||
)?.[1];
|
||||
expect(signedInSessionToken).toBeDefined();
|
||||
|
||||
let refreshedSessionToken: string | undefined;
|
||||
await client.getSession({
|
||||
fetchOptions: {
|
||||
headers,
|
||||
onResponse(context) {
|
||||
const setCookies = context.response.headers.getSetCookie();
|
||||
for (const cookieStr of setCookies) {
|
||||
const parsed = parseSetCookieHeader(cookieStr);
|
||||
const token = parsed.get("better-auth.session_token")?.value;
|
||||
if (token) {
|
||||
refreshedSessionToken = token;
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(refreshedSessionToken).toBeDefined();
|
||||
expect(refreshedSessionToken).toBe(signedInSessionToken);
|
||||
expect(refreshedSessionToken).not.toContain("%25");
|
||||
});
|
||||
|
||||
it("should return the custom session for multi-session", async () => {
|
||||
const headers = new Headers();
|
||||
const testUser = {
|
||||
|
||||
Reference in New Issue
Block a user