diff --git a/AdminPanel/client/package-lock.json b/AdminPanel/client/package-lock.json index 1ba9d979..23bd1d35 100644 --- a/AdminPanel/client/package-lock.json +++ b/AdminPanel/client/package-lock.json @@ -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", diff --git a/AdminPanel/client/package.json b/AdminPanel/client/package.json index 7884270b..c3d74460 100644 --- a/AdminPanel/client/package.json +++ b/AdminPanel/client/package.json @@ -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", diff --git a/AdminPanel/client/src/hooks/useFieldValidation.js b/AdminPanel/client/src/hooks/useFieldValidation.js index abc70c87..9bbbbdc3 100644 --- a/AdminPanel/client/src/hooks/useFieldValidation.js +++ b/AdminPanel/client/src/hooks/useFieldValidation.js @@ -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); diff --git a/AdminPanel/server/package-lock.json b/AdminPanel/server/package-lock.json index 954fe8e9..b68f896e 100644 --- a/AdminPanel/server/package-lock.json +++ b/AdminPanel/server/package-lock.json @@ -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", diff --git a/AdminPanel/server/package.json b/AdminPanel/server/package.json index 8aef1a5b..17424aa3 100644 --- a/AdminPanel/server/package.json +++ b/AdminPanel/server/package.json @@ -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" diff --git a/AdminPanel/server/sources/routes/adminpanel/router.js b/AdminPanel/server/sources/routes/adminpanel/router.js index 53a6b19e..9acc3149 100644 --- a/AdminPanel/server/sources/routes/adminpanel/router.js +++ b/AdminPanel/server/sources/routes/adminpanel/router.js @@ -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) { diff --git a/AdminPanel/server/sources/routes/config/config.service.js b/AdminPanel/server/sources/routes/config/config.service.js index 1f15531a..ac90c459 100644 --- a/AdminPanel/server/sources/routes/config/config.service.js +++ b/AdminPanel/server/sources/routes/config/config.service.js @@ -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; } diff --git a/Common/config/schemas/config.schema.json b/Common/config/schemas/config.schema.json index fea6d0bb..afdee7ae 100644 --- a/Common/config/schemas/config.schema.json +++ b/Common/config/schemas/config.schema.json @@ -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"] }