Skip to content

Commit e8a7934

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

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
@@ -959,6 +959,13 @@ describe('Google service-account token minting', () => {
959959
}
960960
const driveScope = 'https://www.googleapis.com/auth/drive.readonly'
961961
const now = new Date('2026-09-09T18:00:00.000Z')
962+
const decryptedKey = {
963+
decrypted: JSON.stringify({
964+
client_email: 'crawler@qa-project.iam.gserviceaccount.com',
965+
private_key: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
966+
token_uri: 'https://oauth2.googleapis.com/token',
967+
}),
968+
}
962969

963970
beforeEach(async () => {
964971
const { coalesceLocally } = await vi.importActual<
@@ -970,13 +977,7 @@ describe('Google service-account token minting', () => {
970977
vi.setSystemTime(now)
971978
vi.stubGlobal('fetch', fetchMock)
972979
fetchMock.mockImplementation(async () => Response.json({ access_token: 'google-access-token' }))
973-
mocks.decryptSecret.mockResolvedValue({
974-
decrypted: JSON.stringify({
975-
client_email: 'crawler@qa-project.iam.gserviceaccount.com',
976-
private_key: privateKey.export({ type: 'pkcs8', format: 'pem' }).toString(),
977-
token_uri: 'https://oauth2.googleapis.com/token',
978-
}),
979-
})
980+
mocks.decryptSecret.mockResolvedValue(decryptedKey)
980981
})
981982

982983
afterEach(() => {
@@ -1076,6 +1077,24 @@ describe('Google service-account token minting', () => {
10761077
expect(fetchMock).toHaveBeenCalledTimes(6)
10771078
})
10781079

1080+
it('stops a cancelled mint waiting on a shared key read while the others finish', async () => {
1081+
queueTableRows(credential, [row])
1082+
const decrypted = createDeferred<{ decrypted: string }>()
1083+
mocks.decryptSecret.mockReturnValue(decrypted.promise)
1084+
const controller = new AbortController()
1085+
const reason = new DOMException('Caller cancelled', 'AbortError')
1086+
const concurrent = getServiceAccountToken('credential-1', [driveScope], 'a@example.com')
1087+
const cancelled = getServiceAccountToken('credential-1', [driveScope], 'b@example.com', {
1088+
signal: controller.signal,
1089+
})
1090+
await vi.waitFor(() => expect(mocks.decryptSecret).toHaveBeenCalledOnce())
1091+
controller.abort(reason)
1092+
await expect(cancelled).rejects.toBe(reason)
1093+
decrypted.resolve(decryptedKey)
1094+
await expect(concurrent).resolves.toBe('google-access-token')
1095+
expect(fetchMock).toHaveBeenCalledOnce()
1096+
})
1097+
10791098
it('preserves cancellation through the service-account resolver registry', async () => {
10801099
const controller = new AbortController()
10811100
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)