Skip to content

Commit 86bfae3

Browse files
Breaking: [AEA-0000] - An attempt at making a single lambda work on both real and mock auth (#241)
## Summary - ❗ Breaking Change ### Details This is my attempt at making the lambda able to receive both real and mock auth tokens. There's an environment variable that we can set to enable mock auth, and if it's present AND the user is using what appears to be a mock token, then we process it as such. Otherwise, we fall back on proper authentication. --------- Co-authored-by: jonathanwelch1-nhs <[email protected]>
1 parent 5381e13 commit 86bfae3

File tree

12 files changed

+405
-192
lines changed

12 files changed

+405
-192
lines changed

packages/auth_demo/src/App.tsx

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { authConfig } from './configureAmplify'
1010
Amplify.configure(authConfig, { ssr: true })
1111

1212
const trackerUserInfoEndpoint = "/api/tracker-user-info"
13-
const mockTrackerUserInfoEndpoint = "/api/mock-tracker-user-info"
1413

1514
function App() {
1615
const [user, setUser] = useState(null)
@@ -66,14 +65,13 @@ function App() {
6665
}
6766
}
6867

69-
const fetchTrackerUserInfo = async (isMock: boolean) => {
68+
const fetchTrackerUserInfo = async () => {
7069
setLoading(true)
7170
setTrackerUserInfoData(null)
7271
setError(null)
7372

74-
let endpoint = isMock ? mockTrackerUserInfoEndpoint : trackerUserInfoEndpoint
7573
try {
76-
const response = await axios.get(endpoint, {
74+
const response = await axios.get(trackerUserInfoEndpoint, {
7775
headers: {
7876
Authorization: `Bearer ${idToken}`,
7977
'NHSD-Session-URID': '555254242106'
@@ -108,20 +106,12 @@ function App() {
108106

109107
<div style={{ marginTop: '20px' }}>
110108
<button
111-
onClick={() => fetchTrackerUserInfo(false)}
109+
onClick={fetchTrackerUserInfo}
112110
disabled={!isSignedIn}
113111
>
114112
Fetch Tracker User Info
115113
</button>
116114
</div>
117-
<div style={{ marginTop: '20px' }}>
118-
<button
119-
onClick={() => fetchTrackerUserInfo(true)}
120-
disabled={!isSignedIn}
121-
>
122-
Fetch Mock Tracker User Info
123-
</button>
124-
</div>
125115

126116
{loading && <p>Loading...</p>}
127117
{trackerUserInfoData && (

packages/cdk/resources/RestApiGatewayMethods.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export interface RestApiGatewayMethodsProps {
1818
readonly tokenLambda: NodejsFunction
1919
readonly mockTokenLambda: NodejsFunction
2020
readonly trackerUserInfoLambda: LambdaFunction
21-
readonly mockTrackerUserInfoLambda: LambdaFunction
2221
readonly useMockOidc: boolean
2322
readonly authorizer: CognitoUserPoolsAuthorizer
2423

@@ -61,17 +60,6 @@ export class RestApiGatewayMethods extends Construct{
6160
authorizer: props.authorizer
6261
})
6362

64-
// mock tracker-user-info endpoint
65-
if (props.useMockOidc) {
66-
const mockTrackerUserInfoLambdaResource = props.restApiGateway.root.addResource("mock-tracker-user-info")
67-
mockTrackerUserInfoLambdaResource.addMethod("GET", new LambdaIntegration(props.mockTrackerUserInfoLambda.lambda, {
68-
credentialsRole: props.restAPiGatewayRole
69-
}), {
70-
authorizationType: AuthorizationType.COGNITO,
71-
authorizer: props.authorizer
72-
})
73-
}
74-
7563
/* Dummy Method/Resource to test cognito auth */
7664

7765
const mockNoAuth = new MockIntegration({

packages/cdk/resources/api/apiFunctions.ts

Lines changed: 47 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
21
import {Construct} from "constructs"
3-
42
import {LambdaFunction} from "../LambdaFunction"
53
import {ITableV2} from "aws-cdk-lib/aws-dynamodb"
64
import {
@@ -39,7 +37,6 @@ export interface ApiFunctionsProps {
3937
readonly mockPoolIdentityProviderName: string
4038
readonly logRetentionInDays: number
4139
readonly deploymentRole: IRole
42-
4340
}
4441

4542
/**
@@ -48,7 +45,6 @@ export interface ApiFunctionsProps {
4845
export class ApiFunctions extends Construct {
4946
public readonly apiFunctionsPolicies: Array<IManagedPolicy>
5047
public readonly trackerUserInfoLambda: LambdaFunction
51-
public readonly mockTrackerUserInfoLambda: LambdaFunction
5248
public readonly primaryJwtPrivateKey: Secret
5349

5450
public constructor(scope: Construct, id: string, props: ApiFunctionsProps) {
@@ -107,7 +103,7 @@ export class ApiFunctions extends Construct {
107103
]
108104
})
109105

110-
// Secret used by lambdas that holds the JWT private key
106+
// Real JWT Secret
111107
const primaryJwtPrivateKey = new Secret(this, "PrimaryJwtPrivateKey", {
112108
secretName: `${props.stackName}-primaryJwtPrivateKeyTrkUsrNfo`,
113109
secretStringValue: SecretValue.unsafePlainText("ChangeMe"),
@@ -136,9 +132,9 @@ export class ApiFunctions extends Construct {
136132
]
137133
})
138134

139-
// Set up things for mock authorization
140-
let getMockJWTPrivateKeySecret: ManagedPolicy | undefined = undefined
135+
// Setup mock keys if needed
141136
let mockJwtPrivateKey: Secret | undefined = undefined
137+
let getMockJWTPrivateKeySecret: ManagedPolicy | undefined = undefined
142138

143139
if (props.useMockOidc) {
144140
if (
@@ -151,7 +147,6 @@ export class ApiFunctions extends Construct {
151147
throw new Error("Attempt to use mock oidc but variables are not defined")
152148
}
153149

154-
// Secret used by mock user info lambda that holds the JWT private key
155150
mockJwtPrivateKey = new Secret(this, "MockJwtPrivateKey", {
156151
secretName: `${props.stackName}-mockJwtPrivateKeyTrkUsrNfo`,
157152
secretStringValue: SecretValue.unsafePlainText("ChangeMe"),
@@ -173,123 +168,63 @@ export class ApiFunctions extends Construct {
173168
})
174169
}
175170

176-
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
177-
// Lambda setups //
178-
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
171+
// Combine policies
172+
const additionalPolicies = [
173+
props.tokenMappingTableWritePolicy,
174+
props.tokenMappingTableReadPolicy,
175+
props.useTokensMappingKmsKeyPolicy,
176+
useJwtKmsKeyPolicy,
177+
getJWTPrivateKeySecret
178+
]
179179

180-
// CPT USER INFORMATION LAMBDA //
180+
if (props.useMockOidc && getMockJWTPrivateKeySecret) {
181+
additionalPolicies.push(getMockJWTPrivateKeySecret)
182+
}
181183

182-
// Set up things for mock authorization
183-
let mockTrackerUserInfoLambda: LambdaFunction
184-
if (props.useMockOidc) {
185-
// These checks are technically unnecessary, but they are here to prevent the linter from complaining
186-
if (
187-
props.mockOidcjwksEndpoint === undefined ||
188-
props.mockOidcUserInfoEndpoint === undefined ||
189-
props.mockOidcTokenEndpoint === undefined ||
190-
props.mockOidcClientId === undefined ||
191-
props.mockOidcIssuer === undefined ||
192-
getMockJWTPrivateKeySecret === undefined ||
193-
mockJwtPrivateKey === undefined
194-
) {
195-
throw new Error("Attempt to use mock oidc but variables are not defined")
196-
}
184+
// Environment variables
185+
// We pass in both sets of endpoints and keys. The Lambda code determines at runtime which to use.
186+
const lambdaEnv: { [key: string]: string } = {
187+
TokenMappingTableName: props.tokenMappingTable.tableName,
188+
189+
// Real endpoints/credentials
190+
REAL_IDP_TOKEN_PATH: props.primaryOidcTokenEndpoint,
191+
REAL_USER_INFO_ENDPOINT: props.primaryOidcUserInfoEndpoint,
192+
REAL_OIDCJWKS_ENDPOINT: props.primaryOidcjwksEndpoint,
193+
REAL_JWT_PRIVATE_KEY_ARN: primaryJwtPrivateKey.secretArn,
194+
REAL_USER_POOL_IDP: props.primaryPoolIdentityProviderName,
195+
REAL_USE_SIGNED_JWT: "true",
196+
REAL_OIDC_CLIENT_ID: props.primaryOidcClientId,
197+
REAL_OIDC_ISSUER: props.primaryOidcIssuer,
198+
199+
// Indicate if mock mode is available
200+
MOCK_MODE_ENABLED: props.useMockOidc ? "true" : "false"
201+
}
197202

198-
mockTrackerUserInfoLambda = new LambdaFunction(this, "MockTrackerUserInfo", {
199-
serviceName: props.serviceName,
200-
stackName: props.stackName,
201-
lambdaName: `${props.stackName}-mockTrkUsrNfo`,
202-
additionalPolicies: [
203-
props.tokenMappingTableWritePolicy,
204-
props.tokenMappingTableReadPolicy,
205-
props.useTokensMappingKmsKeyPolicy,
206-
useJwtKmsKeyPolicy,
207-
getMockJWTPrivateKeySecret
208-
],
209-
logRetentionInDays: props.logRetentionInDays,
210-
packageBasePath: "packages/trackerUserInfoLambda",
211-
entryPoint: "src/handler.ts",
212-
lambdaEnvironmentVariables: {
213-
idpTokenPath: props.mockOidcTokenEndpoint,
214-
TokenMappingTableName: props.tokenMappingTable.tableName,
215-
UserPoolIdentityProvider: props.mockPoolIdentityProviderName,
216-
oidcjwksEndpoint: props.mockOidcjwksEndpoint,
217-
jwtPrivateKeyArn: mockJwtPrivateKey.secretArn,
218-
userInfoEndpoint: props.mockOidcUserInfoEndpoint,
219-
useSignedJWT: "false",
220-
oidcClientId: props.mockOidcClientId,
221-
oidcIssuer: props.mockOidcIssuer
222-
}
223-
})
203+
// If mock OIDC is enabled, add mock environment variables
204+
if (props.useMockOidc && mockJwtPrivateKey) {
205+
lambdaEnv["MOCK_IDP_TOKEN_PATH"] = props.mockOidcTokenEndpoint!
206+
lambdaEnv["MOCK_USER_INFO_ENDPOINT"] = props.mockOidcUserInfoEndpoint!
207+
lambdaEnv["MOCK_OIDCJWKS_ENDPOINT"] = props.mockOidcjwksEndpoint!
208+
lambdaEnv["MOCK_JWT_PRIVATE_KEY_ARN"] = mockJwtPrivateKey.secretArn
209+
lambdaEnv["MOCK_USER_POOL_IDP"] = props.mockPoolIdentityProviderName
210+
lambdaEnv["MOCK_USE_SIGNED_JWT"] = "false"
211+
lambdaEnv["MOCK_OIDC_CLIENT_ID"] = props.mockOidcClientId!
212+
lambdaEnv["MOCK_OIDC_ISSUER"] = props.mockOidcIssuer!
224213
}
225214

226-
// Proper lambda function for user info
215+
// Single Lambda for both real and mock scenarios
227216
const trackerUserInfoLambda = new LambdaFunction(this, "TrackerUserInfo", {
228217
serviceName: props.serviceName,
229218
stackName: props.stackName,
230-
lambdaName: `${props.stackName}-TrkUsrNfo`,
231-
additionalPolicies: [
232-
props.tokenMappingTableWritePolicy,
233-
props.tokenMappingTableReadPolicy,
234-
props.useTokensMappingKmsKeyPolicy,
235-
useJwtKmsKeyPolicy,
236-
getJWTPrivateKeySecret
237-
],
219+
lambdaName: `${props.stackName}-TrkUsrNfoUnified`,
220+
additionalPolicies: additionalPolicies,
238221
logRetentionInDays: props.logRetentionInDays,
239222
packageBasePath: "packages/trackerUserInfoLambda",
240223
entryPoint: "src/handler.ts",
241-
lambdaEnvironmentVariables: {
242-
idpTokenPath: props.primaryOidcTokenEndpoint,
243-
TokenMappingTableName: props.tokenMappingTable.tableName,
244-
UserPoolIdentityProvider: props.primaryPoolIdentityProviderName,
245-
oidcjwksEndpoint: props.primaryOidcjwksEndpoint,
246-
jwtPrivateKeyArn: primaryJwtPrivateKey.secretArn,
247-
userInfoEndpoint: props.primaryOidcUserInfoEndpoint,
248-
useSignedJWT: "true",
249-
oidcClientId: props.primaryOidcClientId,
250-
oidcIssuer: props.primaryOidcIssuer
251-
}
224+
lambdaEnvironmentVariables: lambdaEnv
252225
})
253226

254-
// PRESCRIPTION SEARCH LAMBDA //
255-
256-
// const prescriptionSearchLambda = new LambdaFunction(this, "PrescriptionSearch", {
257-
// runtime: Runtime.NODEJS_20_X,
258-
// serviceName: props.serviceName,
259-
// stackName: props.stackName,
260-
// lambdaName: `${props.stackName}-prescSearch`,
261-
// additionalPolicies: [
262-
// props.tokenMappingTableWritePolicy,
263-
// props.tokenMappingTableReadPolicy,
264-
// props.useTokensMappingKmsKeyPolicy,
265-
// props.sharedSecrets.useJwtKmsKeyPolicy,
266-
// props.sharedSecrets.getPrimaryJwtPrivateKeyPolicy
267-
// ],
268-
// logRetentionInDays: props.logRetentionInDays,
269-
// packageBasePath: "packages/prescriptionSearchLambda",
270-
// entryPoint: "src/handler.ts",
271-
// lambdaEnvironmentVariables: {
272-
// idpTokenPath: props.primaryOidcTokenEndpoint,
273-
// TokenMappingTableName: props.tokenMappingTable.tableName,
274-
// UserPoolIdentityProvider: props.primaryPoolIdentityProviderName,
275-
// oidcjwksEndpoint: props.primaryOidcjwksEndpoint,
276-
// jwtPrivateKeyArn: props.sharedSecrets.primaryJwtPrivateKey.secretArn,
277-
// userInfoEndpoint: props.primaryOidcUserInfoEndpoint,
278-
// useSignedJWT: "true",
279-
// oidcClientId: props.primaryOidcClientId,
280-
// oidcIssuer: props.primaryOidcIssuer,
281-
// apigeeApiKey: props.apigeeApiKey,
282-
// jwtKid: props.jwtKid
283-
// }
284-
// })
285-
286-
// // Suppress the AwsSolutions-L1 rule for the prescription search Lambda function
287-
// NagSuppressions.addResourceSuppressions(prescriptionSearchLambda.lambda, [
288-
// {
289-
// id: "AwsSolutions-L1",
290-
// reason: "The Lambda function uses the latest runtime version supported at the time of implementation."
291-
// }
292-
// ])
227+
// This one Lambda can handle both mock and real requests at runtime
293228

294229
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
295230
// Permissions //
@@ -300,9 +235,6 @@ export class ApiFunctions extends Construct {
300235
trackerUserInfoLambda.executeLambdaManagedPolicy
301236
// prescriptionSearchLambda.executeLambdaManagedPolicy
302237
]
303-
if (props.useMockOidc) {
304-
apiFunctionsPolicies.push(mockTrackerUserInfoLambda!.executeLambdaManagedPolicy)
305-
}
306238

307239
// *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* //
308240
// Outputs //
@@ -314,9 +246,5 @@ export class ApiFunctions extends Construct {
314246

315247
// CPT user info lambda outputs
316248
this.trackerUserInfoLambda = trackerUserInfoLambda
317-
this.mockTrackerUserInfoLambda = mockTrackerUserInfoLambda!
318-
319-
// // Prescription search lambda outputs
320-
// this.prescriptionSearchLambda = prescriptionSearchLambda.lambda
321249
}
322250
}

packages/cdk/stacks/StatelessResourcesStack.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,6 @@ export class StatelessResourcesStack extends Stack {
187187
tokenLambda: cognitoFunctions.tokenLambda,
188188
mockTokenLambda: cognitoFunctions.mockTokenLambda,
189189
trackerUserInfoLambda: apiFunctions.trackerUserInfoLambda,
190-
mockTrackerUserInfoLambda: apiFunctions.mockTrackerUserInfoLambda,
191190
useMockOidc: useMockOidc,
192191
authorizer: apiGateway.authorizer
193192
})

packages/cpt-ui/components/EpsFooter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function EpsFooter() {
1212

1313
useEffect(() => {
1414
console.log("Viewing site version of commit ID:", COMMIT_ID)
15-
}, [COMMIT_ID])
15+
}, [])
1616

1717
return (
1818
<Footer id="eps_footer" className="eps_footer">

packages/trackerUserInfoLambda/jest.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import type {JestConfigWithTsJest} from "ts-jest"
44
const jestConfig: JestConfigWithTsJest = {
55
...defaultConfig,
66
"rootDir": "./",
7-
setupFiles: ["<rootDir>/.jest/setEnvVars.js"]
7+
setupFiles: ["<rootDir>/.jest/setEnvVars.js"],
8+
moduleNameMapper: {"@/(.*)$": ["<rootDir>/src/$1"]}
89
}
910

1011
export default jestConfig

0 commit comments

Comments
 (0)