AI Implementation feature(861): Admin Area Challenges: List, Import/Export and Add/Edit Modal (#40)
This commit was merged in pull request #40.
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const DIFFICULTIES = ['LOW', 'MEDIUM', 'HIGH'] as const;
|
||||
export const PROTOCOLS = ['NC', 'WEB'] as const;
|
||||
export const CHALLENGE_FORMAT_VERSION = 1;
|
||||
export const CHALLENGE_FORMAT = 'hipctf-challenges';
|
||||
|
||||
export const ChallengeNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, 'Name is required')
|
||||
.max(128, 'Name must be 128 characters or fewer');
|
||||
|
||||
export const ChallengeDifficultySchema = z.enum(DIFFICULTIES);
|
||||
export const ChallengeProtocolSchema = z.enum(PROTOCOLS);
|
||||
|
||||
export const ChallengeIdParamSchema = z.object({
|
||||
id: z.string().min(1).max(64),
|
||||
});
|
||||
|
||||
export const ListChallengesQuerySchema = z.object({
|
||||
q: z.string().trim().min(1).max(128).optional(),
|
||||
categoryId: z.string().min(1).max(64).optional(),
|
||||
});
|
||||
export type ListChallengesQuery = z.infer<typeof ListChallengesQuerySchema>;
|
||||
|
||||
export const ChallengeFileManifestSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
stagedToken: z.string().min(1).max(128).optional(),
|
||||
originalFilename: z.string().min(1).max(255),
|
||||
mimeType: z.string().min(1).max(255),
|
||||
sizeBytes: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||
remove: z.boolean().optional(),
|
||||
});
|
||||
export type ChallengeFileManifest = z.infer<typeof ChallengeFileManifestSchema>;
|
||||
|
||||
const portField = z
|
||||
.number()
|
||||
.int()
|
||||
.min(1)
|
||||
.max(65535)
|
||||
.nullable()
|
||||
.optional();
|
||||
|
||||
const baseShape = {
|
||||
descriptionMd: z.string().max(64_000),
|
||||
categoryId: z.string().min(1).max(64),
|
||||
difficulty: ChallengeDifficultySchema,
|
||||
initialPoints: z.number().int().min(1).max(100000),
|
||||
minimumPoints: z.number().int().min(1).max(100000),
|
||||
decaySolves: z.number().int().min(1).max(10000),
|
||||
flag: z.string().min(1, 'Flag is required').max(4096),
|
||||
protocol: ChallengeProtocolSchema,
|
||||
port: portField,
|
||||
ipAddress: z.string().max(255).default(''),
|
||||
enabled: z.boolean().default(true),
|
||||
files: z.array(ChallengeFileManifestSchema).max(64).optional(),
|
||||
};
|
||||
|
||||
export const CreateChallengeSchema = z
|
||||
.object({
|
||||
name: ChallengeNameSchema,
|
||||
...baseShape,
|
||||
})
|
||||
.superRefine((val, ctx) => {
|
||||
if (val.minimumPoints > val.initialPoints) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['minimumPoints'],
|
||||
message: 'Minimum points must be less than or equal to initial points',
|
||||
});
|
||||
}
|
||||
if (val.protocol === 'NC' && (val.port === null || val.port === undefined)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['port'],
|
||||
message: 'Port is required for NC protocol',
|
||||
});
|
||||
}
|
||||
});
|
||||
export type CreateChallengePayload = z.infer<typeof CreateChallengeSchema>;
|
||||
|
||||
export const UpdateChallengeSchema = z
|
||||
.object({
|
||||
name: ChallengeNameSchema.optional(),
|
||||
...baseShape,
|
||||
files: z.array(ChallengeFileManifestSchema).max(64).optional(),
|
||||
})
|
||||
.superRefine((val, ctx) => {
|
||||
if (val.initialPoints !== undefined && val.minimumPoints !== undefined && val.minimumPoints > val.initialPoints) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['minimumPoints'],
|
||||
message: 'Minimum points must be less than or equal to initial points',
|
||||
});
|
||||
}
|
||||
if (val.protocol === 'NC' && (val.port === null || val.port === undefined)) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['port'],
|
||||
message: 'Port is required for NC protocol',
|
||||
});
|
||||
}
|
||||
});
|
||||
export type UpdateChallengePayload = z.infer<typeof UpdateChallengeSchema>;
|
||||
|
||||
export const ImportQuerySchema = z.object({
|
||||
confirm: z.string().optional(),
|
||||
});
|
||||
|
||||
export const ImportChallengeFileSchema = z.object({
|
||||
originalFilename: z.string().min(1).max(255),
|
||||
mimeType: z.string().min(1).max(255),
|
||||
sizeBytes: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||
dataBase64: z.string().min(1).max(8 * 1024 * 1024),
|
||||
});
|
||||
export type ImportChallengeFilePayload = z.infer<typeof ImportChallengeFileSchema>;
|
||||
|
||||
export const ImportChallengeSchema = z.object({
|
||||
name: ChallengeNameSchema,
|
||||
descriptionMd: z.string().max(64_000),
|
||||
categorySystemKey: z.string().min(1).max(64),
|
||||
difficulty: ChallengeDifficultySchema,
|
||||
initialPoints: z.number().int().min(1).max(100000),
|
||||
minimumPoints: z.number().int().min(1).max(100000),
|
||||
decaySolves: z.number().int().min(1).max(10000),
|
||||
flag: z.string().min(1).max(4096),
|
||||
protocol: ChallengeProtocolSchema,
|
||||
port: portField,
|
||||
ipAddress: z.string().max(255).default(''),
|
||||
enabled: z.boolean().default(true),
|
||||
files: z.array(ImportChallengeFileSchema).max(64).optional(),
|
||||
});
|
||||
export type ImportChallengePayload = z.infer<typeof ImportChallengeSchema>;
|
||||
|
||||
export const ImportDocumentSchema = z.object({
|
||||
format: z.literal(CHALLENGE_FORMAT),
|
||||
version: z.number().int().min(1).max(CHALLENGE_FORMAT_VERSION),
|
||||
exportedAt: z.string().min(1),
|
||||
challenges: z.array(ImportChallengeSchema),
|
||||
});
|
||||
export type ImportDocumentPayload = z.infer<typeof ImportDocumentSchema>;
|
||||
|
||||
export const StageChallengeFileResponseSchema = z.object({
|
||||
token: z.string(),
|
||||
originalFilename: z.string(),
|
||||
mimeType: z.string(),
|
||||
sizeBytes: z.number().int(),
|
||||
});
|
||||
export type StageChallengeFileResponse = z.infer<typeof StageChallengeFileResponseSchema>;
|
||||
Reference in New Issue
Block a user