Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions infrastructure/terraform/components/sbx/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
| <a name="output_client_ssm_path_prefix"></a> [client\_ssm\_path\_prefix](#output\_client\_ssm\_path\_prefix) | n/a |
| <a name="output_cognito_user_pool_client_id"></a> [cognito\_user\_pool\_client\_id](#output\_cognito\_user\_pool\_client\_id) | n/a |
| <a name="output_cognito_user_pool_id"></a> [cognito\_user\_pool\_id](#output\_cognito\_user\_pool\_id) | n/a |
| <a name="output_contact_details_otp_secret_parameter_name"></a> [contact\_details\_otp\_secret\_parameter\_name](#output\_contact\_details\_otp\_secret\_parameter\_name) | n/a |
| <a name="output_contact_details_table_name"></a> [contact\_details\_table\_name](#output\_contact\_details\_table\_name) | n/a |
| <a name="output_deployment"></a> [deployment](#output\_deployment) | Deployment details used for post-deployment scripts |
| <a name="output_download_bucket_name"></a> [download\_bucket\_name](#output\_download\_bucket\_name) | n/a |
Expand Down
4 changes: 4 additions & 0 deletions infrastructure/terraform/components/sbx/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@ output "contact_details_table_name" {
output "templates_quarantine_bucket_key_prefix" {
value = module.backend_api.templates_quarantine_bucket_key_prefix
}

output "contact_details_otp_secret_parameter_name" {
value = module.backend_api.contact_details_otp_secret_parameter_name
}
2 changes: 2 additions & 0 deletions infrastructure/terraform/modules/backend-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,14 @@ No requirements.
| <a name="module_update_template_lambda"></a> [update\_template\_lambda](#module\_update\_template\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| <a name="module_upload_docx_letter_template_lambda"></a> [upload\_docx\_letter\_template\_lambda](#module\_upload\_docx\_letter\_template\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| <a name="module_upload_letter_template_lambda"></a> [upload\_letter\_template\_lambda](#module\_upload\_letter\_template\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/v2.0.29/terraform-lambda.zip | n/a |
| <a name="module_verify_contact_detail_lambda"></a> [verify\_contact\_detail\_lambda](#module\_verify\_contact\_detail\_lambda) | https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/4.0.0/terraform-lambda.zip | n/a |
## Outputs

| Name | Description |
|------|-------------|
| <a name="output_api_base_url"></a> [api\_base\_url](#output\_api\_base\_url) | n/a |
| <a name="output_client_ssm_path_prefix"></a> [client\_ssm\_path\_prefix](#output\_client\_ssm\_path\_prefix) | n/a |
| <a name="output_contact_details_otp_secret_parameter_name"></a> [contact\_details\_otp\_secret\_parameter\_name](#output\_contact\_details\_otp\_secret\_parameter\_name) | n/a |
| <a name="output_contact_details_table_name"></a> [contact\_details\_table\_name](#output\_contact\_details\_table\_name) | n/a |
| <a name="output_download_bucket_name"></a> [download\_bucket\_name](#output\_download\_bucket\_name) | n/a |
| <a name="output_download_bucket_regional_domain_name"></a> [download\_bucket\_regional\_domain\_name](#output\_download\_bucket\_regional\_domain\_name) | n/a |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ data "aws_iam_policy_document" "api_gateway_execution_policy" {
module.submit_routing_config_lambda.function_arn,
module.update_routing_config_lambda.function_arn,
module.update_template_lambda.function_arn,
module.verify_contact_detail_lambda.function_arn,
]
}
}
1 change: 1 addition & 0 deletions infrastructure/terraform/modules/backend-api/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ locals {
UPDATE_TEMPLATE_LAMBDA_ARN = module.update_template_lambda.function_arn
UPLOAD_DOCX_LETTER_LAMBDA_ARN = module.upload_docx_letter_template_lambda.function_arn
UPLOAD_LETTER_LAMBDA_ARN = module.upload_letter_template_lambda.function_arn
VERIFY_CONTACT_DETAIL_LAMBDA_ARN = module.verify_contact_detail_lambda.function_arn
})

backend_lambda_environment_variables = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
module "verify_contact_detail_lambda" {
source = "https://github.com/NHSDigital/nhs-notify-shared-modules/releases/download/4.0.0/terraform-lambda.zip"

project = var.project
environment = var.environment
component = var.component
aws_account_id = var.aws_account_id
region = var.region

kms_key_arn = var.kms_key_arn

function_name = "verify-contact-detail"

function_module_name = "verify-contact-detail"
handler_function_name = "handler"
description = "API endpoint for adding verifying contact details contact details using an OTP"

memory = 2048
timeout = 20
runtime = "nodejs22.x"

log_retention_in_days = var.log_retention_in_days
iam_policy_document = {
body = data.aws_iam_policy_document.verify_contact_detail.json
}

lambda_env_vars = local.backend_lambda_environment_variables
function_s3_bucket = var.function_s3_bucket
function_code_base_path = local.lambdas_dir
function_code_dir = "backend-api/dist/verify-contact-detail"

send_to_firehose = var.send_to_firehose
log_destination_arn = var.log_destination_arn
log_subscription_role_arn = var.log_subscription_role_arn
}

data "aws_iam_policy_document" "verify_contact_detail" {
statement {
sid = "AllowKMSAccess"
effect = "Allow"

actions = [
"kms:Decrypt",
"kms:DescribeKey",
"kms:Encrypt",
"kms:GenerateDataKey*",
"kms:ReEncrypt*",
]

resources = [
var.kms_key_arn
]
}
statement {
sid = "AllowContactDetailsRead"
effect = "Allow"

actions = [
"dynamodb:Query"
]

resources = [
"${aws_dynamodb_table.contact_details.arn}/index/ById",
]
}

statement {
sid = "AllowContactDetailsWrite"
effect = "Allow"

actions = [
"dynamodb:UpdateItem",
]

resources = [aws_dynamodb_table.contact_details.arn]
}

statement {
sid = "AllowOtpSecretRead"
effect = "Allow"

actions = [
"ssm:GetParameter",
]

resources = [
aws_ssm_parameter.contact_details_otp_secret.arn,
]
}
}
4 changes: 4 additions & 0 deletions infrastructure/terraform/modules/backend-api/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,7 @@ output "contact_details_table_name" {
output "templates_quarantine_bucket_key_prefix" {
value = local.csi
}

output "contact_details_otp_secret_parameter_name" {
value = aws_ssm_parameter.contact_details_otp_secret.name
}
136 changes: 111 additions & 25 deletions infrastructure/terraform/modules/backend-api/spec.tmpl.json
Original file line number Diff line number Diff line change
Expand Up @@ -473,31 +473,32 @@
],
"type": "object"
},
"ContactDetail": {
"allOf": [
{
"$ref": "#/components/schemas/ContactDetailInput"
"ContactDetailDTO": {
"properties": {
"id": {
"type": "string"
},
{
"properties": {
"id": {
"type": "string"
},
"rawValue": {
"type": "string"
},
"status": {
"$ref": "#/components/schemas/ContactDetailStatus"
}
},
"required": [
"id",
"rawValue",
"status"
],
"type": "object"
"rawValue": {
"type": "string"
},
"status": {
"$ref": "#/components/schemas/ContactDetailStatus"
},
"type": {
"$ref": "#/components/schemas/ContactDetailType"
},
"value": {
"type": "string"
}
]
},
"required": [
"type",
"value",
"rawValue",
"id",
"status"
],
"type": "object"
},
"ContactDetailInput": {
"properties": {
Expand Down Expand Up @@ -542,7 +543,7 @@
"ContactDetailSuccess": {
"properties": {
"data": {
"$ref": "#/components/schemas/ContactDetail"
"$ref": "#/components/schemas/ContactDetailDTO"
},
"statusCode": {
"type": "integer"
Expand All @@ -558,7 +559,7 @@
"properties": {
"data": {
"items": {
"$ref": "#/components/schemas/ContactDetail"
"$ref": "#/components/schemas/ContactDetailDTO"
},
"type": "array"
},
Expand Down Expand Up @@ -1580,6 +1581,17 @@
],
"type": "object"
},
"VerifyContactDetailInput": {
"properties": {
"otp": {
"type": "string"
}
},
"required": [
"otp"
],
"type": "object"
},
"VersionedFileDetails": {
"properties": {
"currentVersion": {
Expand Down Expand Up @@ -3531,6 +3543,80 @@
"uri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${LIST_TEMPLATES_LAMBDA_ARN}/invocations"
}
}
},
"/v1/verify-contact-detail/{contactDetailId}": {
"post": {
"description": "Verify contact detail",
"parameters": [
{
"description": "ID of the contact detail",
"in": "path",
"name": "contactDetailId",
"required": true,
"schema": {
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/VerifyContactDetailInput"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ContactDetailSuccess"
}
}
},
"description": "200 response",
"headers": {
"Content-Type": {
"schema": {
"type": "string"
}
}
}
},
"default": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Failure"
}
}
},
"description": "Error"
}
},
"security": [
{
"authorizer": []
}
],
"summary": "Add new unverified contact details and send an OTP for verification",
"x-amazon-apigateway-integration": {
"contentHandling": "CONVERT_TO_TEXT",
"credentials": "${APIG_EXECUTION_ROLE_ARN}",
"httpMethod": "POST",
"passthroughBehavior": "WHEN_NO_TEMPLATES",
"responses": {
".*": {
"statusCode": "200"
}
},
"timeoutInMillis": 29000,
"type": "AWS_PROXY",
"uri": "arn:aws:apigateway:${AWS_REGION}:lambda:path/2015-03-31/functions/${VERIFY_CONTACT_DETAIL_LAMBDA_ARN}/invocations"
}
}
}
}
}
9 changes: 9 additions & 0 deletions lambdas/backend-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,15 @@ curl -X POST --location "${APIG_STAGE}/v1/contact-details" \
--data '{ "type": "SMS", "value": "07890123456" }'
```

### POST - /v1/verify-contact-detail/:contactDetailId - Verify a contact detail

```bash
curl -X POST --location "${APIG_STAGE}/v1/verify-contact-detail/{CONTACT_DETAIL_ID}" \
--header "Accept: application/json" \
--header "Authorization: $SANDBOX_TOKEN" \
--data '{ "otp": "015678" }'
```

### GET - /v1/contact-details - Retrieve a list of contact details for the authenticated user, filterable by status and type

```bash
Expand Down
3 changes: 2 additions & 1 deletion lambdas/backend-api/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ npx esbuild \
src/update.ts \
src/upload-docx-letter.ts \
src/upload-letter.ts \
src/validate-letter-template-files.ts
src/validate-letter-template-files.ts \
src/verify-contact-detail.ts

cp -r ../../utils/utils/src/email-templates ./dist/submit
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
import { mock } from 'jest-mock-extended';
import type {
ContactDetail,
ContactDetailInput,
} from 'nhs-notify-web-template-management-types';
import type { ContactDetailInput } from 'nhs-notify-web-template-management-types';
import { DatabaseContactDetail } from 'nhs-notify-backend-client/schemas';
import { createHandler } from '@backend-api/api/create-contact-details';
import type { ContactDetailsClient } from '@backend-api/app/contact-details-client';

Expand Down Expand Up @@ -137,11 +135,13 @@ describe('Create Contact Details Handler', () => {
value: '07890123456',
};

const response: ContactDetail = {
const response: DatabaseContactDetail = {
...input,
id: 'id',
rawValue: input.value,
status: 'PENDING_VERIFICATION',
clientId: 'client-id',
otpHash: 'otp-hash',
};

mocks.contactDetailsClient.create.mockResolvedValueOnce({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
import { mock } from 'jest-mock-extended';
import type { ContactDetail } from 'nhs-notify-web-template-management-types';
import type { ContactDetailDto } from 'nhs-notify-web-template-management-types';
import { createHandler } from '@backend-api/api/get-contact-details';
import type { ContactDetailsClient } from '@backend-api/app/contact-details-client';

Expand Down Expand Up @@ -105,7 +105,7 @@ describe('Get Contact Details Handler', () => {
test('should return the contact details', async () => {
const { handler, mocks } = setup();

const response: ContactDetail = {
const response: ContactDetailDto = {
type: 'SMS',
value: '07890123456',
rawValue: '07890123456',
Expand Down
Loading
Loading