@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1157 -0
  3. package/package.json +72 -0
  4. package/src/auth/cyLoginAuth.mjs +123 -0
  5. package/src/index.mjs +188 -0
  6. package/src/middleware/cyLoginAuth.mjs +131 -0
  7. package/src/middleware/govcyConfigSiteData.mjs +38 -0
  8. package/src/middleware/govcyCsrf.mjs +36 -0
  9. package/src/middleware/govcyFormsPostHandler.mjs +83 -0
  10. package/src/middleware/govcyHeadersControl.mjs +22 -0
  11. package/src/middleware/govcyHttpErrorHandler.mjs +63 -0
  12. package/src/middleware/govcyLanguageMiddleware.mjs +19 -0
  13. package/src/middleware/govcyLogger.mjs +15 -0
  14. package/src/middleware/govcyManifestHandler.mjs +46 -0
  15. package/src/middleware/govcyPDFRender.mjs +30 -0
  16. package/src/middleware/govcyPageHandler.mjs +110 -0
  17. package/src/middleware/govcyPageRender.mjs +14 -0
  18. package/src/middleware/govcyRequestTimer.mjs +29 -0
  19. package/src/middleware/govcyReviewPageHandler.mjs +102 -0
  20. package/src/middleware/govcyReviewPostHandler.mjs +147 -0
  21. package/src/middleware/govcyRoutePageHandler.mjs +37 -0
  22. package/src/middleware/govcyServiceEligibilityHandler.mjs +101 -0
  23. package/src/middleware/govcySessionData.mjs +9 -0
  24. package/src/middleware/govcySuccessPageHandler.mjs +112 -0
  25. package/src/public/img/Certificate_A4.svg +30 -0
  26. package/src/public/js/govcyForms.js +21 -0
  27. package/src/resources/govcyResources.mjs +430 -0
  28. package/src/standalone.mjs +7 -0
  29. package/src/utils/govcyApiRequest.mjs +114 -0
  30. package/src/utils/govcyConstants.mjs +4 -0
  31. package/src/utils/govcyDataLayer.mjs +311 -0
  32. package/src/utils/govcyEnvVariables.mjs +45 -0
  33. package/src/utils/govcyFormHandling.mjs +148 -0
  34. package/src/utils/govcyLoadConfigData.mjs +135 -0
  35. package/src/utils/govcyLogger.mjs +30 -0
  36. package/src/utils/govcyNotification.mjs +85 -0
  37. package/src/utils/govcyPdfMaker.mjs +27 -0
  38. package/src/utils/govcyReviewSummary.mjs +205 -0
  39. package/src/utils/govcySubmitData.mjs +530 -0
  40. package/src/utils/govcyUtils.mjs +13 -0
  41. package/src/utils/govcyValidator.mjs +352 -0
package/README.md ADDED
@@ -0,0 +1,1157 @@
1
+ # govcy Express Services
2
+ ![License](https://img.shields.io/github/license/gov-cy/govcy-epxress-services)
3
+ [![Unit test](https://github.com/gov-cy/govcy-express-services/actions/workflows/unit-test.yml/badge.svg)](https://github.com/gov-cy/govcy-express-services/actions/workflows/unit-test.yml)
4
+
5
+ > ⚠️ **Warning:**
6
+ > This package is under active development and is not a finished product. It is intended for testing, acceptance, integration, and browser testing purposes only.
7
+ > **No guarantees are provided regarding stability, security, or compliance. Using this package does not imply your product or service will automatically pass any required assessments, audits, or certifications by the Cyprus government or any other authority.**
8
+ > You are responsible for ensuring your own compliance, security, and quality assurance processes.
9
+
10
+ ## 📝 Description
11
+ This project is an Express-based project that dynamically renders online service forms using `@gov-cy/govcy-frontend-renderer`. It is designed for developers building government services in Cyprus, enabling them to manage user authentication, form submissions, and OpenID authentication workflows in a timely manner.
12
+
13
+ ![govcy-express-services](express-services.png)
14
+
15
+ ## Table of contents
16
+
17
+ - [📝 Description](#-description)
18
+ - [✨ Features](#-features)
19
+ - [📋 Prerequisites](#-prerequisites)
20
+ - [🚀 Quick start](#-quick-start)
21
+ - [📦 Full installation guide](#-full-installation-guide)
22
+ - [🛠️ Usage](#%EF%B8%8F-usage)
23
+ - [🧩 Dynamic services rendering](#-dynamic-services-rendering)
24
+ - [🛡️ Site eligibility checks](#%EF%B8%8F-site-eligibility-checks)
25
+ - [📤 Site submissions](#-site-submissions)
26
+ - [✅ Input validations](#-input-validations)
27
+ - [🛣️ Routes](#%EF%B8%8F-routes)
28
+ - [🔒 Security note](#-security-note)
29
+ - [❓ Troubleshooting / FAQ](#-troubleshooting--faq)
30
+ - [🙏 Credits](#-credits)
31
+ - [💡 Developer notes](#-developer-notes)
32
+ - [📄 License](#-license)
33
+ - [📬 Contact](#contact)
34
+
35
+ ## ✨ Features
36
+ - Dynamic form rendering from JSON templates
37
+ - Support for `textInput`, `textArea`, `select`, `radios`, `checkboxes`, `datePicker`, `dateInput`
38
+ - Support for `conditional radios`
39
+ - Dynamic creation of check your answers page
40
+ - OpenID Connect authentication with CY Login
41
+ - Middleware-based architecture for better maintainability
42
+ - Supports routing for dynamic pages
43
+ - Input validation
44
+ - CSRF protection
45
+ - cyLogin Single Sign-On (SSO) for physical authorized users
46
+ - Pre-filling posted values (in the same session)
47
+ - Site level API eligibility checks
48
+ - API integration with retry logic for form submissions.
49
+
50
+ ## 📋 Prerequisites
51
+ - Node.js 20+
52
+ - npm
53
+ - A CY Login client ID and secret
54
+ - An API endpoint for form submissions (through cyConnect)
55
+
56
+ ## 🚀 Quick start
57
+
58
+ ```sh
59
+ # 1. Install the package
60
+ npm install @gov-cy/govcy-express-services
61
+
62
+ # 2. Generate SSL certificates for local development
63
+ openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.cert -days 365 -nodes
64
+
65
+ # 3. Create a .env file in your project root (see below for required variables)
66
+
67
+ # 4. Add a minimal data config file in /data (see test.json example)
68
+
69
+ # 5. Create an index.mjs file:
70
+ ```
71
+
72
+ ```js
73
+ // index.mjs
74
+ import initializeGovCyExpressService from '@gov-cy/govcy-express-services';
75
+
76
+ const service = initializeGovCyExpressService();
77
+ service.startServer();
78
+ ```
79
+
80
+ ```sh
81
+ # 6. Start the server
82
+ npm start
83
+ ```
84
+
85
+ - Visit [https://localhost:44319](https://localhost:44319) in your browser.
86
+ - Log in with CY Login and start using your dynamic service!
87
+
88
+ ---
89
+
90
+ **Tip:**
91
+ For more details on configuration, environment variables, and advanced features, see the sections below.
92
+
93
+ ## 📦 Full installation guide
94
+ The project acts as an npm package and you need to install it as a dependency in your npm project. Check out the [install notes](INSTALL-NOTES.md) a detailed installation guide.
95
+
96
+ ## 🛠️ Usage
97
+ ### Starting the Server
98
+ Add in your `package.json`:
99
+
100
+ ```json
101
+ "scripts": {
102
+ "start": "node index.mjs"
103
+ }
104
+ ```
105
+
106
+ Then run the server using `npm start`.
107
+
108
+ ```sh
109
+ npm start
110
+ ```
111
+ The server will start on `https://localhost:44319` (see [NOTES.md](NOTES.md#local-development) for more details on this).
112
+
113
+ ### Authentication Middleware
114
+ Authentication is handled via OpenID Connect using CY Login and is configured using environment variables. The middleware ensures users have valid sessions before accessing protected routes.
115
+
116
+ The CY Login tokens are used to also connect with the various APIs through [cyConnect](https://dev.azure.com/cyprus-gov-cds/Documentation/_wiki/wikis/Documentation/74/CY-Connect), so make sure to include the correct `scope` when requesting for a [cyLogin client registration](https://dev.azure.com/cyprus-gov-cds/Documentation/_wiki/wikis/Documentation/34/Developer-Guide).
117
+
118
+ ### 🧩 Dynamic Services Rendering
119
+ Services are rendered dynamically using JSON templates stored in the `/data` folder. All the service configuration, pages, routes, and logic is stored in the JSON files. The service will load `data/:siteId.json` to get the form data when a user visits `/:siteId/:pageUrl`. Checkout the [express-service-shema.json](express-service-shema.json) and the example JSON structure of the **[test.json](data/test.json)** file for more details.
120
+
121
+ Here are some details explaining the JSON structure:
122
+
123
+ - `site` object: Contains information about the site, including the site ID, language, and footer links. See [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/tree/main#site-and-page-meta-data-explained) for more details. Some fields that are only specific to the govcy-express-forms project are the following:
124
+ - `submission_data_version` : The submission data version,
125
+ - `renderer_version` : The govcy-frontend-renderer version,
126
+ - `design_systems_version` : The govcy-design-system version,
127
+ - `homeRedirectPage`: The page to redirect when user visits the route page. Usually this will redirect to gov.cy page. If not provided will show a list of available sites.
128
+ - `matomo `: The Matomo web analytics configuration details.
129
+ - `eligibilityAPIEndpoints` : An array of API endpoints, to be used for service eligibility. See more on the [Eligibility API Endoints](#%EF%B8%8F-site-eligibility-checks) section below.
130
+ - `submissionAPIEndpoint`: The submission API endpoint, to be used for submitting the form. See more on the [Submission API Endoint](#-site-submissions) section below.
131
+ - `pages` array: An array of page objects, each representing a page in the site.
132
+ - `pageData` object: Contains the metadata to be rendered on the page. See [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/tree/main#site-and-page-meta-data-explained) for more details
133
+ - `pageTemplate` object: Contains the page template to be rendered on the page. See [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer/tree/main#json-input-template) for more details
134
+ - `elements` array: An array of elements to be rendered on the page. See all supported [govcy-frontend-renderer elements](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md) for more details
135
+
136
+
137
+ A typical service flow that includes pages `index`, `question-1`, `question-2` under the `pages` array in the JSON file looks like this:
138
+
139
+ ```mermaid
140
+ flowchart TD
141
+ govcy-page --> isAuth{Is User Authenticated?}
142
+ isAuth -- Yes<br><br> Eligibility Check --> index([:siteId/index])
143
+ isAuth -- No --> cyLogin[cyLogin]
144
+ cyLogin -- Eligibility Check --> index
145
+ index -- Eligibility Check<br> Validations --> question-1[:siteId/question-1]
146
+ question-1 -- Eligibility Check<br> Validations --> question-2[:siteId/question-2]
147
+ question-2 -- Eligibility Check<br> Validations --> review[📄:siteId/review <br> <br> Automatically generated]
148
+ review -- Eligibility Check<br> All Validations --> success([✅:siteId/success <br> <br> Automatically generated])
149
+
150
+ ```
151
+
152
+ Some pages are generated automatically by the project, such as the `review` and `success` pages.
153
+
154
+ #### Pages
155
+
156
+ Pages defined in the JSON file under the `pages` array, they rendered based on the [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer) library, and they are served by the `/:siteId/:pageUrl` route. The `pageData.nextPage` field is used to determine the next page to render.
157
+
158
+ Here's an example of a page defined in the JSON file:
159
+
160
+ ```json
161
+ {
162
+ "pageData": {
163
+ "url": "index",
164
+ "title": {
165
+ "el": "Your email",
166
+ "en": "Το email σας"
167
+ },
168
+ "layout": "layouts/govcyBase.njk",
169
+ "mainLayout": "two-third",
170
+ "nextPage": "telephone-number"
171
+ },
172
+ "pageTemplate": {
173
+ "sections": [
174
+ {
175
+ "name": "beforeMain",
176
+ "elements": [
177
+ {
178
+ "element": "backLink",
179
+ "params": {}
180
+ }
181
+ ],
182
+ "params": {}
183
+ },
184
+ {
185
+ "name": "main",
186
+ "elements": [
187
+ {
188
+ "element": "form",
189
+ "params": {
190
+ "elements": [
191
+ {
192
+ "element": "textInput",
193
+ "params": {
194
+ "label": {
195
+ "en": "What is your email?",
196
+ "el": "Ποιο είναι το email σας?"
197
+ },
198
+ "id": "email",
199
+ "name": "email",
200
+ "hint": {
201
+ "en": "We’ll only use this email for this application",
202
+ "el": "Θα χρησιμοποιήσουμε το email σας μόνο για αυτήν την υπηρεσία"
203
+ },
204
+ "type": "email",
205
+ "isPageHeading": true,
206
+ "fixedWidth": "50"
207
+ },
208
+ "validations": [
209
+ {
210
+ "check": "required",
211
+ "params": {
212
+ "message": {
213
+ "en": "Enter your email",
214
+ "el": "Εισαγάγετε το email σας"
215
+ }
216
+ }
217
+ },
218
+ {
219
+ "check": "valid",
220
+ "params": {
221
+ "checkValue": "email",
222
+ "message": {
223
+ "en": "Your email must be a valid email address",
224
+ "el": "To emial πρέπει να είναι έχει μορφή email address"
225
+ }
226
+ }
227
+ }
228
+ ]
229
+ },
230
+ {
231
+ "element": "button",
232
+ "params": {
233
+ "id": "continue",
234
+ "variant": "primary",
235
+ "text": {
236
+ "el": "Συνέχεια",
237
+ "en": "Continue"
238
+ }
239
+ }
240
+ }
241
+ ]
242
+ }
243
+ }
244
+ ]
245
+ }
246
+ ]
247
+ }
248
+ }
249
+ ```
250
+
251
+ The JSON structure is based on the [govcy-frontend-renderer's JSON template](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/README.md#json-template-example).
252
+
253
+ Lets break down the JSON config for this page:
254
+
255
+ - **pageData** are the page's meta data, such as the URL, title, layout, mainLayout, and nextPage.
256
+ - `pageData.url` is the URL of the page, in this case it's `:siteId/index`
257
+ - `pageData.title` is the title of the page, in this case it's `Your email`. This will be used in the `review`, `success` pages, the PDF, the email, and the submission platform.
258
+ - `pageData.layout` is the layout used to render the page. The project only supports the default layout `layouts/govcyBase.njk`
259
+ - `pageData.mainLayout` is the layout of the `main` section of the page, in this case it's `two-third`. It can be either `two-third` or `max-width`,
260
+ - `pageData.nextPage` is the next page to redirect to when the user clicks the `continue` button and all validations pass, in this case it will redirect to `/:siteId/telephone-number`
261
+ - **pageTemplate** is the page's template, which is a JSON object that contains the sections and elements of the page. Check out the [govcy-frontend-renderer's documentation](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/README.md) for more details.
262
+
263
+ **Forms vs static content**
264
+
265
+ - If the `pageTemplate` includes a `form` element in the `main` section and `button` element, the system will treat it as form and will:
266
+ - Perform the eligibility checks
267
+ - Display the form
268
+ - Collect the form data
269
+ - Validate the form data
270
+ - Store the form data in the systems data layer
271
+ - Redirect the user to the next page (or `review` page if the user came from the review page)
272
+ - Else if the `pageTemplate` does not include a `form` element in the `main` section, the system will treat it as static content and will:
273
+ - Not perform the eligibility checks
274
+ - Display the static content
275
+
276
+ **Notes**:
277
+ - Check out the [govcy-frontend-renderer's design elements](https://github.com/gov-cy/govcy-frontend-renderer/blob/main/DESIGN_ELEMENTS.md) for more details on the supported elements and their parameters.
278
+ - Check out the [input validations section](#-input-validations) for more details on how to add validations to the JSON file.
279
+
280
+ #### Review page
281
+
282
+ The `review` page is automatically generated by the project and includes the following sections:
283
+
284
+ - **Summary**: A summary of the data from all the pages in the service.
285
+ - **Change links**: A list of links to each page in the service.
286
+ - **Submit button**: A button to submit the form.
287
+
288
+ When the user clicks a change link, the user is redirected to the corresponding page in the service. After the user clicks on `continue` button the user is redirected back to the `review` page.
289
+
290
+ When the user clicks the `Submit` button, all the data gathered from the site's forms within this session are validated based on the validation definition in the JSON file, and if they pass they are submitted to the configured API endpoint.
291
+
292
+ #### Success page
293
+
294
+ The `success` page is automatically generated by the project, is accessible only when a submission is made successfully, and includes the following sections:
295
+
296
+ - **Success banner**: A banner indicating that the form was successfully submitted, with the reference number of the submission.
297
+ - **PDF Download link**: A link to download the PDF of the submission's data in a human-readable format.
298
+ - **Summary**: A summary of the data from all the pages in the service.
299
+
300
+ ### 🛡️ Site eligibility checks
301
+
302
+ The project uses an array of API endpoints to check the eligibility of a service/site. To use this feature, you need to configure the following in your JSON file under the `site` object:
303
+
304
+ ```json
305
+ "eligibilityAPIEndpoints" : [
306
+ {
307
+ "url": "TEST_ELIGIBILITY_1_API_URL",
308
+ "method": "POST",
309
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
310
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
311
+ "cashingTimeoutMinutes": 2,
312
+ "params": {
313
+ "checkFor": "isCitizen,isAdult"
314
+ },
315
+ "response": {
316
+ "errorResponse": {
317
+ "102": {
318
+ "error": "user not administrator",
319
+ "page": "/test/user-not-admin"
320
+ }
321
+ }
322
+ }
323
+ },
324
+ {
325
+ "url": "TEST_ELIGIBILITY_2_API_URL",
326
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
327
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
328
+ "cashingTimeoutMinutes": 60,
329
+ "response": {
330
+ "errorResponse": {
331
+ "105": {
332
+ "error": "user not registration",
333
+ "page": "/test/user-not-registered"
334
+ }
335
+ }
336
+ }
337
+ }
338
+ ]
339
+ ```
340
+
341
+ If no `eligibilityAPIEndpoints` are configured, the system will not check for service eligibility for the specific site.
342
+
343
+ Lets break the JSON config down:
344
+
345
+ - `eligibilityAPIEndpoints` : An array of API endpoints, to be used for service eligibility.
346
+ - `url`: The enviromental variable that holds the URL of the API endpoint.
347
+ - `method`: The HTTP method to use when making the request.
348
+ - `clientId`: The enviromental variable that holds the client ID to use when making the request.
349
+ - `clientSecret`: The enviromental variable that holds the client secret to use when making the request.
350
+ - `cashingTimeoutMinutes`: The number of minutes to cache the response from the API endpoint. If set to `0`, the API endpoint will be called every time.
351
+ - `params`: An object of key-value pairs that will be added to the request body when making the request.
352
+ - `response`: An object of expected response when `succeeded===false`, to be used for the system to know which error page to show.
353
+
354
+ The above config references the following environment variables that need to be set:
355
+
356
+ ```dotenv
357
+ TEST_ELIGIBILITY_1_API_URL=http://localhost:3002/check1
358
+ TEST_ELIGIBILITY_2_API_URL=http://localhost:3002/check2
359
+ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
360
+ TEST_SUBMISSION_API_SERVIVE_ID=123
361
+ ```
362
+
363
+ With the above config, when a user visits a page under the specific site, `/:siteId/*`, the service sends a request to the configured eligibility API endpoints. If any of the API endpoints returns `succeeded: false`, the user is redirected to the error page specified in the `response` object.
364
+
365
+ The response is cached to the session storage for the specified number of minutes. If the `cashingTimeoutMinutes` is set to `0`, the API endpoint will be called every time.
366
+
367
+ #### Eligibility API request and response
368
+
369
+ For each eligibility API endpoint, the project sends a request to the API endpoint. The project uses the [CY Connect - OAuth 2.0 (CY Login)](https://dev.azure.com/cyprus-gov-cds/Documentation/_wiki/wikis/Documentation/122/CY-Connect-OAuth-2.0-(CY-Login)) authentication policy, so the user's `<access_token>` is sent in the `Authorization` header.
370
+
371
+ **Eligibility Request**
372
+
373
+ - **HTTP Method**:
374
+ - Defined per endpoint in the method property (defaults to GET if not specified).
375
+ - **URL**:
376
+ - Resolved from the url property in your config (from the environment variable).
377
+ - **Headers**:
378
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
379
+ - **client-key**: `<clientKey>` (from config/env)
380
+ - **service-id**: `<serviceId>` (from config/env)
381
+ - **Accept**: text/plain
382
+ **Parameters**: The params object in your config is sent as query parameters for GET requests and as the request body for POST requests.
383
+
384
+ **Example GET Request:**
385
+
386
+ ```
387
+ GET /check-eligibility?checkFor=isCitizen,isAdult HTTP/1.1
388
+ Host: localhost:3002
389
+ Authorization: Bearer eyJhbGciOi...
390
+ client-key: 12345678901234567890123456789000
391
+ service-id: 123
392
+ Accept: text/plain
393
+ ```
394
+
395
+ **Example POST Request**:
396
+
397
+ ```
398
+ POST /check-eligibility HTTP/1.1
399
+ Host: localhost:3002
400
+ Authorization: Bearer eyJhbGciOi...
401
+ client-key: 12345678901234567890123456789000
402
+ service-id: 123
403
+ Accept: text/plain
404
+ Content-Type: application/json
405
+
406
+ {
407
+ "checkFor": "isCitizen,isAdult"
408
+ }
409
+ ```
410
+
411
+ **Eligibility Response**
412
+
413
+ The API is expected to return a JSON response with the following structure (see [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs) for normalization):
414
+
415
+ **On Success:**
416
+ ```json
417
+ {
418
+ "Succeeded": true,
419
+ "ErrorCode": 0,
420
+ "ErrorMessage": null,
421
+ }
422
+ ```
423
+
424
+ **On Failure:**
425
+ ```json
426
+ {
427
+ "Succeeded": false,
428
+ "ErrorCode": 102,
429
+ "ErrorMessage": "user not administrator"
430
+ }
431
+ ```
432
+
433
+
434
+ **Notes**:
435
+ - If no `eligibilityAPIEndpoints` are configured, the system will not check for service eligibility for the specific site.
436
+ - The response is normalized to always use PascalCase keys (`Succeeded`, `ErrorCode`, etc.), regardless of the backend’s casing.
437
+ - If `Succeeded` is false, the system will look up the `ErrorCode` in your config to determine which error page to show.
438
+
439
+ **Caching**
440
+ - The response from each eligibility endpoint is cached in the session for the number of minutes specified by `cashingTimeoutMinutes`.
441
+ - If `cashingTimeoutMinutes` is set to 0, the API endpoint will be called every time (no caching).
442
+ - If omitted or null, the result is cached indefinitely.
443
+
444
+ **Error Handling**
445
+ - If the API returns `Succeeded: false`, the user is redirected to the error page specified in your config for that error code.
446
+ - If the API response is invalid or the request fails after retries, a generic error is shown.
447
+
448
+ **References**
449
+ - Eligibility check logic: See [govcyServiceEligibilityHandler.mjs](src/middleware/govcyServiceEligibilityHandler.mjs)
450
+ - API call, normalization and retries: See [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs)
451
+
452
+ ### 📤 Site Submissions
453
+
454
+ The project uses an API endpoint to submit the form data. The project uses the [CY Connect - OAuth 2.0 (CY Login)](https://dev.azure.com/cyprus-gov-cds/Documentation/_wiki/wikis/Documentation/122/CY-Connect-OAuth-2.0-(CY-Login)) authentication policy, so the user's `<access_token>` is sent in the `Authorization` header.
455
+
456
+ To use this feature, you need to configure the following in your JSON file under the `site` object:
457
+
458
+ ```json
459
+ "submissionAPIEndpoint": {
460
+ "url": "TEST_SUBMISSION_API_URL",
461
+ "clientKey": "TEST_SUBMISSION_API_CLIENT_KEY",
462
+ "serviceId": "TEST_SUBMISSION_API_SERVIVE_ID",
463
+ "response": {
464
+ "errorResponse": {
465
+ "102": {
466
+ "error": "user not administrator",
467
+ "page": "/test/user-not-admin"
468
+ },
469
+ "105": {
470
+ "error": "user not registration",
471
+ "page": "/test/user-not-registered"
472
+ }
473
+ }
474
+ }
475
+ }
476
+ ```
477
+ Lets break the JSON config down:
478
+
479
+ - `submissionAPIEndpoint`: The submission API endpoint, to be used for submitting the form.
480
+ - `url`: The enviromental variable that holds the URL of the API endpoint.
481
+ - `clientId`: The enviromental variable that holds the client ID to use when making the request.
482
+ - `clientSecret`: The enviromental variable that holds the client secret to use when making the request.
483
+ - `response`: An object of expected response when `Succeeded===false`, to be used for the system to know which error page to show.
484
+
485
+ The above config references the following environment variables that need to be set:
486
+
487
+ ```dotenv
488
+ TEST_SUBMISSION_API_URL=http://localhost:3002/success
489
+ TEST_SUBMISSION_API_CLIENT_KEY=12345678901234567890123456789000
490
+ TEST_SUBMISSION_API_SERVIVE_ID=123
491
+ ```
492
+
493
+ With the above config, when a user submits the `review` page, the service sends a request to the configured submission API endpoint.
494
+
495
+ #### Submission API Request and Response
496
+
497
+ **Submission Request:**
498
+
499
+ - **HTTP Method**: POST
500
+ - **URL**: Resolved from the url property in your config (from the environment variable).
501
+ - **Headers**:
502
+ - **Authorization**: `Bearer <access_token>` (form user's cyLogin access token)
503
+ - **client-key**: `<clientKey>` (from config/env)
504
+ - **service-id**: `<serviceId>` (from config/env)
505
+ - **Accept**: `text/plain`
506
+ - **Body**: The body contains the prepared submission data, which is a JSON object with all the form data collected from the user across all pages.
507
+
508
+ **Example Request:**
509
+
510
+ ```
511
+ POST /submission-endpoint HTTP/1.1
512
+ Host: localhost:3002
513
+ Authorization: Bearer eyJhbGciOi...
514
+ client-key: 12345678901234567890123456789000
515
+ service-id: 123
516
+ Accept: text/plain
517
+ Content-Type: application/json
518
+
519
+ {
520
+ "AccountName": "John Doe",
521
+ "Iban": "CY12002001230000000123456789",
522
+ "Swift": "BANKCY2NXXX",
523
+ "Objection": "Accept",
524
+ "ReceiveSettlement": "no",
525
+ ...
526
+ }
527
+ ```
528
+
529
+ **Submission Response**
530
+
531
+ The API is expected to return a JSON response with the following structure (see [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs) for normalization):
532
+
533
+ **On Success:**
534
+ ```json
535
+ {
536
+ "Succeeded": true,
537
+ "ErrorCode": 0,
538
+ "ErrorMessage": null,
539
+ "Data": {
540
+ "submission_id": "12345678-x"
541
+ }
542
+ }
543
+ ```
544
+
545
+ **On Failure:**
546
+ ```json
547
+ {
548
+ "Succeeded": false,
549
+ "ErrorCode": 102,
550
+ "ErrorMessage": "user not administrator"
551
+ }
552
+ ```
553
+
554
+ **Notes**:
555
+ - The response is normalized to always use PascalCase keys (`Succeeded`, `ErrorCode`, etc.), regardless of the backend’s casing.
556
+ - If `Succeeded` is false, the system will look up the `ErrorCode` in your config to determine which error page to show.
557
+
558
+ **Error Handling**
559
+ - If the API returns `Succeeded: false`, the user is redirected to the error page specified in your config for that error code.
560
+ - If the API response is invalid or the request fails after retries, a generic error is shown.
561
+
562
+ **References**
563
+ - Request/response logic: See [govcyReviewPostHandler.mjs](src/middleware/govcyReviewPostHandler.mjs)
564
+ - API call, normalization and retries: See [govcyApiRequest.mjs](src/utils/govcyApiRequest.mjs)
565
+
566
+ #### Submission Data
567
+
568
+ The data is collected from the form elements and the data layer and are sent via the submission API in the following format:
569
+
570
+ ```json
571
+ "submissionData": { // Site level successful submission data
572
+ "submission_username" : "", // User's username
573
+ "submission_email" : "", // User's email
574
+ "submission_data": "{}", // Raw data as submitted by the user in each page
575
+ "submission_data_version": "",// The submission data version
576
+ "print_friendly_data": "[]", // Print friendly data
577
+ "renderer_data" :"{}", // Renderer data of the summary list
578
+ "renderer_version": "", // The renderer version
579
+ "design_systems_version": "", // The design systems version
580
+ "service": "{}" // Service info
581
+ }
582
+ ```
583
+ ##### Submission Data Sample
584
+
585
+ <details>
586
+ <summary>Here's a sample submission data JSON (as an object, before stringification)</summary>
587
+
588
+ > ℹ️ **Note:**
589
+ > When sent to the API, the fields `submission_data`, `renderer_data`, `print_friendly_data`, and `service` are stringified using `JSON.stringify()`.
590
+ > The sample below shows the structure **before** stringification for clarity.
591
+
592
+ ```json
593
+ {
594
+ "submission_username": "username", // User's username
595
+ "submission_email": "email@example.com", // User's email
596
+ "submission_data_version": "0.1", // Submission data version
597
+ "submission_data": { // Submission raw data. Object, will be stringified
598
+ "index": { // Page level
599
+ "formData": {
600
+ "id_select": ["id", "arc"], // field level. Could be string or array
601
+ "id_number": "654654",
602
+ "arc_number": "",
603
+ "aka": "232323",
604
+ "_csrf": "o6s80zgvowsmzm3q1djl03etarbd1pnd"
605
+ }
606
+ },
607
+ "appointment": {
608
+ "formData": {
609
+ "diorismos": "monimos",
610
+ "fileno_monimos": "3233",
611
+ "eidikotita_monimos": "1",
612
+ "fileno_sumvasiouxos": "",
613
+ "eidikotita_sumvasiouxos": "",
614
+ "fileno_aoristou": "",
615
+ "eidikotita_aoristou": "",
616
+ "program": "",
617
+ "fileno_orismenou": "",
618
+ "_csrf": "o6s80zgvowsmzm3q1djl03etarbd1pnd"
619
+ }
620
+ },
621
+ "takeover": {
622
+ "formData": {
623
+ "date_start_day": "11",
624
+ "date_start_month": "12",
625
+ "date_start_year": "2020",
626
+ "date_on_contract": "date_other",
627
+ "date_contract": "16/04/2025",
628
+ "reason": "24324dssf",
629
+ "_csrf": "o6s80zgvowsmzm3q1djl03etarbd1pnd"
630
+ }
631
+ }
632
+ },
633
+ "submission_data_version": "1", // Submission data version
634
+ "renderer_data": { // Summary list renderer data ready for rendering . Object, will be stringified
635
+ "element": "summaryList",
636
+ "params": {
637
+ "items": [
638
+ {
639
+ "key": {
640
+ "el": "Στοιχεία του εκπαιδευτικού",
641
+ "en": "Educator's details",
642
+ "tr": ""
643
+ },
644
+ "value": [
645
+ {
646
+ "element": "summaryList",
647
+ "params": {
648
+ "items": [
649
+ {
650
+ "key": {
651
+ "el": "Ταυτοποίηση",
652
+ "en": "Identification"
653
+ },
654
+ "value": [
655
+ {
656
+ "element": "textElement",
657
+ "params": {
658
+ "text": {
659
+ "en": "Ταυτότητα, ARC",
660
+ "el": "Ταυτότητα, ARC",
661
+ "tr": "Ταυτότητα, ARC"
662
+ },
663
+ "type": "span"
664
+ }
665
+ }
666
+ ]
667
+ },
668
+ {
669
+ "key": {
670
+ "el": "Εισαγάγετε αριθμό ταυτότητας",
671
+ "en": "Enter ID number"
672
+ },
673
+ "value": [
674
+ {
675
+ "element": "textElement",
676
+ "params": {
677
+ "text": {
678
+ "en": "121212",
679
+ "el": "121212",
680
+ "tr": "121212"
681
+ },
682
+ "type": "span"
683
+ }
684
+ }
685
+ ]
686
+ },
687
+ {
688
+ "key": {
689
+ "el": "Αριθμός κοινωνικών ασφαλίσεων",
690
+ "en": "Social Insurance Number"
691
+ },
692
+ "value": [
693
+ {
694
+ "element": "textElement",
695
+ "params": {
696
+ "text": {
697
+ "en": "112121",
698
+ "el": "112121",
699
+ "tr": "112121"
700
+ },
701
+ "type": "span"
702
+ }
703
+ }
704
+ ]
705
+ }
706
+ ]
707
+ }
708
+ }
709
+ ]
710
+ },
711
+ {
712
+ "key": {
713
+ "el": "Διορισμός εκπαιδευτικού",
714
+ "en": "Teachers appointment",
715
+ "tr": ""
716
+ },
717
+ "value": [
718
+ {
719
+ "element": "summaryList",
720
+ "params": {
721
+ "items": [
722
+ {
723
+ "key": {
724
+ "el": "Τι διορισμό έχει ο εκπαιδευτικός;",
725
+ "en": "What type of appointment does the teacher have?"
726
+ },
727
+ "value": [
728
+ {
729
+ "element": "textElement",
730
+ "params": {
731
+ "text": {
732
+ "en": "Συμβασιούχος",
733
+ "el": "Συμβασιούχος",
734
+ "tr": "Συμβασιούχος"
735
+ },
736
+ "type": "span"
737
+ }
738
+ }
739
+ ]
740
+ },
741
+ {
742
+ "key": {
743
+ "el": "Αριθμός φακέλου (ΠΜΠ)",
744
+ "en": "File Number"
745
+ },
746
+ "value": [
747
+ {
748
+ "element": "textElement",
749
+ "params": {
750
+ "text": {
751
+ "en": "1212",
752
+ "el": "1212",
753
+ "tr": "1212"
754
+ },
755
+ "type": "span"
756
+ }
757
+ }
758
+ ]
759
+ },
760
+ {
761
+ "key": {
762
+ "el": "Ειδικότητα",
763
+ "en": "Specialty"
764
+ },
765
+ "value": [
766
+ {
767
+ "element": "textElement",
768
+ "params": {
769
+ "text": {
770
+ "en": "Καθηγητής",
771
+ "el": "Καθηγητής",
772
+ "tr": "Καθηγητής"
773
+ },
774
+ "type": "span"
775
+ }
776
+ }
777
+ ]
778
+ }
779
+ ]
780
+ }
781
+ }
782
+ ]
783
+ },
784
+ {
785
+ "key": {
786
+ "el": "Ημερομηνία ανάληψης",
787
+ "en": "Takeover date",
788
+ "tr": ""
789
+ },
790
+ "value": [
791
+ {
792
+ "element": "summaryList",
793
+ "params": {
794
+ "items": [
795
+ {
796
+ "key": {
797
+ "el": "Ημερομηνία ανάληψης",
798
+ "en": "Start Date"
799
+ },
800
+ "value": [
801
+ {
802
+ "element": "textElement",
803
+ "params": {
804
+ "text": {
805
+ "en": "16/04/2025",
806
+ "el": "16/04/2025",
807
+ "tr": "16/04/2025"
808
+ },
809
+ "type": "span"
810
+ }
811
+ }
812
+ ]
813
+ },
814
+ {
815
+ "key": {
816
+ "el": "Η ημερομηνία αυτή είναι η ίδια με αυτή του συμβολαίου;",
817
+ "en": "Is this date the same as the contract date?"
818
+ },
819
+ "value": [
820
+ {
821
+ "element": "textElement",
822
+ "params": {
823
+ "text": {
824
+ "en": "Ναι, είναι η ίδια με αυτή του συμβολαίου",
825
+ "el": "Ναι, είναι η ίδια με αυτή του συμβολαίου",
826
+ "tr": "Ναι, είναι η ίδια με αυτή του συμβολαίου"
827
+ },
828
+ "type": "span"
829
+ }
830
+ }
831
+ ]
832
+ }
833
+ ]
834
+ }
835
+ }
836
+ ]
837
+ }
838
+ ]
839
+ }
840
+ },
841
+ "print_friendly_data": [ // Print friendly data. Object, will be stringified
842
+ {
843
+ "pageUrl": "index", // Page URL
844
+ "pageTitle": { // Page title
845
+ "el": "Στοιχεία του εκπαιδευτικού",
846
+ "en": "Educator's details",
847
+ "tr": ""
848
+ },
849
+ "fields": [ // Fields
850
+ {
851
+ "id": "id_select", // Field ID
852
+ "label": { // Field label
853
+ "el": "Ταυτοποίηση",
854
+ "en": "Identification"
855
+ },
856
+ "value": ["id", "arc"], // Field value. Could be string or array
857
+ "valueLabel": [ // Field value label. Could be string or array
858
+ {
859
+ "el": "Ταυτότητα",
860
+ "en": "ID",
861
+ "tr": ""
862
+ },
863
+ {
864
+ "el": "ARC",
865
+ "en": "ARC",
866
+ "tr": ""
867
+ }
868
+ ]
869
+ },
870
+ {
871
+ "id": "id_number",
872
+ "label": {
873
+ "el": "Εισαγάγετε αριθμό ταυτότητας",
874
+ "en": "Enter ID number"
875
+ },
876
+ "value": "654654",
877
+ "valueLabel": {
878
+ "el": "654654",
879
+ "en": "654654"
880
+ }
881
+ },
882
+ {
883
+ "id": "aka",
884
+ "label": {
885
+ "el": "Αριθμός κοινωνικών ασφαλίσεων",
886
+ "en": "Social Insurance Number"
887
+ },
888
+ "value": "232323",
889
+ "valueLabel": {
890
+ "el": "232323",
891
+ "en": "232323"
892
+ }
893
+ }
894
+ ]
895
+ },
896
+ {
897
+ "pageUrl": "appointment",
898
+ "pageTitle": {
899
+ "el": "Διορισμός εκπαιδευτικού",
900
+ "en": "Teachers appointment",
901
+ "tr": ""
902
+ },
903
+ "fields": [
904
+ {
905
+ "id": "diorismos",
906
+ "label": {
907
+ "el": "Τι διορισμό έχει ο εκπαιδευτικός;",
908
+ "en": "What type of appointment does the teacher have?"
909
+ },
910
+ "value": "monimos",
911
+ "valueLabel": {
912
+ "el": "Μόνιμος επί δοκιμασία",
913
+ "en": "Permanent on probation",
914
+ "tr": ""
915
+ }
916
+ },
917
+ {
918
+ "id": "fileno_monimos",
919
+ "label": {
920
+ "el": "Αριθμός φακέλου (ΠΜΠ)",
921
+ "en": "File Number"
922
+ },
923
+ "value": "3233",
924
+ "valueLabel": {
925
+ "el": "3233",
926
+ "en": "3233"
927
+ }
928
+ },
929
+ {
930
+ "id": "eidikotita_monimos",
931
+ "label": {
932
+ "el": "Ειδικότητα",
933
+ "en": "Specialty"
934
+ },
935
+ "value": "1",
936
+ "valueLabel": {
937
+ "el": "Δάσκαλος",
938
+ "en": "Elementary teacher",
939
+ "tr": ""
940
+ }
941
+ }
942
+ ]
943
+ },
944
+ {
945
+ "pageUrl": "takeover",
946
+ "pageTitle": {
947
+ "el": "Ημερομηνία ανάληψης",
948
+ "en": "Takeover date",
949
+ "tr": ""
950
+ },
951
+ "fields": [
952
+ {
953
+ "id": "date_start",
954
+ "label": {
955
+ "el": "Ημερομηνία ανάληψης",
956
+ "en": "Start Date"
957
+ },
958
+ "value": "2020-12-11",
959
+ "valueLabel": {
960
+ "el": "11/12/2020",
961
+ "en": "11/12/2020"
962
+ }
963
+ },
964
+ {
965
+ "id": "date_on_contract",
966
+ "label": {
967
+ "el": "Η ημερομηνία αυτή είναι η ίδια με αυτή του συμβολαίου;",
968
+ "en": "Is this date the same as the contract date?"
969
+ },
970
+ "value": "date_other",
971
+ "valueLabel": {
972
+ "el": "Όχι, αυτή είναι διαφορετική",
973
+ "en": "No, this is different",
974
+ "tr": ""
975
+ }
976
+ },
977
+ {
978
+ "id": "date_contract",
979
+ "label": {
980
+ "el": "Ημερομηνία συμβολαίου",
981
+ "en": "Contract Date"
982
+ },
983
+ "value": "16/04/2025",
984
+ "valueLabel": {
985
+ "el": "16/04/2025",
986
+ "en": "16/04/2025"
987
+ }
988
+ },
989
+ {
990
+ "id": "reason",
991
+ "label": {
992
+ "el": "Αιτιολόγηση καθυστέρησης στην ανάληψη καθηκόντων",
993
+ "en": "Reason for delay in assuming duties"
994
+ },
995
+ "value": "24324dssf",
996
+ "valueLabel": {
997
+ "el": "24324dssf",
998
+ "en": "24324dssf"
999
+ }
1000
+ }
1001
+ ]
1002
+ }
1003
+ ],
1004
+ "renderer_version": "1.14.1", // Renderer version
1005
+ "design_systems_version": "3.1.0", // Design systems version
1006
+ "service": { // Service metadata. Object, will be stringified
1007
+ "id": "takeover",
1008
+ "title": {
1009
+ "el": "Βεβαίωση ανάληψης καθηκόντων εκπαιδευτικών",
1010
+ "en": "Certificate of teachers takeover"
1011
+ }
1012
+ }
1013
+ }
1014
+
1015
+ ```
1016
+ </details>
1017
+
1018
+ ### ✅ Input Validations
1019
+
1020
+ The project includes input validation for the following elements:
1021
+
1022
+ - `textInput`
1023
+ - `textArea`
1024
+ - `select`
1025
+ - `radios`
1026
+ - `checkboxes`
1027
+ - `datePicker`
1028
+ - `dateInput`
1029
+
1030
+ The validation rules for each element are defined in the `"validations` array for each element. The project support the following validations:
1031
+
1032
+ - `valid`: Checks the value against the specified rule's `checkValue`. Available rules:
1033
+ - `numeric`: Numeri input
1034
+ - `numDecimal`: Numeric decimal input
1035
+ - `currency`: Currency input (numeric with 2 decimal places)
1036
+ - `alpha`: Alphabetic input
1037
+ - `alphaNum`: Alphanumeric input
1038
+ - `name`: Name input
1039
+ - `tel`: Telephone input
1040
+ - `mobile`: Mobile input
1041
+ - `telCY`: Cyprus telephone input
1042
+ - `mobileCY`: Cyprus mobile input
1043
+ - `iban`: IBAN input
1044
+ - `email`: Email input
1045
+ - `date`: Date input (DD/MM/YYYY)
1046
+ - `dateISO`: ISO date input `YYYY-M-D`
1047
+ - `dateDMY`: European/Common Format date input `D/M/YYYY`
1048
+ - `required`: Checks if the value is not null, undefined, or an empty string (after trimming).
1049
+ - `length`: Checks if the value has a maximum length passed in the `checkValue` parameter.
1050
+ - `regCheck`: Checks if the value matches the specified regular expression passed in the `checkValue` parameter.
1051
+ - `minValue`: Checks if the value is greater than or equal to the specified minimum value passed in the `checkValue` parameter.
1052
+ - `maxValue`: Checks if the value is less than or equal to the specified maximum value passed in the `checkValue` parameter.
1053
+ - `minValueDate`: Checks if the value is greater than or equal to the specified minimum date passed in the `checkValue` parameter.
1054
+ - `maxValueDate`: Checks if the value is less than or equal to the specified maximum date passed in the `checkValue` parameter.
1055
+ - `minLength`: Checks if the value has a minimum length passed in the `checkValue` parameter.
1056
+
1057
+ Example:
1058
+
1059
+ ```json
1060
+ "validations": [
1061
+ {
1062
+ "check": "required",
1063
+ "params": {
1064
+ "message": {
1065
+ "en": "Enter your IBAN",
1066
+ "el": "Εισαγάγετε το IBAN σας"
1067
+ }
1068
+ }
1069
+ },
1070
+ {
1071
+ "check": "valid",
1072
+ "params": {
1073
+ "checkValue": "iban",
1074
+ "message": {
1075
+ "en": "IBAN must be a valιd iban, for example",
1076
+ "el": "To ΙΒΑΝ πρέπει να είναι έχει μορφή iban"
1077
+ }
1078
+ }
1079
+ }
1080
+ ]
1081
+ ```
1082
+
1083
+ ### 🛣️ Routes
1084
+ The project uses express.js to serve the following routes:
1085
+
1086
+ #### Service routes:
1087
+ - **`/:siteId`**: Requires **cyLogin** authentication for **authorized individual users**. Redirects to `/:siteId/index`.
1088
+ - **`/:siteId/:pageUrl`**: Requires **cyLogin** authentication for **authorized individual users**. Based on `/data/:siteId.json`, Renders the specified page template. Validates page and saves data to session. If validation fails, errors are displayed with links to the inputs.
1089
+ - **`/:siteId/review`**: Requires **cyLogin** authentication for **authorized individual users**. Renders the check your answers page template. Validates all pages in the service and submits the data to the configured API endpoint. If validation fails, errors are displayed with links to the relevant pages.
1090
+ - **`/:siteId/success`**: Requires **cyLogin** authentication for **authorized individual users**. Renders latest successful submission.
1091
+ - **`/:siteId/success/pdf`**: Requires **cyLogin** authentication for **authorized individual users**. Downloads the PDF of the latest successful submission.
1092
+
1093
+ #### Authentication routes:
1094
+ - **`/signin-oidc`**: CY Login authentication endpoint.
1095
+ - **`/login`**: Redirect to CY Login login page.
1096
+ - **`/logout`**: CY Login logout endpoint.
1097
+
1098
+ Absolutely! Here’s a **ready-to-paste Troubleshooting / FAQ section** you can add near the end of your README, just before Credits or Developer notes.
1099
+
1100
+ ## 🔒 Security note
1101
+ - Always set a strong, random `SESSION_SECRET` in your `.env` file. Never commit secrets or credentials to version control.
1102
+ - In production, ensure cookies are set with `secure`, `httpOnly`, and `sameSite` attributes to protect against common web vulnerabilities.
1103
+ - Make sure your server is running behind HTTPS in production.
1104
+ - Regularly rotate secrets and credentials, and restrict access to your `.env` and configuration files.
1105
+ - Validate user input to prevent injection attacks.
1106
+ - Review and update your dependencies regularly to address security vulnerabilities.
1107
+
1108
+ **Tip:**
1109
+ This project enables CSRF protection and secure session cookies by default, but it is your responsibility to keep secrets and environment variables safe in production.
1110
+
1111
+ ## ❓ Troubleshooting / FAQ
1112
+
1113
+ ### SSL certificate errors on local development
1114
+ - **Problem:** Browser shows a warning or refuses to connect to `https://localhost:44319`.
1115
+ - **Solution:** Make sure you have generated self-signed certificates as described in the installation guide. You may need to trust the certificate in your browser or OS.
1116
+
1117
+ ### Session not persisting / users logged out unexpectedly
1118
+ - **Problem:** Users are logged out or session data is lost between requests.
1119
+ - **Solution:** Ensure your `SESSION_SECRET` is set in .env and is long and random. If running behind a proxy (like nginx), set `trust proxy` in your Express app.
1120
+
1121
+ ### CY Login authentication not working
1122
+ - **Problem:** Users cannot log in or are redirected incorrectly.
1123
+ - **Solution:** Double-check your CY Login client ID, secret, scope and redirect URIs in `.env`. Make sure your app is accessible at the correct URL and port.
1124
+
1125
+ ### API requests fail with 401/403 errors
1126
+ - **Problem:** Eligibility or submission API calls fail with authorization errors.
1127
+ - **Solution:** Ensure the user's access token is being sent in the `Authorization` header. Check that your API endpoint and credentials are correct. Check that the CY Login you have configured has the correct scope that the API endpoint requires.
1128
+
1129
+ ### Changes to JSON config not reflected
1130
+ - **Problem:** Updates to your `/data/:siteId.json` file don’t show up in the app.
1131
+ - **Solution:** Restart the server after making changes to config files, as they are loaded at startup. If the problem persists, check your JSON file syntax and ensure it is valid. Also check if the definition of the elements is as defined in the [govcy-frontend-renderer](https://github.com/gov-cy/govcy-frontend-renderer) library.
1132
+
1133
+ ### Environment variables not loading
1134
+ - **Problem:** The app fails to start or cannot find required configuration values.
1135
+ - **Solution:** Make sure your `.env` file exists in the project root and is formatted correctly. Restart the server after making changes to .env.
1136
+
1137
+ ### Port already in use
1138
+ - **Problem:** The server fails to start with an error like `EADDRINUSE: address already in use`.
1139
+ - **Solution:** Another process is using the same port. Either stop the other process or change the `PORT` value in your `.env` file.
1140
+
1141
+ ### Cannot connect to CY Login or API endpoints
1142
+ - **Problem:** The app cannot reach CY Login or your API endpoints.
1143
+ - **Solution:** Check your network connection, firewall settings, and that the URLs in your `.env` are correct and accessible from your environment.
1144
+
1145
+ ## 🙏 Credits
1146
+ - Cyprus Government Digital Services Factory (DSF) [dsf-admin@dits.dmrid.gov.cy](mailto:dsf-admin@dits.dmrid.gov.cy)
1147
+ - Cyprus Connecting Digital Services Team [cds-support@dits.dmrid.gov.cy](mailto:cds-support@dits.dmrid.gov.cy)
1148
+
1149
+ ## 💡 Developer notes
1150
+ For local develoment checke the [developer notes](./NOTES.md) document.
1151
+
1152
+ ## 📄 License
1153
+ This project is released under the [MIT License](LICENSE).
1154
+
1155
+ ## 📬Contact
1156
+ If you have any questions or feedback, please feel free to reach out to us at [dsf-admin@dits.dmrid.gov.cy](mailto:dsf-admin@dits.dmrid.gov.cy)
1157
+