fix(github): restore the recovery path for revoked user OAuth tokens (#10995)

* fix(github): honour the force flag in checkRefreshToken

`checkRefreshToken` accepts a `force` parameter but never reads it, so the
only caller that passes `force = true` (worker.ts, when re-syncing a user)
behaves exactly like the non-forced call and skips the refresh entirely.

Gate the refresh on `force || expired` instead of on expiry alone. As a
side effect a record with a `refreshToken` but a null `expiresIn` is now
validated when forced, instead of being reported as valid unchecked.

Signed-off-by: koreanjoker <namug014@gmail.com>

* fix(github): return undefined from getOctokit when the token is dead

When `checkRefreshToken` reports failure, `getOctokit` cleared
`record.octokit` and then fell straight through to constructing a new
Octokit from the very token that was just rejected. The method therefore
never returned `undefined` for a revoked user, so the
`(await getOctokit(...)) ?? container.container.octokit` installation-token
fallback that the sync code already writes at 13 call sites was
unreachable.

Return `undefined` after clearing the client so the existing fallback can
take effect.

Signed-off-by: koreanjoker <namug014@gmail.com>

* fix(github): preserve accounts when deserialising a user secret

`updateUser` serialises the whole `GithubUserRecord` — `accounts`
included — into the integration secret, but `secretToUserRecord` placed a
literal `accounts: {}` after the spread of the parsed payload, discarding
whatever was stored.

Every consumer of a record loaded through `getAccount` therefore saw an
empty map. `revokeUserAuth` iterates `Object.entries(record.accounts)`, so
its body never ran and the re-authorisation notice was never written to
any workspace.

Read `accounts` back from the parsed payload, keeping `{}` as the fallback
for records written before the field existed.

Signed-off-by: koreanjoker <namug014@gmail.com>

---------

Signed-off-by: koreanjoker <namug014@gmail.com>
This commit is contained in:
Why_So_Serious
2026-07-27 00:11:38 +07:00
committed by GitHub
parent c36dd74fef
commit e34f546b8c
3 changed files with 6 additions and 3 deletions
+2 -1
View File
@@ -738,7 +738,8 @@ export class PlatformWorker {
}
async checkRefreshToken (ctx: MeasureContext, auth: GithubUserRecord, force: boolean = false): Promise<boolean> {
if (auth.refreshToken != null && auth.expiresIn != null && auth.expiresIn < Date.now() / 1000) {
const expired = auth.expiresIn != null && auth.expiresIn < Date.now() / 1000
if (auth.refreshToken != null && (force || expired)) {
const uri =
'https://github.com/login/oauth/access_token?' +
makeQuery({
+3 -2
View File
@@ -36,11 +36,12 @@ export class UserManager {
}
private secretToUserRecord (secret: IntegrationSecret, login: string): GithubUserRecord | undefined {
const parsed = JSON.parse(secret.secret) ?? {} // TODO: Add security
return {
...(JSON.parse(secret.secret) ?? {}), // TODO: Add security
...parsed,
account: secret.socialId,
_id: login,
accounts: {}
accounts: parsed.accounts ?? {}
}
}
+1
View File
@@ -643,6 +643,7 @@ export class GithubWorker implements IntegrationManager {
ctx.info('get octokit', { account, recordId: record._id, workspace: this.workspace.uuid })
if (!(await this.platform.checkRefreshToken(ctx, record))) {
record.octokit = undefined
return undefined
}
if (record.octokit !== undefined) {
return record.octokit