mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-08 08:12:20 +03:00
Compare commits
1 Commits
test/9178-
...
feat/9284-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e4d54a9cc |
1
changelog.d/features/9284-json-cookie-input.md
Normal file
1
changelog.d/features/9284-json-cookie-input.md
Normal file
@@ -0,0 +1 @@
|
||||
- **feat(providers):** accept JSON cookie objects in normalizeSessionCookieHeader (#9284 — thanks @AIB1TAL0S)
|
||||
@@ -6,15 +6,74 @@ export function stripCookieInputPrefix(rawValue: string): string {
|
||||
return withoutBearer.replace(/^cookie:/i, "").trim();
|
||||
}
|
||||
|
||||
export function normalizeSessionCookieHeader(rawValue: string, defaultCookieName: string): string {
|
||||
const normalized = stripCookieInputPrefix(rawValue);
|
||||
if (!normalized) return "";
|
||||
/**
|
||||
* Parse a JSON array of cookie objects and produce a Cookie header string.
|
||||
*
|
||||
* Accepts the format exported by browser cookie-editor extensions / DevTools:
|
||||
* ```json
|
||||
* [
|
||||
* {"name":"sso","value":"eyJ0eXAi...","domain":".example.com","path":"/"},
|
||||
* {"name":"sso-rw","value":"eyJOTHER..."}
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* Only `name` and `value` are required. Extra fields (domain, path, expires,
|
||||
* httpOnly, secure, sameSite) are silently ignored — they describe the cookie
|
||||
* but are not part of the `Cookie` request header.
|
||||
*
|
||||
* @param rawValue - The user-provided cookie string (possibly JSON).
|
||||
* @returns A Cookie header string, null if the input is not JSON (pass-through).
|
||||
* @throws {Error} If a JSON entry is missing the required `name` or `value` field.
|
||||
*/
|
||||
export function parseJsonCookiesToHeader(rawValue: string): string | null {
|
||||
const trimmed = (rawValue || "").trim();
|
||||
if (!trimmed || !trimmed.startsWith("[")) return null;
|
||||
|
||||
if (normalized.includes("=")) {
|
||||
return normalized;
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return `${defaultCookieName}=${normalized}`;
|
||||
if (!Array.isArray(parsed)) return null;
|
||||
if (parsed.length === 0) return "";
|
||||
|
||||
const parts: string[] = [];
|
||||
for (let i = 0; i < parsed.length; i++) {
|
||||
const entry = parsed[i];
|
||||
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
||||
throw new Error(`Invalid cookie JSON at index ${i}: expected an object`);
|
||||
}
|
||||
const record = entry as Record<string, unknown>;
|
||||
|
||||
if (typeof record.name !== "string" || !record.name) {
|
||||
throw new Error(`Invalid cookie JSON at index ${i}: missing required field 'name'`);
|
||||
}
|
||||
if (typeof record.value !== "string") {
|
||||
throw new Error(`Invalid cookie JSON at index ${i}: missing required field 'value'`);
|
||||
}
|
||||
|
||||
parts.push(`${record.name}=${record.value}`);
|
||||
}
|
||||
|
||||
return parts.join("; ");
|
||||
}
|
||||
|
||||
export function normalizeSessionCookieHeader(rawValue: string, defaultCookieName: string): string {
|
||||
const stripped = stripCookieInputPrefix(rawValue);
|
||||
if (!stripped) return "";
|
||||
|
||||
const jsonResult = parseJsonCookiesToHeader(stripped);
|
||||
if (jsonResult !== null) {
|
||||
return jsonResult;
|
||||
}
|
||||
|
||||
if (stripped.includes("=")) {
|
||||
return stripped;
|
||||
}
|
||||
|
||||
return `${defaultCookieName}=${stripped}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
108
tests/unit/json-cookie-input.test.ts
Normal file
108
tests/unit/json-cookie-input.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
const {
|
||||
parseJsonCookiesToHeader,
|
||||
normalizeSessionCookieHeader,
|
||||
} = await import("../../src/lib/providers/webCookieAuth.ts");
|
||||
|
||||
// parseJsonCookiesToHeader — unit tests
|
||||
test("parseJsonCookiesToHeader: valid JSON array returns Cookie header string", () => {
|
||||
const json = `[{"name":"sso","value":"eyJ0eXAi.abc.def"}]`;
|
||||
assert.equal(parseJsonCookiesToHeader(json), "sso=eyJ0eXAi.abc.def");
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: multiple entries joined with ; ", () => {
|
||||
const json = `[
|
||||
{"name":"sso","value":"AAA.bbb"},
|
||||
{"name":"sso-rw","value":"CCC.ddd"},
|
||||
{"name":"cf_clearance","value":"zzz"}
|
||||
]`;
|
||||
assert.equal(parseJsonCookiesToHeader(json), "sso=AAA.bbb; sso-rw=CCC.ddd; cf_clearance=zzz");
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: extra optional fields are ignored gracefully", () => {
|
||||
const json = `[{"name":"session","value":"abc","domain":".example.com","path":"/","httpOnly":true,"secure":true,"sameSite":"Lax"}]`;
|
||||
assert.equal(parseJsonCookiesToHeader(json), "session=abc");
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: missing name throws descriptive error at correct index", () => {
|
||||
const json = `[{"name":"a","value":"1"},{"value":"no-name"}]`;
|
||||
assert.throws(
|
||||
() => parseJsonCookiesToHeader(json),
|
||||
{ message: "Invalid cookie JSON at index 1: missing required field 'name'" }
|
||||
);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: missing value throws descriptive error at correct index", () => {
|
||||
const json = `[{"name":"a","value":"1"},{"name":"no-value"}]`;
|
||||
assert.throws(
|
||||
() => parseJsonCookiesToHeader(json),
|
||||
{ message: "Invalid cookie JSON at index 1: missing required field 'value'" }
|
||||
);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: empty array returns empty string", () => {
|
||||
assert.equal(parseJsonCookiesToHeader("[]"), "");
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: raw string (non-JSON) returns null (pass-through)", () => {
|
||||
assert.equal(parseJsonCookiesToHeader("sso=eyJ0eXAi.abc.def"), null);
|
||||
assert.equal(parseJsonCookiesToHeader("__Secure-authjs.session-token=abc"), null);
|
||||
assert.equal(parseJsonCookiesToHeader("bearer xyz"), null);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: malformed JSON returns null (pass-through, no crash)", () => {
|
||||
assert.equal(parseJsonCookiesToHeader("[not valid json"), null);
|
||||
assert.equal(parseJsonCookiesToHeader("{invalid}"), null);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: empty/whitespace input returns null", () => {
|
||||
assert.equal(parseJsonCookiesToHeader(""), null);
|
||||
assert.equal(parseJsonCookiesToHeader(" "), null);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: parsed non-array JSON returns null", () => {
|
||||
assert.equal(parseJsonCookiesToHeader(`{"name":"test"}`), null);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: entry with empty name throws error", () => {
|
||||
const json = `[{"name":"","value":"abc"}]`;
|
||||
assert.throws(
|
||||
() => parseJsonCookiesToHeader(json),
|
||||
{ message: "Invalid cookie JSON at index 0: missing required field 'name'" }
|
||||
);
|
||||
});
|
||||
|
||||
test("parseJsonCookiesToHeader: entry with empty value returns empty value in header", () => {
|
||||
const json = `[{"name":"session","value":""}]`;
|
||||
assert.equal(parseJsonCookiesToHeader(json), "session=");
|
||||
});
|
||||
|
||||
// Integration tests via normalizeSessionCookieHeader
|
||||
test("normalizeSessionCookieHeader: JSON input returns correct header", () => {
|
||||
const json = `[{"name":"__Secure-authjs.session-token","value":"abc"}]`;
|
||||
assert.equal(
|
||||
normalizeSessionCookieHeader(json, "__Secure-authjs.session-token"),
|
||||
"__Secure-authjs.session-token=abc"
|
||||
);
|
||||
});
|
||||
|
||||
test("normalizeSessionCookieHeader: JSON input with prefix stripped works", () => {
|
||||
const json = `[{"name":"sso","value":"eyJ0eXAi.abc"}]`;
|
||||
assert.equal(
|
||||
normalizeSessionCookieHeader(`Cookie: ${json}`, "sso"),
|
||||
"sso=eyJ0eXAi.abc"
|
||||
);
|
||||
});
|
||||
|
||||
test("normalizeSessionCookieHeader: raw string unchanged after JSON support added", () => {
|
||||
assert.equal(
|
||||
normalizeSessionCookieHeader("__Secure-authjs.session-token=abc", "__Secure-authjs.session-token"),
|
||||
"__Secure-authjs.session-token=abc"
|
||||
);
|
||||
assert.equal(
|
||||
normalizeSessionCookieHeader("bare-value", "__Secure-authjs.session-token"),
|
||||
"__Secure-authjs.session-token=bare-value"
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user