Skip to content

Commit 905e9fd

Browse files
committed
fix(oauth): let a cancelled service-account mint stop waiting on a shared key read
1 parent 1111629 commit 905e9fd

2 files changed

Lines changed: 27 additions & 8 deletions

File tree

‎apps/sim/lib/oauth/credential-service.test.ts‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,13 @@ describe('Google service-account token minting', () => {
899899
}
900900
const driveScope = 'https://www.googleapis.com/auth/drive.readonly'
901901
const now = new Date('2026-09-09T18:00:00.000Z')
902+
const decryptedKey = {
903+
decrypted: JSON.stringify({
904+
client_email: 'crawler@qa-project.iam.gserviceaccount.com',
905+
private_key: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
906+
token_uri: 'https://oauth2.googleapis.com/token',
907+
}),
908+
}
902909

903910
beforeEach(async () => {
904911
const { coalesceLocally } = await vi.importActual<
@@ -910,13 +917,7 @@ describe('Google service-account token minting', () => {
910917
vi.setSystemTime(now)
911918
vi.stubGlobal('fetch', fetchMock)
912919
fetchMock.mockImplementation(async () => Response.json({ access_token: 'google-access-token' }))
913-
mocks.decryptSecret.mockResolvedValue({
914-
decrypted: JSON.stringify({
915-
client_email: 'crawler@qa-project.iam.gserviceaccount.com',
916-
private_key: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
917-
token_uri: 'https://oauth2.googleapis.com/token',
918-
}),
919-
})
920+
mocks.decryptSecret.mockResolvedValue(decryptedKey)
920921
})
921922

922923
afterEach(() => {
@@ -1016,6 +1017,24 @@ describe('Google service-account token minting', () => {
10161017
expect(fetchMock).toHaveBeenCalledTimes(6)
10171018
})
10181019

1020+
it('stops a cancelled mint waiting on a shared key read while the others finish', async () => {
1021+
queueTableRows(credential, [row])
1022+
const decrypted = createDeferred<{ decrypted: string }>()
1023+
mocks.decryptSecret.mockReturnValue(decrypted.promise)
1024+
const controller = new AbortController()
1025+
const reason = new DOMException('Caller cancelled', 'AbortError')
1026+
const concurrent = getServiceAccountToken('credential-1', [driveScope], 'a@example.com')
1027+
const cancelled = getServiceAccountToken('credential-1', [driveScope], 'b@example.com', {
1028+
signal: controller.signal,
1029+
})
1030+
await vi.waitFor(() => expect(mocks.decryptSecret).toHaveBeenCalledOnce())
1031+
controller.abort(reason)
1032+
await expect(cancelled).rejects.toBe(reason)
1033+
decrypted.resolve(decryptedKey)
1034+
await expect(concurrent).resolves.toBe('google-access-token')
1035+
expect(fetchMock).toHaveBeenCalledOnce()
1036+
})
1037+
10191038
it('preserves cancellation through the service-account resolver registry', async () => {
10201039
const controller = new AbortController()
10211040
const reason = new DOMException('Caller cancelled', 'AbortError')

‎apps/sim/lib/oauth/credential-service.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ export async function getServiceAccountToken(
273273
options?: CredentialTokenResolutionOptions
274274
): Promise<string> {
275275
options?.signal?.throwIfAborted()
276-
const decrypted = await readGoogleServiceAccountKey(credentialId)
276+
const decrypted = await waitWithAbort(readGoogleServiceAccountKey(credentialId), options?.signal)
277277
const keyData = JSON.parse(decrypted) as {
278278
client_email: string
279279
private_key: string

0 commit comments

Comments
 (0)