diff --git a/server/lib/calculateUserClientsForOrgs.ts b/server/lib/calculateUserClientsForOrgs.ts index 02ac0c417..7d8f41a1e 100644 --- a/server/lib/calculateUserClientsForOrgs.ts +++ b/server/lib/calculateUserClientsForOrgs.ts @@ -28,6 +28,159 @@ export async function calculateUserClientsForOrgs( trx?: Transaction ): Promise { const execute = async (transaction: Transaction) => { + const orgCache = new Map(); + const adminRoleCache = new Map< + string, + typeof roles.$inferSelect | null + >(); + const exitNodesCache = new Map< + string, + Awaited> + >(); + const isOrgLicensedCache = new Map(); + const existingClientCache = new Map< + string, + typeof clients.$inferSelect | null + >(); + const roleClientAccessCache = new Map(); + const userClientAccessCache = new Map(); + + const getOrgOlmKey = (orgId: string, olmId: string) => + `${orgId}:${olmId}`; + const getRoleClientKey = (roleId: number, clientId: number) => + `${roleId}:${clientId}`; + const getUserClientKey = (cachedUserId: string, clientId: number) => + `${cachedUserId}:${clientId}`; + + const getOrg = async (orgId: string) => { + if (orgCache.has(orgId)) { + return orgCache.get(orgId) ?? null; + } + + const [org] = await transaction + .select() + .from(orgs) + .where(eq(orgs.orgId, orgId)); + orgCache.set(orgId, org ?? null); + + return org ?? null; + }; + + const getAdminRole = async (orgId: string) => { + if (adminRoleCache.has(orgId)) { + return adminRoleCache.get(orgId) ?? null; + } + + const [adminRole] = await transaction + .select() + .from(roles) + .where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId))) + .limit(1); + adminRoleCache.set(orgId, adminRole ?? null); + + return adminRole ?? null; + }; + + const getExitNodes = async (orgId: string) => { + if (exitNodesCache.has(orgId)) { + return exitNodesCache.get(orgId)!; + } + + const exitNodes = await listExitNodes(orgId); + exitNodesCache.set(orgId, exitNodes); + + return exitNodes; + }; + + const getIsOrgLicensed = async (orgId: string) => { + if (isOrgLicensedCache.has(orgId)) { + return isOrgLicensedCache.get(orgId)!; + } + + const isOrgLicensed = await isLicensedOrSubscribed( + orgId, + tierMatrix.deviceApprovals + ); + isOrgLicensedCache.set(orgId, isOrgLicensed); + + return isOrgLicensed; + }; + + const getExistingClient = async (orgId: string, olmId: string) => { + const key = getOrgOlmKey(orgId, olmId); + if (existingClientCache.has(key)) { + return existingClientCache.get(key) ?? null; + } + + const [existingClient] = await transaction + .select() + .from(clients) + .where( + and( + eq(clients.userId, userId), + eq(clients.orgId, orgId), + eq(clients.olmId, olmId) + ) + ) + .limit(1); + + existingClientCache.set(key, existingClient ?? null); + + return existingClient ?? null; + }; + + const hasRoleClientAccess = async ( + roleId: number, + clientId: number + ) => { + const key = getRoleClientKey(roleId, clientId); + if (roleClientAccessCache.has(key)) { + return roleClientAccessCache.get(key)!; + } + + const [existingRoleClient] = await transaction + .select() + .from(roleClients) + .where( + and( + eq(roleClients.roleId, roleId), + eq(roleClients.clientId, clientId) + ) + ) + .limit(1); + + const hasAccess = Boolean(existingRoleClient); + roleClientAccessCache.set(key, hasAccess); + + return hasAccess; + }; + + const hasUserClientAccess = async ( + cachedUserId: string, + clientId: number + ) => { + const key = getUserClientKey(cachedUserId, clientId); + if (userClientAccessCache.has(key)) { + return userClientAccessCache.get(key)!; + } + + const [existingUserClient] = await transaction + .select() + .from(userClients) + .where( + and( + eq(userClients.userId, cachedUserId), + eq(userClients.clientId, clientId) + ) + ) + .limit(1); + + const hasAccess = Boolean(existingUserClient); + userClientAccessCache.set(key, hasAccess); + + return hasAccess; + }; + // Get all OLMs for this user const userOlms = await transaction .select() @@ -54,7 +207,9 @@ export async function calculateUserClientsForOrgs( .innerJoin(roles, eq(userOrgRoles.roleId, roles.roleId)) .where(eq(userOrgs.userId, userId)); - const userOrgIds = [...new Set(userOrgRoleRows.map((r) => r.userOrgs.orgId))]; + const userOrgIds = [ + ...new Set(userOrgRoleRows.map((r) => r.userOrgs.orgId)) + ]; const orgIdToRoleRows = new Map< string, (typeof userOrgRoleRows)[0][] @@ -64,6 +219,13 @@ export async function calculateUserClientsForOrgs( list.push(r); orgIdToRoleRows.set(r.userOrgs.orgId, list); } + const orgRequiresDeviceApprovalRole = new Map(); + for (const [orgId, roleRowsForOrg] of orgIdToRoleRows.entries()) { + orgRequiresDeviceApprovalRole.set( + orgId, + roleRowsForOrg.some((r) => r.roles.requireDeviceApproval) + ); + } // For each OLM, ensure there's a client in each org the user is in for (const olm of userOlms) { @@ -71,10 +233,7 @@ export async function calculateUserClientsForOrgs( const roleRowsForOrg = orgIdToRoleRows.get(orgId)!; const userOrg = roleRowsForOrg[0].userOrgs; - const [org] = await transaction - .select() - .from(orgs) - .where(eq(orgs.orgId, orgId)); + const org = await getOrg(orgId); if (!org) { logger.warn( @@ -91,11 +250,7 @@ export async function calculateUserClientsForOrgs( } // Get admin role for this org (needed for access grants) - const [adminRole] = await transaction - .select() - .from(roles) - .where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId))) - .limit(1); + const adminRole = await getAdminRole(orgId); if (!adminRole) { logger.warn( @@ -105,64 +260,50 @@ export async function calculateUserClientsForOrgs( } // Check if a client already exists for this OLM+user+org combination - const [existingClient] = await transaction - .select() - .from(clients) - .where( - and( - eq(clients.userId, userId), - eq(clients.orgId, orgId), - eq(clients.olmId, olm.olmId) - ) - ) - .limit(1); + const existingClient = await getExistingClient( + orgId, + olm.olmId + ); if (existingClient) { // Ensure admin role has access to the client - const [existingRoleClient] = await transaction - .select() - .from(roleClients) - .where( - and( - eq(roleClients.roleId, adminRole.roleId), - eq( - roleClients.clientId, - existingClient.clientId - ) - ) - ) - .limit(1); + const hasRoleAccess = await hasRoleClientAccess( + adminRole.roleId, + existingClient.clientId + ); - if (!existingRoleClient) { + if (!hasRoleAccess) { await transaction.insert(roleClients).values({ roleId: adminRole.roleId, clientId: existingClient.clientId }); + roleClientAccessCache.set( + getRoleClientKey( + adminRole.roleId, + existingClient.clientId + ), + true + ); logger.debug( `Granted admin role access to existing client ${existingClient.clientId} for OLM ${olm.olmId} in org ${orgId} (user ${userId})` ); } // Ensure user has access to the client - const [existingUserClient] = await transaction - .select() - .from(userClients) - .where( - and( - eq(userClients.userId, userId), - eq( - userClients.clientId, - existingClient.clientId - ) - ) - ) - .limit(1); + const hasUserAccess = await hasUserClientAccess( + userId, + existingClient.clientId + ); - if (!existingUserClient) { + if (!hasUserAccess) { await transaction.insert(userClients).values({ userId, clientId: existingClient.clientId }); + userClientAccessCache.set( + getUserClientKey(userId, existingClient.clientId), + true + ); logger.debug( `Granted user access to existing client ${existingClient.clientId} for OLM ${olm.olmId} in org ${orgId} (user ${userId})` ); @@ -175,7 +316,7 @@ export async function calculateUserClientsForOrgs( } // Get exit nodes for this org - const exitNodesList = await listExitNodes(orgId); + const exitNodesList = await getExitNodes(orgId); if (exitNodesList.length === 0) { logger.warn( @@ -206,14 +347,11 @@ export async function calculateUserClientsForOrgs( const niceId = await getUniqueClientName(orgId); - const isOrgLicensed = await isLicensedOrSubscribed( - userOrg.orgId, - tierMatrix.deviceApprovals - ); + const isOrgLicensed = await getIsOrgLicensed(userOrg.orgId); const requireApproval = build !== "oss" && isOrgLicensed && - roleRowsForOrg.some((r) => r.roles.requireDeviceApproval); + orgRequiresDeviceApprovalRole.get(orgId) === true; const newClientData: InferInsertModel = { userId, @@ -232,6 +370,10 @@ export async function calculateUserClientsForOrgs( .insert(clients) .values(newClientData) .returning(); + existingClientCache.set( + getOrgOlmKey(orgId, olm.olmId), + newClient + ); // create approval request if (requireApproval) { @@ -257,12 +399,20 @@ export async function calculateUserClientsForOrgs( roleId: adminRole.roleId, clientId: newClient.clientId }); + roleClientAccessCache.set( + getRoleClientKey(adminRole.roleId, newClient.clientId), + true + ); // Grant user access to the client await transaction.insert(userClients).values({ userId, clientId: newClient.clientId }); + userClientAccessCache.set( + getUserClientKey(userId, newClient.clientId), + true + ); logger.debug( `Created client for OLM ${olm.olmId} in org ${orgId} (user ${userId}) with access granted to admin role and user`