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

chore: remove logs for secret and change invalid token query logic #6907

Merged
merged 4 commits into from
Apr 23, 2024

Conversation

gastonfournier
Copy link
Contributor

@gastonfournier gastonfournier commented Apr 23, 2024

About the changes

What's going on is the following:

  1. When a token is not found in the token's cache we try to find it in the db
  2. To prevent a denial of service attack using invalid tokens, we cache the invalid tokens so we don't hit the db.
  3. The issue is that we stored this token in the cache regardless we found it or not. And if the token was valid the first time we'd add a timestamp to avoid querying this token again the next time.
  4. The next iteration the token should be in the cache:
    this.activeTokens.push(token);
    but for some reason it is not and therefore we have to make a query. But this is where the query prevention mechanism kicks in because it finds the token in the cache and kicks us out. This PR fixes this by only storing in the cache for misses if not found:
    // prevent querying the same invalid secret multiple times. Expire after 5 minutes
    this.queryAfter.set(secret, addMinutes(new Date(), 5));

The token was added to the cache because we were not checking if it had expired. Now we added a check and we also have a log for expired tokens. Some improvement opportunities:

  • I don't think we display that a token has expired in the UI which probably led to this issue
  • When a token expired we don't display a specific error message or error response saying that which is not very helpful for users

Copy link

vercel bot commented Apr 23, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Ignored Deployments
Name Status Preview Comments Updated (UTC)
unleash-docs ⬜️ Ignored (Inspect) Visit Preview Apr 23, 2024 11:41am
unleash-monorepo-frontend ⬜️ Ignored (Inspect) Visit Preview Apr 23, 2024 11:41am

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

  • Declining Code Health: 3 findings(s) 🚩

View detailed results in CodeScene

Comment on lines +148 to +175
if (!token) {
if (isPast(nextAllowedQuery)) {
this.logger.info(`Token not found in cache, querying database`);
if (this.queryAfter.size > 1000) {
// establish a max limit for queryAfter size to prevent memory leak
this.logger.info(
'queryAfter size exceeded 1000, clearing cache',
);
this.queryAfter.clear();
}

const stopCacheTimer = this.timer('getTokenWithCache.query');
token = await this.store.get(secret);
if (token) {
if (token?.expiresAt && isPast(token.expiresAt)) {
this.logger.info('Token has expired');
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
this.queryAfter.set(secret, addMinutes(new Date(), 5));
token = undefined;
} else {
this.activeTokens.push(token);
}
} else {
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
this.queryAfter.set(secret, addMinutes(new Date(), 5));
}
stopCacheTimer();
} else {

Choose a reason for hiding this comment

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

❌ Getting worse: Complex Method
ApiTokenService.getTokenWithCache increases in cyclomatic complexity from 9 to 12, threshold = 9

Suppress

Copy link
Member

Choose a reason for hiding this comment

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

Code scene kinda has a point here imo

Comment on lines +148 to +175
if (!token) {
if (isPast(nextAllowedQuery)) {
this.logger.info(`Token not found in cache, querying database`);
if (this.queryAfter.size > 1000) {
// establish a max limit for queryAfter size to prevent memory leak
this.logger.info(
'queryAfter size exceeded 1000, clearing cache',
);
this.queryAfter.clear();
}

const stopCacheTimer = this.timer('getTokenWithCache.query');
token = await this.store.get(secret);
if (token) {
if (token?.expiresAt && isPast(token.expiresAt)) {
this.logger.info('Token has expired');
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
this.queryAfter.set(secret, addMinutes(new Date(), 5));
token = undefined;
} else {
this.activeTokens.push(token);
}
} else {
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
this.queryAfter.set(secret, addMinutes(new Date(), 5));
}
stopCacheTimer();
} else {

Choose a reason for hiding this comment

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

❌ New issue: Bumpy Road Ahead
ApiTokenService.getTokenWithCache has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function

Suppress

Copy link
Member

Choose a reason for hiding this comment

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

And this one too

Comment on lines +148 to +175
if (!token) {
if (isPast(nextAllowedQuery)) {
this.logger.info(`Token not found in cache, querying database`);
if (this.queryAfter.size > 1000) {
// establish a max limit for queryAfter size to prevent memory leak
this.logger.info(
'queryAfter size exceeded 1000, clearing cache',
);
this.queryAfter.clear();
}

const stopCacheTimer = this.timer('getTokenWithCache.query');
token = await this.store.get(secret);
if (token) {
if (token?.expiresAt && isPast(token.expiresAt)) {
this.logger.info('Token has expired');
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
this.queryAfter.set(secret, addMinutes(new Date(), 5));
token = undefined;
} else {
this.activeTokens.push(token);
}
} else {
// prevent querying the same invalid secret multiple times. Expire after 5 minutes
this.queryAfter.set(secret, addMinutes(new Date(), 5));
}
stopCacheTimer();
} else {

Choose a reason for hiding this comment

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

❌ New issue: Deep, Nested Complexity
ApiTokenService.getTokenWithCache has a nested complexity depth of 4, threshold = 4

Suppress

Copy link
Contributor

@chriswk chriswk left a comment

Choose a reason for hiding this comment

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

Nice, though, do we really want our tests to write to console.log?

Copy link
Member

@sighphyre sighphyre left a comment

Choose a reason for hiding this comment

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

LGTM comments optional

Copy link

@codescene-delta-analysis codescene-delta-analysis bot left a comment

Choose a reason for hiding this comment

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

Code Health Quality Gates: FAILED

  • Declining Code Health: 3 findings(s) 🚩

View detailed results in CodeScene

@gastonfournier
Copy link
Contributor Author

LGTM comments optional

Yes, I'm aware of the additional complexity but for the sake of time and moving on to other important tasks I'll let this one slip

@gastonfournier gastonfournier enabled auto-merge (squash) April 23, 2024 11:44
@gastonfournier gastonfournier merged commit 3e4ed38 into main Apr 23, 2024
6 of 7 checks passed
@gastonfournier gastonfournier deleted the auth-logs branch April 23, 2024 11:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

None yet

3 participants