@gov-cy/govcy-express-services 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1157 -0
- package/package.json +72 -0
- package/src/auth/cyLoginAuth.mjs +123 -0
- package/src/index.mjs +188 -0
- package/src/middleware/cyLoginAuth.mjs +131 -0
- package/src/middleware/govcyConfigSiteData.mjs +38 -0
- package/src/middleware/govcyCsrf.mjs +36 -0
- package/src/middleware/govcyFormsPostHandler.mjs +83 -0
- package/src/middleware/govcyHeadersControl.mjs +22 -0
- package/src/middleware/govcyHttpErrorHandler.mjs +63 -0
- package/src/middleware/govcyLanguageMiddleware.mjs +19 -0
- package/src/middleware/govcyLogger.mjs +15 -0
- package/src/middleware/govcyManifestHandler.mjs +46 -0
- package/src/middleware/govcyPDFRender.mjs +30 -0
- package/src/middleware/govcyPageHandler.mjs +110 -0
- package/src/middleware/govcyPageRender.mjs +14 -0
- package/src/middleware/govcyRequestTimer.mjs +29 -0
- package/src/middleware/govcyReviewPageHandler.mjs +102 -0
- package/src/middleware/govcyReviewPostHandler.mjs +147 -0
- package/src/middleware/govcyRoutePageHandler.mjs +37 -0
- package/src/middleware/govcyServiceEligibilityHandler.mjs +101 -0
- package/src/middleware/govcySessionData.mjs +9 -0
- package/src/middleware/govcySuccessPageHandler.mjs +112 -0
- package/src/public/img/Certificate_A4.svg +30 -0
- package/src/public/js/govcyForms.js +21 -0
- package/src/resources/govcyResources.mjs +430 -0
- package/src/standalone.mjs +7 -0
- package/src/utils/govcyApiRequest.mjs +114 -0
- package/src/utils/govcyConstants.mjs +4 -0
- package/src/utils/govcyDataLayer.mjs +311 -0
- package/src/utils/govcyEnvVariables.mjs +45 -0
- package/src/utils/govcyFormHandling.mjs +148 -0
- package/src/utils/govcyLoadConfigData.mjs +135 -0
- package/src/utils/govcyLogger.mjs +30 -0
- package/src/utils/govcyNotification.mjs +85 -0
- package/src/utils/govcyPdfMaker.mjs +27 -0
- package/src/utils/govcyReviewSummary.mjs +205 -0
- package/src/utils/govcySubmitData.mjs +530 -0
- package/src/utils/govcyUtils.mjs +13 -0
- package/src/utils/govcyValidator.mjs +352 -0
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module govcyValidator
|
|
3
|
+
* @fileoverview This module provides validation functions for form elements.
|
|
4
|
+
* It includes a function to validate form elements based on specified rules and conditions.
|
|
5
|
+
* It also handles conditional elements and checks for specific input types.
|
|
6
|
+
* Validation Types Breakdown:
|
|
7
|
+
* - `required`: Checks if the value is not null, undefined, or an empty string (after trimming).
|
|
8
|
+
* - `valid`: Executes the appropriate validation based on the checkValue (e.g., numeric, telCY, etc.).
|
|
9
|
+
* - `length`: Ensures that the value's length doesn't exceed the specified limit.
|
|
10
|
+
* - `regCheck`: Performs custom regex validation as per the rule's checkValue.
|
|
11
|
+
*/
|
|
12
|
+
import * as govcyResources from "../resources/govcyResources.mjs";
|
|
13
|
+
import { ALLOWED_FORM_ELEMENTS } from "./govcyConstants.mjs";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* This function validates a value based on the provided rules.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} value The value to validate
|
|
19
|
+
* @param {Array} rules The validation rules to apply
|
|
20
|
+
* @returns Error message text object if validation fails, otherwise null
|
|
21
|
+
*/
|
|
22
|
+
function validateValue(value, rules) {
|
|
23
|
+
const validationRules = {
|
|
24
|
+
// Valid validation rules
|
|
25
|
+
numeric: (val) => /^\d+$/.test(val),
|
|
26
|
+
numDecimal: (val) => /^\d+(,\d+)?$/.test(val),
|
|
27
|
+
currency: (val) => /^\d+(,\d{1,2})?$/.test(val),
|
|
28
|
+
alpha: (val) => /^[A-Za-zΑ-Ωα-ω\u0370-\u03ff\u1f00-\u1fff\s]+$/.test(val),
|
|
29
|
+
alphaNum: (val) => /^[A-Za-zΑ-Ωα-ω\u0370-\u03ff\u1f00-\u1fff0-9\s]+$/.test(val),
|
|
30
|
+
noSpecialChars: (val) => /^([0-9]|[A-Z]|[a-z]|[α-ω]|[Α-Ω]|[,]|[.]|[-]|[(]|[)]|[?]|[!]|[;]|[:]|[\n]|[\r]|[ _]|[\u0370-\u03ff\u1f00-\u1fff])+$/.test(val),
|
|
31
|
+
name: (val) => /^[A-Za-zΑ-Ωα-ω\u0370-\u03ff\u1f00-\u1fff\s'-]+$/.test(val),
|
|
32
|
+
tel: (val) => /^(?:\+|00)?[\d\s\-()]{8,20}$/.test(val.replace(/[\s\-()]/g, '')),
|
|
33
|
+
mobile: (val) => /^(?:\+|00)?[\d\s\-()]{8,20}$/.test(val.replace(/[\s\-()]/g, '')),
|
|
34
|
+
// telCY: (val) => /^(?:\+|00)?357[-\s]?(2|9)\d{7}$/.test(val.replace(/[\s\-()]/g, '')),
|
|
35
|
+
// telCY: (val) => /^(?:\+|00)?357?(2|9)\d{7}$/.test(val.replace(/[\s\-()]/g, '')),
|
|
36
|
+
telCY: (val) => {
|
|
37
|
+
const normalized = val.replace(/[\s\-()]/g, '');
|
|
38
|
+
const isValid = /^(?:\+357|00357)?(2|9)\d{7}$/.test(normalized);
|
|
39
|
+
return isValid;
|
|
40
|
+
},
|
|
41
|
+
// mobileCY: (val) => /^(?:\+|00)?357[-\s]?9\d{7}$/.test(val.replace(/[\s\-()]/g, '')),
|
|
42
|
+
mobileCY: (val) => {
|
|
43
|
+
const normalized = val.replace(/[\s\-()]/g, ''); // Remove spaces, hyphens, and parentheses
|
|
44
|
+
return /^(?:\+357|00357)?9\d{7}$/.test(normalized); // Match Cypriot mobile numbers
|
|
45
|
+
},
|
|
46
|
+
iban: (val) => {
|
|
47
|
+
const cleanedIBAN = val.replace(/[\s-]/g, '').toUpperCase(); // Remove spaces/hyphens and convert to uppercase
|
|
48
|
+
const regex = /^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/;
|
|
49
|
+
|
|
50
|
+
// Validate structure and checksum
|
|
51
|
+
return regex.test(cleanedIBAN) && validateIBANChecksum(cleanedIBAN);
|
|
52
|
+
},
|
|
53
|
+
email: (val) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val),
|
|
54
|
+
date: (val) => !isNaN(Date.parse(val)),
|
|
55
|
+
dateISO: (val) => {
|
|
56
|
+
if (!/^\d{4}-\d{1,2}-\d{1,2}$/.test(val)) return false; // Basic format check
|
|
57
|
+
|
|
58
|
+
const [year, month, day] = val.split("-").map(Number);
|
|
59
|
+
const date = new Date(year, month - 1, day); // JavaScript months are 0-based
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
date.getFullYear() === year &&
|
|
63
|
+
date.getMonth() === month - 1 &&
|
|
64
|
+
date.getDate() === day
|
|
65
|
+
);
|
|
66
|
+
},
|
|
67
|
+
dateDMY: (val) => {
|
|
68
|
+
if (!/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(val)) return false; // First check format
|
|
69
|
+
|
|
70
|
+
const [day, month, year] = val.split('/').map(Number); // Convert to numbers
|
|
71
|
+
const date = new Date(year, month - 1, day); // Month is zero-based in JS
|
|
72
|
+
|
|
73
|
+
// Validate actual date parts
|
|
74
|
+
return (
|
|
75
|
+
date.getFullYear() === year &&
|
|
76
|
+
date.getMonth() === month - 1 &&
|
|
77
|
+
date.getDate() === day
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
// Other validation rules
|
|
81
|
+
length: (val, length) => val.length <= length,
|
|
82
|
+
required: (val) => !(val === null || val === undefined || (typeof val === 'string' && val.trim() === "")),
|
|
83
|
+
regCheck: (val, regex) => new RegExp(regex).test(val),
|
|
84
|
+
//Min and Max
|
|
85
|
+
minValue: (val, min) => {
|
|
86
|
+
const normalizedVal = normalizeNumber(val); // Normalize the input
|
|
87
|
+
if (isNaN(normalizedVal)) {
|
|
88
|
+
return false; // Return false if val cannot be converted to a number
|
|
89
|
+
}
|
|
90
|
+
return normalizedVal >= min;
|
|
91
|
+
},
|
|
92
|
+
maxValue: (val, max) => {
|
|
93
|
+
const normalizedVal = normalizeNumber(val); // Normalize the input
|
|
94
|
+
if (isNaN(normalizedVal)) {
|
|
95
|
+
return false; // Return false if val cannot be converted to a number
|
|
96
|
+
}
|
|
97
|
+
return normalizedVal <= max;
|
|
98
|
+
},
|
|
99
|
+
minValueDate: (val, minDate) => {
|
|
100
|
+
const valueDate = parseDate(val); // Parse the input date
|
|
101
|
+
const min = parseDate(minDate); // Parse the minimum date
|
|
102
|
+
if (isNaN(valueDate) || isNaN(min)) {
|
|
103
|
+
return false; // Return false if either date is invalid
|
|
104
|
+
}
|
|
105
|
+
return valueDate >= min;
|
|
106
|
+
},
|
|
107
|
+
maxValueDate: (val, maxDate) => {
|
|
108
|
+
const valueDate = parseDate(val); // Parse the input date
|
|
109
|
+
const max = parseDate(maxDate); // Parse the maximum date
|
|
110
|
+
if (isNaN(valueDate) || isNaN(max)) {
|
|
111
|
+
return false; // Return false if either date is invalid
|
|
112
|
+
}
|
|
113
|
+
return valueDate <= max;
|
|
114
|
+
},
|
|
115
|
+
minLength: (val, min) => val.length >= min
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
for (const rule of rules) {
|
|
119
|
+
// Extract rule parameters
|
|
120
|
+
const { check, params } = rule;
|
|
121
|
+
// Extract rule parameters
|
|
122
|
+
const { checkValue, message } = params;
|
|
123
|
+
|
|
124
|
+
// Handle "required" rules (check if value is not empty, null, or undefined)
|
|
125
|
+
if (check === "required") {
|
|
126
|
+
const isValid = validationRules.required(value);
|
|
127
|
+
if (!isValid) {
|
|
128
|
+
return message;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for "valid" rules (e.g., numeric, telCY, etc.)
|
|
133
|
+
if (check === "valid" && validationRules[checkValue]) {
|
|
134
|
+
const isValid = validationRules[checkValue](value);
|
|
135
|
+
if (!isValid) {
|
|
136
|
+
return message;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check for "length" rules (e.g., max length check)
|
|
141
|
+
if (check === "length") {
|
|
142
|
+
const isValid = validationRules.length(value, checkValue);
|
|
143
|
+
if (!isValid) {
|
|
144
|
+
return message;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check for "regCheck" rules (custom regex checks)
|
|
149
|
+
if (check === "regCheck") {
|
|
150
|
+
const isValid = validationRules.regCheck(value, checkValue);
|
|
151
|
+
if (!isValid) {
|
|
152
|
+
return message;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Check for "minValue"
|
|
157
|
+
if (check === 'minValue' && !validationRules.minValue(value, checkValue)) {
|
|
158
|
+
return message;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Check for "maxValue"
|
|
162
|
+
if (check === 'maxValue' && !validationRules.maxValue(value, checkValue)) {
|
|
163
|
+
return message;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Check for "minValueDate"
|
|
167
|
+
if (check === 'minValueDate' && !validationRules.minValueDate(value, checkValue)) {
|
|
168
|
+
return message;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Check for "maxValueDate"
|
|
172
|
+
if (check === 'maxValueDate' && !validationRules.maxValueDate(value, checkValue)) {
|
|
173
|
+
return message;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Check for "minLength"
|
|
177
|
+
if (check === 'minLength' && !validationRules.minLength(value, checkValue)) {
|
|
178
|
+
return message;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Helper function to validate IBAN
|
|
187
|
+
function validateIBANChecksum(iban) {
|
|
188
|
+
// Move the first four characters to the end
|
|
189
|
+
const rearranged = iban.slice(4) + iban.slice(0, 4);
|
|
190
|
+
|
|
191
|
+
// Replace letters with numbers (A=10, B=11, ..., Z=35)
|
|
192
|
+
const numericIBAN = rearranged.replace(/[A-Z]/g, (char) => char.charCodeAt(0) - 55);
|
|
193
|
+
|
|
194
|
+
// Perform modulo 97 operation
|
|
195
|
+
let remainder = numericIBAN;
|
|
196
|
+
while (remainder.length > 2) {
|
|
197
|
+
const chunk = remainder.slice(0, 9); // Process in chunks of up to 9 digits
|
|
198
|
+
remainder = (parseInt(chunk, 10) % 97) + remainder.slice(chunk.length);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return parseInt(remainder, 10) % 97 === 1;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Helper function to normalize numbers
|
|
205
|
+
function normalizeNumber(value) {
|
|
206
|
+
if (typeof value !== 'string') {
|
|
207
|
+
return NaN; // Ensure the input is a string
|
|
208
|
+
}
|
|
209
|
+
// Remove thousands separators (.)
|
|
210
|
+
const withoutThousandsSeparator = value.replace(/\./g, '');
|
|
211
|
+
// Replace the decimal separator (,) with a dot (.)
|
|
212
|
+
const normalizedValue = withoutThousandsSeparator.replace(',', '.');
|
|
213
|
+
return parseFloat(normalizedValue); // Convert to a number
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
function parseDate(value) {
|
|
218
|
+
// Check for ISO format (yyyy-mm-dd)
|
|
219
|
+
if (/^\d{4}-\d{1,2}-\d{1,2}$/.test(value)) {
|
|
220
|
+
const [year, month, day] = value.split('-').map(Number);
|
|
221
|
+
const parsedDate = new Date(year, month - 1, day); // JavaScript months are 0-based
|
|
222
|
+
if (
|
|
223
|
+
parsedDate.getFullYear() === year &&
|
|
224
|
+
parsedDate.getMonth() === month - 1 &&
|
|
225
|
+
parsedDate.getDate() === day
|
|
226
|
+
) {
|
|
227
|
+
return parsedDate;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check for DMY format (d/m/yyyy)
|
|
232
|
+
if (/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(value)) {
|
|
233
|
+
const [day, month, year] = value.split('/').map(Number);
|
|
234
|
+
const parsedDate = new Date(year, month - 1, day); // JavaScript months are 0-based
|
|
235
|
+
if (
|
|
236
|
+
parsedDate.getFullYear() === year &&
|
|
237
|
+
parsedDate.getMonth() === month - 1 &&
|
|
238
|
+
parsedDate.getDate() === day
|
|
239
|
+
) {
|
|
240
|
+
return parsedDate;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return NaN; // Return NaN if the format is invalid
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* 🔹 Recursive function to validate form fields, including conditionally displayed fields.
|
|
249
|
+
* @param {Array} elements - The form elements (including conditional ones)
|
|
250
|
+
* @param {Object} formData - The submitted form data
|
|
251
|
+
* @param {string} pageUrl - Use this when linking error summary with the page instead of the element
|
|
252
|
+
* @returns {Object} validationErrors - The object containing validation errors
|
|
253
|
+
*/
|
|
254
|
+
export function validateFormElements(elements, formData, pageUrl) {
|
|
255
|
+
const validationErrors = {};
|
|
256
|
+
elements.forEach(field => {
|
|
257
|
+
const inputElements = ALLOWED_FORM_ELEMENTS;
|
|
258
|
+
//only validate input elements
|
|
259
|
+
if (inputElements.includes(field.element)) {
|
|
260
|
+
const fieldValue = (field.element === "dateInput")
|
|
261
|
+
? [formData[`${field.params.name}_year`],
|
|
262
|
+
formData[`${field.params.name}_month`],
|
|
263
|
+
formData[`${field.params.name}_day`]]
|
|
264
|
+
.filter(Boolean) // Remove empty values
|
|
265
|
+
.join("-") // Join remaining parts
|
|
266
|
+
: formData[field.params.name] || ""; // Get submitted value
|
|
267
|
+
|
|
268
|
+
//Autocheck: check for "checkboxes", "radios", "select" if `fieldValue` is one of the `field.params.items
|
|
269
|
+
if (["checkboxes", "radios", "select"].includes(field.element) && fieldValue !== "") {
|
|
270
|
+
const valuesToCheck = Array.isArray(fieldValue) ? fieldValue : [fieldValue]; // Ensure it's always an array
|
|
271
|
+
const isMatch = valuesToCheck.every(value =>
|
|
272
|
+
field.params.items.some(item => item.value === value)
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
if (!isMatch) {
|
|
276
|
+
validationErrors[(pageUrl ? pageUrl : "") + field.params.name] = {
|
|
277
|
+
id: field.params.id,
|
|
278
|
+
message: govcyResources.staticResources.text.valueNotOnList,
|
|
279
|
+
pageUrl: pageUrl || "",
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (field.validations) {
|
|
285
|
+
// 🔍 Validate the field using all its validation rules
|
|
286
|
+
const errorMessage = validateValue(fieldValue, field.validations);
|
|
287
|
+
if (errorMessage) {
|
|
288
|
+
if (!validationErrors[field.params.name]) {
|
|
289
|
+
validationErrors[(pageUrl ? pageUrl : "") + field.params.name] = {};
|
|
290
|
+
}
|
|
291
|
+
validationErrors[(pageUrl ? pageUrl : "") + field.params.name] = {
|
|
292
|
+
id: field.params.id,
|
|
293
|
+
message: errorMessage,
|
|
294
|
+
pageUrl: pageUrl || "",
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// 🔹 Handle conditional fields (only validate them if the parent condition is met)
|
|
300
|
+
// Handle conditional elements inside radios
|
|
301
|
+
if (field.element === "radios" && field.params.items) {
|
|
302
|
+
field.params.items.forEach(item => {
|
|
303
|
+
if (item.conditionalElements && fieldValue === item.value) {
|
|
304
|
+
if (Array.isArray(item.conditionalElements)) {
|
|
305
|
+
item.conditionalElements.forEach(conditionalElement => {
|
|
306
|
+
|
|
307
|
+
const conditionalFieldValue = (conditionalElement.element === "dateInput")
|
|
308
|
+
? [formData[`${conditionalElement.params.name}_year`],
|
|
309
|
+
formData[`${conditionalElement.params.name}_month`],
|
|
310
|
+
formData[`${conditionalElement.params.name}_day`]]
|
|
311
|
+
.filter(Boolean) // Remove empty values
|
|
312
|
+
.join("-") // Join remaining parts
|
|
313
|
+
: formData[conditionalElement.params.name] || ""; // Get submitted value
|
|
314
|
+
|
|
315
|
+
//Autocheck: check for "checkboxes", "radios", "select" if `fieldValue` is one of the `field.params.items`
|
|
316
|
+
if (["checkboxes", "radios", "select"].includes(conditionalElement.element) && conditionalFieldValue !== "") {
|
|
317
|
+
const valuesToCheck = Array.isArray(conditionalFieldValue) ? conditionalFieldValue : [conditionalFieldValue]; // Ensure it's always an array
|
|
318
|
+
const isMatch = valuesToCheck.every(value =>
|
|
319
|
+
conditionalElement.params.items.some(item => item.value === value)
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
if (!isMatch) {
|
|
323
|
+
validationErrors[(pageUrl ? pageUrl : "") + conditionalElement.params.name] = {
|
|
324
|
+
id: conditionalElement.params.id,
|
|
325
|
+
message: govcyResources.staticResources.text.valueNotOnList,
|
|
326
|
+
pageUrl: pageUrl || "",
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
//if conditional element has validations
|
|
331
|
+
if (conditionalElement.validations) {
|
|
332
|
+
const errorMessage = validateValue(conditionalFieldValue, conditionalElement.validations);
|
|
333
|
+
if (errorMessage) {
|
|
334
|
+
if (!validationErrors[conditionalElement.params.name]) {
|
|
335
|
+
validationErrors[(pageUrl ? pageUrl : "") + conditionalElement.params.name] = {};
|
|
336
|
+
}
|
|
337
|
+
validationErrors[(pageUrl ? pageUrl : "") + conditionalElement.params.name] = {
|
|
338
|
+
id: conditionalElement.params.id,
|
|
339
|
+
message: errorMessage,
|
|
340
|
+
pageUrl: pageUrl || "",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
return validationErrors;
|
|
352
|
+
}
|