mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(desktop): restore native OAuth tokens after restart
This commit is contained in:
parent
539e9b5c1b
commit
df1f825ce7
3 changed files with 56 additions and 2 deletions
|
|
@ -145,6 +145,7 @@ import {
|
|||
import {
|
||||
nativeRefreshUrl,
|
||||
type NativeTokenSet,
|
||||
parseStoredTokenSet,
|
||||
parseTokenResponse,
|
||||
resolveLoginStrategy,
|
||||
tokenNeedsRefresh
|
||||
|
|
@ -6276,11 +6277,15 @@ function _loadNativeTokens(baseUrl: string): NativeTokenSet | null {
|
|||
return null
|
||||
}
|
||||
|
||||
const tokens = parseTokenResponse(JSON.parse(plaintext))
|
||||
const tokens = parseStoredTokenSet(JSON.parse(plaintext))
|
||||
_nativeTokens.set(baseUrl, tokens)
|
||||
|
||||
return tokens
|
||||
} catch {
|
||||
} catch (error) {
|
||||
rememberLog(
|
||||
`[native-oauth] failed to load stored tokens for ${baseUrl}: ${(error as Error).message}`
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import {
|
|||
nativeRefreshUrl,
|
||||
nativeTokenUrl,
|
||||
parseLoopbackCallback,
|
||||
parseStoredTokenSet,
|
||||
parseTokenResponse,
|
||||
resolveLoginStrategy,
|
||||
statusSupportsNativeFlow,
|
||||
|
|
@ -176,6 +177,29 @@ test('parseTokenResponse tolerates an absent refresh token / expiry', () => {
|
|||
assert.equal(t.expiresAt, 0)
|
||||
})
|
||||
|
||||
test('parseStoredTokenSet maps the encrypted on-disk camelCase shape', () => {
|
||||
const t = parseStoredTokenSet({
|
||||
accessToken: 'AT-stored',
|
||||
refreshToken: 'RT-stored',
|
||||
expiresAt: 1893456000,
|
||||
provider: 'self-hosted',
|
||||
userId: 'u-stored'
|
||||
})
|
||||
|
||||
assert.equal(t.accessToken, 'AT-stored')
|
||||
assert.equal(t.refreshToken, 'RT-stored')
|
||||
assert.equal(t.expiresAt, 1893456000)
|
||||
assert.equal(t.provider, 'self-hosted')
|
||||
assert.equal(t.userId, 'u-stored')
|
||||
})
|
||||
|
||||
test('parseStoredTokenSet rejects a non-normalized server response', () => {
|
||||
assert.throws(
|
||||
() => parseStoredTokenSet({ access_token: 'AT-server' }),
|
||||
/missing accessToken/i
|
||||
)
|
||||
})
|
||||
|
||||
// --- refresh timing ---
|
||||
|
||||
test('tokenNeedsRefresh respects the skew window', () => {
|
||||
|
|
|
|||
|
|
@ -193,6 +193,31 @@ export function parseTokenResponse(body: any): NativeTokenSet {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a token set loaded from the encrypted local store.
|
||||
*
|
||||
* The stored representation is already normalized as NativeTokenSet and
|
||||
* therefore uses camelCase. Gateway token responses use snake_case and
|
||||
* remain handled separately by parseTokenResponse().
|
||||
*/
|
||||
export function parseStoredTokenSet(body: any): NativeTokenSet {
|
||||
const accessToken = String(body?.accessToken || '')
|
||||
|
||||
if (!accessToken) {
|
||||
throw new Error('Stored token set missing accessToken')
|
||||
}
|
||||
|
||||
const expiresAt = Number(body?.expiresAt)
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
refreshToken: String(body?.refreshToken || ''),
|
||||
expiresAt: Number.isFinite(expiresAt) ? expiresAt : 0,
|
||||
provider: String(body?.provider || ''),
|
||||
userId: String(body?.userId || '')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* True when a stored token set is at/near expiry and should be refreshed
|
||||
* before use. `skewSeconds` refreshes slightly early to avoid a race where
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue