@gov-cy/govcy-express-services 1.11.4 → 1.12.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/README.md +109 -42
- package/package.json +1 -1
- package/src/utils/govcyValidator.mjs +53 -17
package/README.md
CHANGED
|
@@ -156,17 +156,17 @@ The CY Login settings are configured in the `secrets/.env` file.
|
|
|
156
156
|
|
|
157
157
|
Each service can specify which types of authenticated CY Login profiles are allowed to access it using the `site.cyLoginPolicies` property in its site configuration.
|
|
158
158
|
|
|
159
|
-
```json
|
|
160
|
-
"cyLoginPolicies": ["naturalPerson", "legalPerson", "eidasNaturalPerson"]
|
|
161
|
-
```
|
|
159
|
+
```json
|
|
160
|
+
"cyLoginPolicies": ["naturalPerson", "legalPerson", "eidasNaturalPerson"]
|
|
161
|
+
```
|
|
162
162
|
|
|
163
163
|
##### Supported Policies
|
|
164
164
|
|
|
165
|
-
| Policy name | Description | Typical use |
|
|
166
|
-
| --------------- | ------------------------------------------------------------ | ---------------------------------------------------- |
|
|
167
|
-
| `naturalPerson` | Allows individual users (Cypriot citizens or foreign residents) who have a verified profile in the Civil Registry. Identified by `profile_type: "Individual"` and a 10-digit identifier starting with `00` (citizen) or `05` (foreigner). | Citizen-facing services, personal applications, etc. |
|
|
168
|
-
| `legalPerson` | Allows legal entities (companies, partnerships, organisations) with verified profiles in the Registrar of Companies. Identified by `profile_type: "Organisation"` and a `legal_unique_identifier`. | Business-facing services, company submissions, etc. |
|
|
169
|
-
| `eidasNaturalPerson` | Allows eIDAS natural persons identified by `profile_type: "Individual"` and `unique_identifier` in the `CC/CC/<identifier>` format. | Cross-border eIDAS individual services. |
|
|
165
|
+
| Policy name | Description | Typical use |
|
|
166
|
+
| --------------- | ------------------------------------------------------------ | ---------------------------------------------------- |
|
|
167
|
+
| `naturalPerson` | Allows individual users (Cypriot citizens or foreign residents) who have a verified profile in the Civil Registry. Identified by `profile_type: "Individual"` and a 10-digit identifier starting with `00` (citizen) or `05` (foreigner). | Citizen-facing services, personal applications, etc. |
|
|
168
|
+
| `legalPerson` | Allows legal entities (companies, partnerships, organisations) with verified profiles in the Registrar of Companies. Identified by `profile_type: "Organisation"` and a `legal_unique_identifier`. | Business-facing services, company submissions, etc. |
|
|
169
|
+
| `eidasNaturalPerson` | Allows eIDAS natural persons identified by `profile_type: "Individual"` and `unique_identifier` in the `CC/CC/<identifier>` format. | Cross-border eIDAS individual services. |
|
|
170
170
|
|
|
171
171
|
##### How it works
|
|
172
172
|
|
|
@@ -185,21 +185,21 @@ This maintains backward compatibility with existing services that only supported
|
|
|
185
185
|
|
|
186
186
|
##### Example
|
|
187
187
|
|
|
188
|
-
Allow both natural and legal persons:
|
|
189
|
-
|
|
190
|
-
```json
|
|
191
|
-
"site": {
|
|
192
|
-
"cyLoginPolicies": ["naturalPerson", "legalPerson"]
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Allow Cypriot and eIDAS natural persons:
|
|
197
|
-
|
|
198
|
-
```json
|
|
199
|
-
"site": {
|
|
200
|
-
"cyLoginPolicies": ["naturalPerson", "eidasNaturalPerson"]
|
|
201
|
-
}
|
|
202
|
-
```
|
|
188
|
+
Allow both natural and legal persons:
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
"site": {
|
|
192
|
+
"cyLoginPolicies": ["naturalPerson", "legalPerson"]
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Allow Cypriot and eIDAS natural persons:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
"site": {
|
|
200
|
+
"cyLoginPolicies": ["naturalPerson", "eidasNaturalPerson"]
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
203
|
|
|
204
204
|
Restrict access to natural persons only:
|
|
205
205
|
|
|
@@ -320,6 +320,35 @@ Here is an example JSON config:
|
|
|
320
320
|
"en": "Menu",
|
|
321
321
|
"tr": "Menu"
|
|
322
322
|
},
|
|
323
|
+
"navigation": { //<-- Navigation menu items
|
|
324
|
+
"items": [
|
|
325
|
+
{
|
|
326
|
+
"label": {
|
|
327
|
+
"el": "Αρχική",
|
|
328
|
+
"en": "Home",
|
|
329
|
+
"tr": "Home"
|
|
330
|
+
},
|
|
331
|
+
"href": {
|
|
332
|
+
"el": "/test/",
|
|
333
|
+
"en": "/test/",
|
|
334
|
+
"tr": "/test/"
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
"label": {
|
|
339
|
+
"el": "Αίτηση",
|
|
340
|
+
"en": "The application",
|
|
341
|
+
"tr": "The application"
|
|
342
|
+
},
|
|
343
|
+
"href": {
|
|
344
|
+
"el": "/test/task-list",
|
|
345
|
+
"en": "/test/task-list",
|
|
346
|
+
"tr": "/test/task-list"
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
]
|
|
350
|
+
},
|
|
351
|
+
"menuHideLabelVisibility": true,
|
|
323
352
|
"title": { //<-- Service title (meta)
|
|
324
353
|
"el": "Υπηρεσία τεστ",
|
|
325
354
|
"en": "Test service",
|
|
@@ -789,6 +818,37 @@ Here are some details explaining the JSON structure:
|
|
|
789
818
|
- `submissionDataVersion` : The submission data version,
|
|
790
819
|
- `rendererVersion` : The govcy-frontend-renderer version,
|
|
791
820
|
- `designSystemsVersion` : The govcy-design-system version,
|
|
821
|
+
- <span id="site-navigation"></span>`navigation`: Optional menu navigation items. Example:
|
|
822
|
+
```json
|
|
823
|
+
"navigation": {
|
|
824
|
+
"items": [
|
|
825
|
+
{
|
|
826
|
+
"label": {
|
|
827
|
+
"el": "Αρχική",
|
|
828
|
+
"en": "Home",
|
|
829
|
+
"tr": "Home"
|
|
830
|
+
},
|
|
831
|
+
"href": {
|
|
832
|
+
"el": "/test/",
|
|
833
|
+
"en": "/test/",
|
|
834
|
+
"tr": "/test/"
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
"label": {
|
|
839
|
+
"el": "Αίτηση",
|
|
840
|
+
"en": "The application",
|
|
841
|
+
"tr": "The application"
|
|
842
|
+
},
|
|
843
|
+
"href": {
|
|
844
|
+
"el": "/test/task-list",
|
|
845
|
+
"en": "/test/task-list",
|
|
846
|
+
"tr": "/test/task-list"
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
]
|
|
850
|
+
}
|
|
851
|
+
```
|
|
792
852
|
- `homeRedirectPage`: An object mapping language codes to URLs. When a user visits the root route (e.g., `https://whatever-your-service-is.service.gov.cy/`), the system redirects to the URL for the user's language. If the user's language is not found, it falls back to `"el"` or the first available URL. If not provided, a list of available sites is shown. Example:
|
|
793
853
|
```json
|
|
794
854
|
"homeRedirectPage": {
|
|
@@ -1905,10 +1965,10 @@ The validation rules for each element are defined in the `"validations` array fo
|
|
|
1905
1965
|
- `noSpecialChars`: Consists only of letters, numbers and some other characters
|
|
1906
1966
|
- `noSpecialCharsEl`: Consists only of Greek letters, numbers and some other characters
|
|
1907
1967
|
- `textWide_EL`: Consists of Greek Letters and a wider range of characters
|
|
1908
|
-
- `textWide_EL_Latin`: Consists of Greek Letters, Latin letters, numbers and a wider range of characters
|
|
1909
|
-
- `textWide_EL_Latin_TR`: Consists of Greek Letters, Latin letters, Turkish letters, numbers and a wider range of characters
|
|
1910
|
-
- `textWide_EL_Latn`: Deprecated alias of `textWide_EL_Latin` (still supported for backward compatibility)
|
|
1911
|
-
- `textWide_EL_Latn_TR`: Deprecated alias of `textWide_EL_Latin_TR` (still supported for backward compatibility)
|
|
1968
|
+
- `textWide_EL_Latin`: Consists of Greek Letters, Latin letters, numbers and a wider range of characters
|
|
1969
|
+
- `textWide_EL_Latin_TR`: Consists of Greek Letters, Latin letters, Turkish letters, numbers and a wider range of characters
|
|
1970
|
+
- `textWide_EL_Latn`: Deprecated alias of `textWide_EL_Latin` (still supported for backward compatibility)
|
|
1971
|
+
- `textWide_EL_Latn_TR`: Deprecated alias of `textWide_EL_Latin_TR` (still supported for backward compatibility)
|
|
1912
1972
|
- `textWide_UTF`: Consists of any letters, numbers and a wider range of characters
|
|
1913
1973
|
- `numeric`: Numeric input
|
|
1914
1974
|
- `numDecimal`: Numeric decimal input
|
|
@@ -1926,7 +1986,10 @@ The validation rules for each element are defined in the `"validations` array fo
|
|
|
1926
1986
|
- `date`: Date input (DD/MM/YYYY)
|
|
1927
1987
|
- `dateISO`: ISO date input `YYYY-M-D`
|
|
1928
1988
|
- `dateDMY`: European/Common Format date input `D/M/YYYY`
|
|
1929
|
-
- `maxCurrentYear`: Maximum current year input
|
|
1989
|
+
- `maxCurrentYear`: Maximum current year input. Use it for year-only inputs e.g. `2026` or formats that start with year e.g. `YYYY-M-D` (which works well with `dateInput` component). Do not use it with `D/M/YYYY` values (e.g. datePicker values); use date-based checks instead (`minCurrentDate`, `maxCurrentDate`, `withinDaysBeforeToday`, `withinDaysAfterToday`).
|
|
1990
|
+
- `minCurrentYear`: Minimum current year input. Use it for year-only inputs e.g. `2026` or formats that start with year e.g. `YYYY-M-D` (which works well with `dateInput` component). Do not use it with `D/M/YYYY` values (e.g. datePicker values); use date-based checks instead (`minCurrentDate`, `maxCurrentDate`, `withinDaysBeforeToday`, `withinDaysAfterToday`).
|
|
1991
|
+
- `minCurrentDate`: Checks if the value is greater than or equal to the current date.
|
|
1992
|
+
- `maxCurrentDate`: Checks if the value is less than or equal to the current date.
|
|
1930
1993
|
- `required`: Checks if the value is not null, undefined, or an empty string (after trimming).
|
|
1931
1994
|
- `length`: Checks if the value has a maximum length passed in the `checkValue` parameter.
|
|
1932
1995
|
- `regCheck`: Checks if the value matches the specified regular expression passed in the `checkValue` parameter.
|
|
@@ -1934,8 +1997,12 @@ The validation rules for each element are defined in the `"validations` array fo
|
|
|
1934
1997
|
- `maxValue`: Checks if the value is less than or equal to the specified maximum value passed in the `checkValue` parameter.
|
|
1935
1998
|
- `minValueDate`: Checks if the value is greater than or equal to the specified minimum date passed in the `checkValue` parameter.
|
|
1936
1999
|
- `maxValueDate`: Checks if the value is less than or equal to the specified maximum date passed in the `checkValue` parameter.
|
|
2000
|
+
- `withinDaysBeforeToday`: Checks if the value is within the last `N` days (between `today - N` and `today`), where `N` is passed in `checkValue`.
|
|
2001
|
+
- `withinDaysAfterToday`: Checks if the value is within the next `N` days (between `today` and `today + N`), where `N` is passed in `checkValue`.
|
|
1937
2002
|
- `minLength`: Checks if the value has a minimum length passed in the `checkValue` parameter.
|
|
1938
2003
|
|
|
2004
|
+
For `withinDaysBeforeToday` and `withinDaysAfterToday`, validation compares calendar dates only (local server timezone), not time-of-day. Supported input date formats are `YYYY-M-D` / `YYYY-MM-DD` and `D/M/YYYY` / `DD/MM/YYYY`.
|
|
2005
|
+
|
|
1939
2006
|
Example:
|
|
1940
2007
|
|
|
1941
2008
|
```json
|
|
@@ -2066,16 +2133,16 @@ To use data layer values, use the special `dataLayer[]` array. For example `data
|
|
|
2066
2133
|
- `formData` is a reserved word for the form data (already inputed data by the user) for that page
|
|
2067
2134
|
- `showExtra`refers to a input component with that name
|
|
2068
2135
|
|
|
2069
|
-
The `dataLayer` typically contains keys such as:
|
|
2070
|
-
- `inputData`: **All data submitted by the user through forms**
|
|
2071
|
-
- `eligibilityResults`: **Cached results from service eligibility API checks**
|
|
2072
|
-
- `user.profile_type`: **Authenticated user profile type from CY Login context**
|
|
2073
|
-
- `user.policy`: **The matched CY Login policy name for the current session**
|
|
2136
|
+
The `dataLayer` typically contains keys such as:
|
|
2137
|
+
- `inputData`: **All data submitted by the user through forms**
|
|
2138
|
+
- `eligibilityResults`: **Cached results from service eligibility API checks**
|
|
2139
|
+
- `user.profile_type`: **Authenticated user profile type from CY Login context**
|
|
2140
|
+
- `user.policy`: **The matched CY Login policy name for the current session**
|
|
2074
2141
|
|
|
2075
2142
|
Example structure for a service with ID `my-service`:
|
|
2076
2143
|
|
|
2077
|
-
```js
|
|
2078
|
-
dataLayer = {
|
|
2144
|
+
```js
|
|
2145
|
+
dataLayer = {
|
|
2079
2146
|
'my-service.inputData.index.formData.fullName': 'John Smith',
|
|
2080
2147
|
'my-service.inputData.index.formData.age': '34',
|
|
2081
2148
|
'my-service.inputData.contact-details.formData.telephone': '+35712345678',
|
|
@@ -2085,13 +2152,13 @@ dataLayer = {
|
|
|
2085
2152
|
"permanent_residence"
|
|
2086
2153
|
],
|
|
2087
2154
|
'my-service.inputData.want-to-apply.formData.option-radio': 'yes',
|
|
2088
|
-
'my-service.eligibilityResults.check1.succeeded': true,
|
|
2089
|
-
'my-service.eligibilityResults.check2.ErrorCode': 0,
|
|
2090
|
-
'user.profile_type': 'Individual',
|
|
2091
|
-
'user.policy': 'naturalPerson'
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
```
|
|
2155
|
+
'my-service.eligibilityResults.check1.succeeded': true,
|
|
2156
|
+
'my-service.eligibilityResults.check2.ErrorCode': 0,
|
|
2157
|
+
'user.profile_type': 'Individual',
|
|
2158
|
+
'user.policy': 'naturalPerson'
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
```
|
|
2095
2162
|
|
|
2096
2163
|
If any part of the key path is missing (e.g., the page hasn’t been visited yet or a form field was left empty), the expression will safely return `undefined` and **will not throw an error**. This behavior is by design, so that conditional logic expressions can fail silently and fallback gracefully.
|
|
2097
2164
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gov-cy/govcy-express-services",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.1",
|
|
4
4
|
"description": "An Express-based system that dynamically renders services using @gov-cy/govcy-frontend-renderer and posts data to a submission API.",
|
|
5
5
|
"author": "DMRID - DSF Team",
|
|
6
6
|
"license": "MIT",
|
|
@@ -146,16 +146,42 @@ function validateValue(value, rules) {
|
|
|
146
146
|
}
|
|
147
147
|
return valueDate >= min;
|
|
148
148
|
},
|
|
149
|
-
maxValueDate: (val, maxDate) => {
|
|
150
|
-
const valueDate = parseDate(val); // Parse the input date
|
|
151
|
-
const max = parseDate(maxDate); // Parse the maximum date
|
|
152
|
-
if (isNaN(valueDate) || isNaN(max)) {
|
|
153
|
-
return false; // Return false if either date is invalid
|
|
154
|
-
}
|
|
155
|
-
return valueDate <= max;
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
|
|
149
|
+
maxValueDate: (val, maxDate) => {
|
|
150
|
+
const valueDate = parseDate(val); // Parse the input date
|
|
151
|
+
const max = parseDate(maxDate); // Parse the maximum date
|
|
152
|
+
if (isNaN(valueDate) || isNaN(max)) {
|
|
153
|
+
return false; // Return false if either date is invalid
|
|
154
|
+
}
|
|
155
|
+
return valueDate <= max;
|
|
156
|
+
},
|
|
157
|
+
withinDaysBeforeToday: (val, daysBeforeToday) => {
|
|
158
|
+
const valueDate = parseDate(val);
|
|
159
|
+
const days = Number.parseInt(daysBeforeToday, 10);
|
|
160
|
+
if (isNaN(valueDate) || Number.isNaN(days)) return false;
|
|
161
|
+
|
|
162
|
+
const today = new Date();
|
|
163
|
+
const valueOnly = new Date(valueDate.getFullYear(), valueDate.getMonth(), valueDate.getDate());
|
|
164
|
+
const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
165
|
+
const minAllowedDate = new Date(todayOnly);
|
|
166
|
+
minAllowedDate.setDate(minAllowedDate.getDate() - days);
|
|
167
|
+
|
|
168
|
+
return valueOnly >= minAllowedDate && valueOnly <= todayOnly;
|
|
169
|
+
},
|
|
170
|
+
withinDaysAfterToday: (val, daysAfterToday) => {
|
|
171
|
+
const valueDate = parseDate(val);
|
|
172
|
+
const days = Number.parseInt(daysAfterToday, 10);
|
|
173
|
+
if (isNaN(valueDate) || Number.isNaN(days)) return false;
|
|
174
|
+
|
|
175
|
+
const today = new Date();
|
|
176
|
+
const valueOnly = new Date(valueDate.getFullYear(), valueDate.getMonth(), valueDate.getDate());
|
|
177
|
+
const todayOnly = new Date(today.getFullYear(), today.getMonth(), today.getDate());
|
|
178
|
+
const maxAllowedDate = new Date(todayOnly);
|
|
179
|
+
maxAllowedDate.setDate(maxAllowedDate.getDate() + days);
|
|
180
|
+
|
|
181
|
+
return valueOnly >= todayOnly && valueOnly <= maxAllowedDate;
|
|
182
|
+
},
|
|
183
|
+
minLength: (val, min) => val.length >= min
|
|
184
|
+
};
|
|
159
185
|
|
|
160
186
|
for (const rule of rules) {
|
|
161
187
|
// Extract rule parameters
|
|
@@ -219,13 +245,23 @@ function validateValue(value, rules) {
|
|
|
219
245
|
return message;
|
|
220
246
|
}
|
|
221
247
|
|
|
222
|
-
// Check for "minLength"
|
|
223
|
-
if (check === 'minLength' && !validationRules.minLength(value, checkValue)) {
|
|
224
|
-
return message;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
248
|
+
// Check for "minLength"
|
|
249
|
+
if (check === 'minLength' && !validationRules.minLength(value, checkValue)) {
|
|
250
|
+
return message;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Check for "withinDaysBeforeToday"
|
|
254
|
+
if (check === 'withinDaysBeforeToday' && !validationRules.withinDaysBeforeToday(value, checkValue)) {
|
|
255
|
+
return message;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check for "withinDaysAfterToday"
|
|
259
|
+
if (check === 'withinDaysAfterToday' && !validationRules.withinDaysAfterToday(value, checkValue)) {
|
|
260
|
+
return message;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
}
|
|
229
265
|
|
|
230
266
|
return null;
|
|
231
267
|
}
|