import Config from '../config/Config'; import { StatusCodes } from 'http-status-codes'; import { CustomValidator, ErrorMessage, FieldMessageFactory, Meta } from 'express-validator/src/base'; import { BailOptions, ValidationChain } from 'express-validator/src/chain'; import GitlabManager from '../managers/GitlabManager'; import express from 'express'; import logger from '../shared/logging/WinstonLogger'; import Json5FileValidator from '../shared/helpers/Json5FileValidator'; import ExerciseResultsFile from '../shared/types/Dojo/ExerciseResultsFile'; import ParamsCallbackManager from '../middlewares/ParamsCallbackManager'; import ExerciseManager from '../managers/ExerciseManager'; declare type DojoMeta = Meta & { req: express.Request }; declare type DojoCustomValidator = (input: unknown, meta: DojoMeta) => unknown; declare type DojoCustomValidatorSchemaOptions = { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: CustomValidator | ValidationChain, options?: CustomValidator } class DojoValidators { private toValidatorSchemaOptions(arg: { errorMessage?: FieldMessageFactory | ErrorMessage, negated?: boolean, bail?: boolean | BailOptions, if?: DojoCustomValidator | ValidationChain, options?: DojoCustomValidator }) { // This is a hack to make the types work with req arg as express.Request instead of Request defined by express-validator return arg as unknown as DojoCustomValidatorSchemaOptions; } private getParamValue(req: express.Request, path: string): unknown { return 'body' in req && path in req.body ? req.body[path] : req.query[path]; } readonly nullSanitizer = this.toValidatorSchemaOptions({ options: value => { try { return value === 'null' || value === 'undefined' || value === '' ? null : value; } catch ( error ) { logger.error(`null sanitizer error: ${ error }`); return value; } } }); readonly jsonSanitizer = this.toValidatorSchemaOptions({ options: value => { try { return JSON.parse(value as string); } catch ( e ) { return value; } } }); readonly templateUrlValidator = this.toValidatorSchemaOptions({ bail : true, errorMessage: 'Template doesn\'t exist or you don\'t have access to it', options : (_value, { req, path }) => { return new Promise((resolve, reject) => { const template = this.getParamValue(req, path) as string; if ( template ) { GitlabManager.checkTemplateAccess(template, req).then(templateAccess => { templateAccess !== StatusCodes.OK ? reject() : resolve(true); }); } resolve(true); }); } }); readonly templateUrlSanitizer = this.toValidatorSchemaOptions({ options: (value, { req, path }) => { try { const template = this.getParamValue(req, path); if ( template ) { const gitlabUrlWithCredentials = Config.gitlab.urls[0].replace(/^([a-z]{3,5}:\/{2})?(.*)/, `$1${ Config.gitlab.account.username }:${ Config.gitlab.account.token }@$2`); return `${ gitlabUrlWithCredentials }${ template }.git`; } else { return Config.assignment.default.template; } } catch ( error ) { logger.error(`Template url sanitizer error: ${ error }`); return value; } } }); readonly exerciseResultsValidator = this.toValidatorSchemaOptions({ bail : true, errorMessage: 'Results: not provided or invalid format', options : (_value, { req, path }) => { return new Promise((resolve, reject) => { const results = this.getParamValue(req, path) as string; if ( results ) { Json5FileValidator.validateFile(ExerciseResultsFile, results, false).isValid ? resolve(true) : reject(); } else { reject(); } }); } }); readonly exerciseIdOrUrlValidator = this.toValidatorSchemaOptions({ bail : true, errorMessage: 'ExerciseIdOrUrl: not provided or invalid', options : (_value, { req, path }) => { return new Promise((resolve, reject) => { const exerciseIdOrUrl = this.getParamValue(req, path) as string; if ( exerciseIdOrUrl ) { ParamsCallbackManager.initBoundParams(req); ExerciseManager.get(exerciseIdOrUrl).then(exercise => { req.boundParams.exercise = exercise; exercise !== undefined ? resolve(true) : reject(); }).catch(() => { reject(); }); } else { reject(); } }); } }); } export default new DojoValidators();