import { prisma } from "~/db.server"; import { env } from "~/env.server"; /** * Service for managing OAuth integration grants and webhooks */ export class OAuthIntegrationService { /** * Create integration grants for OAuth client when user authorizes */ async createIntegrationGrants(params: { clientId: string; userId: string; integrationAccountIds: string[]; }): Promise { // Get internal client ID const client = await prisma.oAuthClient.findUnique({ where: { clientId: params.clientId }, select: { id: true, webhookUrl: true, webhookSecret: true }, }); if (!client) { throw new Error("Invalid OAuth client"); } // Create grants for each selected integration const grants = params.integrationAccountIds.map((integrationAccountId) => ({ clientId: client.id, userId: params.userId, integrationAccountId, isActive: true, })); await prisma.oAuthIntegrationGrant.createMany({ data: grants, skipDuplicates: true, // Avoid conflicts if grant already exists }); // Send webhook notification if webhook URL is configured if (client.webhookUrl) { await this.sendIntegrationWebhooks({ clientId: params.clientId, userId: params.userId, integrationAccountIds: params.integrationAccountIds, eventType: "integration.connected", webhookUrl: client.webhookUrl, webhookSecret: client.webhookSecret ?? undefined, }); } } /** * Revoke integration grants for OAuth client */ async revokeIntegrationGrants(params: { clientId: string; userId: string; integrationAccountIds?: string[]; // If not provided, revoke all }): Promise { // Get internal client ID const client = await prisma.oAuthClient.findUnique({ where: { clientId: params.clientId }, select: { id: true, webhookUrl: true, webhookSecret: true }, }); if (!client) { throw new Error("Invalid OAuth client"); } const whereClause: any = { clientId: client.id, userId: params.userId, isActive: true, }; if (params.integrationAccountIds) { whereClause.integrationAccountId = { in: params.integrationAccountIds, }; } // Get the grants being revoked for webhook notification const grantsToRevoke = await prisma.oAuthIntegrationGrant.findMany({ where: whereClause, include: { integrationAccount: true, }, }); // Revoke the grants await prisma.oAuthIntegrationGrant.updateMany({ where: whereClause, data: { isActive: false, revokedAt: new Date(), }, }); // Send webhook notification if webhook URL is configured if (client.webhookUrl && grantsToRevoke.length > 0) { await this.sendIntegrationWebhooks({ clientId: params.clientId, userId: params.userId, integrationAccountIds: grantsToRevoke.map( (g) => g.integrationAccountId, ), eventType: "integration.disconnected", webhookUrl: client.webhookUrl, webhookSecret: client.webhookSecret ?? undefined, }); } } /** * Get connected integrations for OAuth client */ async getConnectedIntegrations(params: { clientId: string; userId: string }) { // Get internal client ID const client = await prisma.oAuthClient.findUnique({ where: { clientId: params.clientId }, select: { id: true }, }); if (!client) { throw new Error("Invalid OAuth client"); } const integrationAccounts = await prisma.integrationAccount.findMany({ where: { workspace: { userId: params.userId, }, isActive: true, }, include: { integrationDefinition: true, }, }); return integrationAccounts.map((integrationAccount) => { const integrationConfig = integrationAccount.integrationConfiguration as any; return { id: integrationAccount.id, provider: integrationAccount.integrationDefinition.slug, mcpEndpoint: integrationConfig.mcp ? `${env.LOGIN_ORIGIN}/api/v1/mcp/${integrationAccount.integrationDefinition.slug}` : undefined, connectedAt: integrationAccount.createdAt, name: integrationAccount.integrationDefinition.name, icon: integrationAccount.integrationDefinition.icon, }; }); } /** * Send webhook notifications for integration events */ private async sendIntegrationWebhooks(params: { clientId: string; userId: string; integrationAccountIds: string[]; eventType: "integration.connected" | "integration.disconnected"; webhookUrl: string; webhookSecret?: string; }) { return params; } } export const oauthIntegrationService = new OAuthIntegrationService();