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
16 changes: 16 additions & 0 deletions examples/default-middlewares-example/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const { orka } = require('../../build');

const w = orka({
diamorphosis: { configFolder: './examples/default-middlewares-example' },
routesPath: './examples/default-middlewares-example/routes.js',
beforeStart: () => {
const config = require('./config');
console.log(`Going to start env: ${config.nodeEnv}`);
}
});

if (!module.parent) {
w.start();
}

module.exports = w;
26 changes: 26 additions & 0 deletions examples/default-middlewares-example/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
nodeEnv: 'demo',
log: {
json: true
},
app: {
name: 'foo'
},
cors: {
publicPrefixes: ['/api/allowAll'],
allowedOrigins: ['localhost:3220', 'lvh.me', '*.lvh.me'],
},
riviere: {
bodyKeysRegex: '.*'
},
visitor: {
setCookie: true,
cookie: 'wmc',
maxAge: '7d',
variant: 'v1',
secure: true,
forwarded_headers: ['user-agent', 'x-request-id', 'referer', 'origin'],
waitForAttribution: false,
},
port: 3220
};
5 changes: 5 additions & 0 deletions examples/default-middlewares-example/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
get: {
'/test': async (ctx, next) => (ctx.body = 'ok')
}
};
25 changes: 20 additions & 5 deletions src/initializers/koa/add-visitor-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,9 @@ export default function (config): Middleware {
ctx.cookies.secure = true;
}

let cookieDomain = domain((ctx.origin && new URL(ctx.origin).hostname) || ctx.hostname);
if (config.visitor.getCookieDomain && typeof config.visitor.getCookieDomain === 'function') {
cookieDomain = config.visitor.getCookieDomain(ctx);
}

const options: SetOption = {
domain: cookieDomain,
domain: getCookieDomain(ctx, config),
maxAge: config.visitor.maxAge && ms(config.visitor.maxAge),
httpOnly: false,
secure: config.visitor.secure,
Expand All @@ -59,4 +55,23 @@ export default function (config): Middleware {
}
return next();
};

function getCookieDomain(ctx: Context, config): string {
// custom cb function takes precedence
if (config.visitor.getCookieDomain && typeof config.visitor.getCookieDomain === 'function') {
return config.visitor.getCookieDomain(ctx);
}

// utilizes the original origin
// even when app is behind proxy
// https://github.com/koajs/koa/issues/1008#issuecomment-2872014479
if (URL.canParse(ctx.request.URL.origin)) {
return domain(ctx.request.URL.hostname);
} else {
// get hostname from Host header
// or X-Forwarded-Host when is proxy enabled
// https://github.com/koajs/koa/blob/master/lib/request.js#L281
return domain(ctx.hostname);
}
}
}
134 changes: 134 additions & 0 deletions test/examples/default-middlewares-example.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import * as should from 'should';
import * as supertest from 'supertest';
import * as uuid from 'uuid';

describe('Default middlewares examples', () => {
let server;
after(async () => {
if (server) await server.stop();
});

before(async () => {
const serverPath = '../../examples/default-middlewares-example/app';
delete require.cache[require.resolve(serverPath)];
server = require(serverPath);
await server.start();
await new Promise(t => setTimeout(t, 1000));
});

describe('add-visitor-id middleware', function() {
it('/test sets a visitor cookie', async () => {
const response = await supertest('localhost:3220')
.get('/test')
.expect(200);

const cookies = response.headers['set-cookie'];
should(cookies).not.be.undefined();

const visitorCookie = cookies.find(cookie => cookie.startsWith('wmc='));
should(visitorCookie).not.be.undefined();
visitorCookie.should.match(/domain=localhost/i);
visitorCookie.should.match(/secure/i);
visitorCookie.should.match(/samesite=none/i);

const [, encoded] = visitorCookie.split(';')[0].split('=');
const decoded = JSON.parse(decodeURIComponent(encoded));
uuid.validate(decoded.cookie_id).should.be.true();
});

it('/test reuses the visitor cookie when already present', async () => {
const visitor = uuid.v4();
const cookie = encodeURIComponent(JSON.stringify({ cookie_id: visitor }));

const response = await supertest('localhost:3220')
.get('/test')
.set('Cookie', `wmc=${cookie}`)
.expect(200);

should(response.headers['set-cookie']).be.undefined();
});

describe('when the Origin header is malformed', function() {
it('/test sets a visitor cookie', async () => {
const response = await supertest('localhost:3220')
.get('/test')
.set('origin', 'invalid')
.expect(200);

const cookies = response.headers['set-cookie'];
should(cookies).not.be.undefined();

const visitorCookie = cookies.find(cookie => cookie.startsWith('wmc='));
should(visitorCookie).not.be.undefined();
visitorCookie.should.match(/domain=localhost/i);
visitorCookie.should.match(/secure/i);
visitorCookie.should.match(/samesite=none/i);

const [, encoded] = visitorCookie.split(';')[0].split('=');
const decoded = JSON.parse(decodeURIComponent(encoded));
uuid.validate(decoded.cookie_id).should.be.true();
});

it('derives the cookie domain from the host, not from Origin', async () => {
const response = await supertest('localhost:3220')
.get('/test')
.set('origin', 'https://evil.example.com')
.expect(200);
const visitorCookie = response.headers['set-cookie'].find(c => c.startsWith('wmc='));
should(visitorCookie).not.be.undefined();
visitorCookie.should.match(/domain=localhost/i);
visitorCookie.should.not.match(/evil\.example\.com/i);
});
});

describe('when callback for cookie domain is provided from config', function() {
before(function() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
before(function() {
before(function() {
server.config.visitor.getCookieDomain = () => 'domain-provider-cb';
});
after(function() {
delete server.config.visitor.getCookieDomain;
});

to avoid leaking getCookieDomain
very minor as it is the last test in the file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

server.config.visitor.getCookieDomain = () => 'domain-provider-cb';
});

after(function() {
delete server.config.visitor.getCookieDomain;
});

it('uses the callback to set a visitor cookie domain', async () => {
const response = await supertest('localhost:3220')
.get('/test')
.set('origin', 'invalid')
.expect(200);

const cookies = response.headers['set-cookie'];
should(cookies).not.be.undefined();

const visitorCookie = cookies.find(cookie => cookie.startsWith('wmc='));
should(visitorCookie).not.be.undefined();
visitorCookie.should.match(/domain=domain-provider-cb/i);
visitorCookie.should.match(/secure/i);
visitorCookie.should.match(/samesite=none/i);

const [, encoded] = visitorCookie.split(';')[0].split('=');
const decoded = JSON.parse(decodeURIComponent(encoded));
uuid.validate(decoded.cookie_id).should.be.true();
});

it('precedes the origin header uses the callback to set a visitor cookie', async () => {
const response = await supertest('localhost:3220')
.get('/test')
.set('origin', 'http://localhost:3220')
.expect(200);

const cookies = response.headers['set-cookie'];
should(cookies).not.be.undefined();

const visitorCookie = cookies.find(cookie => cookie.startsWith('wmc='));
should(visitorCookie).not.be.undefined();
visitorCookie.should.match(/domain=domain-provider-cb/i);
visitorCookie.should.match(/secure/i);
visitorCookie.should.match(/samesite=none/i);

const [, encoded] = visitorCookie.split(';')[0].split('=');
const decoded = JSON.parse(decodeURIComponent(encoded));
uuid.validate(decoded.cookie_id).should.be.true();
});
});
});
});
2 changes: 1 addition & 1 deletion test/initializers/koa/add-visitor-id.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('add-visitor-id', function () {
beforeEach(function () {
getRequestContextStub = sandbox.stub();
ctx = {
origin: 'https://apply.workable.com/foo',
request: { URL: new URL('https://apply.workable.com/foo') },
state: {},
cookies: {
get: () => encodeURIComponent(JSON.stringify({ cookie_id: visitor }))
Expand Down
Loading