mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-07 08:19:52 +03:00
Merge remote-tracking branch 'origin/v3.4' into dev
This commit is contained in:
30
interface/src/validators/ap.ts
Normal file
30
interface/src/validators/ap.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import Schema from 'async-validator';
|
||||
import { isAPEnabled } from '../framework/ap/APSettingsForm';
|
||||
import { APSettings } from '../types';
|
||||
import { IP_ADDRESS_VALIDATOR } from './shared';
|
||||
|
||||
export const createAPSettingsValidator = (apSettings: APSettings) =>
|
||||
new Schema({
|
||||
provision_mode: { required: true, message: 'Please provide a provision mode' },
|
||||
...(isAPEnabled(apSettings) && {
|
||||
ssid: [
|
||||
{ required: true, message: 'Please provide an SSID' },
|
||||
{ type: 'string', max: 32, message: 'SSID must be 32 characters or less' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: 'Please provide an access point password' },
|
||||
{ type: 'string', min: 8, max: 64, message: 'Password must be 8-64 characters' }
|
||||
],
|
||||
channel: [
|
||||
{ required: true, message: 'Please provide a network channel' },
|
||||
{ type: 'number', message: 'Channel must be between 1 and 14' }
|
||||
],
|
||||
max_clients: [
|
||||
{ required: true, message: 'Please specify a value for max clients' },
|
||||
{ type: 'number', min: 1, max: 9, message: 'Max clients must be between 1 and 9' }
|
||||
],
|
||||
local_ip: [{ required: true, message: 'Local IP address is required' }, IP_ADDRESS_VALIDATOR],
|
||||
gateway_ip: [{ required: true, message: 'Gateway IP address is required' }, IP_ADDRESS_VALIDATOR],
|
||||
subnet_mask: [{ required: true, message: 'Subnet mask is required' }, IP_ADDRESS_VALIDATOR]
|
||||
})
|
||||
});
|
||||
12
interface/src/validators/authentication.ts
Normal file
12
interface/src/validators/authentication.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import Schema from 'async-validator';
|
||||
|
||||
export const SIGN_IN_REQUEST_VALIDATOR = new Schema({
|
||||
username: {
|
||||
required: true,
|
||||
message: 'Please provide a username'
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: 'Please provide a password'
|
||||
}
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
export { default as isHostname } from './isHostname';
|
||||
export { default as isIP } from './isIP';
|
||||
export { default as optional } from './optional';
|
||||
export { default as or } from './or';
|
||||
export { default as isPath } from './isPath';
|
||||
export { default as isIPv4 } from './isIPv4';
|
||||
export * from './ap';
|
||||
export * from './authentication';
|
||||
export * from './mqtt';
|
||||
export * from './ntp';
|
||||
export * from './security';
|
||||
export * from './shared';
|
||||
export * from './system';
|
||||
export * from './network';
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
const hostnameLengthRegex = /^.{0,48}$/;
|
||||
const hostnamePatternRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
||||
|
||||
export default function isHostname(hostname: string) {
|
||||
return (
|
||||
hostnameLengthRegex.test(hostname) && hostnamePatternRegex.test(hostname)
|
||||
);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
const ipAddressRegexp = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/;
|
||||
|
||||
export default function isIp(ipAddress: string) {
|
||||
return ipAddressRegexp.test(ipAddress);
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
const ipv4AddressRegexp = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
|
||||
export default function isIpv4(ipAddress: string) {
|
||||
return ipv4AddressRegexp.test(ipAddress);
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
const pathLengthRegex = /^[^.]{0,108}$/;
|
||||
const pathPatternRegex = /^([a-zA-Z0-9_][a-zA-Z0-9/_-]*[a-zA-Z0-9_])$/;
|
||||
|
||||
export default function isPath(path: string) {
|
||||
return pathLengthRegex.test(path) && pathPatternRegex.test(path);
|
||||
}
|
||||
18
interface/src/validators/mqtt.ts
Normal file
18
interface/src/validators/mqtt.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Schema from 'async-validator';
|
||||
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
||||
|
||||
export const MQTT_SETTINGS_VALIDATOR = new Schema({
|
||||
host: [{ required: true, message: 'Host is required' }, IP_OR_HOSTNAME_VALIDATOR],
|
||||
port: [
|
||||
{ required: true, message: 'Port is required' },
|
||||
{ type: 'number', min: 0, max: 65535, message: 'Port must be between 0 and 65535' }
|
||||
],
|
||||
keep_alive: [
|
||||
{ required: true, message: 'Keep alive is required' },
|
||||
{ type: 'number', min: 1, max: 86400, message: 'Keep alive must be between 1 and 86400' }
|
||||
],
|
||||
max_topic_length: [
|
||||
{ required: true, message: 'Max topic length is required' },
|
||||
{ type: 'number', min: 16, max: 1024, message: 'Max topic length must be between 16 and 1024' }
|
||||
]
|
||||
});
|
||||
21
interface/src/validators/network.ts
Normal file
21
interface/src/validators/network.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import Schema from 'async-validator';
|
||||
import { NetworkSettings } from '../types';
|
||||
import { HOSTNAME_VALIDATOR, IP_ADDRESS_VALIDATOR } from './shared';
|
||||
|
||||
export const createNetworkSettingsValidator = (networkSettings: NetworkSettings) =>
|
||||
new Schema({
|
||||
ssid: [{ type: 'string', max: 32, message: 'SSID must be 32 characters or less' }],
|
||||
password: { type: 'string', max: 64, message: 'Password must be 64 characters or less' },
|
||||
hostname: [{ required: true, message: 'Hostname is required' }, HOSTNAME_VALIDATOR],
|
||||
...(networkSettings.static_ip_config && {
|
||||
local_ip: [{ required: true, message: 'Local IP is required' }, IP_ADDRESS_VALIDATOR],
|
||||
gateway_ip: [{ required: true, message: 'Gateway IP is required' }, IP_ADDRESS_VALIDATOR],
|
||||
subnet_mask: [{ required: true, message: 'Subnet mask is required' }, IP_ADDRESS_VALIDATOR],
|
||||
dns_ip_1: IP_ADDRESS_VALIDATOR,
|
||||
dns_ip_2: IP_ADDRESS_VALIDATOR
|
||||
}),
|
||||
tx_power: [
|
||||
{ required: true, message: 'Tx Power is required' },
|
||||
{ type: 'number', min: 0, max: 20, message: 'Tx Power must be between 0 and 20dBm' }
|
||||
]
|
||||
});
|
||||
10
interface/src/validators/ntp.ts
Normal file
10
interface/src/validators/ntp.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Schema from 'async-validator';
|
||||
import { IP_OR_HOSTNAME_VALIDATOR } from './shared';
|
||||
|
||||
export const NTP_SETTINGS_VALIDATOR = new Schema({
|
||||
server: [{ required: true, message: 'Server is required' }, IP_OR_HOSTNAME_VALIDATOR],
|
||||
tz_label: {
|
||||
required: true,
|
||||
message: 'Time zone is required'
|
||||
}
|
||||
});
|
||||
@@ -1,4 +0,0 @@
|
||||
const OPTIONAL = (validator: (value: any) => boolean) => (value: any) =>
|
||||
!value || validator(value);
|
||||
|
||||
export default OPTIONAL;
|
||||
@@ -1,8 +0,0 @@
|
||||
const OR = (
|
||||
validator1: (value: any) => boolean,
|
||||
validator2: (value: any) => boolean
|
||||
) => {
|
||||
return (value: any) => validator1(value) || validator2(value);
|
||||
};
|
||||
|
||||
export default OR;
|
||||
36
interface/src/validators/security.ts
Normal file
36
interface/src/validators/security.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import Schema, { InternalRuleItem } from 'async-validator';
|
||||
import { User } from '../types';
|
||||
|
||||
export const SECURITY_SETTINGS_VALIDATOR = new Schema({
|
||||
jwt_secret: [
|
||||
{ required: true, message: 'JWT secret is required' },
|
||||
{ type: 'string', min: 1, max: 64, message: 'JWT secret must be between 1 and 64 characters' }
|
||||
]
|
||||
});
|
||||
|
||||
export const createUserValidator = (users: User[], creating: boolean) =>
|
||||
new Schema({
|
||||
username: [
|
||||
{ required: true, message: 'Username is required' },
|
||||
{
|
||||
type: 'string',
|
||||
pattern: /^[a-zA-Z0-9_\\.]{1,24}$/,
|
||||
message: "Must be 1-24 characters: alpha numeric, '_' or '.'"
|
||||
},
|
||||
...(creating ? [createUniqueUsernameValidator(users)] : [])
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: 'Please provide a password' },
|
||||
{ type: 'string', min: 1, max: 64, message: 'Password must be 1-64 characters' }
|
||||
]
|
||||
});
|
||||
|
||||
export const createUniqueUsernameValidator = (users: User[]) => ({
|
||||
validator(rule: InternalRuleItem, username: string, callback: (error?: string) => void) {
|
||||
if (username && users.find((u) => u.username === username)) {
|
||||
callback('Username already in use');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
});
|
||||
60
interface/src/validators/shared.ts
Normal file
60
interface/src/validators/shared.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import Schema, { InternalRuleItem, ValidateOption } from 'async-validator';
|
||||
|
||||
export const validate = <T extends object>(
|
||||
validator: Schema,
|
||||
source: Partial<T>,
|
||||
options?: ValidateOption
|
||||
): Promise<T> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
validator.validate(source, options ? options : {}, (errors, fieldErrors) => {
|
||||
if (errors) {
|
||||
reject(fieldErrors);
|
||||
} else {
|
||||
resolve(source as T);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// updated to support both IPv4 and IPv6
|
||||
const IP_ADDRESS_REGEXP =
|
||||
// eslint-disable-next-line max-len
|
||||
/((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))/;
|
||||
|
||||
const isValidIpAddress = (value: string) => IP_ADDRESS_REGEXP.test(value);
|
||||
|
||||
export const IP_ADDRESS_VALIDATOR = {
|
||||
validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) {
|
||||
if (value && !isValidIpAddress(value)) {
|
||||
callback('Must be an IP address');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const HOSTNAME_LENGTH_REGEXP = /^.{0,63}$/;
|
||||
const HOSTNAME_PATTERN_REGEXP =
|
||||
/^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9-]*[A-Za-z0-9])$/;
|
||||
|
||||
const isValidHostname = (value: string) => HOSTNAME_LENGTH_REGEXP.test(value) && HOSTNAME_PATTERN_REGEXP.test(value);
|
||||
|
||||
export const HOSTNAME_VALIDATOR = {
|
||||
validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) {
|
||||
if (value && !isValidHostname(value)) {
|
||||
callback('Must be a valid hostname of up to 63 characters');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const IP_OR_HOSTNAME_VALIDATOR = {
|
||||
validator(rule: InternalRuleItem, value: string, callback: (error?: string) => void) {
|
||||
if (value && !(isValidIpAddress(value) || isValidHostname(value))) {
|
||||
callback('Must be a valid IP address or hostname of up to 63 characters');
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
};
|
||||
12
interface/src/validators/system.ts
Normal file
12
interface/src/validators/system.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import Schema from 'async-validator';
|
||||
|
||||
export const OTA_SETTINGS_VALIDATOR = new Schema({
|
||||
port: [
|
||||
{ required: true, message: 'Port is required' },
|
||||
{ type: 'number', min: 1025, max: 65535, message: 'Port must be between 1025 and 65535' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: 'Password is required' },
|
||||
{ type: 'string', min: 1, max: 64, message: 'Password must be between 1 and 64 characters' }
|
||||
]
|
||||
});
|
||||
Reference in New Issue
Block a user