Skip to content

Commit e74a04a

Browse files
committed
fix(dependent): credential dependent endpoints
1 parent 364bb19 commit e74a04a

25 files changed

Lines changed: 434 additions & 126 deletions

File tree

apps/sim/app/api/auth/oauth/utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,12 @@ interface AccountInsertData {
2525
accessTokenExpiresAt?: Date
2626
}
2727

28-
async function resolveOAuthAccountId(
28+
/**
29+
* Resolves a credential ID to its underlying account ID.
30+
* If `credentialId` matches a `credential` row, returns its `accountId`.
31+
* Otherwise assumes `credentialId` is already a raw `account.id` (legacy).
32+
*/
33+
export async function resolveOAuthAccountId(
2934
credentialId: string
3035
): Promise<{ accountId: string; usedCredentialTable: boolean } | null> {
3136
const [credentialRow] = await db

apps/sim/app/api/auth/oauth/wealthbox/item/route.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
66
import { getSession } from '@/lib/auth'
77
import { validateEnum, validatePathSegment } from '@/lib/core/security/input-validation'
88
import { generateRequestId } from '@/lib/core/utils/request'
9-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
9+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
1010

1111
export const dynamic = 'force-dynamic'
1212

@@ -57,7 +57,16 @@ export async function GET(request: NextRequest) {
5757
return NextResponse.json({ error: itemIdValidation.error }, { status: 400 })
5858
}
5959

60-
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
60+
const resolved = await resolveOAuthAccountId(credentialId)
61+
if (!resolved) {
62+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
63+
}
64+
65+
const credentials = await db
66+
.select()
67+
.from(account)
68+
.where(eq(account.id, resolved.accountId))
69+
.limit(1)
6170

6271
if (!credentials.length) {
6372
logger.warn(`[${requestId}] Credential not found`, { credentialId })
@@ -74,7 +83,11 @@ export async function GET(request: NextRequest) {
7483
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
7584
}
7685

77-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
86+
const accessToken = await refreshAccessTokenIfNeeded(
87+
resolved.accountId,
88+
session.user.id,
89+
requestId
90+
)
7891

7992
if (!accessToken) {
8093
logger.error(`[${requestId}] Failed to obtain valid access token`)

apps/sim/app/api/auth/oauth/wealthbox/items/route.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { eq } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { getSession } from '@/lib/auth'
77
import { generateRequestId } from '@/lib/core/utils/request'
8-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
8+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
99

1010
export const dynamic = 'force-dynamic'
1111

@@ -47,8 +47,16 @@ export async function GET(request: NextRequest) {
4747
)
4848
}
4949

50-
// Get the credential from the database
51-
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
50+
const resolved = await resolveOAuthAccountId(credentialId)
51+
if (!resolved) {
52+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
53+
}
54+
55+
const credentials = await db
56+
.select()
57+
.from(account)
58+
.where(eq(account.id, resolved.accountId))
59+
.limit(1)
5260

5361
if (!credentials.length) {
5462
logger.warn(`[${requestId}] Credential not found`, { credentialId })
@@ -57,7 +65,6 @@ export async function GET(request: NextRequest) {
5765

5866
const credential = credentials[0]
5967

60-
// Check if the credential belongs to the user
6168
if (credential.userId !== session.user.id) {
6269
logger.warn(`[${requestId}] Unauthorized credential access attempt`, {
6370
credentialUserId: credential.userId,
@@ -66,8 +73,11 @@ export async function GET(request: NextRequest) {
6673
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
6774
}
6875

69-
// Refresh access token if needed
70-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
76+
const accessToken = await refreshAccessTokenIfNeeded(
77+
resolved.accountId,
78+
session.user.id,
79+
requestId
80+
)
7181

7282
if (!accessToken) {
7383
logger.error(`[${requestId}] Failed to obtain valid access token`)

apps/sim/app/api/cron/renew-subscriptions/route.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,27 @@ import { createLogger } from '@sim/logger'
44
import { and, eq, or } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { verifyCronAuth } from '@/lib/auth/internal'
7-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
7+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
88

99
const logger = createLogger('TeamsSubscriptionRenewal')
1010

11-
async function getCredentialOwnerUserId(credentialId: string): Promise<string | null> {
11+
async function getCredentialOwner(
12+
credentialId: string
13+
): Promise<{ userId: string; accountId: string } | null> {
14+
const resolved = await resolveOAuthAccountId(credentialId)
15+
if (!resolved) {
16+
logger.error(`Failed to resolve OAuth account for credential ${credentialId}`)
17+
return null
18+
}
1219
const [credentialRecord] = await db
1320
.select({ userId: account.userId })
1421
.from(account)
15-
.where(eq(account.id, credentialId))
22+
.where(eq(account.id, resolved.accountId))
1623
.limit(1)
1724

18-
return credentialRecord?.userId ?? null
25+
return credentialRecord
26+
? { userId: credentialRecord.userId, accountId: resolved.accountId }
27+
: null
1928
}
2029

2130
/**
@@ -88,17 +97,17 @@ export async function GET(request: NextRequest) {
8897
continue
8998
}
9099

91-
const credentialOwnerUserId = await getCredentialOwnerUserId(credentialId)
92-
if (!credentialOwnerUserId) {
100+
const credentialOwner = await getCredentialOwner(credentialId)
101+
if (!credentialOwner) {
93102
logger.error(`Credential owner not found for credential ${credentialId}`)
94103
totalFailed++
95104
continue
96105
}
97106

98107
// Get fresh access token
99108
const accessToken = await refreshAccessTokenIfNeeded(
100-
credentialId,
101-
credentialOwnerUserId,
109+
credentialOwner.accountId,
110+
credentialOwner.userId,
102111
`renewal-${webhook.id}`
103112
)
104113

apps/sim/app/api/providers/route.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
66
import { checkInternalAuth } from '@/lib/auth/hybrid'
77
import { generateRequestId } from '@/lib/core/utils/request'
88
import { checkWorkspaceAccess } from '@/lib/workspaces/permissions/utils'
9-
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
9+
import { refreshTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
1010
import type { StreamingExecution } from '@/executor/types'
1111
import { executeProviderRequest } from '@/providers'
1212

@@ -360,15 +360,20 @@ function sanitizeObject(obj: any): any {
360360
async function resolveVertexCredential(requestId: string, credentialId: string): Promise<string> {
361361
logger.info(`[${requestId}] Resolving Vertex AI credential: ${credentialId}`)
362362

363+
const resolved = await resolveOAuthAccountId(credentialId)
364+
if (!resolved) {
365+
throw new Error(`Vertex AI credential not found: ${credentialId}`)
366+
}
367+
363368
const credential = await db.query.account.findFirst({
364-
where: eq(account.id, credentialId),
369+
where: eq(account.id, resolved.accountId),
365370
})
366371

367372
if (!credential) {
368373
throw new Error(`Vertex AI credential not found: ${credentialId}`)
369374
}
370375

371-
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
376+
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, resolved.accountId)
372377

373378
if (!accessToken) {
374379
throw new Error('Failed to get Vertex AI access token')

apps/sim/app/api/tools/gmail/label/route.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
66
import { getSession } from '@/lib/auth'
77
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
88
import { generateRequestId } from '@/lib/core/utils/request'
9-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
9+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
1010

1111
export const dynamic = 'force-dynamic'
1212

@@ -41,10 +41,15 @@ export async function GET(request: NextRequest) {
4141
return NextResponse.json({ error: labelIdValidation.error }, { status: 400 })
4242
}
4343

44+
const resolved = await resolveOAuthAccountId(credentialId)
45+
if (!resolved) {
46+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
47+
}
48+
4449
const credentials = await db
4550
.select()
4651
.from(account)
47-
.where(and(eq(account.id, credentialId), eq(account.userId, session.user.id)))
52+
.where(and(eq(account.id, resolved.accountId), eq(account.userId, session.user.id)))
4853
.limit(1)
4954

5055
if (!credentials.length) {
@@ -58,7 +63,11 @@ export async function GET(request: NextRequest) {
5863
`[${requestId}] Using credential: ${credential.id}, provider: ${credential.providerId}`
5964
)
6065

61-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
66+
const accessToken = await refreshAccessTokenIfNeeded(
67+
resolved.accountId,
68+
session.user.id,
69+
requestId
70+
)
6271

6372
if (!accessToken) {
6473
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })

apps/sim/app/api/tools/gmail/labels/route.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { db } from '@sim/db'
22
import { account } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4-
import { and, eq } from 'drizzle-orm'
4+
import { eq } from 'drizzle-orm'
55
import { type NextRequest, NextResponse } from 'next/server'
66
import { getSession } from '@/lib/auth'
77
import { validateAlphanumericId } from '@/lib/core/security/input-validation'
88
import { generateRequestId } from '@/lib/core/utils/request'
9-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
9+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
1010
export const dynamic = 'force-dynamic'
1111

1212
const logger = createLogger('GmailLabelsAPI')
@@ -45,27 +45,33 @@ export async function GET(request: NextRequest) {
4545
return NextResponse.json({ error: credentialIdValidation.error }, { status: 400 })
4646
}
4747

48-
let credentials = await db
48+
const resolved = await resolveOAuthAccountId(credentialId)
49+
if (!resolved) {
50+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
51+
}
52+
53+
const credentials = await db
4954
.select()
5055
.from(account)
51-
.where(and(eq(account.id, credentialId), eq(account.userId, session.user.id)))
56+
.where(eq(account.id, resolved.accountId))
5257
.limit(1)
5358

5459
if (!credentials.length) {
55-
credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
56-
if (!credentials.length) {
57-
logger.warn(`[${requestId}] Credential not found`)
58-
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
59-
}
60+
logger.warn(`[${requestId}] Credential not found`)
61+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
6062
}
6163

62-
const credential = credentials[0]
64+
const accountRow = credentials[0]
6365

6466
logger.info(
65-
`[${requestId}] Using credential: ${credential.id}, provider: ${credential.providerId}`
67+
`[${requestId}] Using credential: ${accountRow.id}, provider: ${accountRow.providerId}`
6668
)
6769

68-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, credential.userId, requestId)
70+
const accessToken = await refreshAccessTokenIfNeeded(
71+
resolved.accountId,
72+
accountRow.userId,
73+
requestId
74+
)
6975

7076
if (!accessToken) {
7177
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })

apps/sim/app/api/tools/microsoft_planner/tasks/route.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { eq } from 'drizzle-orm'
66
import { type NextRequest, NextResponse } from 'next/server'
77
import { getSession } from '@/lib/auth'
88
import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation'
9-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
9+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
1010
import type { PlannerTask } from '@/tools/microsoft_planner/types'
1111

1212
const logger = createLogger('MicrosoftPlannerTasksAPI')
@@ -42,7 +42,16 @@ export async function GET(request: NextRequest) {
4242
return NextResponse.json({ error: planIdValidation.error }, { status: 400 })
4343
}
4444

45-
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
45+
const resolved = await resolveOAuthAccountId(credentialId)
46+
if (!resolved) {
47+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
48+
}
49+
50+
const credentials = await db
51+
.select()
52+
.from(account)
53+
.where(eq(account.id, resolved.accountId))
54+
.limit(1)
4655

4756
if (!credentials.length) {
4857
logger.warn(`[${requestId}] Credential not found`, { credentialId })
@@ -59,7 +68,11 @@ export async function GET(request: NextRequest) {
5968
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
6069
}
6170

62-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
71+
const accessToken = await refreshAccessTokenIfNeeded(
72+
resolved.accountId,
73+
session.user.id,
74+
requestId
75+
)
6376

6477
if (!accessToken) {
6578
logger.error(`[${requestId}] Failed to obtain valid access token`)

apps/sim/app/api/tools/onedrive/files/route.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { eq } from 'drizzle-orm'
66
import { type NextRequest, NextResponse } from 'next/server'
77
import { getSession } from '@/lib/auth'
88
import { validateMicrosoftGraphId } from '@/lib/core/security/input-validation'
9-
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
9+
import { refreshAccessTokenIfNeeded, resolveOAuthAccountId } from '@/app/api/auth/oauth/utils'
1010

1111
export const dynamic = 'force-dynamic'
1212

@@ -45,7 +45,16 @@ export async function GET(request: NextRequest) {
4545

4646
logger.info(`[${requestId}] Fetching credential`, { credentialId })
4747

48-
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
48+
const resolved = await resolveOAuthAccountId(credentialId)
49+
if (!resolved) {
50+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
51+
}
52+
53+
const credentials = await db
54+
.select()
55+
.from(account)
56+
.where(eq(account.id, resolved.accountId))
57+
.limit(1)
4958
if (!credentials.length) {
5059
logger.warn(`[${requestId}] Credential not found`, { credentialId })
5160
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
@@ -60,7 +69,11 @@ export async function GET(request: NextRequest) {
6069
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
6170
}
6271

63-
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
72+
const accessToken = await refreshAccessTokenIfNeeded(
73+
resolved.accountId,
74+
session.user.id,
75+
requestId
76+
)
6477
if (!accessToken) {
6578
logger.error(`[${requestId}] Failed to obtain valid access token`)
6679
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })

0 commit comments

Comments
 (0)