Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit 17179ae

Browse files
committed
Patch tough-cookie bug in TokenManager
1 parent 119527c commit 17179ae

File tree

3 files changed

+90
-2
lines changed

3 files changed

+90
-2
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
- [FIXED] Issue where new session cookies from pre-emptive renewal would not persist beyond the original session
3+
lifetime.
4+
15
# 4.5.0 (2021-08-26)
26
- [IMPROVED] - Document IDs and attachment names are now rejected if they could cause an unexpected
37
Cloudant request. We have seen that some applications pass unsantized document IDs to SDK functions

lib/tokens/TokenManager.js

+44
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,50 @@ class TokenManager {
2727
this._isTokenRenewing = false;
2828

2929
this._tokenExchangeEE = new EventEmitter().setMaxListeners(Infinity);
30+
31+
// START monkey patch for https://github.com/salesforce/tough-cookie/issues/154
32+
// Use the tough-cookie CookieJar from the RequestJar
33+
const cookieJar = this._jar ? this._jar._jar : false;
34+
// Check if we've already patched the jar
35+
if (cookieJar && !cookieJar.cloudantPatch) {
36+
// Set the patching flag
37+
cookieJar.cloudantPatch = true;
38+
// Replace the store's updateCookie function with one that applies a patch to newCookie
39+
const originalUpdateCookieFn = cookieJar.store.updateCookie;
40+
cookieJar.store.updateCookie = function(oldCookie, newCookie, cb) {
41+
// Add current time as an update timestamp to the newCookie
42+
newCookie.cloudantPatchUpdateTime = new Date();
43+
// Replace the cookie's expiryTime function with one that uses cloudantPatchUpdateTime
44+
// in place of creation time to check the expiry.
45+
const originalExpiryTimeFn = newCookie.expiryTime;
46+
newCookie.expiryTime = function(now) {
47+
// The original expiryTime check is relative to a time in this order:
48+
// 1. supplied now argument
49+
// 2. this.creation (original cookie creation time)
50+
// 3. current time
51+
// This patch replaces 2 with an expiry check relative to the cloudantPatchUpdateTime if set instead of
52+
// the creation time by passing it as the now argument.
53+
return originalExpiryTimeFn.call(
54+
newCookie,
55+
newCookie.cloudantPatchUpdateTime || now
56+
);
57+
};
58+
// Finally delegate back to the original update function or the fallback put (which is set by Cookie
59+
// when an update function is not present on the store). Since we always set an update function for our
60+
// patch we need to also provide that fallback.
61+
if (originalUpdateCookieFn) {
62+
originalUpdateCookieFn.call(
63+
cookieJar.store,
64+
oldCookie,
65+
newCookie,
66+
cb
67+
);
68+
} else {
69+
cookieJar.store.putCookie(newCookie, cb);
70+
}
71+
};
72+
}
73+
// END cookie jar monkey patch
3074
}
3175

3276
_autoRenew(defaultMaxAgeSecs) {

test/plugins/cookieauth.js

+42-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
const assert = require('assert');
1919
const Client = require('../../lib/client.js');
20+
const Cloudant = require('../../cloudant.js');
2021
const nock = require('../nock.js');
2122
const uuidv4 = require('uuid/v4'); // random
2223

@@ -31,10 +32,10 @@ const COOKIEAUTH_PLUGIN = [ { cookieauth: { autoRenew: false } } ];
3132
// mock cookies
3233

3334
const MOCK_COOKIE = 'AuthSession=Y2xbZWr0bQlpcc19ZQN8OeU4OWFCNYcZOxgdhy-QRDp4i6JQrfkForX5OU5P';
34-
const MOCK_SET_COOKIE_HEADER = { 'set-cookie': `${MOCK_COOKIE}; Version=1; Max-Age=86400; Path=/; HttpOnly` };
35+
const MOCK_SET_COOKIE_HEADER = { 'set-cookie': `${MOCK_COOKIE}; Version=1; Max-Age=1; Path=/; HttpOnly` };
3536

3637
const MOCK_COOKIE_2 = 'AuthSession=Q2fbIWc0kQspdc39OQL89eS4PWECcYEZDxgdgy-0RCp2i0dcrDkfoWX7OI5A';
37-
const MOCK_SET_COOKIE_HEADER_2 = { 'set-cookie': `${MOCK_COOKIE_2}; Version=1; Max-Age=86400; Path=/; HttpOnly` };
38+
const MOCK_SET_COOKIE_HEADER_2 = { 'set-cookie': `${MOCK_COOKIE_2}; Version=1; Max-Age=1; Path=/; HttpOnly` };
3839

3940
describe('#db CookieAuth Plugin', function() {
4041
before(function(done) {
@@ -358,5 +359,44 @@ describe('#db CookieAuth Plugin', function() {
358359
done();
359360
});
360361
});
362+
363+
it('pre-emptive renewal outlasts original session', function() {
364+
if (process.env.NOCK_OFF) {
365+
this.skip();
366+
}
367+
368+
var mocks = nock(SERVER)
369+
.post('/_session', {name: ME, password: PASSWORD})
370+
.reply(200, {ok: true}, MOCK_SET_COOKIE_HEADER)
371+
.get(DBNAME)
372+
.matchHeader('cookie', MOCK_COOKIE)
373+
.reply(200, {doc_count: 0})
374+
// pre-emptive renewals every 500 ms
375+
.post('/_session', {name: ME, password: PASSWORD})
376+
.times(2)
377+
.reply(200, {ok: true}, MOCK_SET_COOKIE_HEADER_2)
378+
.get(DBNAME)
379+
.matchHeader('cookie', MOCK_COOKIE_2)
380+
.reply(200, {doc_count: 0});
381+
382+
var cloudantClient = new Cloudant({
383+
url: SERVER,
384+
username: ME,
385+
password: PASSWORD,
386+
maxAttempt: 1
387+
// Note using default cookieauth plugin
388+
});
389+
return cloudantClient.db.get(DBNAME.substring(1)) /* Remove leading slash */
390+
.then(() => {
391+
// Wait long enough for a pre-emptive renewal and orignal session to lapse
392+
return new Promise(resolve => setTimeout(resolve, 1000));
393+
})
394+
.then(() => {
395+
return cloudantClient.db.get(DBNAME.substring(1));
396+
})
397+
.finally(() => {
398+
mocks.done();
399+
});
400+
});
361401
});
362402
});

0 commit comments

Comments
 (0)