Skip to content

Commit 8fb0c9a

Browse files
committed
fix(search): add an Atlassian site to the login that already holds it
Atlassian keeps one grant per login and adds each consented site to it, so connecting the same login for another Confluence or Jira site was refused as an already connected account. Treat it as a refresh of that account, which keeps the new grant; every other provider still refuses a duplicate.
1 parent d644eb9 commit 8fb0c9a

5 files changed

Lines changed: 41 additions & 3 deletions

File tree

‎apps/docs/content/docs/search/connect-your-account.mdx‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ After your admin adds a source in **Settings → Sources**, connect your own acc
2525
To connect another account of the same app, such as Google Drive for two different work accounts, open the app and choose **Add account**, then pick the other account when the provider asks. You can connect up to five accounts per app, and Search looks across all of them. Open an account to see its tool permissions when the app supports them; each account's menu has **Reconnect** and **Disconnect**.
2626

2727
- Slack accounts must belong to your organization's connected workspace.
28+
- For another Confluence or Jira site on the same Atlassian login, choose **Add account** or **Reconnect** and pick the site. The site joins your existing account, and Search covers every site that login can reach.
2829
- Zoom, Lucid, and Databricks allow one account, because Sim cannot yet tell their accounts apart.
2930
- If the provider signs you straight back into a connected account, sign in to the other account with the provider first, then choose **Add account** again.
3031

‎apps/sim/lib/credential-groups/__integration__/multiple-provider-accounts.integration.ts‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ const optionFor = (provider: CredentialGroupProvider) => ({
9393
const DRIVE = optionFor('google-drive')
9494
const GITHUB = optionFor('github-repositories')
9595
const SLACK = optionFor('slack')
96+
const CONFLUENCE = optionFor('confluence')
9697

9798
function connect(
9899
code: string,
@@ -110,7 +111,7 @@ function connect(
110111
email,
111112
enrollmentStatus: 'completed',
112113
option,
113-
options: [DRIVE, GITHUB, SLACK],
114+
options: [DRIVE, GITHUB, SLACK, CONFLUENCE],
114115
}
115116
const attempt: CredentialGroupOAuthAttempt = {
116117
userId,
@@ -188,7 +189,7 @@ beforeAll(async () => {
188189
organizationId,
189190
publicId: generateId(),
190191
name: 'Connected accounts',
191-
options: [DRIVE, GITHUB, SLACK],
192+
options: [DRIVE, GITHUB, SLACK, CONFLUENCE],
192193
})
193194
await db.insert(member).values({
194195
id: generateId(),
@@ -337,6 +338,26 @@ describe('reconnecting one of several accounts', () => {
337338
})
338339
})
339340

341+
describe('adding another site to an Atlassian login', () => {
342+
it('keeps the new grant on the existing account instead of refusing it', async () => {
343+
const first = await connect('person@atlassian', { kind: 'create' }, CONFLUENCE)
344+
const [before] = await rows()
345+
346+
const added = await connect('person@atlassian', { kind: 'create' }, CONFLUENCE)
347+
348+
expect(added).toMatchObject({ created: false, credentialId: first.credentialId })
349+
const after = await rows()
350+
expect(after).toHaveLength(1)
351+
expect(after[0]?.tokens).not.toBe(before?.tokens)
352+
})
353+
354+
it('still adds a different Atlassian login as its own account', async () => {
355+
await connect('person@atlassian', { kind: 'create' }, CONFLUENCE)
356+
await connect('other@atlassian', { kind: 'create' }, CONFLUENCE)
357+
expect(await rows()).toHaveLength(2)
358+
})
359+
})
360+
340361
describe('adding an account that needs reconnecting', () => {
341362
it('refreshes it instead of calling it already connected', async () => {
342363
const first = await connect('work@first-tenant.test', { kind: 'create' })

‎apps/sim/lib/credential-groups/account-rules.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ export function resolveAccountWrite<Account extends ConnectedAccount>({
3434
limit,
3535
accounts,
3636
identity,
37+
grantSpansSites = false,
3738
}: {
3839
intent?: CredentialGroupConnectionIntent
3940
limit: number
41+
/** Connecting the same login again extends its grant, so it refreshes that account. */
42+
grantSpansSites?: boolean
4043
accounts: Account[]
4144
identity: { subject: string; tenant: string | null } | null
4245
}): Account | undefined {
@@ -75,7 +78,7 @@ export function resolveAccountWrite<Account extends ConnectedAccount>({
7578

7679
if (intent?.kind === 'create') {
7780
if (existing && !existing.revokedAt) {
78-
if (existing.managedOauthStatus === 'active')
81+
if (existing.managedOauthStatus === 'active' && !grantSpansSites)
7982
throw accountConflict('account_already_connected')
8083
return existing
8184
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { getCredentialGroupProviderAdapter } from '@/lib/credential-groups/provider-registry'
3636
import {
3737
type CredentialGroupProvider,
38+
credentialGroupGrantSpansSites,
3839
getCredentialGroupProviderService,
3940
isCredentialGroupProvider,
4041
} from '@/lib/credential-groups/providers'
@@ -247,6 +248,7 @@ async function persistGrant(
247248
limit: CREDENTIAL_GROUP_ACCOUNTS_PER_PROVIDER_LIMIT,
248249
accounts,
249250
identity: { subject: grant.providerSubjectId, tenant: grant.providerTenantId },
251+
grantSpansSites: credentialGroupGrantSpansSites(adapter.provider),
250252
})
251253

252254
let refreshToken = grant.refreshToken

‎apps/sim/lib/credential-groups/providers.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ interface CredentialGroupProviderSupport {
5252
serviceId: string
5353
description: string
5454
configuration: 'oauth' | 'slack_custom_bot'
55+
/**
56+
* One login holds a single grant that gains each site it consents to, so connecting that login
57+
* again adds a site to the existing account rather than a second account.
58+
*/
59+
grantSpansSites?: true
5560
}
5661

5762
const CREDENTIAL_GROUP_PROVIDER_SUPPORT: Record<
@@ -127,11 +132,13 @@ const CREDENTIAL_GROUP_PROVIDER_SUPPORT: Record<
127132
serviceId: 'confluence',
128133
description: 'Let each person connect one Confluence account',
129134
configuration: 'oauth',
135+
grantSpansSites: true,
130136
},
131137
jira: {
132138
serviceId: 'jira',
133139
description: 'Let each person connect one Jira account',
134140
configuration: 'oauth',
141+
grantSpansSites: true,
135142
},
136143
airtable: {
137144
serviceId: 'airtable',
@@ -258,6 +265,10 @@ export function getCredentialGroupProviderService(
258265
return service
259266
}
260267

268+
export function credentialGroupGrantSpansSites(provider: CredentialGroupProvider): boolean {
269+
return CREDENTIAL_GROUP_PROVIDER_SUPPORT[provider].grantSpansSites === true
270+
}
271+
261272
export function getCredentialGroupProviderId(provider: CredentialGroupProvider): string {
262273
return getCredentialGroupProviderService(provider).providerId
263274
}

0 commit comments

Comments
 (0)