Skip to content

Commit

Permalink
feat(test) post auth test (#1020)
Browse files Browse the repository at this point in the history
* feat(test) post auth test

* update test for mostly full coverage

* update word to not look like a secret

* some test clean up and refactoring

* clean up

* allow bad secret value for test

* refined a test, and made pr changes
  • Loading branch information
thwalker6 authored Jan 15, 2025
1 parent 8003585 commit db402ba
Show file tree
Hide file tree
Showing 9 changed files with 228 additions and 38 deletions.
99 changes: 99 additions & 0 deletions lib/lambda/postAuth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { describe, it, expect, vi, afterAll } from "vitest";
import { Context } from "aws-lambda";
import { handler } from "./postAuth";
import {
makoStateSubmitter,
setMockUsername,
superUser,
TEST_IDM_USERS,
USER_POOL_ID,
} from "mocks";

const callback = vi.fn();
describe("process emails Handler", () => {
afterAll(() => {
setMockUsername(makoStateSubmitter);
});
it("should return an error due to missing arn", async () => {
delete process.env.idmAuthzApiKeyArn;

await expect(handler({ test: "test" }, {} as Context, callback)).rejects.toThrowError(
"ERROR: process.env.idmAuthzApiKeyArn is required",
);
});
it("should return an error due to a missing endpoint", async () => {
delete process.env.idmAuthzApiEndpoint;
await expect(handler({ test: "test" }, {} as Context, callback)).rejects.toThrowError(
"ERROR: process.env.idmAuthzApiEndpoint is required",
);
});
it("should return an error due to the arn being incorrect", async () => {
process.env.idmAuthzApiKeyArn = "bad-ARN"; // pragma: allowlist secret
await expect(handler({ test: "test" }, {} as Context, callback)).rejects.toThrowError(
"Failed to fetch secret bad-ARN: Secret bad-ARN has no SecretString field present in response",
);
});

it("should return the request if it is missing an identity", async () => {
const consoleSpy = vi.spyOn(console, "log");
const missingIdentity = await handler(
{
request: {
userAttributes: TEST_IDM_USERS.testStateIDMUserMissingIdentity,
},
},
{} as Context,
callback,
);
expect(consoleSpy).toBeCalledWith("User is not managed externally. Nothing to do.");
expect(missingIdentity).toStrictEqual({
request: {
userAttributes: TEST_IDM_USERS.testStateIDMUserMissingIdentity,
},
});
});
it("should log an error since it cannot authorize the user", async () => {
const errorSpy = vi.spyOn(console, "error");
const missingIdentity = await handler(
{
request: {
userAttributes: TEST_IDM_USERS.testStateIDMUser,
},
},
{} as Context,
callback,
);
const error = new Error("Network response was not ok. Response was 401: Unauthorized");
expect(errorSpy).toHaveBeenCalledWith("Error performing post auth:", error);
expect(errorSpy).toBeCalledTimes(1);
expect(missingIdentity).toStrictEqual({
request: {
userAttributes: TEST_IDM_USERS.testStateIDMUser,
},
});
});
it("should return the user and update the user in the service", async () => {
const consoleSpy = vi.spyOn(console, "log");
const validUser = await handler(
{
request: {
userAttributes: TEST_IDM_USERS.testStateIDMUserGood,
},
userName: superUser.Username,
userPoolId: USER_POOL_ID,
},
{} as Context,
callback,
);
expect(consoleSpy).toBeCalledWith(
`Attributes for user ${superUser.Username} updated successfully.`,
);
expect(validUser).toStrictEqual({
request: {
userAttributes: TEST_IDM_USERS.testStateIDMUserGood,
},
userName: superUser.Username,
userPoolId: USER_POOL_ID,
});
});
});
54 changes: 17 additions & 37 deletions lib/lambda/postAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,16 @@ import {
import { getSecret } from "shared-utils";

// Initialize Cognito client
const client = new CognitoIdentityProviderClient({});

const client = new CognitoIdentityProviderClient({
region: process.env.region || process.env.REGION_A || "us-east-1",
});
export const handler: Handler = async (event) => {
console.log(JSON.stringify(event, null, 2));

// Check if idmInfoSecretArn is provided
if (!process.env.idmAuthzApiKeyArn) {
throw "ERROR: process.env.idmAuthzApiKeyArn is required";
}
if (!process.env.idmAuthzApiEndpoint) {
throw "ERROR: process.env.idmAuthzApiKeyArn is required";
throw "ERROR: process.env.idmAuthzApiEndpoint is required";
}

const apiEndpoint: string = process.env.idmAuthzApiEndpoint;
Expand All @@ -32,26 +31,21 @@ export const handler: Handler = async (event) => {

const { request } = event;
const { userAttributes } = request;

if (!userAttributes.identities) {
console.log("User is not managed externally. Nothing to do.");
} else {
console.log("Getting user attributes from external API");

try {
const username = userAttributes["custom:username"]; // This is the four-letter IDM username
const response = await fetch(
`${apiEndpoint}/api/v1/authz/id/all?userId=${username}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
const response = await fetch(`${apiEndpoint}/api/v1/authz/id/all?userId=${username}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
);
});
if (!response.ok) {
console.log(response);
throw new Error(
`Network response was not ok. Response was ${response.status}: ${response.statusText}`,
);
Expand Down Expand Up @@ -108,22 +102,14 @@ async function updateUserAttributes(params: any): Promise<void> {
const user = await client.send(getUserCommand);

// Check for existing "custom:cms-roles"
const cmsRolesAttribute = user.UserAttributes?.find(
(attr) => attr.Name === "custom:cms-roles",
);
const cmsRolesAttribute = user.UserAttributes?.find((attr) => attr.Name === "custom:cms-roles");
const existingRoles =
cmsRolesAttribute && cmsRolesAttribute.Value
? cmsRolesAttribute.Value.split(",")
: [];
cmsRolesAttribute && cmsRolesAttribute.Value ? cmsRolesAttribute.Value.split(",") : [];

// Check for existing "custom:state"
const stateAttribute = user.UserAttributes?.find(
(attr) => attr.Name === "custom:state",
);
const stateAttribute = user.UserAttributes?.find((attr) => attr.Name === "custom:state");
const existingStates =
stateAttribute && stateAttribute.Value
? stateAttribute.Value.split(",")
: [];
stateAttribute && stateAttribute.Value ? stateAttribute.Value.split(",") : [];

// Prepare for updating user attributes
const attributeData: any = {
Expand All @@ -146,8 +132,7 @@ async function updateUserAttributes(params: any): Promise<void> {
),
)
: new Set(["onemac-micro-super"]); // Ensure "onemac-micro-super" is always included
attributeData.UserAttributes[rolesIndex].Value =
Array.from(newRoles).join(",");
attributeData.UserAttributes[rolesIndex].Value = Array.from(newRoles).join(",");
} else {
// Add "custom:cms-roles" with "onemac-micro-super"
attributeData.UserAttributes.push({
Expand All @@ -165,14 +150,9 @@ async function updateUserAttributes(params: any): Promise<void> {
if (stateIndex !== -1) {
// Only merge if new states are not empty
const newStates = attributeData.UserAttributes[stateIndex].Value
? new Set(
attributeData.UserAttributes[stateIndex].Value.split(",").concat(
"ZZ",
),
)
? new Set(attributeData.UserAttributes[stateIndex].Value.split(",").concat("ZZ"))
: new Set(["ZZ"]); // Ensure "ZZ" is always included
attributeData.UserAttributes[stateIndex].Value =
Array.from(newStates).join(",");
attributeData.UserAttributes[stateIndex].Value = Array.from(newStates).join(",");
} else {
// Add "custom:state" with "ZZ"
attributeData.UserAttributes.push({
Expand Down
2 changes: 2 additions & 0 deletions lib/vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ beforeEach(() => {
process.env.DLQ_URL = "https://sqs.us-east-1.amazonaws.com/123/test";
process.env.configurationSetName = "SES";
process.env.brokerString = KAFKA_BROKERS;
process.env.idmAuthzApiKeyArn = "test-secret"; // pragma: allowlist secret
process.env.idmAuthzApiEndpoint = "https://dimAuthzEndpoint.com";
});

afterEach(() => {
Expand Down
40 changes: 40 additions & 0 deletions mocks/data/users/idmUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
export const testStateIDMUserMissingIdentity = {
sub: "0000aaaa-0000-00aa-0a0a-aaaaaa000000",
"custom:cms-roles": "onemac-micro-statesubmitter",
"custom:state": "VA,OH,SC,CO,GA,MD",
email_verified: true,
given_name: "State",
family_name: "Person",
username: "abcd",
email: "[email protected]",
};

export const testStateIDMUser = {
sub: "0000aaaa-0000-00aa-0a0a-aaaaaa000000",
"custom:cms-roles": "onemac-micro-statesubmitter",
"custom:state": "VA,OH,SC,CO,GA,MD",
email_verified: true,
given_name: "State",
family_name: "Person",
"custom:username": "fail",
email: "[email protected]",
identities:
'[{"dateCreated":"1709308952587","userId":"abc123","providerName":"IDM","providerType":"OIDC","issuer":null,"primary":"true"}]',
};
export const testStateIDMUserGood = {
sub: "0000aaaa-0000-00aa-0a0a-aaaaaa000000",
"custom:cms-roles": "onemac-micro-super",
"custom:state": "VA,OH,SC,CO,GA,MD",
email_verified: true,
given_name: "State",
family_name: "Person",
"custom:username": "abcd",
email: "[email protected]",
identities:
'[{"dateCreated":"1709308952587","userId":"abc123","providerName":"IDM","providerType":"OIDC","issuer":null,"primary":"true"}]',
};
export const TEST_IDM_USERS = {
testStateIDMUser,
testStateIDMUserGood,
testStateIDMUserMissingIdentity,
};
1 change: 1 addition & 0 deletions mocks/data/users/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,4 @@ export * from "./helpDeskUsers";
export * from "./mockStorage";
export * from "./readOnlyCMSUsers";
export * from "./stateSubmitters";
export * from "./idmUsers";
34 changes: 34 additions & 0 deletions mocks/data/users/stateSubmitters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,39 @@ export const makoStateSubmitter: TestUserData = {
],
Username: "cd400c39-9e7c-4341-b62f-234e2ecb339d",
};
export const superUser: TestUserData = {
UserAttributes: [
{
Name: "email",
Value: "[email protected]",
},
{
Name: "email_verified",
Value: "true",
},
{
Name: "given_name",
Value: "Stateuser",
},
{
Name: "family_name",
Value: "Tester",
},
{
Name: "custom:state",
Value: "ZZ",
},
{
Name: "custom:cms-roles",
Value: "onemac-micro-super",
},
{
Name: "sub",
Value: "cd400c39-9e7c-4341-b62f-234e2ecb339e",
},
],
Username: "cd400c39-9e7c-4341-b62f-234e2ecb339e",
};

export const stateSubmitter: TestUserData = {
UserAttributes: [
Expand Down Expand Up @@ -275,6 +308,7 @@ export const testNewStateSubmitter: TestUserData = {

export const stateSubmitters: TestUserData[] = [
makoStateSubmitter,
superUser,
stateSubmitter,
noDataStateSubmitter,
coStateSubmitter,
Expand Down
4 changes: 3 additions & 1 deletion mocks/handlers/aws/cognito.ts
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,9 @@ export const identityProviderServiceHandler = http.post<
);
return passthrough();
}

if (target == "AWSCognitoIdentityProviderService.AdminUpdateUserAttributes") {
return new HttpResponse(null, { status: 200 });
}
if (target == "AWSCognitoIdentityProviderService.GetUser") {
const { AccessToken } = (await request.json()) as IdpRequestSessionBody;
const username = getUsernameFromAccessToken(AccessToken) || process.env.MOCK_USER_USERNAME;
Expand Down
30 changes: 30 additions & 0 deletions mocks/handlers/aws/idm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { http, HttpResponse } from "msw";

const defaultIDMHandler = http.get(
"https://dimauthzendpoint.com/api/v1/authz/id/all",
async ({ request }) => {
const url = new URL(request.url);
const id = url.searchParams.get("userId");

if (id === "fail") {
return HttpResponse.json({ text: "Failed to retrieve user" }, { status: 401 });
} else if (id === "abcd") {
return HttpResponse.json(
{
userProfileAppRoles: {
userRolesInfoList: [
{
roleName: "onemac-micro-statesubmitter",
roleAttributes: [{ name: "State/Territory", value: "VA" }],
},
],
},
},
{ status: 200 },
);
}
return HttpResponse.json({ text: "Failed to retrieve user" }, { status: 200 });
},
);

export const idmHandlers = [defaultIDMHandler];
2 changes: 2 additions & 0 deletions mocks/handlers/aws/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { lambdaHandlers } from "./lambda";
import { secretsManagerHandlers } from "./secretsManager";
import { stepFunctionHandlers } from "./stepFunctions";
import { emailHandlers } from "./email";
import { idmHandlers } from "./idm";
export const awsHandlers = [
...cloudFormationHandlers,
...cognitoHandlers,
Expand All @@ -13,6 +14,7 @@ export const awsHandlers = [
...secretsManagerHandlers,
...stepFunctionHandlers,
...emailHandlers,
...idmHandlers,
];

export { errorCloudFormationHandler } from "./cloudFormation";
Expand Down

0 comments on commit db402ba

Please sign in to comment.