import { db } from '@sim/db' import { account, workflow as workflowTable } from '@sim/db/schema' import { eq } from 'drizzle-orm' import type { NextRequest } from 'next/server' import { checkHybridAuth } from '@/lib/auth/hybrid' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' export interface CredentialAccessResult { ok: boolean error?: string authType?: 'session' | 'api_key' | 'internal_jwt' requesterUserId?: string credentialOwnerUserId?: string workspaceId?: string } /** * Centralizes auth + collaboration rules for credential use. * - Uses checkHybridAuth to authenticate the caller * - Fetches credential owner * - Authorization rules: * - session/api_key: allow if requester owns the credential; otherwise require workflowId and * verify BOTH requester and owner have access to the workflow's workspace * - internal_jwt: require workflowId (by default) and verify credential owner has access to the * workflow's workspace (requester identity is the system/workflow) */ export async function authorizeCredentialUse( request: NextRequest, params: { credentialId: string; workflowId?: string; requireWorkflowIdForInternal?: boolean } ): Promise { const { credentialId, workflowId, requireWorkflowIdForInternal = true } = params const auth = await checkHybridAuth(request, { requireWorkflowId: requireWorkflowIdForInternal }) if (!auth.success || !auth.userId) { return { ok: false, error: auth.error || 'Authentication required' } } // Lookup credential owner const [credRow] = await db .select({ userId: account.userId }) .from(account) .where(eq(account.id, credentialId)) .limit(1) if (!credRow) { return { ok: false, error: 'Credential not found' } } const credentialOwnerUserId = credRow.userId // If requester owns the credential, allow immediately if (auth.authType !== 'internal_jwt' && auth.userId === credentialOwnerUserId) { return { ok: true, authType: auth.authType, requesterUserId: auth.userId, credentialOwnerUserId, } } // For collaboration paths, workflowId is required to scope to a workspace if (!workflowId) { return { ok: false, error: 'workflowId is required' } } const [wf] = await db .select({ workspaceId: workflowTable.workspaceId }) .from(workflowTable) .where(eq(workflowTable.id, workflowId)) .limit(1) if (!wf || !wf.workspaceId) { return { ok: false, error: 'Workflow not found' } } if (auth.authType === 'internal_jwt') { // Internal calls: verify credential owner belongs to the workflow's workspace const ownerPerm = await getUserEntityPermissions( credentialOwnerUserId, 'workspace', wf.workspaceId ) if (ownerPerm === null) { return { ok: false, error: 'Unauthorized' } } return { ok: true, authType: auth.authType, requesterUserId: auth.userId, credentialOwnerUserId, workspaceId: wf.workspaceId, } } // Session/API key: verify BOTH requester and owner belong to the workflow's workspace const requesterPerm = await getUserEntityPermissions(auth.userId, 'workspace', wf.workspaceId) const ownerPerm = await getUserEntityPermissions( credentialOwnerUserId, 'workspace', wf.workspaceId ) if (requesterPerm === null || ownerPerm === null) { return { ok: false, error: 'Unauthorized' } } return { ok: true, authType: auth.authType, requesterUserId: auth.userId, credentialOwnerUserId, workspaceId: wf.workspaceId, } }