@gov-cy/govcy-express-services 1.11.3 → 1.12.0

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 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:
188
+ Allow both natural and legal persons:
189
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
- ```
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
 
@@ -1905,10 +1905,10 @@ The validation rules for each element are defined in the `"validations` array fo
1905
1905
  - `noSpecialChars`: Consists only of letters, numbers and some other characters
1906
1906
  - `noSpecialCharsEl`: Consists only of Greek letters, numbers and some other characters
1907
1907
  - `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)
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)
1912
1912
  - `textWide_UTF`: Consists of any letters, numbers and a wider range of characters
1913
1913
  - `numeric`: Numeric input
1914
1914
  - `numDecimal`: Numeric decimal input
@@ -1925,16 +1925,23 @@ The validation rules for each element are defined in the `"validations` array fo
1925
1925
  - `email`: Email input
1926
1926
  - `date`: Date input (DD/MM/YYYY)
1927
1927
  - `dateISO`: ISO date input `YYYY-M-D`
1928
- - `dateDMY`: European/Common Format date input `D/M/YYYY`
1929
- - `maxCurrentYear`: Maximum current year input
1928
+ - `dateDMY`: European/Common Format date input `D/M/YYYY`
1929
+ - `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`).
1930
+ - `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`).
1931
+ - `minCurrentDate`: Checks if the value is greater than or equal to the current date.
1932
+ - `maxCurrentDate`: Checks if the value is less than or equal to the current date.
1930
1933
  - `required`: Checks if the value is not null, undefined, or an empty string (after trimming).
1931
1934
  - `length`: Checks if the value has a maximum length passed in the `checkValue` parameter.
1932
1935
  - `regCheck`: Checks if the value matches the specified regular expression passed in the `checkValue` parameter.
1933
- - `minValue`: Checks if the value is greater than or equal to the specified minimum value passed in the `checkValue` parameter.
1934
- - `maxValue`: Checks if the value is less than or equal to the specified maximum value passed in the `checkValue` parameter.
1935
- - `minValueDate`: Checks if the value is greater than or equal to the specified minimum date passed in the `checkValue` parameter.
1936
- - `maxValueDate`: Checks if the value is less than or equal to the specified maximum date passed in the `checkValue` parameter.
1937
- - `minLength`: Checks if the value has a minimum length passed in the `checkValue` parameter.
1936
+ - `minValue`: Checks if the value is greater than or equal to the specified minimum value passed in the `checkValue` parameter.
1937
+ - `maxValue`: Checks if the value is less than or equal to the specified maximum value passed in the `checkValue` parameter.
1938
+ - `minValueDate`: Checks if the value is greater than or equal to the specified minimum date passed in the `checkValue` parameter.
1939
+ - `maxValueDate`: Checks if the value is less than or equal to the specified maximum date passed in the `checkValue` parameter.
1940
+ - `withinDaysBeforeToday`: Checks if the value is within the last `N` days (between `today - N` and `today`), where `N` is passed in `checkValue`.
1941
+ - `withinDaysAfterToday`: Checks if the value is within the next `N` days (between `today` and `today + N`), where `N` is passed in `checkValue`.
1942
+ - `minLength`: Checks if the value has a minimum length passed in the `checkValue` parameter.
1943
+
1944
+ 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`.
1938
1945
 
1939
1946
  Example:
1940
1947
 
@@ -2066,16 +2073,16 @@ To use data layer values, use the special `dataLayer[]` array. For example `data
2066
2073
  - `formData` is a reserved word for the form data (already inputed data by the user) for that page
2067
2074
  - `showExtra`refers to a input component with that name
2068
2075
 
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**
2076
+ The `dataLayer` typically contains keys such as:
2077
+ - `inputData`: **All data submitted by the user through forms**
2078
+ - `eligibilityResults`: **Cached results from service eligibility API checks**
2079
+ - `user.profile_type`: **Authenticated user profile type from CY Login context**
2080
+ - `user.policy`: **The matched CY Login policy name for the current session**
2074
2081
 
2075
2082
  Example structure for a service with ID `my-service`:
2076
2083
 
2077
- ```js
2078
- dataLayer = {
2084
+ ```js
2085
+ dataLayer = {
2079
2086
  'my-service.inputData.index.formData.fullName': 'John Smith',
2080
2087
  'my-service.inputData.index.formData.age': '34',
2081
2088
  'my-service.inputData.contact-details.formData.telephone': '+35712345678',
@@ -2085,13 +2092,13 @@ dataLayer = {
2085
2092
  "permanent_residence"
2086
2093
  ],
2087
2094
  '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
- ```
2095
+ 'my-service.eligibilityResults.check1.succeeded': true,
2096
+ 'my-service.eligibilityResults.check2.ErrorCode': 0,
2097
+ 'user.profile_type': 'Individual',
2098
+ 'user.policy': 'naturalPerson'
2099
+ }
2100
+
2101
+ ```
2095
2102
 
2096
2103
  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
2104
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gov-cy/govcy-express-services",
3
- "version": "1.11.3",
3
+ "version": "1.12.0",
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",
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "@gov-cy/dsf-email-templates": "^2.1.15",
61
- "@gov-cy/govcy-frontend-renderer": "^1.27.0",
61
+ "@gov-cy/govcy-frontend-renderer": "^1.28.1",
62
62
  "axios": "^1.9.0",
63
63
  "cookie-parser": "^1.4.7",
64
64
  "dotenv": "^16.3.1",
@@ -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
- minLength: (val, min) => val.length >= min
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
  }