Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
);
});
});
});