Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(fac): Change the jwks cache duration from 1 day to 6 hours #1439

Merged
merged 2 commits into from
Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/utils/jwt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const JWT_CALLBACK_ERROR_PREFIX = 'error in secret or public key callback: ';
const NO_MATCHING_KID_ERROR_MESSAGE = 'no-matching-kid-error';
const NO_KID_IN_HEADER_ERROR_MESSAGE = 'no-kid-in-header-error';

const ONE_DAY_IN_SECONDS = 24 * 3600;
const HOUR_IN_SECONDS = 3600;

export type Dictionary = { [key: string]: any }

Expand Down Expand Up @@ -60,7 +60,7 @@ export class JwksFetcher implements KeyFetcher {

this.client = jwks({
jwksUri: jwksUrl,
cache: false, // disable jwks-rsa LRU cache as the keys are always cahced for 24 hours.
cache: false, // disable jwks-rsa LRU cache as the keys are always cached for 6 hours.
});
}

Expand All @@ -84,7 +84,7 @@ export class JwksFetcher implements KeyFetcher {
map[signingKey.kid] = signingKey.getPublicKey();
return map;
}, {});
this.publicKeysExpireAt = Date.now() + (ONE_DAY_IN_SECONDS * 1000);
this.publicKeysExpireAt = Date.now() + (HOUR_IN_SECONDS * 6 * 1000);
this.publicKeys = newKeys;
return newKeys;
}).catch((err) => {
Expand Down
16 changes: 8 additions & 8 deletions test/unit/utils/jwt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
const expect = chai.expect;

const ONE_HOUR_IN_SECONDS = 60 * 60;
const ONE_DAY_IN_SECONDS = 86400;
const SIX_HOURS_IN_SECONDS = ONE_HOUR_IN_SECONDS * 6;
const publicCertPath = '/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com';
const jwksPath = '/v1alpha/jwks';

Expand Down Expand Up @@ -709,24 +709,24 @@ describe('JwksFetcher', () => {

return keyFetcher.fetchPublicKeys().then(() => {
expect(https.request).to.have.been.calledOnce;
clock!.tick((ONE_DAY_IN_SECONDS - 1) * 1000);
clock!.tick((SIX_HOURS_IN_SECONDS - 1) * 1000);
Copy link
Contributor

Choose a reason for hiding this comment

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

Time math is a bit strange here. It seems we are running assertions at:

00:00:00 (start; calledOnce)
05:59:59 (calledOnce)
11:59:59 (calledTwice)
17:59:58 (calledTwice)
23:59:58 (calledThrice)

Not really a problem. But just wanted to point it out.

Copy link
Member Author

Choose a reason for hiding this comment

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

We are currently checking the boundaries at 6 hour steps. Should this be cleaned up a bit?

Copy link
Member Author

Choose a reason for hiding this comment

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

We could also check, 0, 5.59, 6, 11.59, 12, 17.59, 18 etc.

Copy link
Contributor

Choose a reason for hiding this comment

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

That's kind of what I expected to see. But it's not a major issue. Feel free to disregard or address in a future PR.

return keyFetcher.fetchPublicKeys();
}).then(() => {
expect(https.request).to.have.been.calledOnce;
clock!.tick(ONE_DAY_IN_SECONDS * 1000); // 24 hours in milliseconds
clock!.tick(SIX_HOURS_IN_SECONDS * 1000); // 6 hours in milliseconds
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the assertion on clock (clock!) is necessary here.

Copy link
Member Author

Choose a reason for hiding this comment

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

We need it because of the way clock is defined in these tests: let clock: sinon.SinonFakeTimers | undefined;
We set it to undefined after each test case:

  afterEach(() => {
    if (clock) {
      clock.restore();
      clock = undefined;
    }
  });

I think just doing clock.restore(); should be enough here. Let me know if you would like me to clean up all the test cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the compiler actually throw for these instances? We initialize clock as follows in some these tests:

clock = sinon.useFakeTimers(1000);

After that compiler should see that clock is no longer undefined.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, even after initializing the compiler throws if the instance is used inside a promise chain.

return keyFetcher.fetchPublicKeys();
}).then(() => {
// App check keys do not contain cache headers so we cache the keys for 24 hours.
// 24 hours has passed
// App check keys do not contain cache headers so we cache the keys for 6 hours.
// 6 hours has passed
expect(https.request).to.have.been.calledTwice;
clock!.tick((ONE_DAY_IN_SECONDS - 1) * 1000);
clock!.tick((SIX_HOURS_IN_SECONDS - 1) * 1000);
return keyFetcher.fetchPublicKeys();
}).then(() => {
expect(https.request).to.have.been.calledTwice;
clock!.tick(ONE_DAY_IN_SECONDS * 1000);
clock!.tick(SIX_HOURS_IN_SECONDS * 1000);
return keyFetcher.fetchPublicKeys();
}).then(() => {
// 48 hours have passed
// 12 hours have passed
expect(https.request).to.have.been.calledThrice;
});
});
Expand Down