diff --git a/apps/api/src/integration-platform/controllers/admin-integrations.controller.ts b/apps/api/src/integration-platform/controllers/admin-integrations.controller.ts index 8a4e83aa40..3f525b57e8 100644 --- a/apps/api/src/integration-platform/controllers/admin-integrations.controller.ts +++ b/apps/api/src/integration-platform/controllers/admin-integrations.controller.ts @@ -69,6 +69,8 @@ export class AdminIntegrationsController { credentialUpdatedAt: credential?.updatedAt, clientIdHint: credential?.clientIdHint, clientSecretHint: credential?.clientSecretHint, + encryptedClientId: credential?.encryptedClientId, + encryptedClientSecret: credential?.encryptedClientSecret, existingCustomSettings: (credential as { customSettings?: Record } | undefined) ?.customSettings || undefined, diff --git a/apps/app/src/app/(app)/[orgId]/admin/integrations/components/IntegrationCard.tsx b/apps/app/src/app/(app)/[orgId]/admin/integrations/components/IntegrationCard.tsx index 24e6cff972..fa621b3d6c 100644 --- a/apps/app/src/app/(app)/[orgId]/admin/integrations/components/IntegrationCard.tsx +++ b/apps/app/src/app/(app)/[orgId]/admin/integrations/components/IntegrationCard.tsx @@ -11,6 +11,7 @@ import { } from '@trycompai/design-system/icons'; import Image from 'next/image'; import { useState } from 'react'; +import { View, ViewOff } from '@trycompai/design-system/icons'; interface AdditionalOAuthSetting { id: string; @@ -38,6 +39,8 @@ export interface Integration { credentialUpdatedAt?: string; clientIdHint?: string; clientSecretHint?: string; + decryptedClientId?: string; + decryptedClientSecret?: string; existingCustomSettings?: Record; setupInstructions?: string; createAppUrl?: string; @@ -152,6 +155,13 @@ export function IntegrationCard({ )} + {integration.hasCredentials && integration.decryptedClientId && ( + + )} + {integration.authType === 'oauth2' && ( +
+ Client ID + + {clientId} + +
+ {clientSecret && ( +
+ Secret + + {showSecret ? clientSecret : `${'•'.repeat(Math.min(clientSecret.length, 20))}${clientSecret.slice(-4)}`} + + +
+ )} + + ); +} + function CredentialsDisplay({ clientIdHint, clientSecretHint, diff --git a/apps/app/src/app/(app)/[orgId]/admin/integrations/page.tsx b/apps/app/src/app/(app)/[orgId]/admin/integrations/page.tsx index aeb41d4fb1..69f5a1b8ba 100644 --- a/apps/app/src/app/(app)/[orgId]/admin/integrations/page.tsx +++ b/apps/app/src/app/(app)/[orgId]/admin/integrations/page.tsx @@ -1,6 +1,7 @@ 'use client'; import { api } from '@/lib/api-client'; +import { decrypt, type EncryptedData } from '@/lib/encryption'; import { Button, Card, @@ -19,6 +20,34 @@ import { useState } from 'react'; import useSWR from 'swr'; import { IntegrationCard, type Integration } from './components/IntegrationCard'; +interface ApiIntegration extends Integration { + encryptedClientId?: EncryptedData; + encryptedClientSecret?: EncryptedData; +} + +async function decryptIntegrations(integrations: ApiIntegration[]): Promise { + return Promise.all( + integrations.map(async (integration) => { + if (!integration.hasCredentials || !integration.encryptedClientId) { + return integration; + } + + try { + const [decryptedClientId, decryptedClientSecret] = await Promise.all([ + decrypt(integration.encryptedClientId), + integration.encryptedClientSecret + ? decrypt(integration.encryptedClientSecret) + : Promise.resolve(undefined), + ]); + + return { ...integration, decryptedClientId, decryptedClientSecret }; + } catch { + return integration; + } + }), + ); +} + export default function AdminIntegrationsPage() { const [searchQuery, setSearchQuery] = useState(''); @@ -28,9 +57,10 @@ export default function AdminIntegrationsPage() { isLoading, mutate, } = useSWR('admin-integrations', async () => { - const response = await api.get('/v1/admin/integrations'); + const response = await api.get('/v1/admin/integrations'); if (response.error) throw new Error(response.error); - return response.data || []; + const raw = response.data || []; + return decryptIntegrations(raw); }); const filteredIntegrations = integrations?.filter((i) => { diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index 085d6f6fb3..c985d60c58 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -21289,6 +21289,11 @@ "description": "Whether member is active", "example": true }, + "deactivated": { + "type": "boolean", + "description": "Whether member is deactivated", + "example": false + }, "fleetDmLabelId": { "type": "object", "description": "FleetDM label ID for member devices", @@ -21313,6 +21318,7 @@ "department", "jobTitle", "isActive", + "deactivated", "fleetDmLabelId", "user" ]