UNPKG

okta-mcp-server

Version:

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

240 lines 7.78 kB
import { z } from 'zod'; import { CSVUserInputSchema, JSONUserInputSchema } from './schemas.js'; /** * Parse CSV data into user objects */ export function parseCSV(csvData, mapping) { const lines = csvData.trim().split('\n'); if (lines.length < 2) { throw new Error('CSV must contain at least a header row and one data row'); } // Parse header const headers = lines[0].split(',').map((h) => h.trim().replace(/^"|"$/g, '')); // Create reverse mapping if provided const fieldMapping = {}; if (mapping) { // Standard fields if (mapping['email']) fieldMapping[mapping['email']] = 'email'; if (mapping['firstName']) fieldMapping[mapping['firstName']] = 'firstName'; if (mapping['lastName']) fieldMapping[mapping['lastName']] = 'lastName'; if (mapping['login']) fieldMapping[mapping['login']] = 'login'; if (mapping['mobilePhone']) fieldMapping[mapping['mobilePhone']] = 'mobilePhone'; if (mapping['department']) fieldMapping[mapping['department']] = 'department'; if (mapping['title']) fieldMapping[mapping['title']] = 'title'; if (mapping['manager']) fieldMapping[mapping['manager']] = 'manager'; // Custom attributes if (mapping['customAttributes']) { Object.entries(mapping['customAttributes']).forEach(([oktaField, csvColumn]) => { fieldMapping[csvColumn] = oktaField; }); } } // Parse data rows const users = []; for (let i = 1; i < lines.length; i++) { const line = lines[i].trim(); if (!line) continue; const values = parseCSVLine(line); const user = {}; headers.forEach((header, index) => { const value = values[index]; if (index < values.length && values?.[index] !== undefined && values[index] !== '') { const fieldName = fieldMapping[header] || header; user[fieldName] = value; } }); users.push(user); } return users; } /** * Parse a single CSV line, handling quoted values */ function parseCSVLine(line) { const values = []; let current = ''; let inQuotes = false; for (let i = 0; i < line.length; i++) { const char = line[i]; const nextChar = line[i + 1]; if (char === '"') { if (inQuotes && nextChar === '"') { // Escaped quote current += '"'; i++; // Skip next quote } else { // Toggle quote state inQuotes = !inQuotes; } } else if (char === ',' && !inQuotes) { // End of field values.push(current.trim()); current = ''; } else { current += char; } } // Add last field values.push(current.trim()); return values; } /** * Convert user objects to CSV format */ export function toCSV(users, fields) { // Create header const headers = fields.map((field) => { // Handle nested fields if (field.includes('.')) { return field.split('.').pop(); } return field; }); const csvLines = [headers.map((h) => `"${h}"`).join(',')]; // Add data rows for (const user of users) { const values = fields.map((field) => { let value = user; // Handle nested fields const parts = field.split('.'); for (const part of parts) { value = value?.[part]; } // Format value if (value === null || value === undefined) { return ''; } if (typeof value === 'object') { return `"${JSON.stringify(value).replace(/"/g, '""')}"`; } const strValue = String(value); if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) { return `"${strValue.replace(/"/g, '""')}"`; } return String(value); }); csvLines.push(values.join(',')); } return csvLines.join('\n'); } /** * Validate and transform CSV user data to Okta format */ export function validateCSVUsers(users) { const validatedUsers = []; const errors = []; users.forEach((user, index) => { try { // Validate required fields const validated = CSVUserInputSchema.parse(user); // Transform to Okta format const oktaUser = { profile: { ...validated, login: validated.login || validated.email, }, }; validatedUsers.push(oktaUser); } catch (error) { if (error instanceof z.ZodError) { errors.push({ index, error: `Row ${index + 1}: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`, }); } else { errors.push({ index, error: `Row ${index + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`, }); } } }); if (errors.length > 0) { const errorMessage = errors.map((e) => e.error).join('\n'); throw new Error(`Validation errors:\n${errorMessage}`); } return validatedUsers; } /** * Validate JSON user data */ export function validateJSONUsers(data) { let parsed; try { parsed = JSON.parse(data); } catch (error) { throw new Error('Invalid JSON format'); } if (!Array.isArray(parsed)) { throw new Error('JSON data must be an array of user objects'); } const validatedUsers = []; const errors = []; parsed.forEach((user, index) => { try { const validated = JSONUserInputSchema.parse(user); // Ensure login is set if (!validated.profile.login) { validated.profile.login = validated.profile.email; } validatedUsers.push(validated); } catch (error) { if (error instanceof z.ZodError) { errors.push({ index, error: `User ${index + 1}: ${error.errors.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`, }); } else { errors.push({ index, error: `User ${index + 1}: ${error instanceof Error ? error.message : 'Unknown error'}`, }); } } }); if (errors.length > 0) { const errorMessage = errors.map((e) => e.error).join('\n'); throw new Error(`Validation errors:\n${errorMessage}`); } return validatedUsers; } /** * Generate sample data for import templates */ export function generateSampleUsers(count, includeOptional) { const samples = []; for (let i = 1; i <= count; i++) { const sample = { email: `user${i}@example.com`, firstName: `First${i}`, lastName: `Last${i}`, }; if (includeOptional) { sample['login'] = `user${i}`; sample['mobilePhone'] = `+1-555-${String(i).padStart(4, '0')}`; sample['department'] = `Department ${i}`; sample['title'] = `Title ${i}`; sample['manager'] = `manager${i}@example.com`; } samples.push(sample); } return samples; } //# sourceMappingURL=parsers.js.map