Merge remote-tracking branch 'origin/v3.4' into dev

This commit is contained in:
proddy
2022-01-23 17:56:52 +01:00
parent 02e2b51814
commit 77e1898512
538 changed files with 32282 additions and 38655 deletions

View 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]
})
});

View 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'
}
});

View File

@@ -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';

View File

@@ -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)
);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View 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' }
]
});

View 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' }
]
});

View 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'
}
});

View File

@@ -1,4 +0,0 @@
const OPTIONAL = (validator: (value: any) => boolean) => (value: any) =>
!value || validator(value);
export default OPTIONAL;

View File

@@ -1,8 +0,0 @@
const OR = (
validator1: (value: any) => boolean,
validator2: (value: any) => boolean
) => {
return (value: any) => validator1(value) || validator2(value);
};
export default OR;

View 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();
}
}
});

View 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();
}
}
};

View 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' }
]
});