reliant-type 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1305 -0
- package/dist/cjs/constants/SECURITY_CONSTANTS.js +49 -0
- package/dist/cjs/constants/SECURITY_CONSTANTS.js.map +1 -0
- package/dist/cjs/constants/VALIDATION_CONSTANTS.js +103 -0
- package/dist/cjs/constants/VALIDATION_CONSTANTS.js.map +1 -0
- package/dist/cjs/core/schema/extensions/SmartInference.js +200 -0
- package/dist/cjs/core/schema/extensions/SmartInference.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/Docs.js +100 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/Docs.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/DocumentationGenerator.js +192 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/DocumentationGenerator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/InteractiveDocumentationGenerator.js +174 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/InteractiveDocumentationGenerator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/SchemaAnalyzer.js +54 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/SchemaAnalyzer.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/TypeScriptGenerator.js +50 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/TypeScriptGenerator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/index.js +15 -0
- package/dist/cjs/core/schema/extensions/components/AutoDocumentation/index.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/ConditionalBuilder.js +70 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/ConditionalBuilder.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/ConditionalElse.js +40 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/ConditionalElse.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/ConditionalThen.js +19 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/ConditionalThen.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/MultiConditionalBuilder.js +20 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/MultiConditionalBuilder.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/MultiConditionalThen.js +49 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/MultiConditionalThen.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/index.js +98 -0
- package/dist/cjs/core/schema/extensions/components/ConditionalValidation/index.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/FormValidator.js +85 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/FormValidator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/LiveValidator.js +133 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/LiveValidator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/StreamValidator.js +351 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/StreamValidator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/index.js +109 -0
- package/dist/cjs/core/schema/extensions/components/RealtimeValidation/index.js.map +1 -0
- package/dist/cjs/core/schema/extensions/index.js +61 -0
- package/dist/cjs/core/schema/extensions/index.js.map +1 -0
- package/dist/cjs/core/schema/extensions/mods/index.js +51 -0
- package/dist/cjs/core/schema/extensions/mods/index.js.map +1 -0
- package/dist/cjs/core/schema/extensions/mods/openapi-converter.js +227 -0
- package/dist/cjs/core/schema/extensions/mods/openapi-converter.js.map +1 -0
- package/dist/cjs/core/schema/extensions/mods/typescript-generator.js +287 -0
- package/dist/cjs/core/schema/extensions/mods/typescript-generator.js.map +1 -0
- package/dist/cjs/core/schema/extensions/mods/validation-engine.js +224 -0
- package/dist/cjs/core/schema/extensions/mods/validation-engine.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/Interface.js +277 -0
- package/dist/cjs/core/schema/mode/interfaces/Interface.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/InterfaceSchema.js +1431 -0
- package/dist/cjs/core/schema/mode/interfaces/InterfaceSchema.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/evaluator/ConditionalEvaluator.js +520 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/evaluator/ConditionalEvaluator.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/parser/ConditionalAST.js +624 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/parser/ConditionalAST.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/parser/ConditionalLexer.js +811 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/parser/ConditionalLexer.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/parser/ConditionalParser.js +599 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/parser/ConditionalParser.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/types/ConditionalTypes.js +89 -0
- package/dist/cjs/core/schema/mode/interfaces/conditional/types/ConditionalTypes.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/errors/ErrorHandler.js +356 -0
- package/dist/cjs/core/schema/mode/interfaces/errors/ErrorHandler.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/errors/types/errors.type.js +80 -0
- package/dist/cjs/core/schema/mode/interfaces/errors/types/errors.type.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/precompilation/FieldPrecompilers.js +778 -0
- package/dist/cjs/core/schema/mode/interfaces/precompilation/FieldPrecompilers.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/precompilation/SchemaPrecompiler.js +523 -0
- package/dist/cjs/core/schema/mode/interfaces/precompilation/SchemaPrecompiler.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/ConditionalTypes.js +681 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/ConditionalTypes.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/IDESupport.js +430 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/IDESupport.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/TypeInference.js +225 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/TypeInference.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/index.js +44 -0
- package/dist/cjs/core/schema/mode/interfaces/typescript/index.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/ConstraintParser.js +1134 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/ConstraintParser.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/TypeGuards.js +256 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/TypeGuards.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/TypeValidators.js +429 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/TypeValidators.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/UnionCache.js +404 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/UnionCache.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/ValidationHelpers.js +851 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/ValidationHelpers.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/mods/passValidator.js +262 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/mods/passValidator.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/mods/securityValidator.js +887 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/mods/securityValidator.js.map +1 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/mods/urlValidation.js +191 -0
- package/dist/cjs/core/schema/mode/interfaces/validators/mods/urlValidation.js.map +1 -0
- package/dist/cjs/core/schema/optimization/ObjectValidationCache.js +462 -0
- package/dist/cjs/core/schema/optimization/ObjectValidationCache.js.map +1 -0
- package/dist/cjs/core/schema/optimization/PerformanceMonitor.js +773 -0
- package/dist/cjs/core/schema/optimization/PerformanceMonitor.js.map +1 -0
- package/dist/cjs/core/schema/optimization/SchemaCompiler.js +600 -0
- package/dist/cjs/core/schema/optimization/SchemaCompiler.js.map +1 -0
- package/dist/cjs/core/types/ValidatorTypes.js +70 -0
- package/dist/cjs/core/types/ValidatorTypes.js.map +1 -0
- package/dist/cjs/core/types/parser.type.js +12 -0
- package/dist/cjs/core/types/parser.type.js.map +1 -0
- package/dist/cjs/core/utils/Make.js +61 -0
- package/dist/cjs/core/utils/Make.js.map +1 -0
- package/dist/cjs/core/utils/Mod.js +1033 -0
- package/dist/cjs/core/utils/Mod.js.map +1 -0
- package/dist/cjs/core/utils/UrlArgs.js +102 -0
- package/dist/cjs/core/utils/UrlArgs.js.map +1 -0
- package/dist/cjs/core/utils/arrayToEnum.js +18 -0
- package/dist/cjs/core/utils/arrayToEnum.js.map +1 -0
- package/dist/cjs/core/utils/createUrlArgsEnumFArray.js +13 -0
- package/dist/cjs/core/utils/createUrlArgsEnumFArray.js.map +1 -0
- package/dist/cjs/core/utils/securityHelpers.js +215 -0
- package/dist/cjs/core/utils/securityHelpers.js.map +1 -0
- package/dist/cjs/core/utils/securityValidatorHelpers.js +65 -0
- package/dist/cjs/core/utils/securityValidatorHelpers.js.map +1 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/esm/constants/SECURITY_CONSTANTS.js +47 -0
- package/dist/esm/constants/SECURITY_CONSTANTS.js.map +1 -0
- package/dist/esm/constants/VALIDATION_CONSTANTS.js +98 -0
- package/dist/esm/constants/VALIDATION_CONSTANTS.js.map +1 -0
- package/dist/esm/core/schema/extensions/SmartInference.js +197 -0
- package/dist/esm/core/schema/extensions/SmartInference.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/Docs.js +98 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/Docs.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/DocumentationGenerator.js +190 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/DocumentationGenerator.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/InteractiveDocumentationGenerator.js +172 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/InteractiveDocumentationGenerator.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/SchemaAnalyzer.js +52 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/SchemaAnalyzer.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/TypeScriptGenerator.js +48 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/TypeScriptGenerator.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/index.js +12 -0
- package/dist/esm/core/schema/extensions/components/AutoDocumentation/index.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/ConditionalBuilder.js +68 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/ConditionalBuilder.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/ConditionalElse.js +38 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/ConditionalElse.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/ConditionalThen.js +17 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/ConditionalThen.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/MultiConditionalBuilder.js +18 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/MultiConditionalBuilder.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/MultiConditionalThen.js +45 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/MultiConditionalThen.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/index.js +92 -0
- package/dist/esm/core/schema/extensions/components/ConditionalValidation/index.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/FormValidator.js +83 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/FormValidator.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/LiveValidator.js +131 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/LiveValidator.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/StreamValidator.js +349 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/StreamValidator.js.map +1 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/index.js +103 -0
- package/dist/esm/core/schema/extensions/components/RealtimeValidation/index.js.map +1 -0
- package/dist/esm/core/schema/extensions/index.js +53 -0
- package/dist/esm/core/schema/extensions/index.js.map +1 -0
- package/dist/esm/core/schema/extensions/mods/index.js +47 -0
- package/dist/esm/core/schema/extensions/mods/index.js.map +1 -0
- package/dist/esm/core/schema/extensions/mods/openapi-converter.js +225 -0
- package/dist/esm/core/schema/extensions/mods/openapi-converter.js.map +1 -0
- package/dist/esm/core/schema/extensions/mods/typescript-generator.js +284 -0
- package/dist/esm/core/schema/extensions/mods/typescript-generator.js.map +1 -0
- package/dist/esm/core/schema/extensions/mods/validation-engine.js +222 -0
- package/dist/esm/core/schema/extensions/mods/validation-engine.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/Interface.js +269 -0
- package/dist/esm/core/schema/mode/interfaces/Interface.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/InterfaceSchema.js +1429 -0
- package/dist/esm/core/schema/mode/interfaces/InterfaceSchema.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/evaluator/ConditionalEvaluator.js +518 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/evaluator/ConditionalEvaluator.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/parser/ConditionalAST.js +620 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/parser/ConditionalAST.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/parser/ConditionalLexer.js +809 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/parser/ConditionalLexer.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/parser/ConditionalParser.js +597 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/parser/ConditionalParser.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/types/ConditionalTypes.js +89 -0
- package/dist/esm/core/schema/mode/interfaces/conditional/types/ConditionalTypes.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/errors/ErrorHandler.js +354 -0
- package/dist/esm/core/schema/mode/interfaces/errors/ErrorHandler.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/errors/types/errors.type.js +80 -0
- package/dist/esm/core/schema/mode/interfaces/errors/types/errors.type.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/precompilation/FieldPrecompilers.js +776 -0
- package/dist/esm/core/schema/mode/interfaces/precompilation/FieldPrecompilers.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/precompilation/SchemaPrecompiler.js +521 -0
- package/dist/esm/core/schema/mode/interfaces/precompilation/SchemaPrecompiler.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/ConditionalTypes.js +681 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/ConditionalTypes.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/IDESupport.js +428 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/IDESupport.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/TypeInference.js +223 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/TypeInference.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/index.js +35 -0
- package/dist/esm/core/schema/mode/interfaces/typescript/index.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/ConstraintParser.js +1132 -0
- package/dist/esm/core/schema/mode/interfaces/validators/ConstraintParser.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/TypeGuards.js +254 -0
- package/dist/esm/core/schema/mode/interfaces/validators/TypeGuards.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/TypeValidators.js +427 -0
- package/dist/esm/core/schema/mode/interfaces/validators/TypeValidators.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/UnionCache.js +400 -0
- package/dist/esm/core/schema/mode/interfaces/validators/UnionCache.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/ValidationHelpers.js +849 -0
- package/dist/esm/core/schema/mode/interfaces/validators/ValidationHelpers.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/mods/passValidator.js +260 -0
- package/dist/esm/core/schema/mode/interfaces/validators/mods/passValidator.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/mods/securityValidator.js +881 -0
- package/dist/esm/core/schema/mode/interfaces/validators/mods/securityValidator.js.map +1 -0
- package/dist/esm/core/schema/mode/interfaces/validators/mods/urlValidation.js +189 -0
- package/dist/esm/core/schema/mode/interfaces/validators/mods/urlValidation.js.map +1 -0
- package/dist/esm/core/schema/optimization/ObjectValidationCache.js +460 -0
- package/dist/esm/core/schema/optimization/ObjectValidationCache.js.map +1 -0
- package/dist/esm/core/schema/optimization/PerformanceMonitor.js +771 -0
- package/dist/esm/core/schema/optimization/PerformanceMonitor.js.map +1 -0
- package/dist/esm/core/schema/optimization/SchemaCompiler.js +598 -0
- package/dist/esm/core/schema/optimization/SchemaCompiler.js.map +1 -0
- package/dist/esm/core/types/ValidatorTypes.js +65 -0
- package/dist/esm/core/types/ValidatorTypes.js.map +1 -0
- package/dist/esm/core/types/parser.type.js +12 -0
- package/dist/esm/core/types/parser.type.js.map +1 -0
- package/dist/esm/core/utils/Make.js +59 -0
- package/dist/esm/core/utils/Make.js.map +1 -0
- package/dist/esm/core/utils/Mod.js +1031 -0
- package/dist/esm/core/utils/Mod.js.map +1 -0
- package/dist/esm/core/utils/UrlArgs.js +98 -0
- package/dist/esm/core/utils/UrlArgs.js.map +1 -0
- package/dist/esm/core/utils/arrayToEnum.js +16 -0
- package/dist/esm/core/utils/arrayToEnum.js.map +1 -0
- package/dist/esm/core/utils/createUrlArgsEnumFArray.js +11 -0
- package/dist/esm/core/utils/createUrlArgsEnumFArray.js.map +1 -0
- package/dist/esm/core/utils/securityHelpers.js +207 -0
- package/dist/esm/core/utils/securityHelpers.js.map +1 -0
- package/dist/esm/core/utils/securityValidatorHelpers.js +62 -0
- package/dist/esm/core/utils/securityValidatorHelpers.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/schema.d.ts +2595 -0
- package/docs/ALL_TYPES.md +120 -0
- package/docs/API-STABILITY.md +336 -0
- package/docs/CONDITIONAL-VALIDATION.md +637 -0
- package/docs/EXAMPLES.md +876 -0
- package/docs/FIELD-TYPES.md +829 -0
- package/docs/GETTING-STARTED.md +394 -0
- package/docs/LIVE-UTILITY.md +1137 -0
- package/docs/QUICK-REFERENCE.md +346 -0
- package/docs/README.md +205 -0
- package/docs/VSCODE-EXTENSION.md +458 -0
- package/package.json +219 -0
- package/src/bench/BENCHMARK-RESULTS.md +211 -0
- package/src/bench/benchmark-results.json +148 -0
- package/src/bench/performance-comparison.ts +218 -0
- package/src/bench/precompilation-benchmark.ts +218 -0
- package/src/constants/SECURITY_CONSTANTS.ts +44 -0
- package/src/constants/VALIDATION_CONSTANTS.ts +176 -0
- package/src/core/README.md +395 -0
- package/src/core/compiler/SchemaTransformer.ts +279 -0
- package/src/core/compiler/TypeAnalyzer.ts +378 -0
- package/src/core/compiler/TypeScriptCompilerIntegration.ts +220 -0
- package/src/core/compiler/TypeToSchemaConverter.ts +288 -0
- package/src/core/index.ts +70 -0
- package/src/core/schema/extensions/AutoDocumentation.ts +572 -0
- package/src/core/schema/extensions/ConditionalValidation.ts +330 -0
- package/src/core/schema/extensions/README.md +171 -0
- package/src/core/schema/extensions/RealtimeValidation.ts +656 -0
- package/src/core/schema/extensions/SmartInference.ts +224 -0
- package/src/core/schema/extensions/components/AutoDocumentation/Docs.ts +98 -0
- package/src/core/schema/extensions/components/AutoDocumentation/DocumentationGenerator.ts +201 -0
- package/src/core/schema/extensions/components/AutoDocumentation/InteractiveDocumentationGenerator.ts +176 -0
- package/src/core/schema/extensions/components/AutoDocumentation/OpenAPIGenerator.ts +175 -0
- package/src/core/schema/extensions/components/AutoDocumentation/SchemaAnalyzer.ts +49 -0
- package/src/core/schema/extensions/components/AutoDocumentation/TypeScriptGenerator.ts +54 -0
- package/src/core/schema/extensions/components/AutoDocumentation/index.ts +17 -0
- package/src/core/schema/extensions/components/ConditionalValidation/ConditionalBuilder.ts +101 -0
- package/src/core/schema/extensions/components/ConditionalValidation/ConditionalElse.ts +65 -0
- package/src/core/schema/extensions/components/ConditionalValidation/ConditionalThen.ts +33 -0
- package/src/core/schema/extensions/components/ConditionalValidation/Extend.ts +75 -0
- package/src/core/schema/extensions/components/ConditionalValidation/MultiConditionalBuilder.ts +16 -0
- package/src/core/schema/extensions/components/ConditionalValidation/MultiConditionalThen.ts +50 -0
- package/src/core/schema/extensions/components/ConditionalValidation/index.ts +104 -0
- package/src/core/schema/extensions/components/RealtimeValidation/FormValidator.ts +88 -0
- package/src/core/schema/extensions/components/RealtimeValidation/LiveValidator.ts +171 -0
- package/src/core/schema/extensions/components/RealtimeValidation/StreamValidator.ts +397 -0
- package/src/core/schema/extensions/components/RealtimeValidation/index.ts +114 -0
- package/src/core/schema/extensions/index.ts +76 -0
- package/src/core/schema/extensions/mods/index.ts +131 -0
- package/src/core/schema/extensions/mods/openapi-converter.ts +338 -0
- package/src/core/schema/extensions/mods/typescript-generator.ts +379 -0
- package/src/core/schema/extensions/mods/validation-engine.ts +295 -0
- package/src/core/schema/mode/interfaces/Interface.ts +364 -0
- package/src/core/schema/mode/interfaces/InterfaceSchema.ts +1838 -0
- package/src/core/schema/mode/interfaces/README.md +278 -0
- package/src/core/schema/mode/interfaces/conditional/evaluator/ConditionalEvaluator.ts +657 -0
- package/src/core/schema/mode/interfaces/conditional/parser/ConditionalAST.ts +826 -0
- package/src/core/schema/mode/interfaces/conditional/parser/ConditionalLexer.ts +992 -0
- package/src/core/schema/mode/interfaces/conditional/parser/ConditionalParser.ts +803 -0
- package/src/core/schema/mode/interfaces/conditional/parser/readme.md +406 -0
- package/src/core/schema/mode/interfaces/conditional/types/ConditionalTypes.ts +273 -0
- package/src/core/schema/mode/interfaces/errors/ErrorHandler.ts +624 -0
- package/src/core/schema/mode/interfaces/errors/types/errors.type.ts +102 -0
- package/src/core/schema/mode/interfaces/precompilation/FieldPrecompilers.ts +962 -0
- package/src/core/schema/mode/interfaces/precompilation/SchemaPrecompiler.ts +667 -0
- package/src/core/schema/mode/interfaces/typescript/ConditionalTypes.ts +1534 -0
- package/src/core/schema/mode/interfaces/typescript/IDESupport.ts +534 -0
- package/src/core/schema/mode/interfaces/typescript/TypeInference.ts +737 -0
- package/src/core/schema/mode/interfaces/typescript/index.ts +92 -0
- package/src/core/schema/mode/interfaces/validators/ConstraintParser.ts +1438 -0
- package/src/core/schema/mode/interfaces/validators/EnhancedErrorReporting.ts +227 -0
- package/src/core/schema/mode/interfaces/validators/TypeGuards.ts +288 -0
- package/src/core/schema/mode/interfaces/validators/TypeValidators.ts +660 -0
- package/src/core/schema/mode/interfaces/validators/UnionCache.ts +508 -0
- package/src/core/schema/mode/interfaces/validators/ValidationHelpers.ts +1257 -0
- package/src/core/schema/mode/interfaces/validators/index.ts +21 -0
- package/src/core/schema/mode/interfaces/validators/mods/passValidator.ts +424 -0
- package/src/core/schema/mode/interfaces/validators/mods/securityValidator.ts +1634 -0
- package/src/core/schema/mode/interfaces/validators/mods/urlValidation.ts +333 -0
- package/src/core/schema/optimization/ObjectValidationCache.ts +560 -0
- package/src/core/schema/optimization/PerformanceInitializer.ts +188 -0
- package/src/core/schema/optimization/PerformanceMonitor.ts +898 -0
- package/src/core/schema/optimization/SchemaCompiler.ts +730 -0
- package/src/core/testing/TestDataGenerator.ts +590 -0
- package/src/core/types/SchemaValidator.type.ts +210 -0
- package/src/core/types/ValidatorTypes.ts +93 -0
- package/src/core/types/extension.type.ts +109 -0
- package/src/core/types/objValidationCache.ts +17 -0
- package/src/core/types/parser.type.ts +15 -0
- package/src/core/types/perfoMonitor.ts +37 -0
- package/src/core/types/scompiler.ts +22 -0
- package/src/core/types/securityValidator.type.ts +10 -0
- package/src/core/types/types.ts +154 -0
- package/src/core/utils/Make.ts +97 -0
- package/src/core/utils/Mod.ts +1168 -0
- package/src/core/utils/UrlArgs.ts +124 -0
- package/src/core/utils/arrayToEnum.ts +89 -0
- package/src/core/utils/createUrlArgsEnumFArray.ts +11 -0
- package/src/core/utils/securityHelpers.ts +341 -0
- package/src/core/utils/securityValidatorHelpers.ts +76 -0
- package/src/index.ts +124 -0
|
@@ -0,0 +1,1634 @@
|
|
|
1
|
+
import { SECURITY_CONSTANTS } from "../../../../../../constants/SECURITY_CONSTANTS";
|
|
2
|
+
import { MAX_OBJECT_DEPTH } from "../../../../../../constants/VALIDATION_CONSTANTS";
|
|
3
|
+
import { SchemaValidationResult } from "../../../../../types/types";
|
|
4
|
+
import {
|
|
5
|
+
validateJsonDeep,
|
|
6
|
+
validateJsonSchema,
|
|
7
|
+
validateIPv4,
|
|
8
|
+
validateIPv6,
|
|
9
|
+
normalizeIPv6,
|
|
10
|
+
validateObjectDeep,
|
|
11
|
+
validateObjectSchema,
|
|
12
|
+
} from "../../../../../utils/securityHelpers";
|
|
13
|
+
import Ajv from "ajv";
|
|
14
|
+
import addFormats from "ajv-formats";
|
|
15
|
+
import {
|
|
16
|
+
ValidationCache,
|
|
17
|
+
ValidationMetrics,
|
|
18
|
+
} from "../../../../../utils/securityValidatorHelpers";
|
|
19
|
+
import { ErrorHandler } from "../../errors/ErrorHandler";
|
|
20
|
+
import { ErrorCode } from "../../errors/types/errors.type";
|
|
21
|
+
|
|
22
|
+
export class SecurityValidators {
|
|
23
|
+
private static ajv: Ajv | null = null;
|
|
24
|
+
private static readonly ajvOptions = {
|
|
25
|
+
strict: false,
|
|
26
|
+
allErrors: true,
|
|
27
|
+
removeAdditional: false,
|
|
28
|
+
useDefaults: false,
|
|
29
|
+
coerceTypes: false,
|
|
30
|
+
validateFormats: true,
|
|
31
|
+
addUsedSchema: false,
|
|
32
|
+
verbose: false,
|
|
33
|
+
loadSchema: false, // Disable remote schema loading for security
|
|
34
|
+
} as const;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Initialize AJV instance with enhanced security configurations
|
|
38
|
+
*/
|
|
39
|
+
private static getAjv(): Ajv {
|
|
40
|
+
if (!this.ajv) {
|
|
41
|
+
this.ajv = new Ajv(this.ajvOptions as any);
|
|
42
|
+
addFormats(this.ajv);
|
|
43
|
+
|
|
44
|
+
// security schema with more comprehensive protections
|
|
45
|
+
this.ajv.addSchema(
|
|
46
|
+
{
|
|
47
|
+
$id: "https://nehonix.space/lib/v/reliant-type",
|
|
48
|
+
type: ["object", "array", "string", "number", "boolean", "null"],
|
|
49
|
+
definitions: {
|
|
50
|
+
secureObject: {
|
|
51
|
+
type: "object",
|
|
52
|
+
not: {
|
|
53
|
+
anyOf: [
|
|
54
|
+
...Array.from(SECURITY_CONSTANTS.DANGEROUS_PROPERTIES).map(
|
|
55
|
+
(prop) => ({
|
|
56
|
+
properties: { [prop]: {} },
|
|
57
|
+
required: [prop],
|
|
58
|
+
})
|
|
59
|
+
),
|
|
60
|
+
{ additionalProperties: { type: "function" } },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
patternProperties: {
|
|
64
|
+
"^(?!__|constructor|prototype).*": { $ref: "#" },
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
if: { type: "object" },
|
|
69
|
+
then: { $ref: "#/definitions/secureObject" },
|
|
70
|
+
else: {
|
|
71
|
+
if: { type: "array" },
|
|
72
|
+
then: { items: { $ref: "#" } },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"secure-json-v2"
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return this.ajv;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check for dangerous properties that could lead to prototype pollution
|
|
83
|
+
*/
|
|
84
|
+
private static hasDangerousProperties(
|
|
85
|
+
obj: any,
|
|
86
|
+
visited = new WeakSet()
|
|
87
|
+
): boolean {
|
|
88
|
+
if (!obj || typeof obj !== "object" || visited.has(obj)) {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
visited.add(obj);
|
|
93
|
+
|
|
94
|
+
// Check current level
|
|
95
|
+
for (const key of Object.keys(obj)) {
|
|
96
|
+
if (SECURITY_CONSTANTS.DANGEROUS_PROPERTIES.has(key)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Recursively check nested objects
|
|
102
|
+
for (const value of Object.values(obj)) {
|
|
103
|
+
if (this.hasDangerousProperties(value, visited)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Execute validation with timeout protection
|
|
113
|
+
*/
|
|
114
|
+
private static async withTimeout<T>(
|
|
115
|
+
operation: () => T | Promise<T>,
|
|
116
|
+
timeoutMs: number = SECURITY_CONSTANTS.MAX_VALIDATION_TIME
|
|
117
|
+
): Promise<T> {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
reject(new Error(`Validation timeout after ${timeoutMs}ms`));
|
|
121
|
+
}, timeoutMs);
|
|
122
|
+
|
|
123
|
+
Promise.resolve(operation()).then(
|
|
124
|
+
(result) => {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
resolve(result);
|
|
127
|
+
},
|
|
128
|
+
(error) => {
|
|
129
|
+
clearTimeout(timer);
|
|
130
|
+
reject(error);
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* text validation with comprehensive security checks
|
|
138
|
+
*/
|
|
139
|
+
static async validateText(
|
|
140
|
+
value: any,
|
|
141
|
+
options: {
|
|
142
|
+
minLength?: number;
|
|
143
|
+
maxLength?: number;
|
|
144
|
+
allowEmpty?: boolean;
|
|
145
|
+
trimWhitespace?: boolean;
|
|
146
|
+
allowedCharacters?: RegExp;
|
|
147
|
+
forbiddenPatterns?: RegExp[];
|
|
148
|
+
encoding?: "utf8" | "ascii" | "latin1";
|
|
149
|
+
preventXSS?: boolean;
|
|
150
|
+
preventSQLInjection?: boolean;
|
|
151
|
+
preventLDAPInjection?: boolean;
|
|
152
|
+
preventCommandInjection?: boolean;
|
|
153
|
+
normalizeUnicode?: boolean;
|
|
154
|
+
maxLines?: number;
|
|
155
|
+
requireAlphanumeric?: boolean;
|
|
156
|
+
allowHTML?: boolean;
|
|
157
|
+
stripHTML?: boolean;
|
|
158
|
+
timeout?: number;
|
|
159
|
+
enableMetrics?: boolean;
|
|
160
|
+
} = {}
|
|
161
|
+
): Promise<SchemaValidationResult> {
|
|
162
|
+
const startTime = Date.now();
|
|
163
|
+
const operationName = "validateText";
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const result = await this.withTimeout(async () => {
|
|
167
|
+
return this.validateTextSync(value, options);
|
|
168
|
+
}, options.timeout);
|
|
169
|
+
|
|
170
|
+
if (options.enableMetrics) {
|
|
171
|
+
ValidationMetrics.record(
|
|
172
|
+
operationName,
|
|
173
|
+
Date.now() - startTime,
|
|
174
|
+
!result.success
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
} catch (error: any) {
|
|
180
|
+
if (options.enableMetrics) {
|
|
181
|
+
ValidationMetrics.record(operationName, Date.now() - startTime, true);
|
|
182
|
+
}
|
|
183
|
+
const msg = `${error instanceof Error ? error.message : "Unknown error"}`;
|
|
184
|
+
return {
|
|
185
|
+
success: false,
|
|
186
|
+
errors: [
|
|
187
|
+
ErrorHandler.createValidationError([], msg, ErrorCode.UNKNOWN_ERROR),
|
|
188
|
+
],
|
|
189
|
+
warnings: [],
|
|
190
|
+
data: value,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
static validateTextSync(value: any, options: any): SchemaValidationResult {
|
|
196
|
+
const result: SchemaValidationResult = {
|
|
197
|
+
success: true,
|
|
198
|
+
errors: [],
|
|
199
|
+
warnings: [],
|
|
200
|
+
data: value,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const {
|
|
204
|
+
minLength = 0,
|
|
205
|
+
maxLength = SECURITY_CONSTANTS.MAX_TEXT_LENGTH,
|
|
206
|
+
allowEmpty = true,
|
|
207
|
+
trimWhitespace = false,
|
|
208
|
+
allowedCharacters,
|
|
209
|
+
forbiddenPatterns = [],
|
|
210
|
+
encoding = "utf8",
|
|
211
|
+
preventXSS = true,
|
|
212
|
+
preventSQLInjection = true,
|
|
213
|
+
preventLDAPInjection = false,
|
|
214
|
+
preventCommandInjection = false,
|
|
215
|
+
normalizeUnicode = false,
|
|
216
|
+
maxLines = Infinity,
|
|
217
|
+
requireAlphanumeric = false,
|
|
218
|
+
allowHTML = false,
|
|
219
|
+
stripHTML = false,
|
|
220
|
+
} = options;
|
|
221
|
+
|
|
222
|
+
if (typeof value !== "string") {
|
|
223
|
+
result.success = false;
|
|
224
|
+
result.errors.push(ErrorHandler.createTypeError([], "string", value));
|
|
225
|
+
return result;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
let text = value;
|
|
229
|
+
|
|
230
|
+
// Early size check to prevent DoS
|
|
231
|
+
if (text.length > maxLength) {
|
|
232
|
+
result.success = false;
|
|
233
|
+
result.errors.push(
|
|
234
|
+
ErrorHandler.createStringError(
|
|
235
|
+
[],
|
|
236
|
+
`Text exceeds maximum length of ${maxLength} characters`,
|
|
237
|
+
value,
|
|
238
|
+
ErrorCode.STRING_TOO_LONG
|
|
239
|
+
)
|
|
240
|
+
);
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Trim whitespace if requested
|
|
245
|
+
if (trimWhitespace) {
|
|
246
|
+
const trimmed = text.trim();
|
|
247
|
+
if (trimmed !== text) {
|
|
248
|
+
text = trimmed;
|
|
249
|
+
result.data = text;
|
|
250
|
+
result.warnings.push("Whitespace trimmed from text");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Unicode normalization
|
|
255
|
+
if (normalizeUnicode) {
|
|
256
|
+
try {
|
|
257
|
+
const normalized = text.normalize("NFC");
|
|
258
|
+
if (normalized !== text) {
|
|
259
|
+
text = normalized;
|
|
260
|
+
result.data = text;
|
|
261
|
+
result.warnings.push("Unicode characters normalized");
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
result.warnings.push("Unicode normalization failed");
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Empty string validation
|
|
269
|
+
if (!allowEmpty && text.length === 0) {
|
|
270
|
+
result.success = false;
|
|
271
|
+
result.errors.push(
|
|
272
|
+
ErrorHandler.createStringError(
|
|
273
|
+
[],
|
|
274
|
+
"Text cannot be empty",
|
|
275
|
+
value,
|
|
276
|
+
ErrorCode.STRING_TOO_SHORT
|
|
277
|
+
)
|
|
278
|
+
);
|
|
279
|
+
return result;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Length validation
|
|
283
|
+
if (text.length < minLength) {
|
|
284
|
+
result.success = false;
|
|
285
|
+
result.errors.push(
|
|
286
|
+
ErrorHandler.createStringError(
|
|
287
|
+
[],
|
|
288
|
+
`Text must be at least ${minLength} characters long`,
|
|
289
|
+
value,
|
|
290
|
+
ErrorCode.STRING_TOO_SHORT
|
|
291
|
+
)
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Line count validation
|
|
296
|
+
if (maxLines !== Infinity) {
|
|
297
|
+
const lineCount = text.split("\n").length;
|
|
298
|
+
if (lineCount > maxLines) {
|
|
299
|
+
result.success = false;
|
|
300
|
+
result.errors.push(
|
|
301
|
+
ErrorHandler.createStringError(
|
|
302
|
+
[],
|
|
303
|
+
`Text cannot exceed ${maxLines} lines`,
|
|
304
|
+
value,
|
|
305
|
+
ErrorCode.STRING_TOO_LONG
|
|
306
|
+
)
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Character encoding validation
|
|
312
|
+
if (encoding === "ascii" && !/^[\x00-\x7F]*$/.test(text)) {
|
|
313
|
+
result.success = false;
|
|
314
|
+
result.errors.push(
|
|
315
|
+
ErrorHandler.createStringError(
|
|
316
|
+
[],
|
|
317
|
+
"Text contains non-ASCII characters",
|
|
318
|
+
value,
|
|
319
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
320
|
+
)
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Alphanumeric requirement
|
|
325
|
+
if (requireAlphanumeric && !/^[a-zA-Z0-9\s]*$/.test(text)) {
|
|
326
|
+
result.success = false;
|
|
327
|
+
result.errors.push(
|
|
328
|
+
ErrorHandler.createStringError(
|
|
329
|
+
[],
|
|
330
|
+
"Text must contain only alphanumeric characters and spaces",
|
|
331
|
+
value,
|
|
332
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
333
|
+
)
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Allowed characters validation
|
|
338
|
+
if (allowedCharacters && !allowedCharacters.test(text)) {
|
|
339
|
+
result.success = false;
|
|
340
|
+
result.errors.push(
|
|
341
|
+
ErrorHandler.createStringError(
|
|
342
|
+
[],
|
|
343
|
+
"Text contains disallowed characters",
|
|
344
|
+
value,
|
|
345
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
346
|
+
)
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Forbidden patterns check
|
|
351
|
+
for (const pattern of forbiddenPatterns) {
|
|
352
|
+
if (pattern.test(text)) {
|
|
353
|
+
result.success = false;
|
|
354
|
+
result.errors.push(
|
|
355
|
+
ErrorHandler.createStringError(
|
|
356
|
+
[],
|
|
357
|
+
"Text contains forbidden pattern",
|
|
358
|
+
value,
|
|
359
|
+
ErrorCode.PATTERN_MISMATCH
|
|
360
|
+
)
|
|
361
|
+
);
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// XSS prevention
|
|
367
|
+
if (preventXSS && !allowHTML) {
|
|
368
|
+
for (const pattern of SECURITY_CONSTANTS.XSS_PATTERNS) {
|
|
369
|
+
if (pattern.test(text)) {
|
|
370
|
+
result.success = false;
|
|
371
|
+
result.errors.push(
|
|
372
|
+
ErrorHandler.createStringError(
|
|
373
|
+
[],
|
|
374
|
+
"Text contains potentially malicious content (XSS)",
|
|
375
|
+
value,
|
|
376
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
377
|
+
)
|
|
378
|
+
);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Additional XSS checks
|
|
384
|
+
if (text.includes("javascript:") || text.includes("data:text/html")) {
|
|
385
|
+
result.success = false;
|
|
386
|
+
result.errors.push(
|
|
387
|
+
ErrorHandler.createStringError(
|
|
388
|
+
[],
|
|
389
|
+
"Text contains dangerous URI schemes",
|
|
390
|
+
value,
|
|
391
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
392
|
+
)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// SQL injection prevention
|
|
398
|
+
if (preventSQLInjection) {
|
|
399
|
+
for (const pattern of SECURITY_CONSTANTS.SQL_PATTERNS) {
|
|
400
|
+
if (pattern.test(text)) {
|
|
401
|
+
result.warnings.push("Text contains SQL-like patterns");
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// LDAP injection prevention
|
|
408
|
+
if (preventLDAPInjection) {
|
|
409
|
+
for (const pattern of SECURITY_CONSTANTS.LDAP_PATTERNS) {
|
|
410
|
+
if (pattern.test(text)) {
|
|
411
|
+
result.success = false;
|
|
412
|
+
result.errors.push(
|
|
413
|
+
ErrorHandler.createStringError(
|
|
414
|
+
[],
|
|
415
|
+
"Text contains LDAP injection patterns",
|
|
416
|
+
value,
|
|
417
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Command injection prevention
|
|
426
|
+
if (preventCommandInjection) {
|
|
427
|
+
for (const pattern of SECURITY_CONSTANTS.COMMAND_INJECTION_PATTERNS) {
|
|
428
|
+
if (pattern.test(text)) {
|
|
429
|
+
result.success = false;
|
|
430
|
+
result.errors.push(
|
|
431
|
+
ErrorHandler.createStringError(
|
|
432
|
+
[],
|
|
433
|
+
"Text contains command injection patterns",
|
|
434
|
+
value,
|
|
435
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
436
|
+
)
|
|
437
|
+
);
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Control character detection
|
|
444
|
+
if (/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/.test(text)) {
|
|
445
|
+
result.warnings.push("Text contains control characters");
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Null byte detection
|
|
449
|
+
if (text.includes("\0")) {
|
|
450
|
+
result.success = false;
|
|
451
|
+
result.errors.push(
|
|
452
|
+
ErrorHandler.createStringError(
|
|
453
|
+
[],
|
|
454
|
+
"Text contains null bytes",
|
|
455
|
+
value,
|
|
456
|
+
ErrorCode.INVALID_STRING_FORMAT
|
|
457
|
+
)
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return result;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* JSON validation with improved security and performance
|
|
466
|
+
*/
|
|
467
|
+
static async validateJson(
|
|
468
|
+
value: any,
|
|
469
|
+
options: {
|
|
470
|
+
maxDepth?: number;
|
|
471
|
+
maxKeys?: number;
|
|
472
|
+
maxStringLength?: number;
|
|
473
|
+
maxArrayLength?: number;
|
|
474
|
+
allowedTypes?: string[];
|
|
475
|
+
forbiddenKeys?: string[];
|
|
476
|
+
schema?: any;
|
|
477
|
+
preventCircular?: boolean;
|
|
478
|
+
maxSize?: number;
|
|
479
|
+
securityMode?: "fast" | "secure" | "strict";
|
|
480
|
+
timeout?: number;
|
|
481
|
+
enableMetrics?: boolean;
|
|
482
|
+
useCache?: boolean;
|
|
483
|
+
} = {}
|
|
484
|
+
): Promise<SchemaValidationResult> {
|
|
485
|
+
const startTime = Date.now();
|
|
486
|
+
const operationName = "validateJson";
|
|
487
|
+
|
|
488
|
+
// Generate cache key if caching is enabled
|
|
489
|
+
const cacheKey = options.useCache
|
|
490
|
+
? `json_${JSON.stringify(value).substring(0, 100)}_${JSON.stringify(options)}`
|
|
491
|
+
: null;
|
|
492
|
+
|
|
493
|
+
if (cacheKey) {
|
|
494
|
+
const cached = ValidationCache.get(cacheKey);
|
|
495
|
+
if (cached) {
|
|
496
|
+
return cached;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
try {
|
|
501
|
+
const result = await this.withTimeout(async () => {
|
|
502
|
+
return this.validateJsonSync(value, options);
|
|
503
|
+
}, options.timeout);
|
|
504
|
+
|
|
505
|
+
if (options.enableMetrics) {
|
|
506
|
+
ValidationMetrics.record(
|
|
507
|
+
operationName,
|
|
508
|
+
Date.now() - startTime,
|
|
509
|
+
!result.success
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (cacheKey && result.success) {
|
|
514
|
+
ValidationCache.set(cacheKey, result);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
return result;
|
|
518
|
+
} catch (error: any) {
|
|
519
|
+
if (options.enableMetrics) {
|
|
520
|
+
ValidationMetrics.record(operationName, Date.now() - startTime, true);
|
|
521
|
+
}
|
|
522
|
+
const msg = `${error instanceof Error ? error.message : "Unknown error"}`;
|
|
523
|
+
const code =
|
|
524
|
+
error instanceof Error ? error.name : ErrorCode.UNKNOWN_ERROR;
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
errors: [ErrorHandler.createValidationError([], msg, code)],
|
|
528
|
+
warnings: [],
|
|
529
|
+
data: value,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
static validateJsonSync(value: any, options: any): SchemaValidationResult {
|
|
535
|
+
const result: SchemaValidationResult = {
|
|
536
|
+
success: true,
|
|
537
|
+
errors: [],
|
|
538
|
+
warnings: [],
|
|
539
|
+
data: value,
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const {
|
|
543
|
+
maxDepth = MAX_OBJECT_DEPTH,
|
|
544
|
+
maxKeys = 1000,
|
|
545
|
+
maxStringLength = 10000,
|
|
546
|
+
maxArrayLength = 1000,
|
|
547
|
+
allowedTypes = ["object", "array", "string", "number", "boolean", "null"],
|
|
548
|
+
forbiddenKeys = [],
|
|
549
|
+
schema,
|
|
550
|
+
preventCircular = true,
|
|
551
|
+
maxSize = SECURITY_CONSTANTS.MAX_JSON_SIZE,
|
|
552
|
+
securityMode = "secure",
|
|
553
|
+
} = options;
|
|
554
|
+
|
|
555
|
+
let parsedData: any;
|
|
556
|
+
|
|
557
|
+
if (typeof value === "string") {
|
|
558
|
+
// Size check for string
|
|
559
|
+
if (value.length > maxSize) {
|
|
560
|
+
result.success = false;
|
|
561
|
+
result.errors.push(
|
|
562
|
+
ErrorHandler.createStringError(
|
|
563
|
+
[],
|
|
564
|
+
`JSON string exceeds maximum size of ${maxSize} characters`,
|
|
565
|
+
value,
|
|
566
|
+
ErrorCode.STRING_TOO_LONG
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
return result;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
parsedData = JSON.parse(value);
|
|
574
|
+
} catch (error) {
|
|
575
|
+
result.success = false;
|
|
576
|
+
result.errors.push(
|
|
577
|
+
ErrorHandler.createValidationError(
|
|
578
|
+
[],
|
|
579
|
+
`Invalid JSON string: ${error instanceof Error ? error.message : "Parse error"}`,
|
|
580
|
+
ErrorCode.INVALID_JSON
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
return result;
|
|
584
|
+
}
|
|
585
|
+
} else if (typeof value === "object" && value !== null) {
|
|
586
|
+
parsedData = value;
|
|
587
|
+
} else {
|
|
588
|
+
result.success = false;
|
|
589
|
+
result.errors.push(
|
|
590
|
+
ErrorHandler.createTypeError([], "string or object", value)
|
|
591
|
+
);
|
|
592
|
+
return result;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Security validations based on mode
|
|
596
|
+
if (securityMode === "secure" || securityMode === "strict") {
|
|
597
|
+
if (this.hasDangerousProperties(parsedData)) {
|
|
598
|
+
result.success = false;
|
|
599
|
+
result.errors.push(
|
|
600
|
+
ErrorHandler.createValidationError(
|
|
601
|
+
[],
|
|
602
|
+
"JSON contains dangerous properties (prototype pollution risk)",
|
|
603
|
+
ErrorCode.INVALID_JSON
|
|
604
|
+
)
|
|
605
|
+
);
|
|
606
|
+
return result;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (securityMode === "strict") {
|
|
610
|
+
const ajv = this.getAjv();
|
|
611
|
+
const validate = ajv.getSchema("secure-json-v2");
|
|
612
|
+
if (validate && !validate(parsedData)) {
|
|
613
|
+
result.success = false;
|
|
614
|
+
result.errors.push(
|
|
615
|
+
ErrorHandler.createValidationError(
|
|
616
|
+
[],
|
|
617
|
+
"JSON failed strict security validation",
|
|
618
|
+
ErrorCode.INVALID_JSON
|
|
619
|
+
)
|
|
620
|
+
);
|
|
621
|
+
if (validate.errors) {
|
|
622
|
+
result.warnings.push(
|
|
623
|
+
`AJV errors: ${JSON.stringify(validate.errors)}`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
return result;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Circular reference detection
|
|
632
|
+
if (preventCircular) {
|
|
633
|
+
try {
|
|
634
|
+
JSON.stringify(parsedData);
|
|
635
|
+
} catch (error) {
|
|
636
|
+
result.success = false;
|
|
637
|
+
result.errors.push(
|
|
638
|
+
ErrorHandler.createValidationError(
|
|
639
|
+
[],
|
|
640
|
+
"JSON contains circular references",
|
|
641
|
+
ErrorCode.INVALID_JSON
|
|
642
|
+
)
|
|
643
|
+
);
|
|
644
|
+
return result;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Deep validation
|
|
649
|
+
const validationResult = validateJsonDeep(parsedData, {
|
|
650
|
+
maxDepth,
|
|
651
|
+
maxKeys,
|
|
652
|
+
maxStringLength,
|
|
653
|
+
maxArrayLength,
|
|
654
|
+
allowedTypes,
|
|
655
|
+
forbiddenKeys,
|
|
656
|
+
currentDepth: 0,
|
|
657
|
+
keyCount: 0,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
result.success = result.success && validationResult.success;
|
|
661
|
+
result.errors.push(...validationResult.errors);
|
|
662
|
+
result.warnings.push(...validationResult.warnings);
|
|
663
|
+
result.data = parsedData;
|
|
664
|
+
|
|
665
|
+
// Schema validation if provided
|
|
666
|
+
if (schema && result.success) {
|
|
667
|
+
const schemaResult = validateJsonSchema(parsedData, schema);
|
|
668
|
+
result.success = result.success && schemaResult.success;
|
|
669
|
+
result.errors.push(...schemaResult.errors);
|
|
670
|
+
result.warnings.push(...schemaResult.warnings);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* IP validation with geographic and security checks
|
|
678
|
+
*/
|
|
679
|
+
static validateIp(
|
|
680
|
+
value: any,
|
|
681
|
+
options: {
|
|
682
|
+
version?: "v4" | "v6" | "both";
|
|
683
|
+
allowPrivate?: boolean;
|
|
684
|
+
allowLoopback?: boolean;
|
|
685
|
+
allowMulticast?: boolean;
|
|
686
|
+
allowReserved?: boolean;
|
|
687
|
+
allowCIDR?: boolean;
|
|
688
|
+
strict?: boolean;
|
|
689
|
+
blockBogons?: boolean;
|
|
690
|
+
allowDocumentation?: boolean;
|
|
691
|
+
} = {}
|
|
692
|
+
): SchemaValidationResult {
|
|
693
|
+
const result: SchemaValidationResult = {
|
|
694
|
+
success: true,
|
|
695
|
+
errors: [],
|
|
696
|
+
warnings: [],
|
|
697
|
+
data: value,
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const {
|
|
701
|
+
version = "both",
|
|
702
|
+
allowPrivate = true,
|
|
703
|
+
allowLoopback = true,
|
|
704
|
+
allowMulticast = false,
|
|
705
|
+
allowReserved = true,
|
|
706
|
+
allowCIDR = false,
|
|
707
|
+
strict = true,
|
|
708
|
+
blockBogons = false,
|
|
709
|
+
allowDocumentation = true,
|
|
710
|
+
} = options;
|
|
711
|
+
|
|
712
|
+
if (typeof value !== "string") {
|
|
713
|
+
result.success = false;
|
|
714
|
+
result.errors.push(
|
|
715
|
+
ErrorHandler.createSimpleError(
|
|
716
|
+
"Expected string for IP address",
|
|
717
|
+
[],
|
|
718
|
+
"string"
|
|
719
|
+
)
|
|
720
|
+
);
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const ip = value.trim();
|
|
725
|
+
|
|
726
|
+
// Basic format validation
|
|
727
|
+
if (!/^[\d\.:a-fA-F\/]+$/.test(ip)) {
|
|
728
|
+
result.success = false;
|
|
729
|
+
result.errors.push(
|
|
730
|
+
ErrorHandler.createValidationError(
|
|
731
|
+
[],
|
|
732
|
+
"IP address contains invalid characters",
|
|
733
|
+
ErrorCode.SECURITY_VIOLATION
|
|
734
|
+
)
|
|
735
|
+
);
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
let ipAddress = ip;
|
|
740
|
+
let cidrPrefix: number | null = null;
|
|
741
|
+
let isIPv4 = false;
|
|
742
|
+
let isIPv6 = false;
|
|
743
|
+
|
|
744
|
+
// Handle CIDR notation
|
|
745
|
+
if (ip.includes("/")) {
|
|
746
|
+
if (!allowCIDR) {
|
|
747
|
+
result.success = false;
|
|
748
|
+
result.errors.push(
|
|
749
|
+
ErrorHandler.createValidationError(
|
|
750
|
+
[],
|
|
751
|
+
"CIDR notation is not allowed",
|
|
752
|
+
ErrorCode.SECURITY_VIOLATION
|
|
753
|
+
)
|
|
754
|
+
);
|
|
755
|
+
return result;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
const parts = ip.split("/");
|
|
759
|
+
if (parts.length !== 2) {
|
|
760
|
+
result.success = false;
|
|
761
|
+
result.errors.push(
|
|
762
|
+
ErrorHandler.createValidationError(
|
|
763
|
+
[],
|
|
764
|
+
"Invalid CIDR notation format",
|
|
765
|
+
ErrorCode.SECURITY_VIOLATION
|
|
766
|
+
)
|
|
767
|
+
);
|
|
768
|
+
return result;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
ipAddress = parts[0];
|
|
772
|
+
const prefixStr = parts[1];
|
|
773
|
+
|
|
774
|
+
if (!/^\d+$/.test(prefixStr)) {
|
|
775
|
+
result.success = false;
|
|
776
|
+
result.errors.push(
|
|
777
|
+
ErrorHandler.createValidationError(
|
|
778
|
+
[],
|
|
779
|
+
"CIDR prefix must be numeric",
|
|
780
|
+
ErrorCode.SECURITY_VIOLATION
|
|
781
|
+
)
|
|
782
|
+
);
|
|
783
|
+
return result;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
cidrPrefix = parseInt(prefixStr, 10);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Validate IP format
|
|
790
|
+
const ipv4Result = validateIPv4(ipAddress, strict);
|
|
791
|
+
const ipv6Result = validateIPv6(ipAddress, strict);
|
|
792
|
+
|
|
793
|
+
isIPv4 = ipv4Result.valid;
|
|
794
|
+
isIPv6 = ipv6Result.valid;
|
|
795
|
+
|
|
796
|
+
// Version validation
|
|
797
|
+
if (version === "v4" && !isIPv4) {
|
|
798
|
+
result.success = false;
|
|
799
|
+
result.errors.push(
|
|
800
|
+
ErrorHandler.createValidationError(
|
|
801
|
+
[],
|
|
802
|
+
"Invalid IPv4 address format",
|
|
803
|
+
ErrorCode.SECURITY_VIOLATION
|
|
804
|
+
)
|
|
805
|
+
);
|
|
806
|
+
return result;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
if (version === "v6" && !isIPv6) {
|
|
810
|
+
result.success = false;
|
|
811
|
+
result.errors.push(
|
|
812
|
+
ErrorHandler.createValidationError(
|
|
813
|
+
[],
|
|
814
|
+
"Invalid IPv6 address format",
|
|
815
|
+
ErrorCode.SECURITY_VIOLATION
|
|
816
|
+
)
|
|
817
|
+
);
|
|
818
|
+
return result;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
if (version === "both" && !isIPv4 && !isIPv6) {
|
|
822
|
+
result.success = false;
|
|
823
|
+
result.errors.push(
|
|
824
|
+
ErrorHandler.createValidationError(
|
|
825
|
+
[],
|
|
826
|
+
"Invalid IP address format (must be valid IPv4 or IPv6)",
|
|
827
|
+
ErrorCode.SECURITY_VIOLATION
|
|
828
|
+
)
|
|
829
|
+
);
|
|
830
|
+
return result;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// CIDR prefix validation
|
|
834
|
+
if (cidrPrefix !== null) {
|
|
835
|
+
if (isIPv4 && (cidrPrefix < 0 || cidrPrefix > 32)) {
|
|
836
|
+
result.success = false;
|
|
837
|
+
result.errors.push(
|
|
838
|
+
ErrorHandler.createValidationError(
|
|
839
|
+
[],
|
|
840
|
+
"Invalid IPv4 CIDR prefix (must be 0-32)",
|
|
841
|
+
ErrorCode.SECURITY_VIOLATION
|
|
842
|
+
)
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
if (isIPv6 && (cidrPrefix < 0 || cidrPrefix > 128)) {
|
|
846
|
+
result.success = false;
|
|
847
|
+
result.errors.push(
|
|
848
|
+
ErrorHandler.createValidationError(
|
|
849
|
+
[],
|
|
850
|
+
"Invalid IPv6 CIDR prefix (must be 0-128)",
|
|
851
|
+
ErrorCode.SECURITY_VIOLATION
|
|
852
|
+
)
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// IPv4 specific validations
|
|
858
|
+
if (isIPv4) {
|
|
859
|
+
const octets = ipAddress.split(".").map(Number);
|
|
860
|
+
|
|
861
|
+
// private IP detection
|
|
862
|
+
if (!allowPrivate) {
|
|
863
|
+
if (
|
|
864
|
+
octets[0] === 10 ||
|
|
865
|
+
(octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31) ||
|
|
866
|
+
(octets[0] === 192 && octets[1] === 168) ||
|
|
867
|
+
(octets[0] === 169 && octets[1] === 254) // Link-local
|
|
868
|
+
) {
|
|
869
|
+
result.success = false;
|
|
870
|
+
result.errors.push(
|
|
871
|
+
ErrorHandler.createValidationError(
|
|
872
|
+
[],
|
|
873
|
+
"Private IPv4 addresses not allowed",
|
|
874
|
+
ErrorCode.SECURITY_VIOLATION
|
|
875
|
+
)
|
|
876
|
+
);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Loopback validation
|
|
881
|
+
if (!allowLoopback && octets[0] === 127) {
|
|
882
|
+
result.success = false;
|
|
883
|
+
result.errors.push(
|
|
884
|
+
ErrorHandler.createValidationError(
|
|
885
|
+
[],
|
|
886
|
+
"Loopback addresses not allowed",
|
|
887
|
+
ErrorCode.SECURITY_VIOLATION
|
|
888
|
+
)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Multicast validation
|
|
893
|
+
if (!allowMulticast && octets[0] >= 224 && octets[0] <= 239) {
|
|
894
|
+
result.success = false;
|
|
895
|
+
result.errors.push(
|
|
896
|
+
ErrorHandler.createValidationError(
|
|
897
|
+
[],
|
|
898
|
+
"Multicast addresses not allowed",
|
|
899
|
+
ErrorCode.SECURITY_VIOLATION
|
|
900
|
+
)
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// Documentation addresses (RFC 5737)
|
|
905
|
+
if (!allowDocumentation) {
|
|
906
|
+
if (
|
|
907
|
+
(octets[0] === 192 && octets[1] === 0 && octets[2] === 2) ||
|
|
908
|
+
(octets[0] === 198 && octets[1] === 51 && octets[2] === 100) ||
|
|
909
|
+
(octets[0] === 203 && octets[1] === 0 && octets[2] === 113)
|
|
910
|
+
) {
|
|
911
|
+
result.success = false;
|
|
912
|
+
result.errors.push(
|
|
913
|
+
ErrorHandler.createValidationError(
|
|
914
|
+
[],
|
|
915
|
+
"Documentation addresses not allowed",
|
|
916
|
+
ErrorCode.SECURITY_VIOLATION
|
|
917
|
+
)
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Bogon detection
|
|
923
|
+
if (blockBogons) {
|
|
924
|
+
if (
|
|
925
|
+
octets[0] === 0 ||
|
|
926
|
+
octets[0] === 255 ||
|
|
927
|
+
(octets[0] >= 240 && octets[0] <= 255)
|
|
928
|
+
) {
|
|
929
|
+
result.success = false;
|
|
930
|
+
result.errors.push(
|
|
931
|
+
ErrorHandler.createValidationError(
|
|
932
|
+
[],
|
|
933
|
+
"Bogon IP address detected",
|
|
934
|
+
ErrorCode.SECURITY_VIOLATION
|
|
935
|
+
)
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Reserved ranges
|
|
941
|
+
if (!allowReserved) {
|
|
942
|
+
if (octets[0] === 0 || octets[0] === 255) {
|
|
943
|
+
result.success = false;
|
|
944
|
+
result.errors.push(
|
|
945
|
+
ErrorHandler.createValidationError(
|
|
946
|
+
[],
|
|
947
|
+
"Reserved IPv4 addresses not allowed",
|
|
948
|
+
ErrorCode.SECURITY_VIOLATION
|
|
949
|
+
)
|
|
950
|
+
);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// IPv6 specific validations
|
|
956
|
+
if (isIPv6) {
|
|
957
|
+
const normalized = normalizeIPv6(ipAddress);
|
|
958
|
+
|
|
959
|
+
if (
|
|
960
|
+
!allowLoopback &&
|
|
961
|
+
normalized === "0000:0000:0000:0000:0000:0000:0000:0001"
|
|
962
|
+
) {
|
|
963
|
+
result.success = false;
|
|
964
|
+
result.errors.push(
|
|
965
|
+
ErrorHandler.createValidationError(
|
|
966
|
+
[],
|
|
967
|
+
"IPv6 loopback address not allowed",
|
|
968
|
+
ErrorCode.SECURITY_VIOLATION
|
|
969
|
+
)
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Unique local addresses (RFC 4193)
|
|
974
|
+
if (
|
|
975
|
+
!allowPrivate &&
|
|
976
|
+
(normalized.startsWith("fc00:") || normalized.startsWith("fd00:"))
|
|
977
|
+
) {
|
|
978
|
+
result.success = false;
|
|
979
|
+
result.errors.push(
|
|
980
|
+
ErrorHandler.createValidationError(
|
|
981
|
+
[],
|
|
982
|
+
"Private IPv6 addresses not allowed",
|
|
983
|
+
ErrorCode.SECURITY_VIOLATION
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Link-local addresses
|
|
989
|
+
if (!allowPrivate && normalized.startsWith("fe80:")) {
|
|
990
|
+
result.success = false;
|
|
991
|
+
result.errors.push(
|
|
992
|
+
ErrorHandler.createValidationError(
|
|
993
|
+
[],
|
|
994
|
+
"Link-local IPv6 addresses not allowed",
|
|
995
|
+
ErrorCode.SECURITY_VIOLATION
|
|
996
|
+
)
|
|
997
|
+
);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (!allowMulticast && normalized.startsWith("ff00:")) {
|
|
1001
|
+
result.success = false;
|
|
1002
|
+
result.errors.push(
|
|
1003
|
+
ErrorHandler.createValidationError(
|
|
1004
|
+
[],
|
|
1005
|
+
"IPv6 multicast addresses not allowed",
|
|
1006
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1007
|
+
)
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Documentation prefix (RFC 3849)
|
|
1012
|
+
if (!allowDocumentation && normalized.startsWith("2001:0db8:")) {
|
|
1013
|
+
result.success = false;
|
|
1014
|
+
result.errors.push(
|
|
1015
|
+
ErrorHandler.createValidationError(
|
|
1016
|
+
[],
|
|
1017
|
+
"Documentation IPv6 addresses not allowed",
|
|
1018
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1019
|
+
)
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
return result;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* object validation with better security and performance
|
|
1029
|
+
*/
|
|
1030
|
+
static validateObject(
|
|
1031
|
+
value: any,
|
|
1032
|
+
options: {
|
|
1033
|
+
allowNull?: boolean;
|
|
1034
|
+
allowArray?: boolean;
|
|
1035
|
+
maxDepth?: number;
|
|
1036
|
+
maxKeys?: number;
|
|
1037
|
+
requiredKeys?: string[];
|
|
1038
|
+
allowedKeys?: string[];
|
|
1039
|
+
forbiddenKeys?: string[];
|
|
1040
|
+
keyPattern?: RegExp;
|
|
1041
|
+
schema?: any;
|
|
1042
|
+
strict?: boolean;
|
|
1043
|
+
preventPrototypePollution?: boolean;
|
|
1044
|
+
maxPropertyNameLength?: number;
|
|
1045
|
+
} = {}
|
|
1046
|
+
): SchemaValidationResult {
|
|
1047
|
+
const result: SchemaValidationResult = {
|
|
1048
|
+
success: true,
|
|
1049
|
+
errors: [],
|
|
1050
|
+
warnings: [],
|
|
1051
|
+
data: value,
|
|
1052
|
+
};
|
|
1053
|
+
|
|
1054
|
+
const {
|
|
1055
|
+
allowNull = false,
|
|
1056
|
+
allowArray = false,
|
|
1057
|
+
maxDepth = MAX_OBJECT_DEPTH,
|
|
1058
|
+
maxKeys = 1000,
|
|
1059
|
+
requiredKeys = [],
|
|
1060
|
+
allowedKeys = [],
|
|
1061
|
+
forbiddenKeys = [],
|
|
1062
|
+
keyPattern,
|
|
1063
|
+
schema,
|
|
1064
|
+
strict = false,
|
|
1065
|
+
preventPrototypePollution = true,
|
|
1066
|
+
maxPropertyNameLength = 100,
|
|
1067
|
+
} = options;
|
|
1068
|
+
|
|
1069
|
+
// Null check
|
|
1070
|
+
if (value === null) {
|
|
1071
|
+
if (!allowNull) {
|
|
1072
|
+
result.success = false;
|
|
1073
|
+
result.errors.push(
|
|
1074
|
+
ErrorHandler.createValidationError(
|
|
1075
|
+
[],
|
|
1076
|
+
"Null values not allowed",
|
|
1077
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1078
|
+
)
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return result;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// Array check
|
|
1085
|
+
if (Array.isArray(value)) {
|
|
1086
|
+
if (!allowArray) {
|
|
1087
|
+
result.success = false;
|
|
1088
|
+
result.errors.push(
|
|
1089
|
+
ErrorHandler.createValidationError(
|
|
1090
|
+
[],
|
|
1091
|
+
"Arrays not allowed",
|
|
1092
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1093
|
+
)
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
return result;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Type check
|
|
1100
|
+
if (typeof value !== "object") {
|
|
1101
|
+
result.success = false;
|
|
1102
|
+
result.errors.push(ErrorHandler.createTypeError([], "object", value));
|
|
1103
|
+
return result;
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Prototype pollution check
|
|
1107
|
+
if (preventPrototypePollution && this.hasDangerousProperties(value)) {
|
|
1108
|
+
result.success = false;
|
|
1109
|
+
result.errors.push(
|
|
1110
|
+
ErrorHandler.createValidationError(
|
|
1111
|
+
[],
|
|
1112
|
+
"Object contains dangerous properties that could lead to prototype pollution",
|
|
1113
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1114
|
+
)
|
|
1115
|
+
);
|
|
1116
|
+
return result;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
const keys = Object.keys(value);
|
|
1120
|
+
|
|
1121
|
+
// Key count validation
|
|
1122
|
+
if (keys.length > maxKeys) {
|
|
1123
|
+
result.success = false;
|
|
1124
|
+
result.errors.push(
|
|
1125
|
+
ErrorHandler.createValidationError(
|
|
1126
|
+
[],
|
|
1127
|
+
`Object has too many keys (${keys.length}), maximum allowed: ${maxKeys}`,
|
|
1128
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1129
|
+
)
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// Required keys validation
|
|
1134
|
+
for (const requiredKey of requiredKeys) {
|
|
1135
|
+
if (!(requiredKey in value)) {
|
|
1136
|
+
result.success = false;
|
|
1137
|
+
result.errors.push(
|
|
1138
|
+
ErrorHandler.createValidationError(
|
|
1139
|
+
[],
|
|
1140
|
+
`Missing required key: ${requiredKey}`,
|
|
1141
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1142
|
+
)
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// key validation
|
|
1148
|
+
for (const key of keys) {
|
|
1149
|
+
// Property name length check
|
|
1150
|
+
if (key.length > maxPropertyNameLength) {
|
|
1151
|
+
result.success = false;
|
|
1152
|
+
result.errors.push(
|
|
1153
|
+
ErrorHandler.createValidationError(
|
|
1154
|
+
[],
|
|
1155
|
+
`Property name '${key.substring(0, 20)}...' exceeds maximum length of ${maxPropertyNameLength}`,
|
|
1156
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1157
|
+
)
|
|
1158
|
+
);
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Dangerous key detection
|
|
1163
|
+
if (SECURITY_CONSTANTS.DANGEROUS_PROPERTIES.has(key)) {
|
|
1164
|
+
result.success = false;
|
|
1165
|
+
result.errors.push(
|
|
1166
|
+
ErrorHandler.createValidationError(
|
|
1167
|
+
[],
|
|
1168
|
+
`Dangerous property name detected: '${key}'`,
|
|
1169
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1170
|
+
)
|
|
1171
|
+
);
|
|
1172
|
+
continue;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Allowed keys check
|
|
1176
|
+
if (allowedKeys.length > 0 && !allowedKeys.includes(key)) {
|
|
1177
|
+
if (strict) {
|
|
1178
|
+
result.success = false;
|
|
1179
|
+
result.errors.push(
|
|
1180
|
+
ErrorHandler.createValidationError(
|
|
1181
|
+
[],
|
|
1182
|
+
`Key '${key}' is not in allowed keys list`,
|
|
1183
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1184
|
+
)
|
|
1185
|
+
);
|
|
1186
|
+
} else {
|
|
1187
|
+
result.warnings.push(`Key '${key}' is not in allowed keys list`);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Forbidden keys check
|
|
1192
|
+
if (forbiddenKeys.includes(key)) {
|
|
1193
|
+
result.success = false;
|
|
1194
|
+
result.errors.push(
|
|
1195
|
+
ErrorHandler.createValidationError(
|
|
1196
|
+
[],
|
|
1197
|
+
`Key '${key}' is forbidden`,
|
|
1198
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1199
|
+
)
|
|
1200
|
+
);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
// Key pattern validation
|
|
1204
|
+
if (keyPattern && !keyPattern.test(key)) {
|
|
1205
|
+
result.success = false;
|
|
1206
|
+
result.errors.push(
|
|
1207
|
+
ErrorHandler.createValidationError(
|
|
1208
|
+
[],
|
|
1209
|
+
`Key '${key}' does not match required pattern`,
|
|
1210
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1211
|
+
)
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Check for suspicious patterns in key names
|
|
1216
|
+
if (/^__|.*prototype.*|.*constructor.*$/i.test(key)) {
|
|
1217
|
+
result.warnings.push(`Suspicious property name detected: '${key}'`);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// Deep structure validation
|
|
1222
|
+
const deepResult = validateObjectDeep(value, maxDepth, 0);
|
|
1223
|
+
result.success = result.success && deepResult.success;
|
|
1224
|
+
result.errors.push(...deepResult.errors);
|
|
1225
|
+
result.warnings.push(...deepResult.warnings);
|
|
1226
|
+
|
|
1227
|
+
// Schema validation if provided
|
|
1228
|
+
if (schema && result.success) {
|
|
1229
|
+
const schemaResult = validateObjectSchema(value, schema);
|
|
1230
|
+
result.success = result.success && schemaResult.success;
|
|
1231
|
+
result.errors.push(...schemaResult.errors);
|
|
1232
|
+
result.warnings.push(...schemaResult.warnings);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
return result;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Validate email address with comprehensive checks
|
|
1240
|
+
*/
|
|
1241
|
+
static validateEmail(
|
|
1242
|
+
value: any,
|
|
1243
|
+
options: {
|
|
1244
|
+
allowInternational?: boolean;
|
|
1245
|
+
maxLength?: number;
|
|
1246
|
+
allowedDomains?: string[];
|
|
1247
|
+
blockedDomains?: string[];
|
|
1248
|
+
requireMX?: boolean;
|
|
1249
|
+
strict?: boolean;
|
|
1250
|
+
} = {}
|
|
1251
|
+
): SchemaValidationResult {
|
|
1252
|
+
const result: SchemaValidationResult = {
|
|
1253
|
+
success: true,
|
|
1254
|
+
errors: [],
|
|
1255
|
+
warnings: [],
|
|
1256
|
+
data: value,
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
const {
|
|
1260
|
+
allowInternational = true,
|
|
1261
|
+
maxLength = 254,
|
|
1262
|
+
allowedDomains = [],
|
|
1263
|
+
blockedDomains = [],
|
|
1264
|
+
strict = true,
|
|
1265
|
+
} = options;
|
|
1266
|
+
|
|
1267
|
+
if (typeof value !== "string") {
|
|
1268
|
+
result.success = false;
|
|
1269
|
+
result.errors.push(ErrorHandler.createTypeError([], "string", value));
|
|
1270
|
+
return result;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const email = value.trim().toLowerCase();
|
|
1274
|
+
|
|
1275
|
+
// Length check
|
|
1276
|
+
if (email.length > maxLength) {
|
|
1277
|
+
result.success = false;
|
|
1278
|
+
result.errors.push(
|
|
1279
|
+
ErrorHandler.createStringError(
|
|
1280
|
+
[],
|
|
1281
|
+
`Email exceeds maximum length of ${maxLength} characters`,
|
|
1282
|
+
value,
|
|
1283
|
+
ErrorCode.STRING_TOO_LONG
|
|
1284
|
+
)
|
|
1285
|
+
);
|
|
1286
|
+
return result;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
// Basic format check - must contain exactly one @
|
|
1290
|
+
const atSymbolCount = (email.match(/@/g) || []).length;
|
|
1291
|
+
if (atSymbolCount !== 1) {
|
|
1292
|
+
result.success = false;
|
|
1293
|
+
result.errors.push(
|
|
1294
|
+
ErrorHandler.createValidationError(
|
|
1295
|
+
[],
|
|
1296
|
+
"Email must contain exactly one @ symbol",
|
|
1297
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1298
|
+
)
|
|
1299
|
+
);
|
|
1300
|
+
return result;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
const [localPart, domain] = email.split("@");
|
|
1304
|
+
|
|
1305
|
+
// Check for empty parts
|
|
1306
|
+
if (!localPart || !domain) {
|
|
1307
|
+
result.success = false;
|
|
1308
|
+
result.errors.push(
|
|
1309
|
+
ErrorHandler.createValidationError(
|
|
1310
|
+
[],
|
|
1311
|
+
"Email local part and domain cannot be empty",
|
|
1312
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1313
|
+
)
|
|
1314
|
+
);
|
|
1315
|
+
return result;
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// Local part length validation
|
|
1319
|
+
if (localPart.length > 64) {
|
|
1320
|
+
result.success = false;
|
|
1321
|
+
result.errors.push(
|
|
1322
|
+
ErrorHandler.createValidationError(
|
|
1323
|
+
[],
|
|
1324
|
+
"Email local part exceeds 64 characters",
|
|
1325
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1326
|
+
)
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
// Plus addressing check - FIXED LOGIC
|
|
1330
|
+
if (localPart.includes("+")) {
|
|
1331
|
+
result.success = false;
|
|
1332
|
+
result.errors.push(
|
|
1333
|
+
ErrorHandler.createValidationError(
|
|
1334
|
+
[],
|
|
1335
|
+
"Plus addressing (+ symbols) is not allowed",
|
|
1336
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1337
|
+
)
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
// Local part character validation - FIXED TO PROPERLY HANDLE INVALID CHARACTERS
|
|
1342
|
+
if (strict) {
|
|
1343
|
+
// Practical validation - common characters only (excludes # and other problematic chars)
|
|
1344
|
+
const validLocalPartRegex = /^[a-zA-Z0-9._+-]+$/;
|
|
1345
|
+
if (!validLocalPartRegex.test(localPart)) {
|
|
1346
|
+
result.success = false;
|
|
1347
|
+
result.errors.push(
|
|
1348
|
+
ErrorHandler.createValidationError(
|
|
1349
|
+
[],
|
|
1350
|
+
"Email local part contains invalid characters",
|
|
1351
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1352
|
+
)
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
} else {
|
|
1356
|
+
// RFC 5322 compliant - more permissive but excludes problematic chars like #
|
|
1357
|
+
const validLocalPartRegex =
|
|
1358
|
+
/^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
|
|
1359
|
+
if (!validLocalPartRegex.test(localPart)) {
|
|
1360
|
+
result.success = false;
|
|
1361
|
+
result.errors.push(
|
|
1362
|
+
ErrorHandler.createValidationError(
|
|
1363
|
+
[],
|
|
1364
|
+
"Email local part contains invalid characters",
|
|
1365
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1366
|
+
)
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Check for consecutive dots
|
|
1372
|
+
if (localPart.includes("..")) {
|
|
1373
|
+
result.success = false;
|
|
1374
|
+
result.errors.push(
|
|
1375
|
+
ErrorHandler.createValidationError(
|
|
1376
|
+
[],
|
|
1377
|
+
"Email local part cannot contain consecutive dots",
|
|
1378
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1379
|
+
)
|
|
1380
|
+
);
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Check for dots at start or end
|
|
1384
|
+
if (localPart.startsWith(".") || localPart.endsWith(".")) {
|
|
1385
|
+
result.success = false;
|
|
1386
|
+
result.errors.push(
|
|
1387
|
+
ErrorHandler.createValidationError(
|
|
1388
|
+
[],
|
|
1389
|
+
"Email local part cannot start or end with a dot",
|
|
1390
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1391
|
+
)
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
// Domain validation
|
|
1396
|
+
if (domain.length > 255) {
|
|
1397
|
+
result.success = false;
|
|
1398
|
+
result.errors.push(
|
|
1399
|
+
ErrorHandler.createValidationError(
|
|
1400
|
+
[],
|
|
1401
|
+
"Email domain exceeds 255 characters",
|
|
1402
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1403
|
+
)
|
|
1404
|
+
);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Domain format validation
|
|
1408
|
+
if (strict) {
|
|
1409
|
+
// Basic domain validation - must have at least one dot and valid TLD
|
|
1410
|
+
const validDomainRegex = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
1411
|
+
if (!validDomainRegex.test(domain)) {
|
|
1412
|
+
result.success = false;
|
|
1413
|
+
result.errors.push(
|
|
1414
|
+
ErrorHandler.createValidationError(
|
|
1415
|
+
[],
|
|
1416
|
+
"Invalid domain format - must contain at least one dot and valid TLD",
|
|
1417
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1418
|
+
)
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
} else {
|
|
1422
|
+
// More strict domain validation
|
|
1423
|
+
const validDomainRegex =
|
|
1424
|
+
/^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
|
|
1425
|
+
if (!validDomainRegex.test(domain)) {
|
|
1426
|
+
result.success = false;
|
|
1427
|
+
result.errors.push(
|
|
1428
|
+
ErrorHandler.createValidationError(
|
|
1429
|
+
[],
|
|
1430
|
+
"Invalid domain format",
|
|
1431
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1432
|
+
)
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Check for consecutive dots in domain
|
|
1438
|
+
if (domain.includes("..")) {
|
|
1439
|
+
result.success = false;
|
|
1440
|
+
result.errors.push(
|
|
1441
|
+
ErrorHandler.createValidationError(
|
|
1442
|
+
[],
|
|
1443
|
+
"Domain cannot contain consecutive dots",
|
|
1444
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1445
|
+
)
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Check for dots at start or end of domain
|
|
1450
|
+
if (domain.startsWith(".") || domain.endsWith(".")) {
|
|
1451
|
+
result.success = false;
|
|
1452
|
+
result.errors.push(
|
|
1453
|
+
ErrorHandler.createValidationError(
|
|
1454
|
+
[],
|
|
1455
|
+
"Domain cannot start or end with a dot",
|
|
1456
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1457
|
+
)
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// International domain validation
|
|
1462
|
+
if (!allowInternational && /[^\x00-\x7F]/.test(domain)) {
|
|
1463
|
+
result.success = false;
|
|
1464
|
+
result.errors.push(
|
|
1465
|
+
ErrorHandler.createValidationError(
|
|
1466
|
+
[],
|
|
1467
|
+
"International domain names are not allowed",
|
|
1468
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1469
|
+
)
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Domain whitelist/blacklist
|
|
1474
|
+
if (allowedDomains.length > 0 && !allowedDomains.includes(domain)) {
|
|
1475
|
+
result.success = false;
|
|
1476
|
+
result.errors.push(
|
|
1477
|
+
ErrorHandler.createValidationError(
|
|
1478
|
+
[],
|
|
1479
|
+
`Email domain '${domain}' is not allowed`,
|
|
1480
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1481
|
+
)
|
|
1482
|
+
);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
if (blockedDomains.includes(domain)) {
|
|
1486
|
+
result.success = false;
|
|
1487
|
+
result.errors.push(
|
|
1488
|
+
ErrorHandler.createValidationError(
|
|
1489
|
+
[],
|
|
1490
|
+
`Email domain '${domain}' is blocked`,
|
|
1491
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1492
|
+
)
|
|
1493
|
+
);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// Additional validation for common invalid patterns
|
|
1497
|
+
const invalidCharsRegex = /[<>()[\]\\,;:\s@"]/;
|
|
1498
|
+
if (invalidCharsRegex.test(localPart)) {
|
|
1499
|
+
result.success = false;
|
|
1500
|
+
result.errors.push(
|
|
1501
|
+
ErrorHandler.createValidationError(
|
|
1502
|
+
[],
|
|
1503
|
+
"Email local part contains prohibited characters (spaces, quotes, brackets, etc.)",
|
|
1504
|
+
ErrorCode.SECURITY_VIOLATION
|
|
1505
|
+
)
|
|
1506
|
+
);
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Suspicious patterns
|
|
1510
|
+
if (/test|temp|fake|spam|noreply/i.test(email)) {
|
|
1511
|
+
result.warnings.push("Email appears to be temporary or test address");
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// Set the cleaned data
|
|
1515
|
+
result.data = email;
|
|
1516
|
+
|
|
1517
|
+
return result;
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
/**
|
|
1521
|
+
* Get validation performance metrics
|
|
1522
|
+
*/
|
|
1523
|
+
static getMetrics(): Record<string, any> {
|
|
1524
|
+
return ValidationMetrics.getMetrics();
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* Reset validation metrics
|
|
1529
|
+
*/
|
|
1530
|
+
static resetMetrics(): void {
|
|
1531
|
+
ValidationMetrics.reset();
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Clear validation cache
|
|
1536
|
+
*/
|
|
1537
|
+
static clearCache(): void {
|
|
1538
|
+
ValidationCache.clear();
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* Validate multiple values with batch processing
|
|
1543
|
+
*/
|
|
1544
|
+
static async validateBatch<T>(
|
|
1545
|
+
values: T[],
|
|
1546
|
+
validator: (
|
|
1547
|
+
value: T
|
|
1548
|
+
) => Promise<SchemaValidationResult> | SchemaValidationResult,
|
|
1549
|
+
options: {
|
|
1550
|
+
continueOnError?: boolean;
|
|
1551
|
+
maxConcurrency?: number;
|
|
1552
|
+
timeout?: number;
|
|
1553
|
+
} = {}
|
|
1554
|
+
): Promise<{
|
|
1555
|
+
results: SchemaValidationResult[];
|
|
1556
|
+
summary: { total: number; passed: number; failed: number };
|
|
1557
|
+
}> {
|
|
1558
|
+
const {
|
|
1559
|
+
continueOnError = true,
|
|
1560
|
+
maxConcurrency = 10,
|
|
1561
|
+
timeout = 30000,
|
|
1562
|
+
} = options;
|
|
1563
|
+
|
|
1564
|
+
const results: SchemaValidationResult[] = [];
|
|
1565
|
+
const batches: T[][] = [];
|
|
1566
|
+
|
|
1567
|
+
// Create batches
|
|
1568
|
+
for (let i = 0; i < values.length; i += maxConcurrency) {
|
|
1569
|
+
batches.push(values.slice(i, i + maxConcurrency));
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
try {
|
|
1573
|
+
await this.withTimeout(async () => {
|
|
1574
|
+
for (const batch of batches) {
|
|
1575
|
+
const batchPromises = batch.map(async (value) => {
|
|
1576
|
+
try {
|
|
1577
|
+
return await Promise.resolve(validator(value));
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
return {
|
|
1580
|
+
success: false,
|
|
1581
|
+
errors: [
|
|
1582
|
+
ErrorHandler.createValidationError(
|
|
1583
|
+
[],
|
|
1584
|
+
`Batch validation error: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1585
|
+
ErrorCode.UNKNOWN_ERROR
|
|
1586
|
+
),
|
|
1587
|
+
],
|
|
1588
|
+
warnings: [],
|
|
1589
|
+
data: value,
|
|
1590
|
+
} as SchemaValidationResult;
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1595
|
+
results.push(...batchResults);
|
|
1596
|
+
|
|
1597
|
+
// Stop on first error if continueOnError is false
|
|
1598
|
+
if (!continueOnError && batchResults.some((r) => !r.success)) {
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}, timeout);
|
|
1603
|
+
} catch (error) {
|
|
1604
|
+
// Add timeout error for remaining values
|
|
1605
|
+
const remaining = values.length - results.length;
|
|
1606
|
+
for (let i = 0; i < remaining; i++) {
|
|
1607
|
+
results.push({
|
|
1608
|
+
success: false,
|
|
1609
|
+
errors: [
|
|
1610
|
+
ErrorHandler.createValidationError(
|
|
1611
|
+
[],
|
|
1612
|
+
`Batch validation timeout: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
1613
|
+
ErrorCode.UNKNOWN_ERROR
|
|
1614
|
+
),
|
|
1615
|
+
],
|
|
1616
|
+
warnings: [],
|
|
1617
|
+
data: values[results.length + i],
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const summary = {
|
|
1623
|
+
total: results.length,
|
|
1624
|
+
passed: results.filter((r) => r.success).length,
|
|
1625
|
+
failed: results.filter((r) => !r.success).length,
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
return { results, summary };
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
// Export additional utilities
|
|
1633
|
+
export { ValidationMetrics, ValidationCache, SECURITY_CONSTANTS };
|
|
1634
|
+
export { SecurityValidators as Security };
|