Skip to content

Commit f9975ba

Browse files
committed
fix(credentials): restore reconnect params on shared createConnectDraft
1 parent 0ab67ba commit f9975ba

1 file changed

Lines changed: 59 additions & 15 deletions

File tree

Lines changed: 59 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { db } from '@sim/db'
2-
import { pendingCredentialDraft, user } from '@sim/db/schema'
2+
import { credential, pendingCredentialDraft, user } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { generateId } from '@sim/utils/id'
55
import { and, eq, lt } from 'drizzle-orm'
6+
import { defaultCredentialDisplayName } from '@/lib/credentials/display-name'
67
import { getAllOAuthServices } from '@/lib/oauth/utils'
78

89
const logger = createLogger('OAuthConnectDraft')
@@ -16,29 +17,63 @@ export async function createConnectDraft(params: {
1617
userId: string
1718
workspaceId: string
1819
providerId: string
20+
/** Reconnect only: the existing credential the callback should rebind instead of creating a new one. */
21+
credentialId?: string
22+
/** Reconnect only: the credential's actual name, so audit records stay accurate. */
23+
displayName?: string
1924
}): Promise<void> {
20-
const { userId, workspaceId, providerId } = params
21-
const service = getAllOAuthServices().find((candidate) => candidate.providerId === providerId)
22-
23-
let displayName = service?.name ?? providerId
24-
try {
25-
const [row] = await db.select({ name: user.name }).from(user).where(eq(user.id, userId))
26-
if (row?.name) {
27-
displayName = `${row.name}'s ${displayName}`
25+
const { userId, workspaceId, providerId, credentialId } = params
26+
27+
let displayName = params.displayName
28+
if (!displayName) {
29+
const service = getAllOAuthServices().find((s) => s.providerId === providerId)
30+
const serviceName = service?.name ?? providerId
31+
32+
let userName: string | null = null
33+
try {
34+
const [row] = await db.select({ name: user.name }).from(user).where(eq(user.id, userId))
35+
userName = row?.name ?? null
36+
} catch (error) {
37+
// Cosmetic only — fall back to the "My {Service}" default
38+
logger.warn('User name lookup failed for connect draft display name', {
39+
userId,
40+
workspaceId,
41+
providerId,
42+
error,
43+
})
44+
}
45+
46+
// Auto-number against existing workspace credentials so repeat connects for
47+
// the same provider stay distinguishable — same behavior as the connect
48+
// modal, which computes this client-side. Best effort: on failure the name
49+
// simply skips deduplication.
50+
let takenNames: ReadonlySet<string> = new Set<string>()
51+
try {
52+
const rows = await db
53+
.select({ displayName: credential.displayName })
54+
.from(credential)
55+
.where(and(eq(credential.workspaceId, workspaceId), eq(credential.type, 'oauth')))
56+
takenNames = new Set(rows.map((row) => row.displayName.toLowerCase()))
57+
} catch (error) {
58+
// Cosmetic only — proceed without collision numbering
59+
logger.warn('Credential name lookup failed for connect draft deduplication', {
60+
userId,
61+
workspaceId,
62+
providerId,
63+
error,
64+
})
2865
}
29-
} catch {
30-
// Fall back to the service name.
66+
67+
displayName = defaultCredentialDisplayName(userName, serviceName, takenNames)
3168
}
3269

3370
const now = new Date()
3471
const expiresAt = new Date(now.getTime() + DRAFT_TTL_MS)
35-
3672
await db
3773
.delete(pendingCredentialDraft)
3874
.where(
3975
and(eq(pendingCredentialDraft.userId, userId), lt(pendingCredentialDraft.expiresAt, now))
4076
)
41-
4277
await db
4378
.insert(pendingCredentialDraft)
4479
.values({
@@ -47,6 +82,7 @@ export async function createConnectDraft(params: {
4782
workspaceId,
4883
providerId,
4984
displayName,
85+
credentialId: credentialId ?? null,
5086
expiresAt,
5187
createdAt: now,
5288
})
@@ -56,8 +92,16 @@ export async function createConnectDraft(params: {
5692
pendingCredentialDraft.providerId,
5793
pendingCredentialDraft.workspaceId,
5894
],
59-
set: { displayName, expiresAt, createdAt: now },
95+
// credentialId must be written on BOTH paths: a plain connect that reuses a
96+
// stale reconnect draft row would otherwise silently rebind the old
97+
// credential instead of creating a new one.
98+
set: { displayName, credentialId: credentialId ?? null, expiresAt, createdAt: now },
6099
})
61100

62-
logger.info('Created OAuth connect credential draft', { userId, workspaceId, providerId })
101+
logger.info('Created OAuth connect credential draft', {
102+
userId,
103+
workspaceId,
104+
providerId,
105+
credentialId: credentialId ?? null,
106+
})
63107
}

0 commit comments

Comments
 (0)