Skip to content

Commit 65c07e0

Browse files
authored
fix(sso): preserve refresh tokens on service-faults #2925
Problem: Eager cache eviction drops refresh tokens on potentially recoverable faults. Solution: Don't eagerly evict tokens.
1 parent 1a783fe commit 65c07e0

File tree

2 files changed

+45
-9
lines changed

2 files changed

+45
-9
lines changed

src/credentials/sso/ssoAccessTokenProvider.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import globals from '../../shared/extensionGlobals'
88
import { SSOOIDCServiceException } from '@aws-sdk/client-sso-oidc'
99
import { openSsoPortalLink, SsoToken, ClientRegistration, isExpired, SsoProfile } from './model'
1010
import { getCache } from './cache'
11-
import { hasProps, selectFrom } from '../../shared/utilities/tsUtils'
11+
import { hasProps, RequiredProps, selectFrom } from '../../shared/utilities/tsUtils'
1212
import { CancellationError } from '../../shared/utilities/timeoutUtils'
1313
import { OidcClient } from './clients'
1414
import { loadOr } from '../../shared/utilities/cacheUtils'
@@ -72,16 +72,16 @@ export class SsoAccessTokenProvider {
7272
return data?.token
7373
}
7474

75-
await this.invalidate()
76-
77-
if (data.registration && !isExpired(data.registration)) {
75+
if (data.registration && !isExpired(data.registration) && hasProps(data.token, 'refreshToken')) {
7876
const refreshed = await this.refreshToken(data.token, data.registration)
7977

8078
if (refreshed) {
8179
await this.cache.token.save(this.tokenCacheKey, refreshed)
8280
}
8381

8482
return refreshed?.token
83+
} else {
84+
await this.invalidate()
8585
}
8686
}
8787

@@ -99,21 +99,27 @@ export class SsoAccessTokenProvider {
9999

100100
try {
101101
return await this.authorize(registration)
102-
} catch (error) {
103-
if (error instanceof SSOOIDCServiceException && isClientFault(error)) {
102+
} catch (err) {
103+
if (err instanceof SSOOIDCServiceException && isClientFault(err)) {
104104
this.cache.registration.clear(cacheKey)
105105
}
106106

107-
throw error
107+
throw err
108108
}
109109
}
110110

111-
private async refreshToken(token: SsoToken, registration: ClientRegistration) {
112-
if (hasProps(token, 'refreshToken')) {
111+
private async refreshToken(token: RequiredProps<SsoToken, 'refreshToken'>, registration: ClientRegistration) {
112+
try {
113113
const clientInfo = selectFrom(registration, 'clientId', 'clientSecret')
114114
const response = await this.oidc.createToken({ ...clientInfo, ...token, grantType: REFRESH_GRANT_TYPE })
115115

116116
return this.formatToken(response, registration)
117+
} catch (err) {
118+
if (err instanceof SSOOIDCServiceException && isClientFault(err)) {
119+
this.cache.token.clear(this.tokenCacheKey)
120+
}
121+
122+
throw err
117123
}
118124
}
119125

src/test/credentials/sso/ssoAccessTokenProvider.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,36 @@ describe('SsoAccessTokenProvider', function () {
124124
const cachedToken = await cache.token.load(startUrl).then(a => a?.token)
125125
assert.strictEqual(cachedToken, undefined)
126126
})
127+
128+
describe('Exceptions', function () {
129+
it('drops expired tokens if failure was a client-fault', async function () {
130+
const exception = new UnauthorizedClientException({ message: '', $metadata: {} })
131+
when(oidcClient.createToken(anything())).thenReject(exception)
132+
133+
const refreshableToken = createToken(-HOUR_IN_MS, { refreshToken: 'refreshToken' })
134+
const validRegistation = createRegistration(HOUR_IN_MS)
135+
const access = { region, startUrl, token: refreshableToken, registration: validRegistation }
136+
await cache.token.save(startUrl, access)
137+
await assert.rejects(sut.getToken())
138+
139+
const cachedToken = await cache.token.load(startUrl)
140+
assert.strictEqual(cachedToken, undefined)
141+
})
142+
143+
it('preserves expired tokens if failure was not a client-fault', async function () {
144+
const exception = new InternalServerException({ message: '', $metadata: {} })
145+
when(oidcClient.createToken(anything())).thenReject(exception)
146+
147+
const refreshableToken = createToken(-HOUR_IN_MS, { refreshToken: 'refreshToken' })
148+
const validRegistation = createRegistration(HOUR_IN_MS)
149+
const access = { region, startUrl, token: refreshableToken, registration: validRegistation }
150+
await cache.token.save(startUrl, access)
151+
await assert.rejects(sut.getToken())
152+
153+
const cachedToken = await cache.token.load(startUrl).then(a => a?.token)
154+
assert.deepStrictEqual(cachedToken, refreshableToken)
155+
})
156+
})
127157
})
128158

129159
describe('createToken', function () {

0 commit comments

Comments
 (0)