denoland / deno_kv_oauth

High-level OAuth 2.0 powered by Deno KV.

Home Page:https://jsr.io/@deno/kv-oauth

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

bug: Token Refresh Logic is Reversed

mitchwadair opened this issue · comments

Sorry if I'm being dumb here, but I was reading through the code to get a better idea of how tokens are stored/used etc and noticed this. If I am reading this right, the function is returning the access token if there is no refresh token or if tokens.expiresIn is both truthy (meaning both defined and non-zero) and less than 5.

if (
tokens.refreshToken === undefined ||
// 5 second buffer
(tokens.expiresIn && tokens.expiresIn < (5 * SECOND))
) {
return tokens.accessToken;
}

I think there is one main issue with this. If expiresIn is a non-zero value that is less than 5, we probably do want to refresh the token right? There are two cases:

  1. expiresIn is negative, meaning it is already expired and we should refresh
  2. expiresIn indicates that the token will expire soon (the 5 second buffer mentioned in the comment) and we should refresh

With the current logic, isn't the function basically only refreshing the token if it is not going to expire soon or if expiresIn is exactly 0? Should that < actually be a >? Or am I crazy

This would definitely be problematic because there would likely be a large number of requests being made to oauth providers and a bunch KV writes that are unnecessary

I believe I have also figured out why tests are passing with this logic:
In the genTokens helper, refreshToken is not defined

export function genTokens(): Tokens {
return {
accessToken: crypto.randomUUID(),
tokenType: crypto.randomUUID(),
};
}

Because of this, the test for long-expiry passes because this function automatically returns the access token when refreshToken is undefined
await test.step("returns the access token for session with far expiry", async () => {

I am happy to PR a fix to this problem (and regression tests) if my assessment here is correct, lmk

Actually even more issues here than I thought.

The expiresIn property is documented as the time in seconds, but it is multiplied by SECOND, making the condition here off by a factor of 1000 in addition to what I already mentioned here.

Also, when calculating expiresIn it is reversed as well

deno_kv_oauth/src/core.ts

Lines 146 to 147 in b4007c0

const expiresIn =
(Date.now() - Date.parse(storedTokens.expiresAt.toString())) / SECOND;

Here, the expiresIn should be calculated by the current time subtracted from the expiresAt time

Sorry if I'm being dumb here, but I was reading through the code to get a better idea of how tokens are stored/used etc and noticed this. If I am reading this right, the function is returning the access token if there is no refresh token or if tokens.expiresIn is both truthy (meaning both defined and non-zero) and less than 5.

if (
tokens.refreshToken === undefined ||
// 5 second buffer
(tokens.expiresIn && tokens.expiresIn < (5 * SECOND))
) {
return tokens.accessToken;
}

I think there is one main issue with this. If expiresIn is a non-zero value that is less than 5, we probably do want to refresh the token right? There are two cases:

  1. expiresIn is negative, meaning it is already expired and we should refresh

  2. expiresIn indicates that the token will expire soon (the 5 second buffer mentioned in the comment) and we should refresh

With the current logic, isn't the function basically only refreshing the token if it is not going to expire soon or if expiresIn is exactly 0? Should that < actually be a >? Or am I crazy

This would definitely be problematic because there would likely be a large number of requests being made to oauth providers and a bunch KV writes that are unnecessary

You're not crazy. That's a bug. Yes, please do feel free to open a PR 🙂

Actually even more issues here than I thought.

The expiresIn property is documented as the time in seconds, but it is multiplied by SECOND, making the condition here off by a factor of 1000 in addition to what I already mentioned here.

Also, when calculating expiresIn it is reversed as well

deno_kv_oauth/src/core.ts

Lines 146 to 147 in b4007c0

const expiresIn =
(Date.now() - Date.parse(storedTokens.expiresAt.toString())) / SECOND;

Here, the expiresIn should be calculated by the current time subtracted from the expiresAt time

Ah, yes, I think you're right. Thanks for picking up on these. I'll submit fixes as soon as I can. Great work. Thank you.

I'll do a full review of the codebase to ensure there aren't any more of these silly mistakes. Thanks for pointing them out.

Ah, yes, I think you're right. Thanks for picking up on these. I'll submit fixes as soon as I can. Great work. Thank you.

I can get a PR up soon with fixes for the things mentioned in this issue soon so you can focus on other things 👍

Great! Thank you. I believe these issues deserve 2 PRs. Please be sure to provide an explanation of the issues too 🙂

PRs are up!