Selaa lähdekoodia

fix(account): refresh console tokens before expiry (#20558)

Kit Langton 5 kuukautta sitten
vanhempi
commit
00d6841f84

+ 7 - 2
packages/opencode/src/account/index.ts

@@ -119,6 +119,7 @@ class TokenRefreshRequest extends Schema.Class<TokenRefreshRequest>("TokenRefres
 }) {}
 
 const clientId = "opencode-cli"
+const eagerRefreshThreshold = Duration.minutes(5)
 
 const mapAccountServiceError =
   (message = "Account service operation failed") =>
@@ -218,7 +219,9 @@ export namespace Account {
 
           const account = maybeAccount.value
           const now = yield* Clock.currentTimeMillis
-          if (account.token_expiry && account.token_expiry > now) return account.access_token
+          if (account.token_expiry && account.token_expiry > now + Duration.toMillis(eagerRefreshThreshold)) {
+            return account.access_token
+          }
 
           return yield* refreshToken(account)
         }),
@@ -226,7 +229,9 @@ export namespace Account {
 
       const resolveToken = Effect.fnUntraced(function* (row: AccountRow) {
         const now = yield* Clock.currentTimeMillis
-        if (row.token_expiry && row.token_expiry > now) return row.access_token
+        if (row.token_expiry && row.token_expiry > now + Duration.toMillis(eagerRefreshThreshold)) {
+          return row.access_token
+        }
 
         return yield* Cache.get(refreshTokenCache, row.id)
       })

+ 47 - 3
packages/opencode/test/account/service.test.ts

@@ -63,7 +63,7 @@ it.live("orgsByAccount groups orgs per account", () =>
         url: "https://one.example.com",
         accessToken: AccessToken.make("at_1"),
         refreshToken: RefreshToken.make("rt_1"),
-        expiry: Date.now() + 60_000,
+        expiry: Date.now() + 10 * 60_000,
         orgID: Option.none(),
       }),
     )
@@ -75,7 +75,7 @@ it.live("orgsByAccount groups orgs per account", () =>
         url: "https://two.example.com",
         accessToken: AccessToken.make("at_2"),
         refreshToken: RefreshToken.make("rt_2"),
-        expiry: Date.now() + 60_000,
+        expiry: Date.now() + 10 * 60_000,
         orgID: Option.none(),
       }),
     )
@@ -148,6 +148,50 @@ it.live("token refresh persists the new token", () =>
   }),
 )
 
+it.live("token refreshes before expiry when inside the eager refresh window", () =>
+  Effect.gen(function* () {
+    const id = AccountID.make("user-1")
+
+    yield* AccountRepo.use((r) =>
+      r.persistAccount({
+        id,
+        email: "user@example.com",
+        url: "https://one.example.com",
+        accessToken: AccessToken.make("at_old"),
+        refreshToken: RefreshToken.make("rt_old"),
+        expiry: Date.now() + 60_000,
+        orgID: Option.none(),
+      }),
+    )
+
+    let refreshCalls = 0
+    const client = HttpClient.make((req) =>
+      Effect.promise(async () => {
+        if (req.url === "https://one.example.com/auth/device/token") {
+          refreshCalls += 1
+          return json(req, {
+            access_token: "at_new",
+            refresh_token: "rt_new",
+            expires_in: 60,
+          })
+        }
+
+        return json(req, {}, 404)
+      }),
+    )
+
+    const token = yield* Account.Service.use((s) => s.token(id)).pipe(Effect.provide(live(client)))
+
+    expect(String(Option.getOrThrow(token))).toBe("at_new")
+    expect(refreshCalls).toBe(1)
+
+    const row = yield* AccountRepo.use((r) => r.getRow(id))
+    const value = Option.getOrThrow(row)
+    expect(value.access_token).toBe(AccessToken.make("at_new"))
+    expect(value.refresh_token).toBe(RefreshToken.make("rt_new"))
+  }),
+)
+
 it.live("concurrent config and token requests coalesce token refresh", () =>
   Effect.gen(function* () {
     const id = AccountID.make("user-1")
@@ -223,7 +267,7 @@ it.live("config sends the selected org header", () =>
         url: "https://one.example.com",
         accessToken: AccessToken.make("at_1"),
         refreshToken: RefreshToken.make("rt_1"),
-        expiry: Date.now() + 60_000,
+        expiry: Date.now() + 10 * 60_000,
         orgID: Option.none(),
       }),
     )