|
| 1 | +# Secure Form Submissions |
| 2 | + |
| 3 | +This feature allows the user to achieve secure form submissions through the addition of security headers |
| 4 | + |
| 5 | +## Form Configuration |
| 6 | + |
| 7 | +The configuration inherits the `MsalAuthorizerConfig` |
| 8 | +Refer to the [`MSAL Authentication documentation`](/docs/runner/msal-apim-authentication.md) for information about `tenantId`, `clientId`, `clientSecret` and `scopes` |
| 9 | + |
| 10 | +```json |
| 11 | +"secureFormSubmissionConfig": { |
| 12 | + "tenantId": "${env_form_submission_tenant_id}", |
| 13 | + "clientId": "${env_form_submission_client_id}", |
| 14 | + "clientSecret": "${env_form_submission_client_secret}", |
| 15 | + "scopes": ["${env_form_submission_scope1}", "${env_form_submission_scope2}"], |
| 16 | + "useAwsWafUserAgentWorkaround": boolean |
| 17 | + } |
| 18 | +``` |
| 19 | + |
| 20 | +> **Note: useAwsWafUserAgentWorkaround** |
| 21 | +> AWS WAF requires User-Agent to be present as part of auth, setting this option to tru will make the functioality provide one to prevent 403 |
| 22 | +
|
| 23 | +## How to Update env variables |
| 24 | + |
| 25 | +Environment variables can be added or updated by following the guide at <https://ukhsa.atlassian.net/wiki/spaces/IDT/pages/294420993/XGO-007+Adding+New+Environment+Variables> |
| 26 | + |
| 27 | +## How it works |
| 28 | + |
| 29 | +### The following is a top-down view |
| 30 | + |
| 31 | +The [`FormSecurityService`](/runner/src/server/services/formSecurityService.ts) supports our different security mechanisms. |
| 32 | + |
| 33 | +- For the [`webhookHmacSharedKey`](/runner/src/server/services/formSecurityService.ts#L31) approach it generates Hmac and sets HMAC specific headers. |
| 34 | + |
| 35 | +- For the [`APIM`](/runner/src/server/services/formSecurityService.ts#L49) approach it uses server Dependency Injection to register named instances of [`SecureFormSubmissionService`](/runner/src/server/services/secureFormSubmissionService.ts#L137) which exposes a `getAuthHeader` method that returns an object in the form of `{ Authorization: "Bearer <token>"}`. |
| 36 | + Registration into DI is done on [`server initialization`](/runner/src/server/index.ts#L191) for forms declaring the `secureFormSubmissionConfig` |
| 37 | + |
| 38 | +The `StatusService` [`invokes`](/runner/src/server/services/statusService.ts#L137) `FormSecurityService.getSecurityHeaders` method which returns headers based on what approach the form use. It then passes those headers to the `WebhookService` |
| 39 | + |
| 40 | +The `WebhookService` accepts [`additionalHeaders?: Record<string, string>`](/runner/src/server/services/webhookService.ts#L32) as part of it's postRequest method, which it then includes when it makes the request to the form configured webhook output. |
| 41 | + |
| 42 | +### Implementation Details: DI service registration |
| 43 | + |
| 44 | +Per form SecureFormSubmissionService registration |
| 45 | + |
| 46 | +```typescript |
| 47 | +for (const form of enginePlugin.options.configs) { |
| 48 | + const formId = form.id; |
| 49 | + |
| 50 | + if (form.configuration.secureFormSubmissionConfig) { |
| 51 | + const instanceName = getSecureFormSubmissionServiceInstance(formId); |
| 52 | + |
| 53 | + const namedService = new SecureFormSubmissionService( |
| 54 | + form.configuration.secureFormSubmissionConfig |
| 55 | + ); |
| 56 | + |
| 57 | + await server.registerService( |
| 58 | + Schmervice.withName(instanceName, namedService) |
| 59 | + ); |
| 60 | + } |
| 61 | +} |
| 62 | +``` |
| 63 | + |
| 64 | +### Implementation Details: DI service resolution |
| 65 | + |
| 66 | +Once registered, the service can be resolved from the DI using the below. |
| 67 | +Notice we have to do a dynamic resolution - since services are dynamically registered based on what forms are served, there is no knowledge of what services exists and in turn there's no way to statically type the access. |
| 68 | + |
| 69 | +```typescript |
| 70 | +const instanceName = getSecureFormSubmissionServiceInstance(formId); |
| 71 | +if (instanceName) { |
| 72 | + const services = request.services([]) |
| 73 | + |
| 74 | + const serviceInstance = services[instanceName] as SecureFormSubmissionService; |
| 75 | + if (serviceInstance) { |
| 76 | + const headers = await serviceInstance.getAuthHeader(); |
| 77 | + ... |
| 78 | + } |
| 79 | + .... |
| 80 | +} |
| 81 | +``` |
0 commit comments