All files / lib/lambda getAttachmentUrl.ts

97.29% Statements 36/37
81.81% Branches 18/22
100% Functions 5/5
97.29% Lines 36/37

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 128 129 130 131 132 133 134 135 136 137 138 139 140                        1x 8x 8x   1x           7x 1x           6x 6x   6x 5x 1x           4x 4x 4x       4x 1x                 3x 3x 3x 2x     3x 1x                 2x   1x         2x         2x 2x     2x               2x   2x 1x       1x                                       2x     1x             1x       1x    
import { handleOpensearchError } from "./utils";
import { response } from "libs/handler-lib";
import { APIGatewayEvent } from "aws-lambda";
import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts";
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
 
import { getStateFilter } from "../libs/api/auth/user";
import { getPackage, getPackageChangelog } from "../libs/api/package";
import { getDomain } from "libs/utils";
 
// Handler function to get Seatool data
export const handler = async (event: APIGatewayEvent) => {
  try {
    getDomain();
  } catch (error) {
    return response({
      statusCode: 500,
      body: { message: `ERROR: ${error?.message || error}` },
    });
  }
 
  if (!event.body) {
    return response({
      statusCode: 400,
      body: { message: "Event body required" },
    });
  }
 
  try {
    const body = JSON.parse(event.body);
 
    const mainResult = await getPackage(body.id);
    if (!mainResult || !mainResult.found) {
      return response({
        statusCode: 404,
        body: { message: "No record found for the given id" },
      });
    }
 
    const stateFilter = await getStateFilter(event);
    Eif (stateFilter) {
      const stateAccessAllowed = stateFilter?.terms.state.includes(
        mainResult?._source?.state?.toLocaleLowerCase() || "",
      );
 
      if (!stateAccessAllowed) {
        return response({
          statusCode: 403,
          body: { message: "state access not permitted for the given id" },
        });
      }
    }
 
    // add state
    // Do we want to check
    const changelogs = await getPackageChangelog(body.id);
    const attachmentExists = changelogs.hits.hits.some((CL) => {
      return CL._source.attachments?.some(
        (ATT) => ATT.bucket === body.bucket && ATT.key === body.key,
      );
    });
    if (!attachmentExists) {
      return response({
        statusCode: 500,
        body: {
          message: "Attachment details not found for given record id.",
        },
      });
    }
 
    // Now we can generate the presigned url
    const url = await generatePresignedUrl(body.bucket, body.key, body.filename, 60);
 
    return response<unknown>({
      statusCode: 200,
      body: { url },
    });
  } catch (error) {
    return response(handleOpensearchError(error));
  }
};
 
async function getClient(bucket: string) {
  if (bucket.startsWith("uploads")) {
    const stsClient = new STSClient({ region: process.env.region });
 
    // Assume the role
    const assumedRoleResponse = await stsClient.send(
      new AssumeRoleCommand({
        RoleArn: process.env.legacyS3AccessRoleArn,
        RoleSessionName: "AssumedRoleSession",
      }),
    );
 
    // Extract the assumed role credentials
    const assumedCredentials = assumedRoleResponse.Credentials;
 
    if (!assumedCredentials) {
      throw new Error("No assumed credentials");
    }
 
    // Create S3 client using the assumed role's credentials
    return new S3Client({
      credentials: {
        accessKeyId: assumedCredentials.AccessKeyId as string,
        secretAccessKey: assumedCredentials.SecretAccessKey as string,
        sessionToken: assumedCredentials.SessionToken,
      },
    });
  } else E{
    return new S3Client({});
  }
}
 
//TODO: add check for resource before signing URL
async function generatePresignedUrl(
  bucket: string,
  key: string,
  filename: string,
  expirationInSeconds: number,
) {
  // Get an S3 client
  const client = await getClient(bucket);
 
  // Create a command to get the object (you can adjust this according to your use case)
  const getObjectCommand = new GetObjectCommand({
    Bucket: bucket,
    Key: key,
    ResponseContentDisposition: `filename ="${filename}"`,
  });
 
  // Generate a presigned URL
  const presignedUrl = await getSignedUrl(client, getObjectCommand, {
    expiresIn: expirationInSeconds,
  });
 
  return presignedUrl;
}