401 lines
17 KiB
JavaScript
401 lines
17 KiB
JavaScript
import { z } from 'zod';
|
|
import { isAllowedAuthorityHost, isAllowedGraphHost } from '../data/graphHosts.js';
|
|
import { normalizeOsFamily } from '../utils/osFamily.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.preprocess((value) => normalizeOsFamily(value, 'windows'), z.enum(['windows', 'linux', 'other'])).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(),
|
|
sourceType: z.enum(['manual', 'vmware']).optional().default('manual'),
|
|
sourceConnectionId: z.string().nullable().optional(),
|
|
sourceRef: z.string().max(300).optional().default('')
|
|
});
|
|
|
|
export const vcenterConnectionSchema = z.object({
|
|
name: z.string().min(1).max(120),
|
|
targetType: z.enum(['vcenter', 'host']).optional().default('vcenter'),
|
|
baseUrl: z.string().url(),
|
|
username: z.string().min(1).max(200),
|
|
password: z.string().optional().default(''),
|
|
apiMode: z.enum(['auto', 'api', 'rest', 'web-services']).optional().default('auto'),
|
|
requestTimeoutMs: z.coerce.number().int().min(1000).max(120000).optional().default(20000),
|
|
allowUntrustedTls: z.boolean().optional().default(false),
|
|
enabled: z.boolean().optional().default(true),
|
|
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({
|
|
connectionId: z.string().nullable().optional(),
|
|
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 hostGroupRuleSchema = z.object({
|
|
field: z.enum(['name', 'fqdn', 'address', 'tags', 'transport', 'sourceType', 'osFamily']).default('name'),
|
|
operator: z.enum(['contains', 'equals', 'startsWith', 'endsWith']).default('contains'),
|
|
value: z.string().min(1).max(200)
|
|
});
|
|
|
|
export const hostGroupSchema = z.object({
|
|
name: z.string().min(1).max(160),
|
|
description: z.string().max(600).optional().default(''),
|
|
mode: z.enum(['manual', 'dynamic']).default('manual'),
|
|
matchMode: z.enum(['all', 'any']).optional().default('all'),
|
|
rules: z.array(hostGroupRuleSchema).optional().default([]),
|
|
hostIds: z.array(z.string()).optional().default([]),
|
|
visibility: visibilitySchema.default('shared'),
|
|
groupId: z.string().nullable().optional()
|
|
});
|
|
|
|
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().optional().default(''),
|
|
hostGroupId: z.string().optional().default(''),
|
|
credentialId: z.string().min(1)
|
|
}).refine((value) => Boolean(value.hostId || value.hostGroupId), {
|
|
message: 'Choose a host or host group.'
|
|
});
|
|
|
|
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(),
|
|
hostGroupIds: z.array(z.string()).optional()
|
|
});
|
|
|
|
export const scriptCompatibilitySchema = z.object({
|
|
hostId: z.string().optional().default(''),
|
|
hostGroupId: z.string().optional().default(''),
|
|
hostIds: z.array(z.string()).optional().default([]),
|
|
hostGroupIds: z.array(z.string()).optional().default([])
|
|
});
|
|
|
|
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 || [],
|
|
hostGroupIds: parsed.hostGroupIds || []
|
|
};
|
|
}
|