All files / lib/lambda/user-management submitRoleRequests.ts

95.65% Statements 22/23
82.85% Branches 29/35
100% Functions 1/1
95.65% Lines 22/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                        1x               1x           6x                   6x   6x       6x         6x 2x 4x             1x 3x   2x   1x   1x           5x 5x 5x   5x   5x                                 5x           1x                         5x                              
import { createError } from "@middy/util";
import { APIGatewayEvent } from "aws-lambda";
import { produceMessage } from "libs/api/kafka";
import { baseRoleInformationSchema } from "shared-types/events/legacy-user";
import { canRequestAccess, canSelfRevokeAccess, canUpdateAccess } from "shared-utils";
import { z } from "zod";
 
import { authenticatedMiddy, ContextWithAuthenticatedUser } from "../middleware";
import { getUserByEmail } from "./userManagementService";
 
type RoleStatus = "active" | "denied" | "pending" | "revoked";
 
export const submitRoleRequestEventSchema = z
  .object({
    body: baseRoleInformationSchema,
  })
  .passthrough();
 
export type SubmitRoleRequestEvent = APIGatewayEvent & z.infer<typeof submitRoleRequestEventSchema>;
 
export const handler = authenticatedMiddy({
  opensearch: true,
  kafka: true,
  setToContext: true,
  eventSchema: submitRoleRequestEventSchema,
}).handler(async (event: SubmitRoleRequestEvent, context: ContextWithAuthenticatedUser) => {
  const { authenticatedUser } = context;
  const {
    email,
    state,
    role: roleToUpdate,
    eventType,
    grantAccess = "pending",
    requestRoleChange,
    group = null,
    division = null,
  } = event.body;
 
  Iif (!authenticatedUser?.email) {
    throw new Error("Email is undefined");
  }
 
  const userInfo = await getUserByEmail(authenticatedUser.email);
 
  let status: RoleStatus;
  // Determine the status based on the user's role and action
  // Not a role request change; user is updating another role access request
  if (!requestRoleChange && canUpdateAccess(authenticatedUser.role, roleToUpdate)) {
    status = grantAccess;
  } else if (
    !requestRoleChange &&
    grantAccess === "revoked" &&
    userInfo?.email &&
    canSelfRevokeAccess(authenticatedUser.role, userInfo.email, email)
  ) {
    // Not a role request change; user is revoking their own access
    status = "revoked";
  } else if (requestRoleChange && canRequestAccess(authenticatedUser.role)) {
    // User is permitted to request a role change
    status = "pending";
  } else {
    console.warn(`Unauthorized action attempt by ${userInfo?.email}`);
 
    throw createError(
      403,
      JSON.stringify({ message: "You are not authorized to perform this action." }),
    );
  }
 
  const id = `${email}_${state}_${roleToUpdate}`;
  const date = Date.now(); // correct time format?
  const doneByEmail = userInfo?.email || authenticatedUser.email;
  const doneByName =
    userInfo?.fullName || `${authenticatedUser.given_name} ${authenticatedUser.family_name}`; // full name of current user. Cognito (userAttributes) may have a different full name
 
  await produceMessage(
    process?.env?.topicName || "",
    id,
    JSON.stringify({
      eventType,
      email,
      status,
      territory: state,
      role: roleToUpdate, // role for this state or newly requested role
      doneByEmail,
      doneByName,
      date,
      group,
      division,
    }),
  );
 
  if (
    canUpdateAccess(authenticatedUser.role, roleToUpdate) &&
    grantAccess === "active" &&
    group &&
    division
  ) {
    await produceMessage(
      process.env.topicName || "",
      userInfo?.id || id,
      JSON.stringify({
        eventType: "user-info",
        email: doneByEmail,
        group,
        division,
        fullName: doneByName,
      }),
    );
  }
 
  return {
    statusCode: 200,
    body: {
      message: `Request to access ${state} has been submitted.`,
      eventType,
      email,
      status,
      territory: state,
      role: roleToUpdate, // role for this state
      doneByEmail,
      doneByName,
      date,
    },
  };
});