From 47b387b1db9b73a1c81c40e8d67b4e14d09d1a84 Mon Sep 17 00:00:00 2001 From: bttf <2374625+bttf@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:49:12 -0500 Subject: [PATCH 1/2] Fix timestamp bug in createToken function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed critical bug where getUTCMilliseconds() was incorrectly used instead of getTime(). getUTCMilliseconds() returns only the milliseconds component (0-999) of a date, not the full Unix timestamp. This caused the expiry validation to fail incorrectly when comparing timestamps. Changes: - Replace expiry.getUTCMilliseconds() with expiry.getTime() to get the full timestamp - Replace new Date().getUTCMilliseconds() with Date.now() for getting current timestamp 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- lib/core/http.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/http.js b/lib/core/http.js index 52531fe..619bb58 100644 --- a/lib/core/http.js +++ b/lib/core/http.js @@ -230,7 +230,7 @@ module.exports = (appUuid, apiKey, config) => { let wellFormedExpiry; if (expiry) { if (expiry && expiry instanceof Date) { - wellFormedExpiry = expiry.getUTCMilliseconds(); + wellFormedExpiry = expiry.getTime(); } else if (expiry && typeof expiry === 'number') { wellFormedExpiry = expiry; } else { @@ -239,7 +239,7 @@ module.exports = (appUuid, apiKey, config) => { ); } - const now = new Date().getUTCMilliseconds(); + const now = Date.now(); if (wellFormedExpiry < now || wellFormedExpiry - now > 600000) { throw new errors.TokenCreationError( 'Expiry must be in the next 10 minutes' From bb5ad80bff38bc87eb386431108a831924b79c56 Mon Sep 17 00:00:00 2001 From: bttf <2374625+bttf@users.noreply.github.com> Date: Wed, 26 Nov 2025 11:53:12 -0500 Subject: [PATCH 2/2] Add comprehensive tests for createToken expiry validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added tests to cover the expiry validation logic that was fixed in the previous commit: - Validates expiry accepts Date objects within next 10 minutes - Validates expiry rejects past dates - Validates expiry rejects dates more than 10 minutes in future - Validates expiry accepts timestamp numbers within next 10 minutes - Validates expiry rejects past timestamps - Validates expiry rejects timestamps more than 10 minutes in future - Validates expiry rejects invalid types (e.g. strings) These tests ensure the bug fix works correctly and prevent regression. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tests/core/http.test.js | 111 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/tests/core/http.test.js b/tests/core/http.test.js index 0fef8d6..b16a55f 100644 --- a/tests/core/http.test.js +++ b/tests/core/http.test.js @@ -590,6 +590,117 @@ describe('Http Module', () => { }); }); }); + + context('Expiry validation', () => { + context('With Date object', () => { + it('Accepts expiry within next 10 minutes', () => { + const futureExpiry = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes from now + const testResponse = { + token: 'token', + expiry: futureExpiry.getTime(), + createdAt: Date.now(), + }; + const createRunTokenNock = setupNock( + 'https://api.evervault.com', + testApiKey, + true + ) + .post(`/client-side-tokens`) + .reply(200, testResponse); + + return testHttpClient + .createToken('decrypt', { test: 'data' }, futureExpiry) + .then((res) => { + expect(createRunTokenNock.isDone()).to.be.true; + }); + }); + + it('Throws error when expiry is in the past', () => { + const pastExpiry = new Date(Date.now() - 1000); // 1 second ago + return testHttpClient + .createToken('decrypt', { test: 'data' }, pastExpiry) + .catch((err) => { + expect(err).to.be.instanceOf(errors.TokenCreationError); + expect(err.message).to.equal( + 'Expiry must be in the next 10 minutes' + ); + }); + }); + + it('Throws error when expiry is more than 10 minutes in future', () => { + const farFutureExpiry = new Date(Date.now() + 11 * 60 * 1000); // 11 minutes from now + return testHttpClient + .createToken('decrypt', { test: 'data' }, farFutureExpiry) + .catch((err) => { + expect(err).to.be.instanceOf(errors.TokenCreationError); + expect(err.message).to.equal( + 'Expiry must be in the next 10 minutes' + ); + }); + }); + }); + + context('With timestamp number', () => { + it('Accepts timestamp within next 10 minutes', () => { + const futureExpiry = Date.now() + 5 * 60 * 1000; // 5 minutes from now + const testResponse = { + token: 'token', + expiry: futureExpiry, + createdAt: Date.now(), + }; + const createRunTokenNock = setupNock( + 'https://api.evervault.com', + testApiKey, + true + ) + .post(`/client-side-tokens`) + .reply(200, testResponse); + + return testHttpClient + .createToken('decrypt', { test: 'data' }, futureExpiry) + .then((res) => { + expect(createRunTokenNock.isDone()).to.be.true; + }); + }); + + it('Throws error when timestamp is in the past', () => { + const pastExpiry = Date.now() - 1000; // 1 second ago + return testHttpClient + .createToken('decrypt', { test: 'data' }, pastExpiry) + .catch((err) => { + expect(err).to.be.instanceOf(errors.TokenCreationError); + expect(err.message).to.equal( + 'Expiry must be in the next 10 minutes' + ); + }); + }); + + it('Throws error when timestamp is more than 10 minutes in future', () => { + const farFutureExpiry = Date.now() + 11 * 60 * 1000; // 11 minutes from now + return testHttpClient + .createToken('decrypt', { test: 'data' }, farFutureExpiry) + .catch((err) => { + expect(err).to.be.instanceOf(errors.TokenCreationError); + expect(err.message).to.equal( + 'Expiry must be in the next 10 minutes' + ); + }); + }); + }); + + context('With invalid expiry type', () => { + it('Throws error when expiry is a string', () => { + return testHttpClient + .createToken('decrypt', { test: 'data' }, '2024-01-01') + .catch((err) => { + expect(err).to.be.instanceOf(errors.TokenCreationError); + expect(err.message).to.equal( + 'Expiry must be a Date object, got string' + ); + }); + }); + }); + }); }); });