import { z } from 'zod'; import { isAllowedAuthorityHost, isAllowedGraphHost } from '../data/graphHosts.js'; export const loginSchema = z.object({ email: z.string().email(), password: z.string().min(1) }); export const profileSchema = z.object({ email: z.string().email().optional(), displayName: z.string().min(1).max(120), jobTitle: z.string().max(120).optional().default(''), phone: z.string().max(60).optional().default(''), timezone: z.string().max(80).optional().default(''), avatarUrl: z.string().url().or(z.literal('')).optional().default('') }); export const themeSchema = z.object({ theme: z.string().min(1).max(48).regex(/^[a-z0-9_-]+$/) }); export const passwordChangeSchema = z.object({ currentPassword: z.string().optional().default(''), newPassword: z.string().optional().default('') }); export const visibilitySchema = z.enum(['personal', 'shared', 'group']); export const userSchema = z.object({ email: z.string().email(), displayName: z.string().min(1), password: z.string().min(8).optional(), role: z.enum(['admin', 'user']).default('user'), groupIds: z.array(z.string()).default([]) }); export const groupSchema = z.object({ name: z.string().min(1), description: z.string().optional(), userIds: z.array(z.string()).default([]) }); export const credentialSchema = z.object({ name: z.string().min(1), kind: z.enum(['username_password', 'api_key']), username: z.string().optional(), secret: z.string().min(1), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const hostSchema = z.object({ name: z.string().min(1), fqdn: z.string().optional(), address: z.string().min(1), osFamily: z.string().default('windows'), transport: z.enum(['winrm', 'ssh', 'local', 'api']).default('winrm'), port: z.number().int().nullable().optional(), credentialId: z.string().nullable().optional(), tags: z.array(z.string()).default([]), notes: z.string().optional(), visibility: visibilitySchema.default('shared'), groupId: z.string().nullable().optional() }); const vcenterFilterSchema = z.object({ field: z.enum(['name', 'hostname', 'fqdn', 'ip']).optional().default('hostname'), operator: z.enum(['contains', 'equals', 'startsWith', 'endsWith']).optional().default('contains'), value: z.string().max(200).optional().default('') }); export const vcenterPreviewSchema = z.object({ filter: vcenterFilterSchema.optional().default({}), limit: z.coerce.number().int().min(1).max(500).optional().default(100) }); export const vcenterImportSchema = vcenterPreviewSchema.extend({ vmIds: z.array(z.string().min(1)).optional().default([]), credentialId: z.string().nullable().optional(), visibility: visibilitySchema.default('shared'), groupId: z.string().nullable().optional(), transport: z.enum(['winrm', 'ssh', 'local', 'api']).default('winrm'), port: z.coerce.number().int().min(1).max(65535).nullable().optional(), tags: z.array(z.string().max(80)).optional().default([]) }); export const folderSchema = z.object({ parentId: z.string().nullable().optional(), name: z.string().min(1), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const assetFolderSchema = z.object({ parentId: z.string().nullable().optional(), name: z.string().min(1).max(180), description: z.string().max(600).optional().default(''), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const assetUploadSchema = z.object({ folderId: z.string().nullable().optional(), name: z.string().min(1).max(220), originalName: z.string().min(1).max(260).optional(), description: z.string().max(1200).optional().default(''), mimeType: z.string().max(160).optional(), packagePath: z.string().max(500).optional().default(''), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const assetUpdateSchema = z.object({ folderId: z.string().nullable().optional(), name: z.string().min(1).max(220).optional(), description: z.string().max(1200).optional(), packagePath: z.string().max(500).optional(), visibility: visibilitySchema.optional(), groupId: z.string().nullable().optional() }); export const assetLinkSchema = z.object({ targetType: z.enum(['script', 'runplan', 'psadt_profile', 'intune_deployment']).default('script'), targetId: z.string().min(1), linkRole: z.enum(['reference', 'package-file', 'installer', 'support-file', 'icon', 'detection-script']).default('reference'), packagePath: z.string().max(500).optional().default('') }); export const variableNameSchema = z.string().min(1).max(80).regex(/^[A-Za-z_][A-Za-z0-9_]*$/); export const customVariableSchema = z.object({ name: variableNameSchema, description: z.string().max(500).optional().default(''), category: z.string().min(1).max(80).optional().default('Custom'), value: z.string().optional().default(''), valueType: z.enum(['string', 'number', 'boolean', 'json']).default('string'), sensitive: z.boolean().or(z.number()).optional().default(false), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const psadtProfileSchema = z.object({ name: z.string().min(1).max(160), appVendor: z.string().max(120).optional().default(''), appName: z.string().max(160).optional().default(''), appVersion: z.string().max(80).optional().default(''), appArch: z.enum(['x64', 'x86', 'neutral']).optional().default('x64'), appLang: z.string().max(40).optional().default('EN'), appRevision: z.string().max(40).optional().default('01'), toolkitVersion: z.enum(['v4', 'v3-compatible']).optional().default('v4'), templateVersion: z.enum(['v4', 'v3']).optional().default('v4'), deploymentType: z.enum(['Install', 'Uninstall', 'Repair']).optional().default('Install'), deployMode: z.enum(['Auto', 'Interactive', 'Silent', 'NonInteractive']).optional().default('Auto'), requireAdmin: z.boolean().or(z.number()).optional().default(true), zeroConfig: z.boolean().or(z.number()).optional().default(false), suppressReboot: z.boolean().or(z.number()).optional().default(false), closeProcesses: z.array(z.object({ name: z.string().min(1), description: z.string().optional().default('') })).optional().default([]), installTasks: z.array(z.object({ type: z.enum(['exe', 'msi', 'msp', 'script']).default('exe'), phase: z.enum(['Pre-Install', 'Install', 'Post-Install', 'Pre-Uninstall', 'Uninstall', 'Post-Uninstall', 'Pre-Repair', 'Repair', 'Post-Repair']).default('Install'), filePath: z.string().min(1), arguments: z.string().optional().default(''), secureArguments: z.boolean().or(z.number()).optional().default(false) })).optional().default([]), uiPlan: z.record(z.string(), z.any()).optional().default({}), configPlan: z.record(z.string(), z.any()).optional().default({}), admxPlan: z.record(z.string(), z.any()).optional().default({}), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const psadtValidationSchema = z.object({ content: z.string().optional().default(''), scriptId: z.string().optional(), profileId: z.string().optional(), platform: z.enum(['intune', 'configmgr', 'gpo', 'manual']).optional().default('intune') }); export const psadtMigrationSchema = z.object({ content: z.string().optional().default(''), scriptId: z.string().optional(), targetVersion: z.string().optional().default('4.2.0'), createScript: z.boolean().or(z.number()).optional().default(false), nameSuffix: z.string().max(80).optional().default('PSADT 4.2 migrated') }); export const intuneDeploymentSchema = z.object({ name: z.string().min(1).max(160), profileId: z.string().nullable().optional(), scriptId: z.string().nullable().optional(), applicationId: z.string().nullable().optional(), sourceFolder: z.string().max(500).optional().default(''), intunewinFile: z.string().max(500).optional().default(''), appType: z.string().min(1).max(80).optional().default('Windows app (Win32)'), commandStyle: z.enum(['v4', 'legacy-v3']).optional().default('v4'), installBehavior: z.enum(['system', 'user']).optional().default('system'), restartBehavior: z.enum(['return-code', 'suppress', 'intune']).optional().default('return-code'), uiMode: z.enum(['native-v4', 'serviceui-legacy', 'silent']).optional().default('native-v4'), requirements: z.object({ architecture: z.enum(['x64', 'x86', 'both']).optional().default('x64'), minOs: z.string().max(80).optional().default('Windows 10 22H2'), diskSpaceMb: z.number().or(z.string()).optional().default(0), runAs32Bit: z.boolean().or(z.number()).optional().default(false) }).optional().default({}), installCommand: z.string().max(500).optional().default(''), uninstallCommand: z.string().max(500).optional().default(''), detectionType: z.enum(['msi-product-code', 'file-version', 'registry', 'custom-script']).optional().default('custom-script'), detectionRule: z.string().max(3000).optional().default(''), returnCodes: z.array(z.object({ code: z.string().or(z.number()), type: z.string().min(1).max(40), meaning: z.string().max(240).optional().default('') })).optional().default([]), assignments: z.array(z.object({ ring: z.string().min(1).max(80), intent: z.enum(['available', 'required', 'uninstall', 'exclude']).optional().default('required'), groupName: z.string().max(160).optional().default(''), groupId: z.string().max(120).optional().default(''), groupMode: z.enum(['group', 'allDevices', 'allUsers']).optional().default('group'), filterId: z.string().max(120).optional().default(''), filterType: z.enum(['none', 'include', 'exclude']).optional().default('none'), autoUpdate: z.boolean().or(z.number()).optional().default(false), notes: z.string().max(240).optional().default('') })).optional().default([]), relationships: z.array(z.object({ kind: z.enum(['supersedence', 'dependency']).default('supersedence'), targetAppId: z.string().min(1).max(160), targetName: z.string().max(200).optional().default(''), mode: z.string().max(40).optional().default('') })).optional().default([]), status: z.enum(['draft', 'ready', 'published', 'retired']).optional().default('draft'), autoPromoteAfterHours: z.number().int().min(0).or(z.string()).optional().default(0), autoPromoteRing: z.string().max(80).optional().default(''), autoPromoteIntent: z.enum(['available', 'required', 'uninstall', 'exclude']).optional().default('required'), graphAppId: z.string().max(160).optional().default(''), graphConnectionId: z.string().nullable().optional(), notes: z.string().max(1000).optional().default(''), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const applicationSchema = z.object({ name: z.string().min(1).max(160), vendor: z.string().max(120).optional().default(''), publisher: z.string().max(160).optional().default(''), currentVersion: z.string().max(80).optional().default(''), latestKnownVersion: z.string().max(80).optional().default(''), versionSource: z.enum(['manual', 'url', 'winget']).optional().default('manual'), versionSourceRef: z.string().max(500).optional().default(''), graphConnectionId: z.string().nullable().optional(), currentGraphAppId: z.string().max(160).optional().default(''), autoCheck: z.boolean().or(z.number()).optional().default(false), notes: z.string().max(1000).optional().default(''), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const applicationImportSchema = z.object({ connectionId: z.string().min(1), graphAppId: z.string().min(1).max(160), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const graphConnectionSchema = z.object({ name: z.string().min(1).max(160), tenantId: z.string().min(1).max(120), clientId: z.string().min(1).max(120), clientSecret: z.string().optional().default(''), cloud: z.enum(['global', 'gcc', 'gcchigh', 'dod', 'china', 'custom']).optional().default('global'), graphBaseUrl: z.string().url().refine(isAllowedGraphHost, { message: 'graphBaseUrl must be a recognized Microsoft Graph host' }).optional().default('https://graph.microsoft.com'), authorityHost: z.string().url().refine(isAllowedAuthorityHost, { message: 'authorityHost must be a recognized Microsoft login host' }).optional().default('https://login.microsoftonline.com'), defaultApiVersion: z.enum(['v1.0', 'beta']).optional().default('v1.0'), enabled: z.boolean().or(z.number()).optional().default(true), allowWrite: z.boolean().or(z.number()).optional().default(false), requireApproval: z.boolean().or(z.number()).optional().default(false), publisherIds: z.array(z.string()).optional().default([]), approverIds: z.array(z.string()).optional().default([]), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional() }); export const graphDeploymentLinkSchema = z.object({ connectionId: z.string().optional().default(''), graphAppId: z.string().min(1).max(160) }); export const graphDeploymentSyncSchema = z.object({ connectionId: z.string().optional().default(''), graphAppId: z.string().optional().default(''), mode: z.enum(['reports', 'legacy-status', 'both']).optional().default('reports') }); const scriptPayloadSchema = z.object({ folderId: z.string().nullable().optional(), folder_id: z.string().nullable().optional(), name: z.string().min(1), description: z.string().optional().default(''), content: z.string().default(''), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional(), group_id: z.string().nullable().optional() }); export const scriptTestRunSchema = z.object({ hostId: z.string().min(1), credentialId: z.string().min(1) }); const runPlanPayloadSchema = z.object({ name: z.string().min(1), description: z.string().optional().default(''), scriptId: z.string().optional(), script_id: z.string().optional(), visibility: visibilitySchema.default('personal'), groupId: z.string().nullable().optional(), group_id: z.string().nullable().optional(), parallel: z.boolean().or(z.number()).default(true), hostIds: z.array(z.string()).optional() }); export function normalizeScriptPayload(body) { const parsed = scriptPayloadSchema.parse(body); return { folderId: parsed.folderId ?? parsed.folder_id ?? null, name: parsed.name, description: parsed.description || '', content: parsed.content || '', visibility: parsed.visibility, groupId: parsed.visibility === 'group' ? parsed.groupId ?? parsed.group_id ?? null : null }; } export function normalizeRunPlanPayload(body) { const parsed = runPlanPayloadSchema.parse(body); return { name: parsed.name, description: parsed.description || '', scriptId: parsed.scriptId || parsed.script_id, visibility: parsed.visibility, groupId: parsed.visibility === 'group' ? parsed.groupId ?? parsed.group_id ?? null : null, parallel: Boolean(parsed.parallel), hostIds: parsed.hostIds || [] }; }