From cc32242f1845c553bb7aca9a7fd10d3fba6737cc Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 3 Feb 2026 12:23:29 +0000 Subject: [PATCH 01/11] add secret --- package-lock.json | 49 +++++++++++--- packages/cdk/nagSuppressions.ts | 31 +++++++++ packages/cdk/resources/SharedSecrets.ts | 67 +++++++++++++++++++ packages/cdk/resources/api/apiFunctions.ts | 25 ++----- packages/cdk/resources/api/oauth2Functions.ts | 12 ++-- .../cdk/stacks/StatelessResourcesStack.ts | 21 +++--- packages/cognito/package.json | 1 + packages/cognito/src/authorizeMock.ts | 8 ++- packages/cognito/src/tokenMock.ts | 10 +-- packages/common/authFunctions/package.json | 1 + .../authFunctions/src/authenticateRequest.ts | 32 ++++++--- packages/common/doHSClient/package.json | 1 + packages/common/doHSClient/src/doHSClient.ts | 4 +- 13 files changed, 202 insertions(+), 60 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7fe64dddac..eb36a68702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -222,6 +222,7 @@ "resolved": "https://registry.npmjs.org/@aws-amplify/core/-/core-6.16.0.tgz", "integrity": "sha512-YpEtvdXcC06/j3PEsQiN/AYiJh3yLK5aPijFY1SbE0rgSLt9iPPalCOh65vDjybe7SW8qdIlctcR/rROMA88ag==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/types": "3.723.0", @@ -2200,9 +2201,9 @@ "license": "MIT" }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.980.0.tgz", - "integrity": "sha512-TeDBmkR8x3toPnvkFMBG73QqxsWjksFUMJyR0C4tZjVXjFq9igGwq8nHYDrQA0Hony6tGvH0SyNsjsL5w5qTww==", + "version": "3.981.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.981.0.tgz", + "integrity": "sha512-NVSbeeU/IjVobvFrwR4vLaEn3L83SfqRZXjIyBlHtU6agtHVCOJCdhrkK0z7uFahxD9FqqiQTYMYhzIfgL7VjA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", @@ -2215,7 +2216,7 @@ "@aws-sdk/middleware-user-agent": "^3.972.5", "@aws-sdk/region-config-resolver": "^3.972.3", "@aws-sdk/types": "^3.973.1", - "@aws-sdk/util-endpoints": "3.980.0", + "@aws-sdk/util-endpoints": "3.981.0", "@aws-sdk/util-user-agent-browser": "^3.972.3", "@aws-sdk/util-user-agent-node": "^3.972.3", "@smithy/config-resolver": "^4.4.6", @@ -2263,9 +2264,9 @@ } }, "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/util-endpoints": { - "version": "3.980.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.980.0.tgz", - "integrity": "sha512-AjKBNEc+rjOZQE1HwcD9aCELqg1GmUj1rtICKuY8cgwB73xJ4U/kNyqKKpN2k9emGqlfDY2D8itIp/vDc6OKpw==", + "version": "3.981.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.981.0.tgz", + "integrity": "sha512-a8nXh/H3/4j+sxhZk+N3acSDlgwTVSZbX9i55dx41gI1H+geuonuRG+Shv3GZsCb46vzc08RK2qC78ypO8uRlg==", "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "^3.973.1", @@ -2357,6 +2358,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.723.0.tgz", "integrity": "sha512-9IH90m4bnHogBctVna2FnXaIGVORncfdxcqeEIovOxjIJJyHDmEAtA7B91dAM4sruddTbVzOYnqfPVst3odCbA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -2884,6 +2886,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.723.0.tgz", "integrity": "sha512-YyN8x4MI/jMb4LpHsLf+VYqvbColMK8aZeGWVk2fTFsmt8lpTYGaGC1yybSwGX42mZ4W8ucu8SAYSbUraJZEjA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -4560,6 +4563,7 @@ "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", @@ -5198,6 +5202,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -5221,6 +5226,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -8779,6 +8785,7 @@ "dev": true, "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -9022,6 +9029,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -9345,6 +9353,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -9362,6 +9371,7 @@ "integrity": "sha512-V0kuGBX3+prX+DQ/7r2qsv1NsdfnCLnTgnRJ1pYnxykBhGMz+qj+box5lq7XsO5mtZsBqpjwwTu/7wszPfMBcw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -9373,6 +9383,7 @@ "integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/react": "*" } @@ -9483,6 +9494,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -9987,6 +9999,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -11010,6 +11023,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", + "peer": true, "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -11237,6 +11251,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -11645,7 +11660,8 @@ "version": "10.4.5", "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.5.tgz", "integrity": "sha512-fOoP70YLevMZr5avJHx2DU3LNYmC6wM8OwdrNewMZou1kZnPGOeVzBrRjZNgFDHUlulYUjkpFRSpTE3D+n+ZSg==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/conventional-changelog-eslint": { "version": "6.0.0", @@ -12331,6 +12347,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -12391,6 +12408,7 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -14279,6 +14297,7 @@ "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "30.2.0", "@jest/types": "30.2.0", @@ -14982,6 +15001,7 @@ "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -15897,6 +15917,7 @@ "resolved": "https://registry.npmjs.org/nhsuk-frontend/-/nhsuk-frontend-9.6.4.tgz", "integrity": "sha512-y0fi91jhgS1whD7jhNXKbpJ2Lmje/h5qBZ0aXmBbZdNo56805u7SsPJYxq7Uw6ffT86zQzQIxEwPwrjgSm5Whg==", "license": "MIT", + "peer": true, "workspaces": [ "." ], @@ -15909,6 +15930,7 @@ "resolved": "https://registry.npmjs.org/nhsuk-react-components/-/nhsuk-react-components-5.0.0.tgz", "integrity": "sha512-9QbYNEgLXdFaaEbrGs3IR9Gfn3M0a/6VH8a8fjPLWofl9FaP9HArpXh+eKz6D5YzUP6SmA0+0M8b84stJyBqdQ==", "license": "MIT", + "peer": true, "dependencies": { "classnames": "^2.2.6" }, @@ -16856,6 +16878,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -16868,6 +16891,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -17347,6 +17371,7 @@ "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -18278,6 +18303,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -18466,6 +18492,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -18640,6 +18667,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -18917,6 +18945,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -19010,6 +19039,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -19599,6 +19629,7 @@ "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/client-secrets-manager": "^3.981.0", "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", @@ -19624,6 +19655,7 @@ "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", + "@aws-sdk/client-secrets-manager": "^3.981.0", "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", "@middy/core": "^7.0.2", @@ -19649,6 +19681,7 @@ "license": "MIT", "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", + "@aws-sdk/client-secrets-manager": "^3.981.0", "axios": "^1.13.2", "axios-retry": "^4.5.0" }, diff --git a/packages/cdk/nagSuppressions.ts b/packages/cdk/nagSuppressions.ts index 26604aab66..1b0aed2b62 100644 --- a/packages/cdk/nagSuppressions.ts +++ b/packages/cdk/nagSuppressions.ts @@ -258,6 +258,37 @@ export const nagSuppressions = (stack: Stack) => { ] ) + safeAddNagSuppression( + stack, + "/StatelessStack/SharedSecrets/ApigeeApiKey/Resource", + [ + { + id: "AwsSolutions-SMG4", + reason: "Suppress error for not rotating secret. This is by design." + } + ] + ) + safeAddNagSuppression( + stack, + "/StatelessStack/SharedSecrets/ApigeeApiSecret/Resource", + [ + { + id: "AwsSolutions-SMG4", + reason: "Suppress error for not rotating secret. This is by design." + } + ] + ) + safeAddNagSuppression( + stack, + "/StatelessStack/SharedSecrets/ApigeeDoHSApiKey/Resource", + [ + { + id: "AwsSolutions-SMG4", + reason: "Suppress error for not rotating secret. This is by design." + } + ] + ) + } } diff --git a/packages/cdk/resources/SharedSecrets.ts b/packages/cdk/resources/SharedSecrets.ts index e006235d4a..36a3d7494e 100644 --- a/packages/cdk/resources/SharedSecrets.ts +++ b/packages/cdk/resources/SharedSecrets.ts @@ -16,6 +16,9 @@ export interface SharedSecretsProps { readonly stackName: string readonly deploymentRole: IRole readonly useMockOidc?: boolean + readonly apigeeApiKey: string + readonly apigeeApiSecret: string + readonly apigeeDoHSApiKey: string } // Construct for managing shared secrets and associated resources @@ -26,6 +29,11 @@ export class SharedSecrets extends Construct { public readonly useJwtKmsKeyPolicy: ManagedPolicy public readonly getPrimaryJwtPrivateKeyPolicy: ManagedPolicy public readonly getMockJwtPrivateKeyPolicy: ManagedPolicy + public readonly apigeeSecretsKmsKey: IKey + public readonly apigeeApiKey: Secret + public readonly apigeeApiSecret: Secret + public readonly apigeeDoHSApiKey: Secret + public readonly getApigeeSecretsPolicy: ManagedPolicy constructor(scope: Construct, id: string, props: SharedSecretsProps) { super(scope, id) @@ -57,6 +65,65 @@ export class SharedSecrets extends Construct { }) }) + this.apigeeSecretsKmsKey = new Key(this, "ApigeeSecretsKmsKey", { + description: `${props.stackName}-apigeeSecretsKmsKey`, + enableKeyRotation: true, + removalPolicy: RemovalPolicy.DESTROY, + pendingWindow: Duration.days(7), + policy: new PolicyDocument({ + statements: [ + // Allow full IAM permissions for account root + new PolicyStatement({ + sid: "EnableIAMUserPermissions", + effect: Effect.ALLOW, + actions: ["kms:*"], + principals: [new AccountRootPrincipal()], + resources: ["*"] + }), + // Allow the deployment role to encrypt and generate data keys + new PolicyStatement({ + effect: Effect.ALLOW, + principals: [props.deploymentRole], + actions: ["kms:Encrypt", "kms:GenerateDataKey*"], + resources: ["*"] + }) + ] + }) + }) + this.apigeeApiKey = new Secret(this, "ApigeeApiKey", { + secretName: `${props.stackName}-apigeeApiKey`, + secretStringValue: SecretValue.unsafePlainText(props.apigeeApiKey), + encryptionKey: this.apigeeSecretsKmsKey + }) + this.apigeeApiSecret = new Secret(this, "ApigeeApiSecret", { + secretName: `${props.stackName}-apigeeApiSecret`, + secretStringValue: SecretValue.unsafePlainText(props.apigeeApiSecret), + encryptionKey: this.apigeeSecretsKmsKey + }) + this.apigeeDoHSApiKey = new Secret(this, "ApigeeDoHSApiKey", { + secretName: `${props.stackName}-apigeeDoHSApiKey`, + secretStringValue: SecretValue.unsafePlainText(props.apigeeDoHSApiKey), + encryptionKey: this.apigeeSecretsKmsKey + }) + + // Create a managed policy to allow getting the primary JWT private key secret + this.getApigeeSecretsPolicy = new ManagedPolicy(this, "GetApigeeSecretsPolicy", { + statements: [ + new PolicyStatement({ + actions: ["secretsmanager:GetSecretValue"], + resources: [ + this.apigeeApiKey.secretArn, + this.apigeeApiSecret.secretArn, + this.apigeeDoHSApiKey.secretArn] + }), + new PolicyStatement({ + actions: ["kms:DescribeKey", "kms:Decrypt"], + effect: Effect.ALLOW, + resources: [this.apigeeSecretsKmsKey.keyArn] + }) + ] + }) + // Create a managed policy to allow using the KMS key for decryption this.useJwtKmsKeyPolicy = new ManagedPolicy(this, "UseJwtKmsKeyPolicy", { description: "Policy to allow using the JWT KMS key", diff --git a/packages/cdk/resources/api/apiFunctions.ts b/packages/cdk/resources/api/apiFunctions.ts index 9ab9edd71a..8a0ffacc14 100644 --- a/packages/cdk/resources/api/apiFunctions.ts +++ b/packages/cdk/resources/api/apiFunctions.ts @@ -4,8 +4,7 @@ import {SharedSecrets} from "../SharedSecrets" import {ITableV2} from "aws-cdk-lib/aws-dynamodb" import {IManagedPolicy} from "aws-cdk-lib/aws-iam" import {NodejsFunction} from "aws-cdk-lib/aws-lambda-nodejs" -import {Secret} from "aws-cdk-lib/aws-secretsmanager" -import {NagSuppressions} from "cdk-nag" +import {ISecret, Secret} from "aws-cdk-lib/aws-secretsmanager" // Interface for properties needed to create API functions export interface ApiFunctionsProps { @@ -39,9 +38,9 @@ export interface ApiFunctionsProps { readonly apigeeDoHSEndpoint: string readonly apigeePrescriptionsEndpoint: string readonly apigeePersonalDemographicsEndpoint: string - readonly apigeeApiKey: string - readonly apigeeApiSecret: string - readonly apigeeDoHSApiKey: string + readonly apigeeApiKey: ISecret + readonly apigeeApiSecret: ISecret + readonly apigeeDoHSApiKey: ISecret readonly jwtKid: string readonly logLevel: string readonly roleId: string @@ -101,10 +100,9 @@ export class ApiFunctions extends Construct { // Indicate if mock mode is available MOCK_MODE_ENABLED: props.useMockOidc ? "true" : "false", - APIGEE_API_SECRET: props.apigeeApiSecret, - APIGEE_API_KEY: props.apigeeApiKey, + APIGEE_API_SECRET_ARN: props.apigeeApiSecret.secretArn, + APIGEE_API_KEY_ARN: props.apigeeApiKey.secretArn, FULL_CLOUDFRONT_DOMAIN: props.fullCloudfrontDomain - } // If mock OIDC is enabled, add mock environment variables @@ -240,14 +238,6 @@ export class ApiFunctions extends Construct { // Add the policy to apiFunctionsPolicies apiFunctionsPolicies.push(patientSearchLambda.executeLambdaManagedPolicy) - // Suppress the AwsSolutions-L1 rule for the prescription list Lambda function - NagSuppressions.addResourceSuppressions(prescriptionListLambda.lambda, [ - { - id: "AwsSolutions-L1", - reason: "The Lambda function uses the latest runtime version supported at the time of implementation." - } - ]) - // Prescription Details Lambda Function const prescriptionDetailsLambda = new LambdaFunction(this, "PrescriptionDetails", { serviceName: props.serviceName, @@ -266,10 +256,9 @@ export class ApiFunctions extends Construct { apigeePrescriptionsEndpoint: props.apigeePrescriptionsEndpoint, apigeeDoHSEndpoint: props.apigeeDoHSEndpoint, apigeePersonalDemographicsEndpoint: props.apigeePersonalDemographicsEndpoint, - apigeeApiKey: props.apigeeApiKey, jwtKid: props.jwtKid, roleId: props.roleId, - APIGEE_DOHS_API_KEY: props.apigeeDoHSApiKey + APIGEE_DOHS_API_KEY_ARN: props.apigeeDoHSApiKey.secretArn } }) diff --git a/packages/cdk/resources/api/oauth2Functions.ts b/packages/cdk/resources/api/oauth2Functions.ts index 89f30614c9..8ea8837f91 100644 --- a/packages/cdk/resources/api/oauth2Functions.ts +++ b/packages/cdk/resources/api/oauth2Functions.ts @@ -2,7 +2,7 @@ import {Construct} from "constructs" import {LambdaFunction} from "../LambdaFunction" import {ITableV2} from "aws-cdk-lib/aws-dynamodb" import {IManagedPolicy} from "aws-cdk-lib/aws-iam" -import {Secret} from "aws-cdk-lib/aws-secretsmanager" +import {ISecret, Secret} from "aws-cdk-lib/aws-secretsmanager" import {NodejsFunction} from "aws-cdk-lib/aws-lambda-nodejs" import {SharedSecrets} from "../SharedSecrets" @@ -57,8 +57,8 @@ export interface OAuth2FunctionsProps { readonly logRetentionInDays: number readonly logLevel: string readonly jwtKid: string - readonly apigeeApiKey: string - readonly apigeeApiSecret: string + readonly apigeeApiKey: ISecret + readonly apigeeApiSecret: ISecret } /** @@ -212,7 +212,7 @@ export class OAuth2Functions extends Construct { FULL_CLOUDFRONT_DOMAIN: props.fullCloudfrontDomain, StateMappingTableName: props.stateMappingTable.tableName, SessionStateMappingTableName: props.sessionStateMappingTable.tableName, - APIGEE_API_KEY: props.apigeeApiKey + APIGEE_API_KEY_ARN: props.apigeeApiKey.secretArn } }) @@ -259,8 +259,8 @@ export class OAuth2Functions extends Construct { MOCK_OIDC_ISSUER: props.mockOidcIssuer, FULL_CLOUDFRONT_DOMAIN: props.fullCloudfrontDomain, jwtKid: props.jwtKid, - APIGEE_API_KEY: props.apigeeApiKey, - APIGEE_API_SECRET: props.apigeeApiSecret + APIGEE_API_KEY_ARN: props.apigeeApiKey.secretArn, + APIGEE_API_SECRET_ARN: props.apigeeApiSecret.secretArn } }) diff --git a/packages/cdk/stacks/StatelessResourcesStack.ts b/packages/cdk/stacks/StatelessResourcesStack.ts index a9dbae4c7a..ebe71a7147 100644 --- a/packages/cdk/stacks/StatelessResourcesStack.ts +++ b/packages/cdk/stacks/StatelessResourcesStack.ts @@ -81,9 +81,9 @@ export class StatelessResourcesStack extends Stack { const mockOidcjwksEndpoint = this.node.tryGetContext("mockOidcjwksEndpoint") const useMockOidc: boolean = this.node.tryGetContext("useMockOidc") - const apigeeApiKey = this.node.tryGetContext("apigeeApiKey") - const apigeeApiSecret = this.node.tryGetContext("apigeeApiSecret") - const apigeeDoHSApiKey = this.node.tryGetContext("apigeeDoHSApiKey") + const apigeeApiKey: string = this.node.tryGetContext("apigeeApiKey") + const apigeeApiSecret: string = this.node.tryGetContext("apigeeApiSecret") + const apigeeDoHSApiKey: string = this.node.tryGetContext("apigeeDoHSApiKey") const apigeeCIS2TokenEndpoint = this.node.tryGetContext("apigeeCIS2TokenEndpoint") const apigeeMockTokenEndpoint = this.node.tryGetContext("apigeeMockTokenEndpoint") const apigeePrescriptionsEndpoint = this.node.tryGetContext("apigeePrescriptionsEndpoint") @@ -211,7 +211,10 @@ export class StatelessResourcesStack extends Stack { const sharedSecrets = new SharedSecrets(this, "SharedSecrets", { stackName: props.stackName, deploymentRole: deploymentRole, - useMockOidc: useMockOidc + useMockOidc: useMockOidc, + apigeeApiKey: apigeeApiKey, + apigeeDoHSApiKey: apigeeDoHSApiKey, + apigeeApiSecret: apigeeApiSecret }) // Functions for the login OAuth2 proxy lambdas @@ -266,8 +269,8 @@ export class StatelessResourcesStack extends Stack { logRetentionInDays, logLevel, jwtKid, - apigeeApiKey, - apigeeApiSecret + apigeeApiKey: sharedSecrets.apigeeApiKey, + apigeeApiSecret: sharedSecrets.apigeeApiSecret }) // -- functions for API @@ -302,9 +305,9 @@ export class StatelessResourcesStack extends Stack { apigeeMockTokenEndpoint: apigeeMockTokenEndpoint, apigeePrescriptionsEndpoint: apigeePrescriptionsEndpoint, apigeeDoHSEndpoint: apigeeDoHSEndpoint, - apigeeApiKey: apigeeApiKey, - apigeeDoHSApiKey: apigeeDoHSApiKey, - apigeeApiSecret, + apigeeApiKey: sharedSecrets.apigeeApiKey, + apigeeDoHSApiKey: sharedSecrets.apigeeDoHSApiKey, + apigeeApiSecret: sharedSecrets.apigeeApiSecret, jwtKid: jwtKid, roleId: roleId, apigeePersonalDemographicsEndpoint: apigeePersonalDemographicsEndpoint, diff --git a/packages/cognito/package.json b/packages/cognito/package.json index 3d8487f6a6..461cc9267c 100644 --- a/packages/cognito/package.json +++ b/packages/cognito/package.json @@ -16,6 +16,7 @@ "@aws-lambda-powertools/logger": "^2.30.1", "@aws-lambda-powertools/parameters": "^2.30.1", "@aws-sdk/client-dynamodb": "^3.980.0", + "@aws-sdk/client-secrets-manager": "^3.981.0", "@aws-sdk/lib-dynamodb": "^3.980.0", "@cpt-ui-common/authFunctions": "^1.0.0", "@cpt-ui-common/dynamoFunctions": "^1.0.0", diff --git a/packages/cognito/src/authorizeMock.ts b/packages/cognito/src/authorizeMock.ts index 0cf377ae15..4ea4a0fb0e 100644 --- a/packages/cognito/src/authorizeMock.ts +++ b/packages/cognito/src/authorizeMock.ts @@ -1,6 +1,7 @@ import {Logger} from "@aws-lambda-powertools/logger" import {APIGatewayProxyEvent, APIGatewayProxyResult} from "aws-lambda" import {injectLambdaContext} from "@aws-lambda-powertools/logger/middleware" +import {getSecret} from "@aws-lambda-powertools/parameters/secrets" import {MiddyErrorHandler} from "@cpt-ui-common/middyErrorHandler" @@ -23,7 +24,7 @@ const authorizeEndpoint = process.env["IDP_AUTHORIZE_PATH"] as string const cis2ClientId = process.env["OIDC_CLIENT_ID"] as string const userPoolClientId = process.env["COGNITO_CLIENT_ID"] as string const cloudfrontDomain = process.env["FULL_CLOUDFRONT_DOMAIN"] as string -const apigeeApiKey = process.env["APIGEE_API_KEY"] as string +const apigeeApiKeyArn = process.env["APIGEE_API_KEY_ARN"] as string const logger = new Logger({serviceName: "authorize"}) const errorResponseBody = {message: "A system error has occurred"} @@ -32,6 +33,7 @@ const middyErrorHandler = new MiddyErrorHandler(errorResponseBody) const lambdaHandler = async ( event: APIGatewayProxyEvent ): Promise => { + const apigeeApiKey = await getSecret(apigeeApiKeyArn) logger.appendKeys({"apigw-request-id": event.requestContext?.requestId}) // we need to use the base domain for the environment so that pull requests go to that callback uri // as we can only have one callback uri per apigee application @@ -43,7 +45,7 @@ const lambdaHandler = async ( cis2ClientId, userPoolClientId, cloudfrontDomain, - apigeeApiKey + apigeeApiKeyArn }}) // Validate required environment variables @@ -82,7 +84,7 @@ const lambdaHandler = async ( // Build the redirect parameters for CIS2 const responseParameters = { redirect_uri: callbackUri, - client_id: apigeeApiKey, + client_id: apigeeApiKey.toString(), // Ensure client_id is a string response_type: "code", state: newState } diff --git a/packages/cognito/src/tokenMock.ts b/packages/cognito/src/tokenMock.ts index adc2201686..ab1284b1b5 100644 --- a/packages/cognito/src/tokenMock.ts +++ b/packages/cognito/src/tokenMock.ts @@ -50,8 +50,8 @@ const cloudfrontDomain= process.env["FULL_CLOUDFRONT_DOMAIN"] as string const jwtPrivateKeyArn= process.env["jwtPrivateKeyArn"] as string const jwtKid= process.env["jwtKid"] as string const idpTokenPath= process.env["MOCK_IDP_TOKEN_PATH"] as string -const apigeeApiKey = process.env["APIGEE_API_KEY"] as string -const apigeeApiSecret = process.env["APIGEE_API_SECRET"] as string +const apigeeApiKeyArn = process.env["APIGEE_API_KEY_ARN"] as string +const apigeeApiSecretArn = process.env["APIGEE_API_SECRET_ARN"] as string const apigeeMockTokenEndpoint = process.env["MOCK_OIDC_TOKEN_ENDPOINT"] as string const dynamoClient = new DynamoDBClient() @@ -73,6 +73,8 @@ async function createSignedJwt(claims: Record) { } const lambdaHandler = async (event: APIGatewayProxyEvent): Promise => { + const apigeeApiKey = await getSecret(apigeeApiKeyArn) + const apigeeApiSecret = await getSecret(apigeeApiSecretArn) logger.appendKeys({"apigw-request-id": event.requestContext?.requestId}) // we need to use the base domain for the environment so that pull requests go to that callback uri @@ -93,8 +95,8 @@ const lambdaHandler = async (event: APIGatewayProxyEvent): Promise { tokenMappingTableName: process.env["TokenMappingTableName"] as string, sessionManagementTableName: process.env["SessionManagementTableName"] as string, jwtPrivateKeyArn: process.env["jwtPrivateKeyArn"] as string, - apigeeApiKey: process.env["APIGEE_API_KEY"] as string, - apigeeApiSecret: process.env["APIGEE_API_SECRET"] as string, + apigeeApiKeyArn: process.env["APIGEE_API_KEY_ARN"] as string, + apigeeApiSecretArn: process.env["APIGEE_API_SECRET_ARN"] as string, jwtKid: process.env["jwtKid"] as string, apigeeMockTokenEndpoint: process.env["apigeeMockTokenEndpoint"] as string, apigeeCis2TokenEndpoint: process.env["apigeeCIS2TokenEndpoint"] as string, @@ -82,12 +82,17 @@ const refreshTokenFlow = async ( if (existingToken.refreshToken === undefined) { throw new Error("Missing refresh token") } + const apigeeApiKey = await getSecret(authOptions.apigeeApiKeyArn) + const apigeeApiSecret = await getSecret(authOptions.apigeeApiSecretArn) + if (!apigeeApiKey || !apigeeApiSecret) { + throw new Error("Missing Apigee API credentials") + } const refreshResult = await refreshApigeeAccessToken( axiosInstance, apigeeTokenEndpoint, existingToken.refreshToken, - authOptions.apigeeApiKey, - authOptions.apigeeApiSecret, + apigeeApiKey.toString(), + apigeeApiSecret.toString(), logger ) @@ -134,13 +139,18 @@ export async function authenticateRequest( ): Promise { const { jwtPrivateKeyArn, - apigeeApiKey, - apigeeApiSecret, + apigeeApiKeyArn, + apigeeApiSecretArn, jwtKid, apigeeMockTokenEndpoint, apigeeCis2TokenEndpoint, cloudfrontDomain } = authOptions + const apigeeApiKey = await getSecret(apigeeApiKeyArn) + const apigeeApiSecret = await getSecret(apigeeApiSecretArn) + if (!apigeeApiKey || !apigeeApiSecret) { + throw new Error("Missing Apigee API credentials") + } logger.info("Starting authentication flow") // Extract username and determine if this is a mock request @@ -238,8 +248,8 @@ export async function authenticateRequest( const callbackUri = `https://${baseEnvironmentDomain}/oauth2/mock-callback` const tokenExchangeBody = { grant_type: "authorization_code", - client_id: apigeeApiKey, - client_secret: apigeeApiSecret, + client_id: apigeeApiKey.toString(), + client_secret: apigeeApiSecret.toString(), redirect_uri: callbackUri, code: userRecord.apigeeCode } @@ -268,7 +278,7 @@ export async function authenticateRequest( logger, apigeeCis2TokenEndpoint, jwtPrivateKey, - apigeeApiKey, + apigeeApiKey.toString(), jwtKid, userRecord.cis2IdToken ) diff --git a/packages/common/doHSClient/package.json b/packages/common/doHSClient/package.json index c55b5df4e7..a6bfeb4945 100644 --- a/packages/common/doHSClient/package.json +++ b/packages/common/doHSClient/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@aws-lambda-powertools/logger": "^2.30.1", + "@aws-sdk/client-secrets-manager": "^3.981.0", "axios": "^1.13.2", "axios-retry": "^4.5.0" }, diff --git a/packages/common/doHSClient/src/doHSClient.ts b/packages/common/doHSClient/src/doHSClient.ts index 50e218a811..a14616af6c 100644 --- a/packages/common/doHSClient/src/doHSClient.ts +++ b/packages/common/doHSClient/src/doHSClient.ts @@ -1,9 +1,10 @@ import {Logger} from "@aws-lambda-powertools/logger" import axios, {AxiosRequestConfig} from "axios" +import {getSecret} from "@aws-lambda-powertools/parameters/secrets" // Read the DoHS API Key from environment variables const apigeeDoHSEndpoint = process.env["apigeeDoHSEndpoint"] as string -const apigeeDoHSApiKey = process.env["APIGEE_DOHS_API_KEY"] as string +const apigeeDoHSApiKeyArn = process.env["APIGEE_DOHS_API_KEY_ARN"] as string interface DoHSContact { ContactType: string @@ -22,6 +23,7 @@ export interface DoHSOrg { } export const doHSClient = async (odsCodes: Array, logger: Logger): Promise> => { + const apigeeDoHSApiKey = await getSecret(apigeeDoHSApiKeyArn) logger.info("Fetching DoHS API data for ODS codes", {odsCodes}) if (odsCodes.length === 0) { From 5c87ca71bfc537882e1552b0c7c3a49fb6912da2 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 3 Feb 2026 13:30:29 +0000 Subject: [PATCH 02/11] fix test --- packages/cognito/tests/test_authorizeMock.test.ts | 2 +- .../tests/test_authenticateRequest.test.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cognito/tests/test_authorizeMock.test.ts b/packages/cognito/tests/test_authorizeMock.test.ts index 775a9b2ea7..3760f82655 100644 --- a/packages/cognito/tests/test_authorizeMock.test.ts +++ b/packages/cognito/tests/test_authorizeMock.test.ts @@ -16,7 +16,7 @@ describe("authorize mock handler", () => { jest.restoreAllMocks() }) - it("should redirect to CIS2 with correct parameters", async () => { + it.skip("should redirect to CIS2 with correct parameters", async () => { const event = { ...mockAPIGatewayProxyEvent, queryStringParameters: { diff --git a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts index d0db653833..8dc8cdf5cd 100644 --- a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts +++ b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts @@ -66,7 +66,7 @@ jest.unstable_mockModule("../src/index", () => ({ const authModule = await import("../src/authenticateRequest") const {authenticateRequest} = authModule -describe("authenticateRequest", () => { +describe.skip("authenticateRequest", () => { // Common test setup const mockLogger = { @@ -80,9 +80,9 @@ describe("authenticateRequest", () => { tokenMappingTableName: "test-table", sessionManagementTableName: "test-session-table", jwtPrivateKeyArn: "test-key-arn", - apigeeApiKey: "test-api-key", + apigeeApiKeyArn: "test-api-key", jwtKid: "test-kid", - apigeeApiSecret: "test-api-secret", + apigeeApiSecretArn: "test-api-secret", apigeeMockTokenEndpoint: "mock-token-endpoint", apigeeCis2TokenEndpoint: "cis2-token-endpoint", cloudfrontDomain: "test-cloudfront-domain" @@ -230,8 +230,8 @@ describe("authenticateRequest", () => { axiosInstance, mockOptions.apigeeCis2TokenEndpoint, // Use the one from options "expiring-refresh-token", - mockOptions.apigeeApiKey, - "test-api-secret", // API secret from env var + mockOptions.apigeeApiKeyArn, + mockOptions.apigeeApiSecretArn, // API secret from env var mockLogger ) @@ -296,8 +296,8 @@ describe("authenticateRequest", () => { axiosInstance, mockOptions.apigeeCis2TokenEndpoint, // Use the one from options "expiring-refresh-token", - mockOptions.apigeeApiKey, - "test-api-secret", // API secret from env var + mockOptions.apigeeApiKeyArn, + mockOptions.apigeeApiSecretArn, // API secret from env var mockLogger ) From 10728dbc0a9270b82a3027febe0456eab13440ab Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 3 Feb 2026 13:32:57 +0000 Subject: [PATCH 03/11] fix policy --- packages/cdk/resources/api/apiFunctions.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cdk/resources/api/apiFunctions.ts b/packages/cdk/resources/api/apiFunctions.ts index 8a0ffacc14..02d17caf9f 100644 --- a/packages/cdk/resources/api/apiFunctions.ts +++ b/packages/cdk/resources/api/apiFunctions.ts @@ -77,7 +77,8 @@ export class ApiFunctions extends Construct { props.sessionManagementTableReadPolicy, props.useSessionManagementKmsKeyPolicy, props.sharedSecrets.useJwtKmsKeyPolicy, - props.sharedSecrets.getPrimaryJwtPrivateKeyPolicy + props.sharedSecrets.getPrimaryJwtPrivateKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ] if (props.useMockOidc && props.sharedSecrets.getMockJwtPrivateKeyPolicy) { From 747da589b68e9dbb759965866ca4947c5debceac Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 3 Feb 2026 13:51:28 +0000 Subject: [PATCH 04/11] skip tests #skip-qc --- packages/common/doHSClient/tests/test_doHSClient.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/doHSClient/tests/test_doHSClient.test.ts b/packages/common/doHSClient/tests/test_doHSClient.test.ts index 350a85bb24..96963c6323 100644 --- a/packages/common/doHSClient/tests/test_doHSClient.test.ts +++ b/packages/common/doHSClient/tests/test_doHSClient.test.ts @@ -20,7 +20,7 @@ process.env.apigeeDoHSEndpoint = validEndpoint // Now we can safely import the module const {doHSClient} = await import("../src/doHSClient") -describe("doHSClient", () => { +describe.skip("doHSClient", () => { beforeEach(() => { jest.clearAllMocks() // Clean up any pending nock interceptors From 20cee31e50f336898f0ec431465b06ac212122b0 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 3 Feb 2026 14:37:02 +0000 Subject: [PATCH 05/11] fix outh lambda #skip-qc --- packages/cdk/resources/api/oauth2Functions.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/cdk/resources/api/oauth2Functions.ts b/packages/cdk/resources/api/oauth2Functions.ts index 8ea8837f91..6de78586ac 100644 --- a/packages/cdk/resources/api/oauth2Functions.ts +++ b/packages/cdk/resources/api/oauth2Functions.ts @@ -103,7 +103,8 @@ export class OAuth2Functions extends Construct { props.sharedSecrets.getPrimaryJwtPrivateKeyPolicy, props.sessionManagementTableWritePolicy, props.sessionManagementTableReadPolicy, - props.useSessionManagementKmsKeyPolicy + props.useSessionManagementKmsKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ], logRetentionInDays: props.logRetentionInDays, logLevel: props.logLevel, @@ -131,7 +132,8 @@ export class OAuth2Functions extends Construct { additionalPolicies: [ props.stateMappingTableWritePolicy, props.stateMappingTableReadPolicy, - props.useStateMappingKmsKeyPolicy + props.useStateMappingKmsKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ], logRetentionInDays: props.logRetentionInDays, logLevel: props.logLevel, @@ -154,7 +156,8 @@ export class OAuth2Functions extends Construct { additionalPolicies: [ props.stateMappingTableWritePolicy, props.stateMappingTableReadPolicy, - props.useStateMappingKmsKeyPolicy + props.useStateMappingKmsKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ], logRetentionInDays: props.logRetentionInDays, logLevel: props.logLevel, @@ -199,7 +202,8 @@ export class OAuth2Functions extends Construct { props.useStateMappingKmsKeyPolicy, props.sessionStateMappingTableWritePolicy, props.sessionStateMappingTableReadPolicy, - props.useSessionStateMappingKmsKeyPolicy + props.useSessionStateMappingKmsKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ], logRetentionInDays: props.logRetentionInDays, logLevel: props.logLevel, @@ -238,7 +242,8 @@ export class OAuth2Functions extends Construct { props.sessionStateMappingTableWritePolicy, props.useSessionStateMappingKmsKeyPolicy, props.sharedSecrets.useJwtKmsKeyPolicy, - props.sharedSecrets.getMockJwtPrivateKeyPolicy + props.sharedSecrets.getMockJwtPrivateKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ], logRetentionInDays: props.logRetentionInDays, logLevel: props.logLevel, @@ -278,7 +283,8 @@ export class OAuth2Functions extends Construct { props.useStateMappingKmsKeyPolicy, props.sessionStateMappingTableReadPolicy, props.sessionStateMappingTableWritePolicy, - props.useSessionStateMappingKmsKeyPolicy + props.useSessionStateMappingKmsKeyPolicy, + props.sharedSecrets.getApigeeSecretsPolicy ], logRetentionInDays: props.logRetentionInDays, logLevel: props.logLevel, From 6b556e165b7cfd7e6f75f88e97095da730d59cf5 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Tue, 3 Feb 2026 16:50:20 +0000 Subject: [PATCH 06/11] fix test --- packages/cognito/jest.debug.config.ts | 2 +- packages/cognito/tests/test_authorizeMock.test.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/cognito/jest.debug.config.ts b/packages/cognito/jest.debug.config.ts index a306273831..fa3a3ff872 100644 --- a/packages/cognito/jest.debug.config.ts +++ b/packages/cognito/jest.debug.config.ts @@ -1,4 +1,4 @@ -import config from "./jest.config" +import config from "./jest.config.ts" import type {JestConfigWithTsJest} from "ts-jest" const debugConfig: JestConfigWithTsJest = { diff --git a/packages/cognito/tests/test_authorizeMock.test.ts b/packages/cognito/tests/test_authorizeMock.test.ts index 3760f82655..2ee5112a42 100644 --- a/packages/cognito/tests/test_authorizeMock.test.ts +++ b/packages/cognito/tests/test_authorizeMock.test.ts @@ -7,6 +7,10 @@ process.env.useMock = "true" process.env.COGNITO_CLIENT_ID = "userPoolClient123" process.env.FULL_CLOUDFRONT_DOMAIN = "cpt-ui-pr-854.dev.eps.national.nhs.uk" +jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => ({ + getSecret: jest.fn(async () => "apigee_api_key") +})) + // Import the handler after setting the env variables and mocks. import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" const {handler} = await import("../src/authorizeMock") @@ -16,7 +20,7 @@ describe("authorize mock handler", () => { jest.restoreAllMocks() }) - it.skip("should redirect to CIS2 with correct parameters", async () => { + it("should redirect to CIS2 with correct parameters", async () => { const event = { ...mockAPIGatewayProxyEvent, queryStringParameters: { From f9fa8270461f2ff1c1eb3c23a406c1d903a68a0a Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 5 Feb 2026 16:46:19 +0000 Subject: [PATCH 07/11] fix test --- packages/common/authFunctions/jest.config.ts | 11 ----------- .../common/authFunctions/jest.debug.config.ts | 9 --------- .../tests/test_authenticateRequest.test.ts | 19 ++++++++++++------- .../common/authFunctions/vitest.config.ts | 4 +++- 4 files changed, 15 insertions(+), 28 deletions(-) delete mode 100644 packages/common/authFunctions/jest.config.ts delete mode 100644 packages/common/authFunctions/jest.debug.config.ts diff --git a/packages/common/authFunctions/jest.config.ts b/packages/common/authFunctions/jest.config.ts deleted file mode 100644 index e3d83833cd..0000000000 --- a/packages/common/authFunctions/jest.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import defaultConfig from "../../../jest.default.config.ts" -import type {JestConfigWithTsJest} from "ts-jest" - -const jestConfig: JestConfigWithTsJest = { - ...defaultConfig, - "rootDir": ".", - setupFiles: ["/.jest/setEnvVars.js"], - moduleNameMapper: {"@/(.*)$": ["/src/$1"]} -} - -export default jestConfig diff --git a/packages/common/authFunctions/jest.debug.config.ts b/packages/common/authFunctions/jest.debug.config.ts deleted file mode 100644 index a306273831..0000000000 --- a/packages/common/authFunctions/jest.debug.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import config from "./jest.config" -import type {JestConfigWithTsJest} from "ts-jest" - -const debugConfig: JestConfigWithTsJest = { - ...config, - "preset": "ts-jest" -} - -export default debugConfig diff --git a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts index 4463057508..9638b253ed 100644 --- a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts +++ b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts @@ -103,9 +103,9 @@ describe("authenticateRequest", () => { tokenMappingTableName: "test-table", sessionManagementTableName: "test-session-table", jwtPrivateKeyArn: "test-key-arn", - apigeeApiKeyArn: "test-api-key", + apigeeApiKeyArn: "dummy_apigee_api_key_arn", jwtKid: "test-kid", - apigeeApiSecretArn: "test-api-secret", + apigeeApiSecretArn: "dummy_apigee_api_secret_arn", apigeeMockTokenEndpoint: "mock-token-endpoint", apigeeCis2TokenEndpoint: "cis2-token-endpoint", cloudfrontDomain: "test-cloudfront-domain" @@ -127,9 +127,6 @@ describe("authenticateRequest", () => { // Default mock for constructSignedJWTBody mockConstructSignedJWTBody.mockReturnValue({param: "value"}) - - // Ensure process.env is populated - process.env.APIGEE_API_SECRET = "test-api-secret" }) it("should use existing valid token when available", async () => { @@ -232,6 +229,11 @@ describe("authenticateRequest", () => { refreshToken: "refreshed-refresh-token", expiresIn: 3600 }) + mockGetSecret + .mockReturnValueOnce("test-private-key") + .mockReturnValueOnce("test-private-key") + .mockReturnValueOnce("dummy_apigee_api_key_arn") + .mockReturnValueOnce("dummy_apigee_api_secret_arn") const result = await authenticateRequest( "test-user", @@ -298,6 +300,11 @@ describe("authenticateRequest", () => { refreshToken: "refreshed-refresh-token", expiresIn: 3600 }) + mockGetSecret + .mockReturnValueOnce("test-private-key") + .mockReturnValueOnce("test-private-key") + .mockReturnValueOnce("dummy_apigee_api_key_arn") + .mockReturnValueOnce("dummy_apigee_api_secret_arn") const result = await authenticateRequest( "test-user", @@ -427,7 +434,6 @@ describe("authenticateRequest", () => { expect(mockConstructSignedJWTBody).not.toHaveBeenCalled() expect(mockExchangeTokenForApigeeAccessToken).toHaveBeenCalled() expect(mockUpdateTokenMapping).toHaveBeenCalled() - expect(mockGetSecret).not.toHaveBeenCalled() }) it("should acquire new token when no token exists for mocked user", async () => { @@ -470,7 +476,6 @@ describe("authenticateRequest", () => { expect(mockConstructSignedJWTBody).not.toHaveBeenCalled() expect(mockExchangeTokenForApigeeAccessToken).toHaveBeenCalled() expect(mockUpdateTokenMapping).toHaveBeenCalled() - expect(mockGetSecret).not.toHaveBeenCalled() }) it("should handle token refresh failure gracefully", async () => { diff --git a/packages/common/authFunctions/vitest.config.ts b/packages/common/authFunctions/vitest.config.ts index 2d790760a4..b07fa8e1be 100644 --- a/packages/common/authFunctions/vitest.config.ts +++ b/packages/common/authFunctions/vitest.config.ts @@ -35,7 +35,9 @@ const viteConfig = defineConfig({ MOCK_USER_INFO_ENDPOINT: `${MOCK_OIDC_HOST}/userinfo`, MOCK_USER_POOL_IDP: "MockDummyPoolIdentityProvider", MOCK_IDP_TOKEN_PATH: `${MOCK_OIDC_HOST}/token`, - FULL_CLOUDFRONT_DOMAIN: "cpt-ui-pr-854.dev.eps.national.nhs.uk" + FULL_CLOUDFRONT_DOMAIN: "cpt-ui-pr-854.dev.eps.national.nhs.uk", + APIGEE_API_KEY_ARN: "dummy_apigee_api_key_arn", + APIGEE_API_SECRET_ARN: "dummy_apigee_api_secret_arn" } } }) From 423bad7d8ee2572329a4baf77281eae7d114e0c5 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 5 Feb 2026 16:52:44 +0000 Subject: [PATCH 08/11] really fix test --- .../tests/test_authenticateRequest.test.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts index 9638b253ed..4a9cb1d3ec 100644 --- a/packages/common/authFunctions/tests/test_authenticateRequest.test.ts +++ b/packages/common/authFunctions/tests/test_authenticateRequest.test.ts @@ -99,6 +99,8 @@ describe("authenticateRequest", () => { error: vi.fn() } as unknown as Logger + const mockApigeeApiKey = "dummy_apigee_api_key" + const mockApigeeApiSecret = "dummy_apigee_api_secret" const mockOptions = { tokenMappingTableName: "test-table", sessionManagementTableName: "test-session-table", @@ -232,8 +234,8 @@ describe("authenticateRequest", () => { mockGetSecret .mockReturnValueOnce("test-private-key") .mockReturnValueOnce("test-private-key") - .mockReturnValueOnce("dummy_apigee_api_key_arn") - .mockReturnValueOnce("dummy_apigee_api_secret_arn") + .mockReturnValueOnce("dummy_apigee_api_key") + .mockReturnValueOnce("dummy_apigee_api_secret") const result = await authenticateRequest( "test-user", @@ -255,8 +257,8 @@ describe("authenticateRequest", () => { axiosInstance, mockOptions.apigeeCis2TokenEndpoint, // Use the one from options "expiring-refresh-token", - mockOptions.apigeeApiKeyArn, - mockOptions.apigeeApiSecretArn, // API secret from env var + mockApigeeApiKey, + mockApigeeApiSecret, // API secret from env var mockLogger ) @@ -303,8 +305,8 @@ describe("authenticateRequest", () => { mockGetSecret .mockReturnValueOnce("test-private-key") .mockReturnValueOnce("test-private-key") - .mockReturnValueOnce("dummy_apigee_api_key_arn") - .mockReturnValueOnce("dummy_apigee_api_secret_arn") + .mockReturnValueOnce("dummy_apigee_api_key") + .mockReturnValueOnce("dummy_apigee_api_secret") const result = await authenticateRequest( "test-user", @@ -326,8 +328,8 @@ describe("authenticateRequest", () => { axiosInstance, mockOptions.apigeeCis2TokenEndpoint, // Use the one from options "expiring-refresh-token", - mockOptions.apigeeApiKeyArn, - mockOptions.apigeeApiSecretArn, // API secret from env var + mockApigeeApiKey, + mockApigeeApiSecret, // API secret from env var mockLogger ) From 8eeb30e90bccb44e9648ab9c337854454564fe2c Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 5 Feb 2026 16:54:40 +0000 Subject: [PATCH 09/11] run test --- packages/common/doHSClient/tests/test_doHSClient.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/common/doHSClient/tests/test_doHSClient.test.ts b/packages/common/doHSClient/tests/test_doHSClient.test.ts index 96963c6323..350a85bb24 100644 --- a/packages/common/doHSClient/tests/test_doHSClient.test.ts +++ b/packages/common/doHSClient/tests/test_doHSClient.test.ts @@ -20,7 +20,7 @@ process.env.apigeeDoHSEndpoint = validEndpoint // Now we can safely import the module const {doHSClient} = await import("../src/doHSClient") -describe.skip("doHSClient", () => { +describe("doHSClient", () => { beforeEach(() => { jest.clearAllMocks() // Clean up any pending nock interceptors From 8fc4b572376db4181381f14a71d4f6bbfe951ba7 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Thu, 5 Feb 2026 17:21:03 +0000 Subject: [PATCH 10/11] fix test --- packages/common/doHSClient/.vscode/launch.json | 2 +- .../common/doHSClient/jest.debug.config.ts | 2 +- .../doHSClient/tests/test_doHSClient.test.ts | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/common/doHSClient/.vscode/launch.json b/packages/common/doHSClient/.vscode/launch.json index ba322cd1dc..c16b361af4 100644 --- a/packages/common/doHSClient/.vscode/launch.json +++ b/packages/common/doHSClient/.vscode/launch.json @@ -19,7 +19,7 @@ "console": "integratedTerminal", "internalConsoleOptions": "neverOpen", "disableOptimisticBPs": true, - "program": "${workspaceFolder}/../../,,/node_modules/.bin/jest", + "program": "${workspaceFolder}/../../../node_modules/.bin/jest", "windows": { "program": "${workspaceFolder}/node_modules/jest/bin/jest" }, diff --git a/packages/common/doHSClient/jest.debug.config.ts b/packages/common/doHSClient/jest.debug.config.ts index a306273831..fa3a3ff872 100644 --- a/packages/common/doHSClient/jest.debug.config.ts +++ b/packages/common/doHSClient/jest.debug.config.ts @@ -1,4 +1,4 @@ -import config from "./jest.config" +import config from "./jest.config.ts" import type {JestConfigWithTsJest} from "ts-jest" const debugConfig: JestConfigWithTsJest = { diff --git a/packages/common/doHSClient/tests/test_doHSClient.test.ts b/packages/common/doHSClient/tests/test_doHSClient.test.ts index 350a85bb24..169689f203 100644 --- a/packages/common/doHSClient/tests/test_doHSClient.test.ts +++ b/packages/common/doHSClient/tests/test_doHSClient.test.ts @@ -17,6 +17,17 @@ const validEndpoint = "https://api.example.com/dohs" process.env.apigeeApiKey = validApiKey process.env.apigeeDoHSEndpoint = validEndpoint +const mockGetSecret = jest.fn() +jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => { + const getSecret = mockGetSecret.mockImplementation(async () => { + return validApiKey + }) + + return { + getSecret + } +}) + // Now we can safely import the module const {doHSClient} = await import("../src/doHSClient") @@ -40,8 +51,9 @@ describe("doHSClient", () => { it("throws an error if apigeeApiKey is not set", async () => { // Temporarily unset the API key - const originalApiKey = process.env.APIGEE_DOHS_API_KEY - delete process.env.APIGEE_DOHS_API_KEY + mockGetSecret.mockImplementationOnce(async () => { + return null + }) // Re-import the module to pick up the changed environment jest.resetModules() @@ -51,8 +63,6 @@ describe("doHSClient", () => { "Apigee API Key environment variable is not set" ) - // Restore the API key - process.env.APIGEE_DOHS_API_KEY = originalApiKey }) it("throws an error if apigeeDoHSEndpoint is not set", async () => { From 99cfab20ef74a9f059a3ad8ed20a974e7a4aa7f7 Mon Sep 17 00:00:00 2001 From: Anthony Brown Date: Fri, 6 Feb 2026 08:01:08 +0000 Subject: [PATCH 11/11] fix test --- .../cognito/tests/test_authorizeMock.test.ts | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/cognito/tests/test_authorizeMock.test.ts b/packages/cognito/tests/test_authorizeMock.test.ts index 4b1da29179..4c602ae7d2 100644 --- a/packages/cognito/tests/test_authorizeMock.test.ts +++ b/packages/cognito/tests/test_authorizeMock.test.ts @@ -5,17 +5,30 @@ import { it, vi } from "vitest" +// Import the handler after setting the env variables and mocks. +import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" +import {handler} from "../src/authorizeMock" // Set environment variables before importing the handler. process.env.useMock = "true" -jest.unstable_mockModule("@aws-lambda-powertools/parameters/secrets", () => ({ - getSecret: jest.fn(async () => "apigee_api_key") -})) +const { + mockGetSecret +} = vi.hoisted(() => { + return { + mockGetSecret: vi.fn() + } +}) -// Import the handler after setting the env variables and mocks. -import {mockAPIGatewayProxyEvent, mockContext} from "./mockObjects" -import {handler} from "../src/authorizeMock" +vi.mock("@aws-lambda-powertools/parameters/secrets", () => { + const getSecret = mockGetSecret.mockImplementation(async () => { + return "apigee_api_key" + }) + + return { + getSecret + } +}) describe("authorize mock handler", () => { beforeEach(() => {