diff --git a/apps/sim/app/invite/[id]/invite.tsx b/apps/sim/app/invite/[id]/invite.tsx index caa2659d47..10658d8f7f 100644 --- a/apps/sim/app/invite/[id]/invite.tsx +++ b/apps/sim/app/invite/[id]/invite.tsx @@ -200,7 +200,7 @@ export default function Invite() { }, [searchParams, inviteId]) useEffect(() => { - if (!session?.user || !token) return + if (!session?.user) return async function fetchInvitationDetails() { setIsLoading(true) @@ -301,7 +301,7 @@ export default function Invite() { } fetchInvitationDetails() - }, [session?.user, inviteId, token]) + }, [session?.user, inviteId]) const handleAcceptInvitation = async () => { if (!session?.user) return diff --git a/apps/sim/hooks/queries/oauth/oauth-connections.ts b/apps/sim/hooks/queries/oauth/oauth-connections.ts index bdf5855165..c67391a7ce 100644 --- a/apps/sim/hooks/queries/oauth/oauth-connections.ts +++ b/apps/sim/hooks/queries/oauth/oauth-connections.ts @@ -107,6 +107,9 @@ async function fetchOAuthConnections(signal?: AbortSignal): Promise + // If the LLM passed a credential/oauthCredential ID directly, verify the user + // has active credential_member access before proceeding. This prevents + // unauthorized credential usage even if the agent hallucinated or received + // a credential ID the user doesn't have access to. + const suppliedCredentialId = (executionParams.oauthCredential || executionParams.credential) as + | string + | undefined + if (suppliedCredentialId) { + const actorCtx = await getCredentialActorContext(suppliedCredentialId, userId) + if (!actorCtx.member) { + logger.warn('Blocked credential use: user lacks credential_member access', { + credentialId: suppliedCredentialId, + userId, + toolName, + }) + return { + success: false, + error: `You do not have access to credential "${suppliedCredentialId}". Ask the credential admin to add you as a member, or connect your own account.`, + } + } + } + if (toolConfig.oauth?.required && toolConfig.oauth.provider) { const provider = toolConfig.oauth.provider - const accounts = await db - .select() - .from(account) - .where(and(eq(account.providerId, provider), eq(account.userId, userId))) - .limit(1) - if (!accounts.length) { - return { - success: false, - error: `No ${provider} account connected. Please connect your account first.`, + // If the user already supplied a credential ID that passed the check above, + // skip auto-resolution and let executeTool handle it via the token endpoint. + if (!suppliedCredentialId) { + if (!workspaceId) { + return { + success: false, + error: `Cannot resolve ${provider} credential without a workspace context.`, + } } - } - const acc = accounts[0] - const requestId = generateRequestId() - const { accessToken } = await refreshTokenIfNeeded(requestId, acc, acc.id) + const accessibleCreds = await getAccessibleOAuthCredentials(workspaceId, userId) + const match = accessibleCreds.find((c) => c.providerId === provider) - if (!accessToken) { - return { - success: false, - error: `OAuth token not available for ${provider}. Please reconnect your account.`, + if (!match) { + return { + success: false, + error: `No accessible ${provider} account found. You either don't have a ${provider} account connected in this workspace, or you don't have access to the existing one. Please connect your own account.`, + } } - } - executionParams.accessToken = accessToken + // Resolve the credential to its underlying account for token refresh + const matchCtx = await getCredentialActorContext(match.id, userId) + const accountId = matchCtx.credential?.accountId + if (!accountId) { + return { + success: false, + error: `OAuth account for ${provider} not found. Please reconnect your account.`, + } + } + + const [acc] = await db.select().from(account).where(eq(account.id, accountId)).limit(1) + + if (!acc) { + return { + success: false, + error: `OAuth account for ${provider} not found. Please reconnect your account.`, + } + } + + const requestId = generateRequestId() + const { accessToken } = await refreshTokenIfNeeded(requestId, acc, acc.id) + + if (!accessToken) { + return { + success: false, + error: `OAuth token not available for ${provider}. Please reconnect your account.`, + } + } + + executionParams.accessToken = accessToken + } } const hasHostedKeySupport = isHosted && !!toolConfig.hosting @@ -82,6 +133,7 @@ export async function executeIntegrationToolDirect( workflowId, workspaceId, userId, + enforceCredentialAccess: true, } if (toolName === 'function_execute') {