diff --git a/package.json b/package.json index 131fa1fc..a0d94b98 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "./process": "./lib/process/index.js", "./types": "./lib/types/index.js", "./experimental": "./lib/experimental/index.js", - "./dist/formio.core.min.js": "./dist/formio.core.min.js" + "./dist/formio.core.min.js": "./dist/formio.core.min.js", + "./error": "./lib/error/index.js" }, "scripts": { "test": "TEST=1 mocha -r ts-node/register -r tsconfig-paths/register -r mock-local-storage -r jsdom-global/register -b -t 0 'src/**/__tests__/*.test.ts'", diff --git a/src/index.ts b/src/index.ts index 233ebd6c..145533cd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,8 @@ export * from './modules'; export * from './utils'; export * from './process/validation'; +export * from './process/validation/rules'; export * from './process'; export * from './sdk'; export * from './types'; +export * from './error'; diff --git a/src/process/normalize/index.ts b/src/process/normalize/index.ts index 45b513c3..2893a636 100644 --- a/src/process/normalize/index.ts +++ b/src/process/normalize/index.ts @@ -254,6 +254,10 @@ const normalizeTextFieldComponentValue = ( value: any, path: string ) => { + // If the component has truncate multiple spaces enabled, then normalize the value to remove extra spaces. + if (component.truncateMultipleSpaces && typeof value === 'string') { + value = value.trim().replace(/\s{2,}/g, ' '); + } if (component.allowMultipleMasks && component.inputMasks && component.inputMasks.length > 0) { if (Array.isArray(value)) { return value.map((val) => normalizeMaskValue(component, defaultValues, val, path)); diff --git a/src/process/validation/i18n/en.ts b/src/process/validation/i18n/en.ts index 3362078d..d1de6ca3 100644 --- a/src/process/validation/i18n/en.ts +++ b/src/process/validation/i18n/en.ts @@ -31,9 +31,6 @@ export const EN_ERRORS = { invalidValueProperty: 'Invalid Value Property', mask: '{{field}} does not match the mask.', valueIsNotAvailable: '{{ field }} is an invalid value.', - captchaTokenValidation: 'ReCAPTCHA: Token validation error', - captchaTokenNotSpecified: 'ReCAPTCHA: Token is not specified in submission', - captchaFailure: 'ReCaptcha: Response token not found', time: '{{field}} is not a valid time.', invalidDate: '{{field}} is not a valid date', number: '{{field}} is not a valid number.' diff --git a/src/process/validation/rules/databaseRules.ts b/src/process/validation/rules/databaseRules.ts index 54e21f2e..667dd25c 100644 --- a/src/process/validation/rules/databaseRules.ts +++ b/src/process/validation/rules/databaseRules.ts @@ -1,11 +1,9 @@ import { ValidationRuleInfo } from "types"; import { validateUniqueInfo } from "./validateUnique"; -import { validateCaptchaInfo } from "./validateCaptcha"; import { validateResourceSelectValueInfo } from "./validateResourceSelectValue"; // These are the validations that require a database connection. export const databaseRules: ValidationRuleInfo[] = [ validateUniqueInfo, - validateCaptchaInfo, validateResourceSelectValueInfo ]; diff --git a/src/process/validation/rules/validateCaptcha.ts b/src/process/validation/rules/validateCaptcha.ts deleted file mode 100644 index b9f39cea..00000000 --- a/src/process/validation/rules/validateCaptcha.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { FieldError } from '../../../error/FieldError'; -import { RuleFn, ValidationContext } from '../../../types/index'; -import { ProcessorError } from 'error'; -import { ProcessorInfo } from 'types/process/ProcessorInfo'; - -export const shouldValidate = (context: ValidationContext) => { - const { component } = context; - if (component.type === 'recaptcha') { - return true; - } - return false; -}; - -export const validateCaptcha: RuleFn = async (context: ValidationContext) => { - const { value, config, component } = context; - if (!shouldValidate(context)) { - return null; - } - - if (!config || !config.database) { - throw new ProcessorError("Can't test for recaptcha success without a database config object", context, 'validate:validateCaptcha'); - } - try { - if (!value || !value.token) { - return new FieldError('captchaTokenNotSpecified', context, 'catpcha'); - } - if (!value.success) { - return new FieldError('captchaTokenValidation', context, 'captcha'); - } - const captchaResult: boolean = await config.database?.validateCaptcha(value.token); - return (captchaResult === true) ? null : new FieldError('captchaFailure', context, 'captcha'); - } - catch (err: any) { - throw new ProcessorError(err.message || err, context, 'validate:validateCaptcha'); - } -}; - -export const validateCaptchaInfo: ProcessorInfo = { - name: 'validateCaptcha', - process: validateCaptcha, - shouldProcess: shouldValidate, -}; diff --git a/src/process/validation/rules/validateRequired.ts b/src/process/validation/rules/validateRequired.ts index 96fb872e..22c681ce 100644 --- a/src/process/validation/rules/validateRequired.ts +++ b/src/process/validation/rules/validateRequired.ts @@ -8,6 +8,7 @@ import { DayComponent } from 'types'; import { isEmptyObject } from '../util'; +import { isComponentNestedDataType } from 'utils/formUtil'; import { ProcessorInfo } from 'types/process/ProcessorInfo'; const isAddressComponent = (component: any): component is AddressComponent => { @@ -28,7 +29,7 @@ const isComponentThatCannotHaveFalseValue = (component: any): boolean => { return component.type === 'checkbox' || component.type === 'selectboxes' } -const valueIsPresent = (value: any, considerFalseTruthy: boolean): boolean => { +const valueIsPresent = (value: any, considerFalseTruthy: boolean, isNestedDatatype?: boolean): boolean => { // Evaluate for 3 out of 6 falsy values ("", null, undefined), don't check for 0 // and only check for false under certain conditions if (value === null || value === undefined || value === "" || (!considerFalseTruthy && value === false)) { @@ -43,7 +44,7 @@ const valueIsPresent = (value: any, considerFalseTruthy: boolean): boolean => { return false; } // Recursively evaluate - else if (typeof value === 'object') { + else if (typeof value === 'object' && !isNestedDatatype) { return Object.values(value).some((val) => valueIsPresent(val, considerFalseTruthy)); } return true; @@ -74,9 +75,9 @@ export const validateRequiredSync: RuleFnSync = (context: ValidationContext) => return error; } else if (isComponentThatCannotHaveFalseValue(component)) { - return !valueIsPresent(value, false) ? error : null; + return !valueIsPresent(value, false, isComponentNestedDataType(component)) ? error : null; } - return !valueIsPresent(value, true) ? error : null; + return !valueIsPresent(value, true, isComponentNestedDataType(component)) ? error : null; }; export const validateRequiredInfo: ProcessorInfo = { diff --git a/src/types/project/settings/ProjectSettings.ts b/src/types/project/settings/ProjectSettings.ts index ee8d839d..92348efc 100644 --- a/src/types/project/settings/ProjectSettings.ts +++ b/src/types/project/settings/ProjectSettings.ts @@ -9,7 +9,7 @@ import { ProjectFileStorageConfig, ProjectGoogleDriveConfig, ProjectKickboxConfig, - ProjectReCaptchaConfig, + ProjectCaptchaConfig, ProjectSQLConnectorConfig, } from './integrations'; @@ -39,7 +39,8 @@ export type ProjectSettings = { // Integrations email?: ProjectEmailConfig; - recaptcha?: ProjectReCaptchaConfig; + captcha?: ProjectCaptchaConfig; + recaptcha?: ProjectCaptchaConfig; esign?: ProjectESignConfig; google?: ProjectGoogleDriveConfig; kickbox?: ProjectKickboxConfig; diff --git a/src/types/project/settings/integrations/reCaptcha.ts b/src/types/project/settings/integrations/captcha.ts similarity index 54% rename from src/types/project/settings/integrations/reCaptcha.ts rename to src/types/project/settings/integrations/captcha.ts index 0594423a..dd0ed2f0 100644 --- a/src/types/project/settings/integrations/reCaptcha.ts +++ b/src/types/project/settings/integrations/captcha.ts @@ -1,4 +1,4 @@ -export type ProjectReCaptchaConfig = { +export type ProjectCaptchaConfig = { siteKey: string; secretKey: string; }; diff --git a/src/types/project/settings/integrations/index.ts b/src/types/project/settings/integrations/index.ts index 781cf5bd..fb02f5e1 100644 --- a/src/types/project/settings/integrations/index.ts +++ b/src/types/project/settings/integrations/index.ts @@ -2,4 +2,4 @@ export * from './dataConnections'; export * from './email'; export * from './eSign'; export * from './fileStorage'; -export * from './reCaptcha'; +export * from './captcha';