All files / lib/lambda/middleware isAuthenticated.ts

79.31% Statements 23/29
100% Branches 12/12
100% Functions 2/2
79.31% Lines 23/29

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                                3x                   3x 11x   11x     15x 15x 12x 2x     5x 5x   10x     10x 10x             10x   1x   9x     9x 9x               9x           9x 8x 8x                 9x        
import { MiddlewareObj, Request } from "@middy/core";
import { createError } from "@middy/util";
import { getAuthDetails, lookupUserAttributes } from "libs/api/auth/user";
import { FullUser } from "shared-types";
import { isCmsUser } from "shared-utils";
 
import {
  getActiveStatesForUserByEmail,
  getLatestActiveRoleByEmail,
} from "../user-management/userManagementService";
import { storeAuthUserInRequest } from "./utils";
 
export type IsAuthenticatedOptions = {
  setToContext?: boolean;
};
 
const defaults: IsAuthenticatedOptions = {
  setToContext: false,
};
 
/**
 * Authenticates the user and stores their data in internal storage.
 * @param {object} opts Options for running the middleware
 * @param {boolean} opts.setToContext [false] if true, also stores the package in context, so it can be accessed in the handler
 * @returns {MiddleObj} middleware to authenticate the user before the handler runs
 */
export const isAuthenticated = (opts: IsAuthenticatedOptions = {}): MiddlewareObj => {
  const options = { ...defaults, ...opts };
 
  return {
    before: async (request: Request) => {
      let authDetails;
      try {
        authDetails = getAuthDetails(request.event);
        if (!authDetails || !authDetails.userId || !authDetails.poolId) {
          throw new Error("No user or pool id");
        }
      } catch (err) {
        console.error(err);
        throw createError(401, JSON.stringify({ message: "User is not authenticated" }));
      }
      const { userId, poolId } = authDetails;
 
      let userAttributes;
      try {
        userAttributes = await lookupUserAttributes(userId, poolId);
      } catch (err) {
        console.error(err);
        throw createError(500, JSON.stringify({ message: "Internal server error" }), {
          expose: true,
        });
      }
      if (!userAttributes?.email) {
        // if you don't use the expose option here, you won't be able to see the error message
        throw createError(500, JSON.stringify({ message: "User is invalid" }), { expose: true });
      }
      const { email } = userAttributes;
 
      let latestActiveRole;
      try {
        latestActiveRole = await getLatestActiveRoleByEmail(email);
      } catch (err) {
        console.error(err);
        throw createError(500, JSON.stringify({ message: "Internal server error" }), {
          expose: true,
        });
      }
 
      const user: FullUser = {
        ...userAttributes,
        role: latestActiveRole?.role ?? "norole",
        states: [],
      };
 
      if (!isCmsUser(user)) {
        try {
          user.states = await getActiveStatesForUserByEmail(email, latestActiveRole?.role);
        } catch (err) {
          console.error(err);
          throw createError(500, JSON.stringify({ message: "Internal server error" }), {
            expose: true,
          });
        }
      }
 
      storeAuthUserInRequest(user, request, options.setToContext);
    },
  };
};