UNPKG

okta-mcp-server

Version:

Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching

345 lines 14 kB
/** * Cached wrapper for OktaClient to improve performance */ import { logger as baseLogger } from '../utils/logger.js'; import crypto from 'crypto'; const logger = baseLogger.child({ module: 'CachedOktaClient' }); export class CachedOktaClient { client; cache; defaultTtl; keyPrefix; constructor(options) { this.client = options.client; this.cache = options.cache; this.defaultTtl = options.defaultTtl || 300; // 5 minutes this.keyPrefix = options.cacheKeyPrefix || 'okta:'; } /** * Generate a cache key from method name and parameters */ generateCacheKey(method, params) { const paramsHash = crypto .createHash('sha256') .update(JSON.stringify(params || {})) .digest('hex'); return `${this.keyPrefix}${method}:${paramsHash}`; } /** * Get tags for cache invalidation based on resource type */ getTags(resourceType, id) { const tags = [`okta:${resourceType}`]; if (id) { tags.push(`okta:${resourceType}:${id}`); } return tags; } /** * Wrap a method with caching */ async withCache(method, params, fn, options = {}) { const cacheKey = this.generateCacheKey(method, params); // Try to get from cache const cached = await this.cache.get(cacheKey); if (cached !== undefined) { logger.debug(`Cache hit for ${method}`, { params }); return cached; } logger.debug(`Cache miss for ${method}`, { params }); // Execute the actual method const result = await fn(); // Store in cache await this.cache.set(cacheKey, result, { ttl: options.ttl || this.defaultTtl, ...(options.tags && { tags: options.tags }), }); return result; } // Delegate methods to the underlying client with caching async listUsers(params) { return this.withCache('listUsers', params, () => this.client.listUsers(params), { tags: this.getTags('users'), }); } async getUser(userIdOrLogin) { return this.withCache('getUser', { userIdOrLogin }, () => this.client.getUser(userIdOrLogin), { tags: this.getTags('users', userIdOrLogin), }); } async createUser(userData, queryParams) { const result = await this.client.createUser(userData, queryParams); // Invalidate user list cache await this.cache.clearByTag('okta:users'); return result; } async updateUser(userId, userData) { const result = await this.client.updateUser(userId, userData); // Invalidate specific user and user list cache await Promise.all([ this.cache.clearByTag(`okta:users:${userId}`), this.cache.clearByTag('okta:users'), ]); return result; } async deleteUser(userId) { await this.client.deleteUser(userId); // Invalidate specific user and user list cache await Promise.all([ this.cache.clearByTag(`okta:users:${userId}`), this.cache.clearByTag('okta:users'), ]); } async deactivateUser(userId, sendEmail) { await this.client.deactivateUser(userId, sendEmail); // Invalidate specific user cache await this.cache.clearByTag(`okta:users:${userId}`); } async getUserGroups(userId, params) { return this.withCache('getUserGroups', { userId, ...params }, () => this.client.getUserGroups(userId, params), { tags: [`okta:users:${userId}:groups`] }); } // Group management methods async listGroups(params) { return this.withCache('listGroups', params, () => this.client.listGroups(params), { tags: this.getTags('groups'), }); } async getGroup(groupId) { return this.withCache('getGroup', { groupId }, () => this.client.getGroup(groupId), { tags: this.getTags('groups', groupId), }); } async createGroup(groupData) { const result = await this.client.createGroup(groupData); // Invalidate group list cache await this.cache.clearByTag('okta:groups'); return result; } async updateGroup(groupId, groupData) { const result = await this.client.updateGroup(groupId, groupData); // Invalidate specific group and group list cache await Promise.all([ this.cache.clearByTag(`okta:groups:${groupId}`), this.cache.clearByTag('okta:groups'), ]); return result; } async deleteGroup(groupId) { await this.client.deleteGroup(groupId); // Invalidate specific group and group list cache await Promise.all([ this.cache.clearByTag(`okta:groups:${groupId}`), this.cache.clearByTag('okta:groups'), ]); } async listGroupMembers(groupId, params) { return this.withCache('listGroupMembers', { groupId, ...params }, () => this.client.listGroupMembers(groupId, params), { tags: [`okta:groups:${groupId}:members`] }); } async addGroupMember(groupId, userId) { await this.client.addGroupMember(groupId, userId); // Invalidate caches await Promise.all([ this.cache.clearByTag(`okta:groups:${groupId}:members`), this.cache.clearByTag(`okta:users:${userId}:groups`), ]); } async removeGroupMember(groupId, userId) { await this.client.removeGroupMember(groupId, userId); // Invalidate caches await Promise.all([ this.cache.clearByTag(`okta:groups:${groupId}:members`), this.cache.clearByTag(`okta:users:${userId}:groups`), ]); } async getLogs(params) { // Don't cache logs as they're time-sensitive return this.client.getLogs(params); } // Application management methods async listApps(params) { return this.withCache('listApps', params, () => this.client.listApps(params), { tags: this.getTags('apps'), }); } async getApp(appId, expand) { return this.withCache('getApp', { appId, expand }, () => this.client.getApp(appId, expand), { tags: this.getTags('apps', appId), }); } async createApp(appData) { const app = await this.client.createApp(appData); // Invalidate app list cache await this.cache.clearByTag('okta:apps'); return app; } async updateApp(appId, appData) { const app = await this.client.updateApp(appId, appData); // Invalidate specific app and app list cache await Promise.all([ this.cache.clearByTag(`okta:apps:${appId}`), this.cache.clearByTag('okta:apps'), ]); return app; } async deleteApp(appId) { await this.client.deleteApp(appId); // Invalidate specific app and app list cache await Promise.all([ this.cache.clearByTag(`okta:apps:${appId}`), this.cache.clearByTag('okta:apps'), ]); } async activateApp(appId) { await this.client.activateApp(appId); // Invalidate specific app cache await this.cache.clearByTag(`okta:apps:${appId}`); } async deactivateApp(appId) { await this.client.deactivateApp(appId); // Invalidate specific app cache await this.cache.clearByTag(`okta:apps:${appId}`); } async assignUserToApp(appId, userId, assignmentData) { const result = await this.client.assignUserToApp(appId, userId, assignmentData); // Invalidate caches await Promise.all([ this.cache.clearByTag(`okta:apps:${appId}:users`), this.cache.clearByTag(`okta:users:${userId}:apps`), ]); return result; } async assignGroupToApp(appId, groupId, assignmentData) { const result = await this.client.assignGroupToApp(appId, groupId, assignmentData); // Invalidate caches await Promise.all([ this.cache.clearByTag(`okta:apps:${appId}:groups`), this.cache.clearByTag(`okta:groups:${groupId}:apps`), ]); return result; } async removeUserFromApp(appId, userId, sendEmail) { await this.client.removeUserFromApp(appId, userId, sendEmail); // Invalidate caches await Promise.all([ this.cache.clearByTag(`okta:apps:${appId}:users`), this.cache.clearByTag(`okta:users:${userId}:apps`), ]); } async removeGroupFromApp(appId, groupId) { await this.client.removeGroupFromApp(appId, groupId); // Invalidate caches await Promise.all([ this.cache.clearByTag(`okta:apps:${appId}:groups`), this.cache.clearByTag(`okta:groups:${groupId}:apps`), ]); } async listAppUsers(appId, params) { return this.withCache('listAppUsers', { appId, ...params }, () => this.client.listAppUsers(appId, params), { tags: [`okta:apps:${appId}:users`] }); } async listAppGroups(appId, params) { return this.withCache('listAppGroups', { appId, ...params }, () => this.client.listAppGroups(appId, params), { tags: [`okta:apps:${appId}:groups`] }); } async updateAppUser(appId, userId, updateData) { const result = await this.client.updateAppUser(appId, userId, updateData); // Invalidate specific app user cache await this.cache.clearByTag(`okta:apps:${appId}:users`); return result; } // Policy management methods async listPolicies(params) { return this.withCache('listPolicies', params, () => this.client.listPolicies(params), { tags: params?.type ? [`okta:policies:${params.type}`] : ['okta:policies'], }); } async getPolicy(policyId, expand) { return this.withCache('getPolicy', { policyId, expand }, () => this.client.getPolicy(policyId, expand), { tags: [`okta:policies:${policyId}`] }); } async createPolicy(policyData) { const result = await this.client.createPolicy(policyData); // Invalidate policy list cache await this.cache.clearByTag(`okta:policies:${policyData.type}`); await this.cache.clearByTag('okta:policies'); return result; } async updatePolicy(policyId, policyData) { const result = await this.client.updatePolicy(policyId, policyData); // Invalidate specific policy cache await this.cache.clearByTag(`okta:policies:${policyId}`); await this.cache.clearByTag('okta:policies'); return result; } async deletePolicy(policyId) { await this.client.deletePolicy(policyId); // Invalidate cache await this.cache.clearByTag(`okta:policies:${policyId}`); await this.cache.clearByTag('okta:policies'); } async activatePolicy(policyId) { await this.client.activatePolicy(policyId); // Invalidate specific policy cache await this.cache.clearByTag(`okta:policies:${policyId}`); await this.cache.clearByTag('okta:policies'); } async deactivatePolicy(policyId) { await this.client.deactivatePolicy(policyId); // Invalidate specific policy cache await this.cache.clearByTag(`okta:policies:${policyId}`); await this.cache.clearByTag('okta:policies'); } // Policy rule management methods async listPolicyRules(policyId, params) { return this.withCache('listPolicyRules', { policyId, ...params }, () => this.client.listPolicyRules(policyId, params), { tags: [`okta:policies:${policyId}:rules`] }); } async getPolicyRule(policyId, ruleId) { return this.withCache('getPolicyRule', { policyId, ruleId }, () => this.client.getPolicyRule(policyId, ruleId), { tags: [`okta:policies:${policyId}:rules:${ruleId}`] }); } async createPolicyRule(policyId, ruleData) { const result = await this.client.createPolicyRule(policyId, ruleData); // Invalidate policy rules cache await this.cache.clearByTag(`okta:policies:${policyId}:rules`); return result; } async updatePolicyRule(policyId, ruleId, ruleData) { const result = await this.client.updatePolicyRule(policyId, ruleId, ruleData); // Invalidate specific rule cache await this.cache.clearByTag(`okta:policies:${policyId}:rules:${ruleId}`); await this.cache.clearByTag(`okta:policies:${policyId}:rules`); return result; } async deletePolicyRule(policyId, ruleId) { await this.client.deletePolicyRule(policyId, ruleId); // Invalidate cache await this.cache.clearByTag(`okta:policies:${policyId}:rules:${ruleId}`); await this.cache.clearByTag(`okta:policies:${policyId}:rules`); } async activatePolicyRule(policyId, ruleId) { await this.client.activatePolicyRule(policyId, ruleId); // Invalidate specific rule cache await this.cache.clearByTag(`okta:policies:${policyId}:rules:${ruleId}`); await this.cache.clearByTag(`okta:policies:${policyId}:rules`); } async deactivatePolicyRule(policyId, ruleId) { await this.client.deactivatePolicyRule(policyId, ruleId); // Invalidate specific rule cache await this.cache.clearByTag(`okta:policies:${policyId}:rules:${ruleId}`); await this.cache.clearByTag(`okta:policies:${policyId}:rules`); } async close() { await this.client.close(); } /** * Clear all cached data */ async clearCache() { logger.info('Clearing all Okta cache'); await this.cache.clear(); } /** * Clear cache for a specific resource type */ async clearCacheByType(resourceType) { logger.info(`Clearing cache for resource type: ${resourceType}`); await this.cache.clearByTag(`okta:${resourceType}`); } } //# sourceMappingURL=cached-okta-client.js.map