[refactor] Use built-in Ajv regexp validator; For bug 77233

This commit is contained in:
PauI Ostrovckij
2025-10-20 17:48:18 +03:00
parent 6ce5b460a1
commit 5ca413d4c2
8 changed files with 51 additions and 42 deletions

View File

@ -1,5 +1,5 @@
{
"name": "docscloud",
"name": "onlyoffice-adminpanel-client",
"version": "1.3.0",
"lockfileVersion": 1,
"requires": true,
@ -1901,6 +1901,11 @@
"require-from-string": "^2.0.2"
}
},
"ajv-errors": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz",
"integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ=="
},
"ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",

View File

@ -1,5 +1,5 @@
{
"name": "docscloud",
"name": "onlyoffice-adminpanel-client",
"version": "1.3.0",
"private": true,
"scripts": {
@ -10,6 +10,7 @@
"@reduxjs/toolkit": "^2.8.2",
"@tanstack/react-query": "^5.83.0",
"ajv": "^8.17.1",
"ajv-errors": "^3.0.0",
"ajv-formats": "^3.0.1",
"axios": "1.7.4",
"prop-types": "^15.8.1",

View File

@ -2,6 +2,7 @@ import {useState, useEffect, useCallback} from 'react';
import {useSelector} from 'react-redux';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import addErrors from 'ajv-errors';
import {selectSchema, selectSchemaLoading, selectSchemaError} from '../store/slices/configSlice';
/**
@ -22,19 +23,7 @@ export const useFieldValidation = () => {
// Build AJV validator with custom and standard formats
const ajv = new Ajv({allErrors: true, strict: false});
addFormats(ajv); // Add standard formats including email
// Register formats from schema $defs.formats (regex strings)
const formats = schema?.$defs?.formats;
if (formats && typeof formats === 'object') {
for (const [name, patternString] of Object.entries(formats)) {
try {
const re = new RegExp(patternString);
ajv.addFormat(name, re);
} catch (e) {
console.warn('Invalid format regex in schema $defs.formats:', name, e.message);
}
}
}
addErrors(ajv);
const validateFn = ajv.compile(schema);
setValidator(() => validateFn);

View File

@ -55,6 +55,11 @@
"require-from-string": "^2.0.2"
}
},
"ajv-errors": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz",
"integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ=="
},
"ajv-formats": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",

View File

@ -1,5 +1,5 @@
{
"name": "adminpanel",
"name": "onlyoffice-adminpanel-server",
"version": "1.0.0",
"homepage": "https://www.onlyoffice.com",
"private": true,
@ -19,6 +19,7 @@
"express": "^4.19.2",
"ajv": "^8.17.1",
"ajv-formats": "^2.1.1",
"ajv-errors": "^3.0.0",
"joi": "^17.13.3",
"jsonwebtoken": "^9.0.2",
"ms": "^2.1.3"

View File

@ -140,7 +140,9 @@ router.post('/setup', async (req, res) => {
const passwordValidationResult = validatePassword(ctx, password);
if (!passwordValidationResult.isValid) {
return res.status(400).json({error: 'Password is too weak'});
return res
.status(400)
.json({error: 'Password must me at least 8 characters long, contain at least one digit, one uppercase letter and one special character'});
}
await passwordManager.saveAdminPassword(ctx, password);
@ -174,7 +176,9 @@ router.post('/change-password', requireAuth, async (req, res) => {
const passwordValidationResult = validatePassword(ctx, newPassword);
if (!passwordValidationResult.isValid) {
return res.status(400).json({error: 'Password is too weak'});
return res
.status(400)
.json({error: 'Password must me at least 8 characters long, contain at least one digit, one uppercase letter and one special character'});
}
if (currentPassword === newPassword) {

View File

@ -33,6 +33,7 @@
'use strict';
const Ajv = require('ajv');
const addFormats = require('ajv-formats');
const addErrors = require('ajv-errors');
const logger = require('../../../../../Common/sources/logger');
const tenantManager = require('../../../../../Common/sources/tenantManager');
const supersetSchema = require('../../../../../Common/config/schemas/config.schema.json');
@ -48,14 +49,6 @@ const AJV_FILTER_CONFIG = {allErrors: true, strict: false, removeAdditional: tru
*/
function registerAjvExtras(instance) {
instance.addKeyword({keyword: X_SCOPE_KEYWORD, schemaType: ['string', 'array'], errors: false});
const formats = supersetSchema?.$defs?.formats;
if (formats && typeof formats === 'object') {
for (const [name, patternString] of Object.entries(formats)) {
const re = new RegExp(patternString);
instance.addFormat(name, re);
}
}
}
/**
@ -66,6 +59,7 @@ function registerAjvExtras(instance) {
function createAjvInstance(config) {
const instance = new Ajv(config);
addFormats(instance);
addErrors(instance);
registerAjvExtras(instance);
return instance;
}

View File

@ -6,12 +6,26 @@
"type": "object",
"additionalProperties": false,
"$defs": {
"formats": {
"cron6": "^\\s*\\S+(?:\\s+\\S+){5}\\s*$",
"passlength": "^.{8,128}$",
"passdigit": ".*\\d.*",
"passupper": ".*[A-Z].*",
"passspecial": ".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*"
"cron6": {
"type": "string",
"pattern": "^\\s*\\S+(?:\\s+\\S+){5}\\s*$",
"errorMessage": "Cron expression must have exactly 6 parts"
},
"passlength": {
"type": "string",
"pattern": "^.{8,128}$"
},
"passdigit": {
"type": "string",
"pattern": ".*\\d.*"
},
"passupper": {
"type": "string",
"pattern": ".*[A-Z].*"
},
"passspecial": {
"type": "string",
"pattern": ".*[!@#$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>\\/?].*"
}
},
"properties": {
@ -88,8 +102,8 @@
"additionalProperties": false,
"x-scope": ["admin", "tenant"],
"properties": {
"filesCron": {"type": "string", "format": "cron6", "x-scope": "admin"},
"documentsCron": {"type": "string", "format": "cron6", "x-scope": "admin"},
"filesCron": {"$ref": "#/$defs/cron6", "x-scope": "admin"},
"documentsCron": {"$ref": "#/$defs/cron6", "x-scope": "admin"},
"files": {"type": "integer", "minimum": 0, "x-scope": "admin"},
"filesremovedatonce": {"type": "integer", "minimum": 0, "x-scope": "admin"},
"sessionidle": {"type": "string", "x-scope": ["admin", "tenant"]},
@ -415,26 +429,22 @@
"description": "Password validation requirements using custom format types",
"properties": {
"minLength": {
"type": "string",
"format": "passlength",
"$ref": "#/$defs/passlength",
"description": "be at least 8 characters long",
"x-scope": ["admin", "tenant"]
},
"hasDigit": {
"type": "string",
"format": "passdigit",
"$ref": "#/$defs/passdigit",
"description": "contain at least one digit",
"x-scope": ["admin", "tenant"]
},
"hasUppercase": {
"type": "string",
"format": "passupper",
"$ref": "#/$defs/passupper",
"description": "contain at least one uppercase letter",
"x-scope": ["admin", "tenant"]
},
"hasSpecialChar": {
"type": "string",
"format": "passspecial",
"$ref": "#/$defs/passspecial",
"description": "contain at least one special character",
"x-scope": ["admin", "tenant"]
}