Skip to content

Commit 1bc27a7

Browse files
committed
fix(search): deny revoked Atlassian grants and bound provider requests
1 parent 987eb2b commit 1bc27a7

6 files changed

Lines changed: 596 additions & 48 deletions

File tree

‎apps/sim/lib/auth/connectors/managed-oauth.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,19 @@ describe('Atlassian managed OAuth connector', () => {
115115
expect(jira.isTerminalRefreshError('invalid_grant')).toBe(true)
116116
expect(jira.isTerminalRefreshError('temporarily_unavailable')).toBe(false)
117117
})
118+
119+
it.each(['jira', 'confluence'] as const)(
120+
'requires reconnect after %s rejects a revoked refresh grant',
121+
(provider) => {
122+
const connector = createAtlassianManagedOAuthConnector(provider)
123+
expect(connector.isTerminalRefreshError('invalid_grant')).toBe(true)
124+
expect(connector.isTerminalRefreshError('unauthorized_client')).toBe(true)
125+
expect(connector.isTerminalRefreshError('invalid_client')).toBe(false)
126+
expect(connector.isTerminalRefreshError('temporarily_unavailable')).toBe(false)
127+
expect(connector.isTerminalRefreshError('server_error')).toBe(false)
128+
expect(connector.isTerminalRefreshError(undefined)).toBe(false)
129+
}
130+
)
118131
})
119132

120133
describe('userinfo-backed managed OAuth connectors', () => {

‎apps/sim/lib/auth/connectors/managed-oauth.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ export function createAtlassianManagedOAuthConnector(
239239
return requiredScopes.every((scope) => granted.has(scope))
240240
},
241241
isTerminalRefreshError(errorCode) {
242-
return errorCode === 'invalid_grant'
242+
return errorCode === 'invalid_grant' || errorCode === 'unauthorized_client'
243243
},
244244
}
245245
}

‎apps/sim/lib/knowledge/__integration__/jira-member.integration.ts‎

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ describe('Jira member indexing and authorization in PostgreSQL', () => {
9999
const failures = new Map<string, 400 | 401>()
100100
const requests: { token: string; page: string | null }[] = []
101101
const refreshAttempts: string[] = []
102+
let refreshError: 'invalid_grant' | 'unauthorized_client' = 'invalid_grant'
102103
let billing: Awaited<ReturnType<typeof resolveBillingAttribution>>
103104
const actor = (userId: string): Principal => ({
104105
kind: 'session',
@@ -126,10 +127,7 @@ describe('Jira member indexing and authorization in PostgreSQL', () => {
126127
if (!person || failures.get(person.accessToken) !== 401)
127128
throw new Error('Unexpected Jira fixture token refresh')
128129
refreshAttempts.push(person.userId)
129-
return Response.json(
130-
{ error: 'invalid_grant', error_description: 'Unknown or invalid refresh token.' },
131-
{ status: 403 }
132-
)
130+
return Response.json({ error: refreshError }, { status: 403 })
133131
}
134132
const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : {}))
135133
const token = headers.get('Authorization')?.replace(/^Bearer /, '') ?? ''
@@ -262,6 +260,7 @@ describe('Jira member indexing and authorization in PostgreSQL', () => {
262260
})
263261

264262
beforeEach(async () => {
263+
refreshError = 'invalid_grant'
265264
failures.clear()
266265
requests.length = 0
267266
refreshAttempts.length = 0
@@ -526,20 +525,27 @@ describe('Jira member indexing and authorization in PostgreSQL', () => {
526525
await assertAccess(alice, ['1', '2'])
527526
})
528527

529-
it('marks a provider-rejected credential for reconnect and immediately hides that member corpus', async () => {
530-
failures.set(alice.accessToken, 401)
531-
const result = await sync()
532-
expect(result.membersFailed).toBe(1)
533-
expect(refreshAttempts).toEqual([alice.userId])
534-
const [saved] = await db.select().from(credential).where(eq(credential.id, alice.credentialId))
535-
expect(saved.managedOauthStatus).toBe('needs_reauth')
536-
await assertAccess(alice, [])
537-
await assertAccess(bob, ['1', '3'])
538-
expect(
539-
await db
528+
it.each(['invalid_grant', 'unauthorized_client'] as const)(
529+
'marks a %s refresh rejection for reconnect and immediately hides that member corpus',
530+
async (errorCode) => {
531+
refreshError = errorCode
532+
failures.set(alice.accessToken, 401)
533+
const result = await sync()
534+
expect(result.membersFailed).toBe(1)
535+
expect(refreshAttempts).toEqual([alice.userId])
536+
const [saved] = await db
540537
.select()
541-
.from(document)
542-
.where(and(eq(document.connectorId, connectorId), isNull(document.deletedAt)))
543-
).toHaveLength(4)
544-
})
538+
.from(credential)
539+
.where(eq(credential.id, alice.credentialId))
540+
expect(saved.managedOauthStatus).toBe('needs_reauth')
541+
await assertAccess(alice, [])
542+
await assertAccess(bob, ['1', '3'])
543+
expect(
544+
await db
545+
.select()
546+
.from(document)
547+
.where(and(eq(document.connectorId, connectorId), isNull(document.deletedAt)))
548+
).toHaveLength(4)
549+
}
550+
)
545551
})

0 commit comments

Comments
 (0)