okta-mcp-server
Version:
Model Context Protocol (MCP) server for Okta API operations with support for bulk operations and caching
345 lines • 14 kB
JavaScript
/**
* 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