mirror of
https://github.com/ONLYOFFICE/server.git
synced 2026-02-10 18:05:07 +08:00
[feature] Revert validateJWT in schema request
This commit is contained in:
@ -21,7 +21,7 @@ const safeFetch = async (url, options = {}) => {
|
||||
};
|
||||
|
||||
export const fetchStatistics = async () => {
|
||||
const response = await safeFetch(`${BACKEND_URL}${API_BASE_PATH}/stat`);
|
||||
const response = await safeFetch(`${BACKEND_URL}${API_BASE_PATH}/stat`, {credentials: 'include'});
|
||||
if (!response.ok) throw new Error('Failed to fetch statistics');
|
||||
return response.json();
|
||||
};
|
||||
@ -34,7 +34,7 @@ export const fetchConfiguration = async () => {
|
||||
};
|
||||
|
||||
export const fetchConfigurationSchema = async () => {
|
||||
const response = await safeFetch(`${BACKEND_URL}${API_BASE_PATH}/config/schema`);
|
||||
const response = await safeFetch(`${BACKEND_URL}${API_BASE_PATH}/config/schema`, {credentials: 'include'});
|
||||
if (!response.ok) throw new Error('Failed to fetch configuration schema');
|
||||
return response.json();
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {fetchUser, selectUser, selectUserLoading, selectIsAuthenticated} from '../../store/slices/userSlice';
|
||||
import {setPasswordSchema} from '../../store/slices/configSlice';
|
||||
import {checkSetupRequired} from '../../api';
|
||||
import Spinner from '../../assets/Spinner.svg';
|
||||
import Login from '../../pages/Login/LoginPage';
|
||||
@ -22,6 +23,11 @@ export default function AuthWrapper({children}) {
|
||||
try {
|
||||
const result = await checkSetupRequired();
|
||||
setSetupRequired(result.setupRequired);
|
||||
|
||||
// Save minimal password schema to Redux for Setup page validation
|
||||
if (result.passwordValidationSchema) {
|
||||
dispatch(setPasswordSchema(result.passwordValidationSchema));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.message === 'SERVER_UNAVAILABLE') {
|
||||
setServerUnavailable(true);
|
||||
@ -32,7 +38,7 @@ export default function AuthWrapper({children}) {
|
||||
};
|
||||
|
||||
checkSetup();
|
||||
}, []);
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!checkingSetup && !setupRequired && !serverUnavailable) {
|
||||
|
||||
@ -2,7 +2,7 @@ import SuccessGreenIcon from '../../assets/SuccessGreen.svg';
|
||||
import FailRedIcon from '../../assets/FailRed.svg';
|
||||
import styles from './PasswordRequirements.module.scss';
|
||||
import {useSelector} from 'react-redux';
|
||||
import {selectSchema} from '../../store/slices/configSlice';
|
||||
import {selectSchema, selectPasswordSchema} from '../../store/slices/configSlice';
|
||||
import {useMemo} from 'react';
|
||||
import {usePasswordValidation} from '../../utils/passwordValidation';
|
||||
|
||||
@ -13,12 +13,19 @@ import {usePasswordValidation} from '../../utils/passwordValidation';
|
||||
* @param {boolean} isVisible - Whether to show the requirements (e.g., on focus)
|
||||
*/
|
||||
function PasswordRequirements({password, isVisible = false}) {
|
||||
const schema = useSelector(selectSchema);
|
||||
const fullSchema = useSelector(selectSchema);
|
||||
const passwordSchema = useSelector(selectPasswordSchema);
|
||||
const {invalidRules, isValid} = usePasswordValidation(password);
|
||||
|
||||
// Use fullSchema (admin panel) or passwordSchema (Setup page)
|
||||
const schema = fullSchema || passwordSchema;
|
||||
const passwordValidation = schema?.properties?.adminPanel?.properties?.passwordValidation;
|
||||
|
||||
const requirements = useMemo(() => {
|
||||
if (!passwordValidation?.properties) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rules = [
|
||||
{key: 'minLength', format: 'passlength'},
|
||||
{key: 'hasDigit', format: 'passdigit'},
|
||||
@ -41,10 +48,15 @@ function PasswordRequirements({password, isVisible = false}) {
|
||||
|
||||
const validRequirements = requirements.filter(req => req.isValid).length;
|
||||
const totalRequirements = requirements.length;
|
||||
const progress = (validRequirements / totalRequirements) * 100;
|
||||
const progress = totalRequirements > 0 ? (validRequirements / totalRequirements) * 100 : 0;
|
||||
|
||||
const shouldShow = isVisible || (!isValid && password);
|
||||
|
||||
// Don't show if schema is not loaded yet
|
||||
if (!schema || !passwordValidation?.properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!shouldShow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -3,22 +3,29 @@ 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';
|
||||
import {selectSchema, selectPasswordSchema, selectSchemaLoading, selectSchemaError} from '../store/slices/configSlice';
|
||||
|
||||
/**
|
||||
* Hook for field validation using backend schema
|
||||
* Uses passwordSchema (minimal) for Setup page, or schema (full) for admin panel
|
||||
* @returns {Object} { validateField, getFieldError, isLoading, error }
|
||||
*/
|
||||
export const useFieldValidation = () => {
|
||||
const [validator, setValidator] = useState(null);
|
||||
const [fieldErrors, setFieldErrors] = useState({});
|
||||
const [cachedSchema, setCachedSchema] = useState(null);
|
||||
|
||||
const schema = useSelector(selectSchema);
|
||||
const fullSchema = useSelector(selectSchema);
|
||||
const passwordSchema = useSelector(selectPasswordSchema);
|
||||
const isLoading = useSelector(selectSchemaLoading);
|
||||
const error = useSelector(selectSchemaError);
|
||||
|
||||
// Prefer fullSchema (admin panel) over passwordSchema (Setup page)
|
||||
const schema = fullSchema || passwordSchema;
|
||||
|
||||
useEffect(() => {
|
||||
if (schema && !validator) {
|
||||
// Only recreate validator if schema actually changed
|
||||
if (schema && schema !== cachedSchema) {
|
||||
try {
|
||||
// Build AJV validator with custom and standard formats
|
||||
const ajv = new Ajv({allErrors: true, strict: false});
|
||||
@ -27,11 +34,12 @@ export const useFieldValidation = () => {
|
||||
|
||||
const validateFn = ajv.compile(schema);
|
||||
setValidator(() => validateFn);
|
||||
setCachedSchema(schema);
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize field validator:', err);
|
||||
}
|
||||
}
|
||||
}, [schema, validator]);
|
||||
}, [schema, cachedSchema]);
|
||||
|
||||
/**
|
||||
* Validates a single field value against the schema
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import {useEffect} from 'react';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
import {selectSchema, selectSchemaLoading, selectSchemaError, fetchSchema} from '../store/slices/configSlice';
|
||||
import {selectIsAuthenticated} from '../store/slices/userSlice';
|
||||
|
||||
/**
|
||||
* Hook to load schema on app startup
|
||||
* Hook to load schema for authenticated users
|
||||
* Fetches schema immediately when the hook is first used
|
||||
*/
|
||||
export const useSchemaLoader = () => {
|
||||
@ -11,13 +12,14 @@ export const useSchemaLoader = () => {
|
||||
const schema = useSelector(selectSchema);
|
||||
const schemaLoading = useSelector(selectSchemaLoading);
|
||||
const schemaError = useSelector(selectSchemaError);
|
||||
const isAuthenticated = useSelector(selectIsAuthenticated);
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch schema if not loaded (always fetch, no auth required)
|
||||
if (!schema && !schemaLoading && !schemaError) {
|
||||
// Load schema only for authenticated users
|
||||
if (isAuthenticated && !schema && !schemaLoading && !schemaError) {
|
||||
dispatch(fetchSchema());
|
||||
}
|
||||
}, [schema, schemaLoading, schemaError, dispatch]);
|
||||
}, [isAuthenticated, schema, schemaLoading, schemaError, dispatch]);
|
||||
|
||||
return {
|
||||
schema,
|
||||
|
||||
@ -39,7 +39,8 @@ export const rotateWopiKeysAction = createAsyncThunk('config/rotateWopiKeys', as
|
||||
|
||||
const initialState = {
|
||||
config: null,
|
||||
schema: null,
|
||||
schema: null, // Full schema for admin panel
|
||||
passwordSchema: null, // Minimal schema for Setup page password validation
|
||||
loading: false,
|
||||
schemaLoading: false,
|
||||
saving: false,
|
||||
@ -64,6 +65,9 @@ const configSlice = createSlice({
|
||||
},
|
||||
clearError: state => {
|
||||
state.error = null;
|
||||
},
|
||||
setPasswordSchema: (state, action) => {
|
||||
state.passwordSchema = action.payload;
|
||||
}
|
||||
},
|
||||
extraReducers: builder => {
|
||||
@ -127,11 +131,12 @@ const configSlice = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
export const {updateLocalConfig, clearConfig, clearError} = configSlice.actions;
|
||||
export const {updateLocalConfig, clearConfig, clearError, setPasswordSchema} = configSlice.actions;
|
||||
|
||||
// Selectors
|
||||
export const selectConfig = state => state.config.config;
|
||||
export const selectSchema = state => state.config.schema;
|
||||
export const selectPasswordSchema = state => state.config.passwordSchema;
|
||||
export const selectConfigLoading = state => state.config.loading;
|
||||
export const selectSchemaLoading = state => state.config.schemaLoading;
|
||||
export const selectConfigSaving = state => state.config.saving;
|
||||
|
||||
@ -9,6 +9,7 @@ const adminPanelJwtSecret = require('../../jwtSecret');
|
||||
const tenantManager = require('../../../../../Common/sources/tenantManager');
|
||||
const commonDefines = require('../../../../../Common/sources/commondefines');
|
||||
const {validateScoped} = require('../config/config.service');
|
||||
const supersetSchema = require('../../../../../Common/config/schemas/config.schema.json');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -83,7 +84,8 @@ function requireAuth(req, res, next) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if AdminPanel setup is required
|
||||
* Check if initial setup is required and get password validation schema
|
||||
* Returns setup status and minimal schema for password validation
|
||||
*/
|
||||
router.get('/setup/required', async (req, res) => {
|
||||
const ctx = new operationContext.Context();
|
||||
@ -100,7 +102,22 @@ router.get('/setup/required', async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
res.json({setupRequired});
|
||||
// Include minimal password validation schema for setup page
|
||||
const passwordValidationSchema = {
|
||||
$defs: supersetSchema.$defs,
|
||||
properties: {
|
||||
adminPanel: {
|
||||
properties: {
|
||||
passwordValidation: supersetSchema.properties.adminPanel.properties.passwordValidation
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
setupRequired,
|
||||
passwordValidationSchema
|
||||
});
|
||||
} catch (error) {
|
||||
ctx.logger.error('Setup check error: %s', error.stack);
|
||||
res.status(500).json({error: 'Internal server error'});
|
||||
|
||||
@ -36,7 +36,7 @@ router.get('/', validateJWT, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/schema', async (_req, res) => {
|
||||
router.get('/schema', validateJWT, async (_req, res) => {
|
||||
res.json(supersetSchema);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user