Skip to content

Commit

Permalink
feat: add functionality to merge cookies from upstream and proxy server
Browse files Browse the repository at this point in the history
  • Loading branch information
vlad-tkachenko committed Nov 17, 2023
1 parent cb8aed8 commit 43a0eea
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 8 deletions.
3 changes: 3 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ MAPPINGS_API='[
}
}
},
{
"pattern": "/api-optional/.*"
},
{
"pattern": "/forbidden-api/.*",
"auth": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"node-graceful-shutdown": "^1.1.5",
"openid-client": "^5.4.3",
"pino": "^8.14.1",
"prxi": "^0.3.1"
"prxi": "^0.4.0"
},
"devDependencies": {
"@testdeck/mocha": "^0.3.3",
Expand Down
34 changes: 33 additions & 1 deletion src/handlers/ProxyHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IncomingMessage, ServerResponse } from "http";
import { IncomingMessage, OutgoingHttpHeaders, ServerResponse } from "http";
import { HttpMethod, ProxyRequest, RequestHandlerConfig } from "prxi";
import { invalidateAuthCookies, sendErrorResponse, sendRedirect, setAuthCookies } from "../utils/ResponseUtils";
import { getConfig } from "../config/getConfig";
Expand Down Expand Up @@ -111,6 +111,38 @@ export class ProxyHandler implements RequestHandlerConfig {

await proxyRequest({
proxyRequestHeaders,
onBeforeResponse: (res: ServerResponse, outgoingHeaders: OutgoingHttpHeaders) => {
const setCookieName = 'set-cookie';
const setCookieHeader = res.getHeader(setCookieName);
if (setCookieHeader) {
const outgoingCookieHeader = outgoingHeaders[setCookieName];
if (!outgoingCookieHeader) {
// when no need to merge cookies
outgoingHeaders[setCookieName] = setCookieHeader;
} else {
// merge cookies
const cookies: Array<string> = [];

// merge function
const merge = (cookiesToSet: string | number | string[]) => {
/* istanbul ignore else */
if (Array.isArray(cookiesToSet)) {
for(const cookie of cookiesToSet) {
cookies.push(cookie);
}
} else {
cookies.push(cookiesToSet.toString())
}
}

// merge cookies
merge(outgoingCookieHeader);
merge(setCookieHeader);

outgoingHeaders[setCookieName] = cookies;
}
}
}
});
}

Expand Down
67 changes: 65 additions & 2 deletions test/TokenRefresh.suite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,14 @@ class TokenRefreshSuite extends BaseSuite {
}

@test()
async corruptedToken() {
async corruptedTokenRequiredAuth() {
const uri = '/api/test?q=str';

await this.withNewPage(getConfig().hostURL + '/pages/test', async (page) => {
await this.loginOnKeycloak(page);

await this.navigate(page, getConfig().hostURL + uri);
let json = await this.getJsonFromPage(page);
const json = await this.getJsonFromPage(page);

strictEqual(json.http.originalUrl, uri);
strictEqual(json.request.query.q, 'str');
Expand All @@ -51,6 +51,69 @@ class TokenRefreshSuite extends BaseSuite {
await this.navigate(page, getConfig().hostURL + uri);
const text = await this.getTextFromPage(page);
strictEqual(text, '401: Unauthorized');

const cookies = await page.cookies()
const refreshTokenCookie = cookies.find(c => c.name === getConfig().cookies.names.refreshToken );
strictEqual(refreshTokenCookie, undefined);
});
}

@test()
async corruptedTokenOptionalAuth() {
const uri = '/api-optional/test?q=str';

await this.withNewPage(getConfig().hostURL + '/pages/test', async (page) => {
await this.loginOnKeycloak(page);

await this.navigate(page, getConfig().hostURL + uri);
let json = await this.getJsonFromPage(page);

strictEqual(json.http.originalUrl, uri);
strictEqual(json.request.query.q, 'str');

// remove access token cookie, replace refresh one with corrupted value
await page.deleteCookie({ name: getConfig().cookies.names.accessToken });
await page.setCookie( { name: getConfig().cookies.names.refreshToken, value: 'oops' });

// do the same check once again
await this.navigate(page, getConfig().hostURL + uri);
json = await this.getJsonFromPage(page);

strictEqual(json.http.originalUrl, uri);
strictEqual(json.request.query.q, 'str');

const cookies = await page.cookies()
const refreshTokenCookie = cookies.find(c => c.name === getConfig().cookies.names.refreshToken );
strictEqual(refreshTokenCookie, undefined);
});
}

@test()
async corruptedTokenOptionalAuthWithCookieMerge() {
const uri = '/api-optional/test?echo_header=set-cookie%3Atest%3Dyes';

await this.withNewPage(getConfig().hostURL + '/pages/test', async (page) => {
await this.loginOnKeycloak(page);

await this.navigate(page, getConfig().hostURL + uri);
let json = await this.getJsonFromPage(page);

strictEqual(json.http.originalUrl, uri);

// remove access token cookie, replace refresh one with corrupted value
await page.deleteCookie({ name: getConfig().cookies.names.accessToken });
await page.setCookie( { name: getConfig().cookies.names.refreshToken, value: 'oops' });

// do the same check once again
await this.navigate(page, getConfig().hostURL + uri);
json = await this.getJsonFromPage(page);

strictEqual(json.http.originalUrl, uri);

const cookies = await page.cookies()
const refreshTokenCookie = cookies.find(c => c.name === getConfig().cookies.names.refreshToken );
strictEqual(refreshTokenCookie, undefined);
strictEqual(cookies.find(c => c.name === 'test')?.value, 'yes');
});
}

Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2803,10 +2803,10 @@ proxy-from-env@^1.1.0:
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==

prxi@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/prxi/-/prxi-0.3.1.tgz#a2b7213cfbc1b36ead66547f516cb15256399337"
integrity sha512-TmkfXqhRYWNENL4ii32eXvC4kKdLuL4gxWfbxppOxQ8Ngy8tin5NfNW5btAg4zVkZ4T+KkyU22Vs/3ZX8FEyjQ==
prxi@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/prxi/-/prxi-0.4.0.tgz#219ce044fdc53b692e045a6f2f62c5199173ea5f"
integrity sha512-N83E9T617dof4mza2gCdpUknCN+aRGMUXSGj9seLMtPsB4+ikQG4zk3SiSNu8sZ5mOq+4T0C22yEcdDuaPp0kA==

ps-tree@^1.2.0:
version "1.2.0"
Expand Down

0 comments on commit 43a0eea

Please sign in to comment.