Skip to content

Commit

Permalink
feat(proxy): support cookieDomainRewrite and cookiePathRewrite (#313
Browse files Browse the repository at this point in the history
)

* feat: add cookieDomainRewrite and cookiePathRewrite to proxy

* refactor: simplify

---------

Co-authored-by: Pooya Parsa <[email protected]>
  • Loading branch information
enkot and pi0 authored Feb 16, 2023
1 parent 080bf53 commit 3495dbe
Show file tree
Hide file tree
Showing 4 changed files with 270 additions and 4 deletions.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ app.use(eventHandler(() => '<h1>Hello world!</h1>'))
app.use('/1', eventHandler(() => '<h1>Hello world!</h1>'))
.use('/2', eventHandler(() => '<h1>Goodbye!</h1>'))

// We can proxy requests and rewrite cookie's domain and path
app.use('/api', eventHandler((event) => proxyRequest('https://example.com', {
// f.e. keep one domain unchanged, rewrite one domain and remove other domains
cookieDomainRewrite: {
"example.com": "example.com",
"example.com": "somecompany.co.uk",
"*": "",
},
cookiePathRewrite: {
"/": "/api"
},
}))

// Legacy middleware with 3rd argument are automatically promisified
app.use(fromNodeMiddleware((req, res, next) => { req.setHeader('x-foo', 'bar'); next() }))

Expand Down Expand Up @@ -146,8 +159,8 @@ H3 has a concept of composable utilities that accept `event` (from `eventHandler
- `isMethod(event, expected, allowHead?)`
- `assertMethod(event, expected, allowHead?)`
- `createError({ statusCode, statusMessage, data? })`
- `sendProxy(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
- `proxyRequest(event, { target, headers?, fetchOptions?, fetch?, sendStream? })`
- `sendProxy(event, { target, ...options })`
- `proxyRequest(event, { target, ...options })`
- `fetchWithEvent(event, req, init, { fetch? }?)`
- `getProxyRequestHeaders(event)`
- `sendNoContent(event, code = 204)`
Expand Down
2 changes: 1 addition & 1 deletion src/utils/internal/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* Credits to: https://github.com/tomball for original and https://github.com/chrusart for JavaScript implementation
* @source https://github.com/nfriedly/set-cookie-parser/blob/3eab8b7d5d12c8ed87832532861c1a35520cf5b3/lib/set-cookie.js#L144
*/
export default function splitCookiesString(cookiesString: string) {
export default function splitCookiesString(cookiesString: string): string[] {
if (typeof cookiesString !== "string") {
return [];
}
Expand Down
44 changes: 43 additions & 1 deletion src/utils/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export interface ProxyOptions {
fetchOptions?: RequestInit;
fetch?: typeof fetch;
sendStream?: boolean;
cookieDomainRewrite?: string | Record<string, string>;
cookiePathRewrite?: string | Record<string, string>;
}

const PayloadMethods = new Set(["PATCH", "POST", "PUT", "DELETE"]);
Expand Down Expand Up @@ -75,9 +77,27 @@ export async function sendProxy(
continue;
}
if (key === "set-cookie") {
event.node.res.setHeader("set-cookie", splitCookiesString(value));
const cookies = splitCookiesString(value).map((cookie) => {
if (opts.cookieDomainRewrite) {
cookie = rewriteCookieProperty(
cookie,
opts.cookieDomainRewrite,
"domain"
);
}
if (opts.cookiePathRewrite) {
cookie = rewriteCookieProperty(
cookie,
opts.cookiePathRewrite,
"path"
);
}
return cookie;
});
event.node.res.setHeader("set-cookie", cookies);
continue;
}

event.node.res.setHeader(key, value);
}

Expand Down Expand Up @@ -140,3 +160,25 @@ function _getFetch(_fetch?: typeof fetch) {
"fetch is not available. Try importing `node-fetch-native/polyfill` for Node.js."
);
}

function rewriteCookieProperty(
header: string,
map: string | Record<string, string>,
property: string
) {
const _map = typeof map === "string" ? { "*": map } : map;
return header.replace(
new RegExp(`(;\\s*${property}=)([^;]+)`, "gi"),
(match, prefix, previousValue) => {
let newValue;
if (previousValue in _map) {
newValue = _map[previousValue];
} else if ("*" in _map) {
newValue = _map["*"];
} else {
return match;
}
return newValue ? prefix + newValue : "";
}
);
}
211 changes: 211 additions & 0 deletions test/proxy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
eventHandler,
getHeaders,
getMethod,
setHeader,
readRawBody,
setCookie,
} from "../src";
Expand Down Expand Up @@ -130,4 +131,214 @@ describe("", () => {
]);
});
});

describe("cookieDomainRewrite", () => {
beforeEach(() => {
app.use(
"/debug",
eventHandler((event) => {
setHeader(
event,
"set-cookie",
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
return {};
})
);
});

it("can rewrite cookie domain by string", async () => {
app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/debug", {
fetch,
cookieDomainRewrite: "new.domain",
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});

it("can rewrite cookie domain by mapper object", async () => {
app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/debug", {
fetch,
cookieDomainRewrite: {
"somecompany.co.uk": "new.domain",
},
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});

it("can rewrite domains of multiple cookies", async () => {
app.use(
"/multiple/debug",
eventHandler((event) => {
setHeader(event, "set-cookie", [
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT",
"bar=38afes7a8; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT",
]);
return {};
})
);

app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/multiple/debug", {
fetch,
cookieDomainRewrite: {
"somecompany.co.uk": "new.domain",
},
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT, bar=38afes7a8; Domain=new.domain; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});

it("can remove cookie domain", async () => {
app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/debug", {
fetch,
cookieDomainRewrite: {
"somecompany.co.uk": "",
},
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});
});

describe("cookiePathRewrite", () => {
beforeEach(() => {
app.use(
"/debug",
eventHandler((event) => {
setHeader(
event,
"set-cookie",
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
return {};
})
);
});

it("can rewrite cookie path by string", async () => {
app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/debug", {
fetch,
cookiePathRewrite: "/api",
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});

it("can rewrite cookie path by mapper object", async () => {
app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/debug", {
fetch,
cookiePathRewrite: {
"/": "/api",
},
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});

it("can rewrite paths of multiple cookies", async () => {
app.use(
"/multiple/debug",
eventHandler((event) => {
setHeader(event, "set-cookie", [
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT",
"bar=38afes7a8; Domain=somecompany.co.uk; Path=/; Expires=Wed, 30 Aug 2022 00:00:00 GMT",
]);
return {};
})
);

app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/multiple/debug", {
fetch,
cookiePathRewrite: {
"/": "/api",
},
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT, bar=38afes7a8; Domain=somecompany.co.uk; Path=/api; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});

it("can remove cookie path", async () => {
app.use(
"/",
eventHandler((event) => {
return proxyRequest(event, url + "/debug", {
fetch,
cookiePathRewrite: {
"/": "",
},
});
})
);

const result = await fetch(url + "/");

expect(result.headers.get("set-cookie")).toEqual(
"foo=219ffwef9w0f; Domain=somecompany.co.uk; Expires=Wed, 30 Aug 2022 00:00:00 GMT"
);
});
});
});

0 comments on commit 3495dbe

Please sign in to comment.