import * as plugins from '../plugins.js'; import type { Reception } from './classes.reception.js'; import { AppConnection } from './classes.appconnection.js'; export class AppConnectionManager { public receptionRef: Reception; public get db() { return this.receptionRef.db.smartdataDb; } public typedrouter = new plugins.typedrequest.TypedRouter(); public CAppConnection = plugins.smartdata.setDefaultManagerForDoc(this, AppConnection); private async emitOrganizationAlert(optionsArg: { organizationId: string; eventType: string; severity: plugins.idpInterfaces.data.TAlertSeverity; title: string; body: string; actorUserId: string; relatedEntityId?: string; relatedEntityType?: string; }) { await this.receptionRef.alertManager.createAlertsForEvent({ category: 'admin', organizationId: optionsArg.organizationId, eventType: optionsArg.eventType, severity: optionsArg.severity, title: optionsArg.title, body: optionsArg.body, actorUserId: optionsArg.actorUserId, relatedEntityId: optionsArg.relatedEntityId, relatedEntityType: optionsArg.relatedEntityType, }); } constructor(receptionRefArg: Reception) { this.receptionRef = receptionRefArg; this.receptionRef.typedrouter.addTypedRouter(this.typedrouter); // Handler: Get app connections for an organization this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getAppConnections', async (requestArg) => { // Verify JWT and get user const jwtData = await this.receptionRef.jwtManager.verifyJWTAndGetData(requestArg.jwt); const user = await this.receptionRef.userManager.CUser.getInstance({ id: jwtData.data.userId, }); // Check user has access to the organization const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({ id: requestArg.organizationId, }); if (!organization) { throw new plugins.typedrequest.TypedResponseError('Organization not found'); } const role = await this.receptionRef.roleManager.CRole.getInstance({ data: { organizationId: organization.id, userId: user.id, }, }); if (!role) { throw new plugins.typedrequest.TypedResponseError( 'User not authorized for this organization' ); } // Get all connections for this organization const connections = await this.CAppConnection.getInstances({ 'data.organizationId': requestArg.organizationId, }); const connectionObjects = await Promise.all( connections.map(async (conn) => await conn.createSavableObject()) ); return { connections: connectionObjects, }; } ) ); // Handler: Toggle app connection (connect/disconnect) this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'toggleAppConnection', async (requestArg) => { // Verify JWT and get user const jwtData = await this.receptionRef.jwtManager.verifyJWTAndGetData(requestArg.jwt); const user = await this.receptionRef.userManager.CUser.getInstance({ id: jwtData.data.userId, }); // Check user has admin access to the organization const organization = await this.receptionRef.organizationmanager.COrganization.getInstance({ id: requestArg.organizationId, }); if (!organization) { throw new plugins.typedrequest.TypedResponseError('Organization not found'); } const isAdmin = await organization.checkIfUserIsAdmin(user); if (!isAdmin) { throw new plugins.typedrequest.TypedResponseError( 'Only organization admins can manage app connections' ); } // Get the app const app = await this.receptionRef.appManager.getAppById(requestArg.appId); if (!app) { throw new plugins.typedrequest.TypedResponseError('App not found'); } // Find existing connection let connection = await this.CAppConnection.getInstance({ 'data.organizationId': requestArg.organizationId, 'data.appId': requestArg.appId, }); if (requestArg.action === 'connect') { if (connection && connection.isActive()) { // Already connected return { success: true, connection: await connection.createSavableObject(), }; } if (connection) { // Reconnect existing connection await connection.reconnect(user.id); } else { // Create new connection connection = new AppConnection(); connection.id = plugins.smartunique.shortId(); connection.data = { organizationId: requestArg.organizationId, appId: requestArg.appId, appType: app.type, status: 'active', connectedAt: Date.now(), connectedByUserId: user.id, grantedScopes: app.data.oauthCredentials?.allowedScopes || [], }; await connection.save(); } await this.emitOrganizationAlert({ organizationId: requestArg.organizationId, eventType: 'org_app_connected', severity: 'medium', title: 'Organization app connected', body: `${user.data.email} connected ${app.data.name} to this organization.`, actorUserId: user.id, relatedEntityId: app.id, relatedEntityType: 'global-app', }); return { success: true, connection: await connection.createSavableObject(), }; } else { // Disconnect if (!connection) { return { success: true, }; } await connection.disconnect(); await this.emitOrganizationAlert({ organizationId: requestArg.organizationId, eventType: 'org_app_disconnected', severity: 'medium', title: 'Organization app disconnected', body: `${user.data.email} disconnected ${app.data.name} from this organization.`, actorUserId: user.id, relatedEntityId: app.id, relatedEntityType: 'global-app', }); return { success: true, connection: await connection.createSavableObject(), }; } } ) ); } /** * Get all connections for an organization */ public async getConnectionsForOrganization(organizationId: string): Promise { return await this.CAppConnection.getInstances({ 'data.organizationId': organizationId, }); } /** * Get connection for a specific app and organization */ public async getConnection( organizationId: string, appId: string ): Promise { return await this.CAppConnection.getInstance({ 'data.organizationId': organizationId, 'data.appId': appId, }); } /** * Check if an app is connected to an organization */ public async isAppConnected(organizationId: string, appId: string): Promise { const connection = await this.getConnection(organizationId, appId); return connection?.isActive() || false; } }