diff --git a/config/custom-environment-variables.js b/config/custom-environment-variables.js index b251ac00a..5bdc42062 100644 --- a/config/custom-environment-variables.js +++ b/config/custom-environment-variables.js @@ -10,6 +10,14 @@ module.exports = { __name: "PORT", __format: "number", }, + + aws: { + region: "AWS_REGION", + access_key: "AWS_ACCESS_KEY", + secret_key: "AWS_SECRET_KEY", + identity_store_id: "IDENTITY_STORE_ID", + }, + enableFileLogs: { __name: "ENABLE_FILE_LOGS", __format: "boolean", diff --git a/config/default.js b/config/default.js index 6e5f9cee9..8ad960566 100644 --- a/config/default.js +++ b/config/default.js @@ -20,6 +20,13 @@ module.exports = { org: "Real-Dev-Squad", }, + aws: { + region: "", + access_key: "", + secret_key: "", + identity_store_id: "", + }, + githubOauth: { clientId: "", clientSecret: "", diff --git a/constants/logs.ts b/constants/logs.ts index 2c3c19cec..6c7742a95 100644 --- a/constants/logs.ts +++ b/constants/logs.ts @@ -13,6 +13,7 @@ export const logType = { EXTENSION_REQUESTS: "extensionRequests", TASK: "task", TASK_REQUESTS: "taskRequests", + USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED", ...REQUEST_LOG_TYPE, }; diff --git a/constants/urls.ts b/constants/urls.ts index fe31dc3a5..2943a1463 100644 --- a/constants/urls.ts +++ b/constants/urls.ts @@ -1,5 +1,7 @@ export const GITHUB_URL = "https://github.com"; +export const PROFILE_SVC_GITHUB_URL = "https://github.com/Real-Dev-Squad/sample-profile-service"; module.exports = { GITHUB_URL, + PROFILE_SVC_GITHUB_URL, }; diff --git a/constants/userDataLevels.ts b/constants/userDataLevels.ts index a907e265a..88c792dec 100644 --- a/constants/userDataLevels.ts +++ b/constants/userDataLevels.ts @@ -7,7 +7,7 @@ const ACCESS_LEVEL = { const ROLE_LEVEL = { private: ["super_user"], - internal: ["super_user"], + internal: ["super_user", "cloudfare_worker"], confidential: ["super_user"], }; diff --git a/controllers/arts.js b/controllers/arts.js index b259d21dc..0b2fede85 100644 --- a/controllers/arts.js +++ b/controllers/arts.js @@ -50,6 +50,10 @@ const getSelfArts = async (req, res) => { try { const { id } = req.userData; const arts = await artsQuery.fetchUserArts(id); + res.set( + "X-Deprecation-Warning", + "WARNING: This endpoint is deprecated and will be removed in the future. Please use /arts/:userId to get the art details." + ); return res.json({ message: "User arts returned successfully!", arts, @@ -64,6 +68,11 @@ const getUserArts = async (req, res) => { try { const userId = req.params.userId; const arts = await artsQuery.fetchUserArts(userId); + + if (!arts || arts.length === 0) { + return res.status(204).send(); + } + return res.json({ message: `User Arts of userId ${userId} returned successfully`, arts, diff --git a/controllers/awsAccess.ts b/controllers/awsAccess.ts new file mode 100644 index 000000000..779a4b8b7 --- /dev/null +++ b/controllers/awsAccess.ts @@ -0,0 +1,46 @@ +import { PROFILE_SVC_GITHUB_URL } from "../constants/urls"; +import {addUserToGroup, createUser, fetchAwsUserIdByUsername} from "../utils/awsFunctions"; +const dataAccess = require("../services/dataAccessLayer"); +const userDataLevels = require('../constants/userDataLevels'); + +export const addUserToAWSGroup = async (req, res) => { + const { groupId, userId } = req.body; + + try { + const userInfoData = await dataAccess.retrieveUsers({ discordId: userId, level: userDataLevels.ACCESS_LEVEL.INTERNAL, role: 'cloudfare_worker'}); + if (!userInfoData.userExists) { + return res.status(400).json({ error: "User not found" }); + } else if(!userInfoData.user.email) { + return res.status(400).json({ error: `User email is required to create an AWS user. Please update your email by setting up Profile service, url : ${PROFILE_SVC_GITHUB_URL}` }); + } + + let awsUserId = await fetchAwsUserIdByUsername(userInfoData.user.username); + + let userCreationResponse = null; + + if (awsUserId === null){ + // We need to create the user in AWS before and then fetch its Id + userCreationResponse = await createUser(userInfoData.user.username, userInfoData.user.email); + awsUserId = userCreationResponse.UserId; + } + + let userAdditionResponse = await addUserToGroup(groupId, awsUserId) + + if (userAdditionResponse){ + if (userAdditionResponse.conflict){ + return res.status(200).json({ + message: `User ${userId} is already part of the AWS group, please try signing in.` + }) + } else { + return res.status(200).json({ + message: `User ${userId} successfully added to group ${groupId}.` + }); + } + } + } catch (error) { + logger.error(`Error in adding user - ${userId} to AWS group - ${groupId} error - ${error}`); + return res.status(500).json({ + error: `Something went wrong, please try again` + }); + } +}; diff --git a/controllers/discordactions.js b/controllers/discordactions.js index fdca8f83b..7c71c885e 100644 --- a/controllers/discordactions.js +++ b/controllers/discordactions.js @@ -6,6 +6,7 @@ const discordRolesModel = require("../models/discordactions"); const discordServices = require("../services/discordService"); const { fetchAllUsers, fetchUser } = require("../models/users"); const { generateCloudFlareHeaders } = require("../utils/discord-actions"); +const { addLog } = require("../models/logs"); const discordDeveloperRoleId = config.get("discordDeveloperRoleId"); const discordMavenRoleId = config.get("discordMavenRoleId"); @@ -63,6 +64,60 @@ const createGroupRole = async (req, res) => { } }; +/** + * Controller function to handle the soft deletion of a group role. + * + * @param {Object} req - The request object + * @param {Object} res - The response object + * @returns {Promise} + */ +const deleteGroupRole = async (req, res) => { + const { groupId } = req.params; + + try { + const { roleExists, existingRoles } = await discordRolesModel.isGroupRoleExists({ groupId }); + + if (!roleExists) { + return res.boom.notFound("Group role not found"); + } + + const roleData = existingRoles.data(); + + const discordDeletion = await discordServices.deleteGroupRoleFromDiscord(roleData.roleid); + + if (!discordDeletion.success) { + return res.boom.badImplementation(discordDeletion.message); + } + + const { isSuccess } = await discordRolesModel.deleteGroupRole(groupId, req.userData.id); + + if (!isSuccess) { + logger.error(`Role deleted from Discord but failed to delete from database for groupId: ${groupId}`); + return res.boom.badImplementation("Group role deletion failed"); + } + + const groupDeletionLog = { + type: "group-role-deletion", + meta: { + userId: req.userData.id, + }, + body: { + groupId: groupId, + roleName: roleData.rolename, + discordRoleId: roleData.roleid, + action: "delete", + }, + }; + await addLog(groupDeletionLog.type, groupDeletionLog.meta, groupDeletionLog.body); + return res.status(200).json({ + message: "Group role deleted successfully", + }); + } catch (error) { + logger.error(`Error while deleting group role: ${error}`); + return res.boom.badImplementation("Internal server error"); + } +}; + /** * Gets all group-roles * @@ -491,4 +546,5 @@ module.exports = { setRoleToUsersWith31DaysPlusOnboarding, getUserDiscordInvite, generateInviteForUser, + deleteGroupRole, }; diff --git a/models/discordactions.js b/models/discordactions.js index 6576a9b6f..97115e952 100644 --- a/models/discordactions.js +++ b/models/discordactions.js @@ -46,6 +46,31 @@ const createNewRole = async (roleData) => { } }; +/** + * Soft deletes a group role by marking it as deleted in the database. + * This function updates the role document in Firestore, setting isDeleted to true + * and recording who deleted it and when. + * + * @param {string} groupId - The ID of the group role to be deleted + * @param {string} deletedBy - The ID of the user performing the deletion for logging purpose + * @returns {Promise} An object indicating whether the operation was successful + */ +const deleteGroupRole = async (groupId, deletedBy) => { + try { + const roleRef = admin.firestore().collection("discord-roles").doc(groupId); + await roleRef.update({ + isDeleted: true, + deletedAt: admin.firestore.Timestamp.fromDate(new Date()), + deletedBy: deletedBy, + }); + + return { isSuccess: true }; + } catch (error) { + logger.error(`Error in deleteGroupRole: ${error}`); + return { isSuccess: false }; + } +}; + const removeMemberGroup = async (roleId, discordId) => { try { const backendResponse = await deleteRoleFromDatabase(roleId, discordId); @@ -139,10 +164,13 @@ const updateGroupRole = async (roleData, docId) => { const isGroupRoleExists = async (options = {}) => { try { - const { rolename = null, roleid = null } = options; + const { groupId = null, rolename = null, roleid = null } = options; let existingRoles; - if (rolename && roleid) { + if (groupId) { + existingRoles = await discordRoleModel.doc(groupId).get(); + return { roleExists: existingRoles.exists, existingRoles }; + } else if (rolename && roleid) { existingRoles = await discordRoleModel .where("rolename", "==", rolename) .where("roleid", "==", roleid) @@ -153,9 +181,8 @@ const isGroupRoleExists = async (options = {}) => { } else if (roleid) { existingRoles = await discordRoleModel.where("roleid", "==", roleid).limit(1).get(); } else { - throw Error("Either rolename or roleId is required"); + throw Error("Either rolename, roleId, or groupId is required"); } - return { roleExists: !existingRoles.empty, existingRoles }; } catch (err) { logger.error("Error in getting all group-roles", err); @@ -1075,4 +1102,5 @@ module.exports = { getUserDiscordInvite, addInviteToInviteModel, groupUpdateLastJoinDate, + deleteGroupRole, }; diff --git a/models/userStatus.js b/models/userStatus.js index 4f7288d7c..0a250a814 100644 --- a/models/userStatus.js +++ b/models/userStatus.js @@ -23,6 +23,7 @@ const { userState } = require("../constants/userStatus"); const discordRoleModel = firestore.collection("discord-roles"); const memberRoleModel = firestore.collection("member-group-roles"); const usersCollection = firestore.collection("users"); +const config = require("config"); const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); const { generateAuthTokenForCloudflare } = require("../utils/discord-actions"); diff --git a/models/users.js b/models/users.js index 5d9f84fd2..e0746de92 100644 --- a/models/users.js +++ b/models/users.js @@ -24,6 +24,8 @@ const admin = require("firebase-admin"); const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages"); const { AUTHORITIES } = require("../constants/authorities"); const { formatUsername } = require("../utils/username"); +const { logType } = require("../constants/logs"); +const { addLog } = require("../services/logService"); /** * Adds or updates the user data @@ -48,6 +50,13 @@ const addOrUpdate = async (userData, userId = null) => { }, { merge: true } ); + + const logData = { + type: logType.USER_DETAILS_UPDATED, + meta: { userId: userId }, + body: userData, + }; + await addLog(logData.type, logData.meta, logData.body); } return { isNewUser, userId }; @@ -63,6 +72,14 @@ const addOrUpdate = async (userData, userId = null) => { } if (user && !user.empty && user.docs !== null) { await userModel.doc(user.docs[0].id).set({ ...userData, updated_at: Date.now() }, { merge: true }); + + const logData = { + type: logType.USER_DETAILS_UPDATED, + meta: { userId: user.docs[0].id }, + body: userData, + }; + await addLog(logData.type, logData.meta, logData.body); + const data = user.docs[0].data(); return { isNewUser: false, diff --git a/package.json b/package.json index b01a32092..a20c360cd 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "tdd:watch": "sh scripts/tests/tdd.sh" }, "dependencies": { + "@aws-sdk/client-identitystore": "^3.665.0", "@types/nodemailer": "^6.4.15", "axios": "1.7.2", "cloudinary": "2.0.3", diff --git a/routes/arts.ts b/routes/arts.ts index 3d0b08eb9..07d71e098 100644 --- a/routes/arts.ts +++ b/routes/arts.ts @@ -3,10 +3,12 @@ const router = express.Router(); import authenticate from "../middlewares/authenticate"; import arts from "../controllers/arts"; import artValidator from "../middlewares/validators/arts"; +import { devFlagMiddleware } from "../middlewares/devFlag"; router.get("/", arts.fetchArts); -router.get("/user/self", authenticate, arts.getSelfArts); -router.get("/user/:userId", authenticate, arts.getUserArts); +router.get("/user/self", authenticate, arts.getSelfArts); // this route is soon going to be deprecated soon, please use /arts/:userId endpoint. +router.get("/user/:userId", authenticate, arts.getUserArts); // this route is soon going to be deprecated soon, please use /arts/:userId endpoint. +router.get("/:userId", devFlagMiddleware, authenticate, arts.getUserArts); router.post("/user/add", authenticate, artValidator.createArt, arts.addArt); module.exports = router; diff --git a/routes/awsAccess.ts b/routes/awsAccess.ts new file mode 100644 index 000000000..be4332aa4 --- /dev/null +++ b/routes/awsAccess.ts @@ -0,0 +1,8 @@ +import express from "express" +import { addUserToAWSGroup } from "../controllers/awsAccess"; +const router = express.Router(); +const { verifyDiscordBot } = require("../middlewares/authorizeBot"); + +router.post("/access", verifyDiscordBot, addUserToAWSGroup); + +module.exports = router; \ No newline at end of file diff --git a/routes/discordactions.js b/routes/discordactions.js index 745a306df..1d7621787 100644 --- a/routes/discordactions.js +++ b/routes/discordactions.js @@ -15,6 +15,7 @@ const { updateUsersNicknameStatus, syncDiscordGroupRolesInFirestore, setRoleToUsersWith31DaysPlusOnboarding, + deleteGroupRole, } = require("../controllers/discordactions"); const { validateGroupRoleBody, @@ -29,11 +30,19 @@ const ROLES = require("../constants/roles"); const { Services } = require("../constants/bot"); const { verifyCronJob } = require("../middlewares/authorizeBot"); const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndService"); - +const { devFlagMiddleware } = require("../middlewares/devFlag"); const router = express.Router(); router.post("/groups", authenticate, checkIsVerifiedDiscord, validateGroupRoleBody, createGroupRole); router.get("/groups", authenticate, checkIsVerifiedDiscord, getAllGroupRoles); +router.delete( + "/groups/:groupId", + authenticate, + checkIsVerifiedDiscord, + authorizeRoles([SUPERUSER]), + devFlagMiddleware, + deleteGroupRole +); router.post("/roles", authenticate, checkIsVerifiedDiscord, validateMemberRoleBody, addGroupRoleToMember); router.get("/invite", authenticate, getUserDiscordInvite); router.post("/invite", authenticate, checkCanGenerateDiscordLink, generateInviteForUser); diff --git a/routes/index.ts b/routes/index.ts index 8cd97bd3e..985d1f424 100644 --- a/routes/index.ts +++ b/routes/index.ts @@ -2,6 +2,7 @@ import express from "express"; const app = express.Router(); import { devFlagMiddleware } from "../middlewares/devFlag"; +app.use("/aws/groups", devFlagMiddleware, require("./awsAccess")) app.use("/answers", require("./answers")); app.use("/auctions", require("./auctions")); app.use("/arts", require("./arts")); diff --git a/services/discordMembersService.js b/services/discordMembersService.js index 1169e2434..4ed1b14a1 100644 --- a/services/discordMembersService.js +++ b/services/discordMembersService.js @@ -1,5 +1,5 @@ const jwt = require("jsonwebtoken"); - +const config = require("config"); const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); /** diff --git a/services/discordService.js b/services/discordService.js index bb3517609..1244fe9d3 100644 --- a/services/discordService.js +++ b/services/discordService.js @@ -103,7 +103,40 @@ const setUserDiscordNickname = async (userName, discordId) => { }; } catch (err) { logger.error("Error in updating discord Nickname", err); - throw err; + throw new Error(err); + } +}; + +/** + * Deletes a group role from the Discord server. + * This function sends a DELETE request to the Discord API to remove the role. + * It's part of the soft delete process, where we remove the role from Discord + * but keep a record of it in our database. + * + * @param {string} roleId - The Discord ID of the role to be deleted + * @returns {Promise} The response from the Discord API + * @throws {Error} If the deletion fails or there's a network error + */ + +const deleteGroupRoleFromDiscord = async (roleId) => { + try { + const authToken = generateAuthTokenForCloudflare(); + const response = await fetch(`${DISCORD_BASE_URL}/roles/${roleId}?dev=true`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${authToken}`, + }, + }); + + if (response.status === 204) { + return { success: true, message: "Role deleted successfully" }; + } + + return { success: false, message: "Failed to delete role from discord" }; + } catch (err) { + logger.error("Error deleting role from Discord", err); + return { success: false, message: "Internal server error" }; } }; @@ -114,4 +147,5 @@ module.exports = { addRoleToUser, removeRoleFromUser, setUserDiscordNickname, + deleteGroupRoleFromDiscord, }; diff --git a/services/logService.ts b/services/logService.ts new file mode 100644 index 000000000..2af85ef62 --- /dev/null +++ b/services/logService.ts @@ -0,0 +1,35 @@ +import firestore from "../utils/firestore"; +const logsModel = firestore.collection("logs"); +import admin from "firebase-admin"; +const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages"); + +interface LogMeta { + userId?: string; + [key: string]: number | string | object; +} + +interface LogBody { + [key: string]: number | string | object; +} + +/** + * Adds log + * + * @param type { string }: Type of the log + * @param meta { LogMeta }: Meta data of the log + * @param body { LogBody }: Body of the log + */ +export const addLog = async (type: string, meta: LogMeta, body: LogBody): Promise> => { + try { + const log = { + type, + timestamp: admin.firestore.Timestamp.fromDate(new Date()), + meta, + body, + }; + return await logsModel.add(log); + } catch (err) { + logger.error("Error in adding log", err); + throw new Error(INTERNAL_SERVER_ERROR); + } +}; \ No newline at end of file diff --git a/test/config/test.js b/test/config/test.js index 688b40c26..f0e0396c9 100644 --- a/test/config/test.js +++ b/test/config/test.js @@ -23,6 +23,13 @@ module.exports = { clientId: "clientId", clientSecret: "clientSecret", }, + aws: { + region: "us-east-1", + access_key: "test-access-key", + secret_key: "test-secret-key", + identity_store_id: "test-identity-store-id", + }, + firestore: `{ "type": "service_account", "project_id": "test-project-id-for-emulator", diff --git a/test/fixtures/user/user.js b/test/fixtures/user/user.js index 485255efc..65de62f97 100644 --- a/test/fixtures/user/user.js +++ b/test/fixtures/user/user.js @@ -54,7 +54,6 @@ module.exports = () => { twitter_id: "whatifi", discordJoinedAt: "2023-04-06T01:47:34.488000+00:00", phone: "1234567891", - email: "abc1@gmail.com", picture: { publicId: "profile/mtS4DhUvNYsKqI7oCWVB/aenklfhtjldc5ytei3ar", url: "https://res.cloudinary.com/realdevsquad/image/upload/v1667685133/profile/mtS4DhUvNYsKqI7oCWVB/aenklfhtjldc5ytei3ar.jpg", diff --git a/test/integration/arts.test.js b/test/integration/arts.test.js index e8bb4f780..0721558f3 100644 --- a/test/integration/arts.test.js +++ b/test/integration/arts.test.js @@ -1,6 +1,8 @@ const chai = require("chai"); const { expect } = chai; const chaiHttp = require("chai-http"); +const sinon = require("sinon"); +const artsQuery = require("../../models/arts"); const app = require("../../server"); const authService = require("../../services/authService"); @@ -13,14 +15,17 @@ const artData = require("../fixtures/arts/arts")(); const config = require("config"); const cookieName = config.get("userToken.cookieName"); +const { addJoinData } = require("../../models/users"); +const joinData = require("../fixtures/user/join"); chai.use(chaiHttp); describe("Arts", function () { let jwt; + let userId = ""; beforeEach(async function () { - const userId = await addUser(); + userId = await addUser(); jwt = authService.generateAuthToken({ userId }); await arts.addArt(artData[0], userId); }); @@ -108,6 +113,10 @@ describe("Arts", function () { expect(res.body.arts).to.be.a("array"); expect(res.body.arts[0]).to.be.a("object"); expect(res.body.arts[0].title).to.equal(artData[0].title); + expect(res).to.have.header( + "X-Deprecation-Warning", + "WARNING: This endpoint is deprecated and will be removed in the future. Please use /arts/:userId to get the art details." + ); return done(); }); @@ -134,4 +143,96 @@ describe("Arts", function () { }); }); }); + + describe("GET /arts/:userId", function () { + beforeEach(async function () { + await addJoinData(joinData(userId)[0]); + }); + + it("Should get all the arts of the user", function (done) { + chai + .request(app) + .get(`/arts/${userId}?dev=true`) + .set("cookie", `${cookieName}=${jwt}`) + .end((err, res) => { + if (err) { + return done(err); + } + expect(res).to.have.status(200); + expect(res.body).to.be.a("object"); + expect(res.body.message).to.equal(`User Arts of userId ${userId} returned successfully`); + expect(res.body.arts).to.be.a("array"); + expect(res.body.arts[0]).to.be.a("object"); + expect(res.body.arts[0].title).to.equal(artData[0].title); + + return done(); + }); + }); + + it("Should return 401, for Unauthenticated User", function (done) { + chai + .request(app) + .get(`/arts/${userId}?dev=true`) + .end((err, res) => { + if (err) { + return done(err); + } + + expect(res).to.have.status(401); + expect(res.body).to.be.a("object"); + expect(res.body).to.deep.equal({ + statusCode: 401, + error: "Unauthorized", + message: "Unauthenticated User", + }); + + return done(); + }); + }); + + it("Should return 204 No Content if no arts are found", function (done) { + sinon.stub(artsQuery, "fetchUserArts").resolves([]); + + chai + .request(app) + .get(`/arts/${userId}?dev=true`) + .set("cookie", `${cookieName}=${jwt}`) + .end((err, res) => { + artsQuery.fetchUserArts.restore(); + + if (err) { + return done(err); + } + + expect(res).to.have.status(204); + expect(res.body).to.deep.equal({}); + return done(); + }); + }); + + it("Should return 500 Internal Server Error if there is an exception", function (done) { + sinon.stub(artsQuery, "fetchUserArts").throws(new Error("Database error")); + + chai + .request(app) + .get(`/arts/${userId}?dev=true`) + .set("cookie", `${cookieName}=${jwt}`) + .end((err, res) => { + artsQuery.fetchUserArts.restore(); + + if (err) { + return done(err); + } + + expect(res).to.have.status(500); + expect(res.body).to.deep.equal({ + statusCode: 500, + error: "Internal Server Error", + message: "An internal server error occurred", + }); + + return done(); + }); + }); + }); }); diff --git a/test/integration/awsAccess.test.ts b/test/integration/awsAccess.test.ts new file mode 100644 index 000000000..58b9f7e78 --- /dev/null +++ b/test/integration/awsAccess.test.ts @@ -0,0 +1,149 @@ +import chai, {expect} from "chai"; +import sinon from 'sinon'; +import chaiHttp from 'chai-http'; +import * as awsFunctions from '../../utils/awsFunctions'; +import bot from "../utils/generateBotToken"; +import { PROFILE_SVC_GITHUB_URL } from '../../constants/urls'; + +const app = require("../../server"); +const userData = require("../fixtures/user/user")(); +const authorizeBot = require("../../middlewares/authorizeBot"); +const addUser = require("../utils/addUser"); +const cleanDb = require("../utils/cleanDb"); +const { CLOUDFLARE_WORKER } = require("../../constants/bot") + +chai.use(chaiHttp); + +describe('addUserToAWSGroup', function(){ + let req: any; + const AWS_ACCESS_API_URL = `/aws/groups/access?dev=true` + + beforeEach(async () => { + await addUser(userData[0]); + await addUser(userData[1]); + sinon.restore(); + req = { + headers: {}, + }; + const jwtToken = bot.generateToken({ name: CLOUDFLARE_WORKER }); + req.headers.authorization = `Bearer ${jwtToken}`; + }) + + afterEach(async () => { + await cleanDb(); + }); + + it('should return 400 and user not found with wrong discord Id passed', function(done){ + const res = chai + .request(app) + .post(AWS_ACCESS_API_URL) + .set('Authorization', req.headers.authorization) + .send({ + groupId: 'test-group-id', + userId: '3000230293' + }) + .end((err, res) => { + if (err) { + return done(err); + } + expect(res.status).to.be.equal(400); + expect(res.body).to.have.property('error') + .that.equals(`User not found`); + return done(); + }) + }); + + it('should return 400 when user email is missing', function(done) { + const res = chai + .request(app) + .post(AWS_ACCESS_API_URL) + .set('Authorization', req.headers.authorization) + .send({ + groupId: 'test-group-id', + userId: '1234567890' + }) + .end((err, res) => { + if (err) { + return done(err); + } + expect(res.status).to.be.equal(400); + expect(res.body).to.have.property('error') + .that.equals(`User email is required to create an AWS user. Please update your email by setting up Profile service, url : ${PROFILE_SVC_GITHUB_URL}`); + return done(); + }); + }); + + + it("Should create user and add to group, if the user is not present in AWS already", function(done){ + sinon.stub(awsFunctions, "createUser").resolves({ UserId: "new-aws-user-id" }); + sinon.stub(awsFunctions, "addUserToGroup").resolves({ conflict: false }); + sinon.stub(awsFunctions, "fetchAwsUserIdByUsername").resolves(null); + + const res = chai + .request(app) + .post(AWS_ACCESS_API_URL) + .set('Authorization', req.headers.authorization) + .send({ + groupId: 'test-group-id', + userId: '12345' + }) + .end((err, res) => { + if (err) { + return done(err); + } + expect(res.status).to.be.equal(200); + expect(res.body).to.have.property('message', + `User 12345 successfully added to group test-group-id.` + ); + return done(); + }); + }); + + it("Should add the user to the group if the user is already part of AWS account", function(done){ + sinon.stub(awsFunctions, "addUserToGroup").resolves({ conflict: false }); + sinon.stub(awsFunctions, "fetchAwsUserIdByUsername").resolves("existing-user-id-123"); + + const res = chai + .request(app) + .post(AWS_ACCESS_API_URL) + .set('Authorization', req.headers.authorization) + .send({ + groupId: 'test-group-id', + userId: '12345' + }) + .end((err, res) => { + if (err) { + return done(err); + } + expect(res.status).to.be.equal(200) + expect(res.body).to.have.property('message', + 'User 12345 successfully added to group test-group-id.' + ); + return done(); + }); + }); + + it("Should return the signin URL if the user is already added to the group", function(done) { + sinon.stub(awsFunctions, "addUserToGroup").resolves({ conflict: true }); + sinon.stub(awsFunctions, "fetchAwsUserIdByUsername").resolves("existing-user-id-123"); + + const res = chai + .request(app) + .post(AWS_ACCESS_API_URL) + .set('Authorization', req.headers.authorization) + .send({ + groupId: 'test-group-id', + userId: '12345' + }) + .end((err, res) => { + if (err) { + return done(err); + } + expect(res.status).to.be.equal(200); + expect(res.body).to.have.property('message', + 'User 12345 is already part of the AWS group, please try signing in.' + ); + return done(); + }); + }); +}); diff --git a/test/integration/discordactions.test.js b/test/integration/discordactions.test.js index 9b361990c..d943f85e6 100644 --- a/test/integration/discordactions.test.js +++ b/test/integration/discordactions.test.js @@ -213,6 +213,182 @@ describe("Discord actions", function () { }); }); + describe("DELETE /discord-actions/groups/:groupId", function () { + let groupId; + // eslint-disable-next-line mocha/no-setup-in-describe + const roleData = groupData[0]; + + beforeEach(async function () { + const docRef = await discordRoleModel.add(roleData); + groupId = docRef.id; + + superUserId = await addUser(superUser); + superUserAuthToken = authService.generateAuthToken({ userId: superUserId }); + + sinon.stub(discordRolesModel, "deleteGroupRole").resolves({ isSuccess: true }); + }); + + afterEach(async function () { + sinon.restore(); + await cleanDb(); + }); + + it("should return 404 when not in dev mode", function (done) { + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(404); + expect(res.body.error).to.equal("Not Found"); + done(err); + }); + }); + + it("should return 404 if group role not found", function (done) { + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: false, + existingRoles: { data: () => ({ ...roleData, roleid: roleData.roleid }) }, + }); + + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(404); + expect(res.body.error).to.equal("Not Found"); + done(err); + }); + }); + + it("should successfully delete the group role from discord server", function (done) { + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { data: () => ({ ...roleData, roleid: roleData.roleid }) }, + }); + + sinon.stub(discordServices, "deleteGroupRoleFromDiscord").resolves({ + success: true, + message: "Role deleted successfully", + }); + + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(200); + expect(res.body.message).to.equal("Group role deleted successfully"); + done(err); + }); + }); + + it("should return 500 when discord role deletion fails", function (done) { + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { data: () => ({ ...roleData, roleid: roleData.roleid }) }, + }); + + sinon.stub(discordServices, "deleteGroupRoleFromDiscord").resolves({ + success: false, + message: "Failed to delete role from Discord", + }); + + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(500); + expect(res.body.error).to.equal("Internal Server Error"); + done(err); + }); + }); + + it("should return 500 when discord service throws an error", function (done) { + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { data: () => ({ ...roleData, roleid: roleData.roleid }) }, + }); + + sinon.stub(discordServices, "deleteGroupRoleFromDiscord").resolves({ + success: false, + message: "Internal server error", + }); + + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(500); + expect(res.body.error).to.equal("Internal Server Error"); + done(err); + }); + }); + + it("should successfully delete a group role from database", function (done) { + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { data: () => ({ ...roleData, roleid: roleData.roleid }) }, + }); + + sinon.stub(discordServices, "deleteGroupRoleFromDiscord").resolves({ + success: true, + message: "Role deleted successfully", + }); + + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(200); + expect(res.body.message).to.equal("Group role deleted successfully"); + done(err); + }); + }); + + it("should return 500 when deletion fails", function (done) { + sinon.restore(); + sinon.stub(discordRolesModel, "isGroupRoleExists").resolves({ + roleExists: true, + existingRoles: { data: () => ({ ...roleData, roleid: roleData.roleid }) }, + }); + + sinon.stub(discordServices, "deleteGroupRoleFromDiscord").resolves({ + success: true, + message: "Role deleted successfully", + }); + + sinon.stub(discordRolesModel, "deleteGroupRole").resolves({ isSuccess: false }); + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(500); + expect(res.body.error).to.equal("Internal Server Error"); + done(err); + }); + }); + + it("should return 500 when an internal error occurs", function (done) { + sinon.restore(); + sinon.stub(discordRolesModel, "isGroupRoleExists").throws(new Error("Database error")); + chai + .request(app) + .delete(`/discord-actions/groups/${groupId}?dev=true`) + .set("cookie", `${cookieName}=${superUserAuthToken}`) + .end((err, res) => { + expect(res).to.have.status(500); + expect(res.body.error).to.equal("Internal Server Error"); + done(err); + }); + }); + }); + describe("POST /discord-actions/roles", function () { let roleid; diff --git a/test/integration/logs.test.js b/test/integration/logs.test.js index 79f3091aa..d0e27657d 100644 --- a/test/integration/logs.test.js +++ b/test/integration/logs.test.js @@ -108,7 +108,7 @@ describe("/logs", function () { } expect(res).to.have.status(200); expect(res.body.message).to.equal("All Logs fetched successfully"); - expect(res.body.data).to.lengthOf(7); + expect(res.body.data).to.lengthOf(9); return done(); }); }); @@ -202,6 +202,37 @@ describe("/logs", function () { }); }); }); + + describe("Add logs when user doc is update", function () { + let jwt; + let userId; + + beforeEach(async function () { + userId = await addUser(); + jwt = authService.generateAuthToken({ userId }); + }); + + it("Should update the users and capture the logs", async function () { + const res = await chai.request(app).patch("/users/self").set("cookie", `${cookieName}=${jwt}`).send({ + first_name: "Test first_name", + }); + + expect(res).to.have.status(204); + + const logRes = await chai + .request(app) + .get("/logs/USER_DETAILS_UPDATED") + .set("cookie", `${cookieName}=${superUserToken}`); + + expect(logRes).to.have.status(200); + expect(logRes.body.message).to.equal("Logs fetched successfully"); + + const log = logRes.body.logs[0]; + expect(log).to.have.property("meta"); + expect(log).to.have.property("body"); + expect(log.type).to.equal("USER_DETAILS_UPDATED"); + }); + }); }); async function addLogs() { diff --git a/test/unit/middlewares/authenticate.test.js b/test/unit/middlewares/authenticate.test.js new file mode 100644 index 000000000..acb011a07 --- /dev/null +++ b/test/unit/middlewares/authenticate.test.js @@ -0,0 +1,116 @@ +const Sinon = require("sinon"); +const { expect } = require("chai"); +const authMiddleware = require("../../../middlewares/authenticate"); +const authService = require("../../../services/authService"); +const dataAccess = require("../../../services/dataAccessLayer"); +const config = require("config"); + +describe("Authentication Middleware", function () { + let req, res, nextSpy; + + beforeEach(function () { + req = { + cookies: { + [config.get("userToken.cookieName")]: "validToken", + }, + headers: {}, + }; + res = { + cookie: Sinon.spy(), + boom: { + unauthorized: Sinon.spy(), + forbidden: Sinon.spy(), + }, + }; + nextSpy = Sinon.spy(); + }); + + afterEach(function () { + Sinon.restore(); + }); + + describe("Token Verification", function () { + it("should allow unrestricted user with valid token", async function () { + const user = { id: "user123", roles: { restricted: false } }; + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").returns({ userId: user.id }); + const retrieveUsersStub = Sinon.stub(dataAccess, "retrieveUsers").resolves({ user }); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.returnValues[0]).to.deep.equal({ userId: user.id }); + expect(verifyAuthTokenStub.calledWith("validToken")).to.equal(true); + + expect(retrieveUsersStub.calledOnce).to.equal(true); + const retrievedValue = await retrieveUsersStub.returnValues[0]; + expect(retrievedValue).to.deep.equal({ user }); + + expect(nextSpy.calledOnce).to.equal(true); + expect(res.boom.unauthorized.notCalled).to.equal(true); + expect(res.boom.forbidden.notCalled).to.equal(true); + }); + + it("should deny restricted user access for non-GET requests", async function () { + req.method = "POST"; + const user = { id: "user123", roles: { restricted: true } }; + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").returns({ userId: user.id }); + const retrieveUsersStub = Sinon.stub(dataAccess, "retrieveUsers").resolves({ user }); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.returnValues[0]).to.deep.equal({ userId: user.id }); + expect(verifyAuthTokenStub.calledWith("validToken")).to.equal(true); + + expect(retrieveUsersStub.calledOnce).to.equal(true); + const retrievedValue = await retrieveUsersStub.returnValues[0]; + expect(retrievedValue).to.deep.equal({ user }); + + expect(res.boom.forbidden.calledOnce).to.equal(true); + expect(res.boom.forbidden.firstCall.args[0]).to.equal("You are restricted from performing this action"); + expect(nextSpy.notCalled).to.equal(true); + }); + + it("should deny access with invalid token", async function () { + req.cookies[config.get("userToken.cookieName")] = "invalidToken"; + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").throws(new Error("Invalid token")); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.threw()).to.equal(true); + expect(verifyAuthTokenStub.exceptions[0].message).to.equal("Invalid token"); + expect(verifyAuthTokenStub.calledWith("invalidToken")).to.equal(true); + + expect(res.boom.unauthorized.calledOnce).to.equal(true); + expect(res.boom.unauthorized.firstCall.args[0]).to.equal("Unauthenticated User"); + expect(nextSpy.notCalled).to.equal(true); + }); + }); + + describe("Error Handling", function () { + it("should deny access when token is missing in production", async function () { + const originalEnv = process.env.NODE_ENV; + process.env.NODE_ENV = "production"; + + await authMiddleware(req, res, nextSpy); + + expect(res.boom.unauthorized.calledOnce).to.equal(true); + process.env.NODE_ENV = originalEnv; + }); + + it("should handle unexpected errors gracefully", async function () { + const verifyAuthTokenStub = Sinon.stub(authService, "verifyAuthToken").throws(new Error("Unexpected error")); + + await authMiddleware(req, res, nextSpy); + + expect(verifyAuthTokenStub.calledOnce).to.equal(true); + expect(verifyAuthTokenStub.threw()).to.equal(true); + expect(verifyAuthTokenStub.exceptions[0].message).to.equal("Unexpected error"); + + expect(res.boom.unauthorized.calledOnce).to.equal(true); + expect(res.boom.unauthorized.firstCall.args[0]).to.equal("Unauthenticated User"); + expect(nextSpy.notCalled).to.equal(true); + }); + }); +}); diff --git a/test/unit/models/discordactions.test.js b/test/unit/models/discordactions.test.js index 5b773b13e..249e70601 100644 --- a/test/unit/models/discordactions.test.js +++ b/test/unit/models/discordactions.test.js @@ -23,6 +23,7 @@ const { isGroupRoleExists, addGroupRoleToMember, deleteRoleFromDatabase, + deleteGroupRole, updateDiscordImageForVerification, enrichGroupDataWithMembershipInfo, fetchGroupToUserMapping, @@ -172,7 +173,7 @@ describe("discordactions", function () { it("should throw an error if rolename and roleid are not passed", async function () { return isGroupRoleExists({}).catch((err) => { expect(err).to.be.an.instanceOf(Error); - expect(err.message).to.equal("Either rolename or roleId is required"); + expect(err.message).to.equal("Either rolename, roleId, or groupId is required"); }); }); @@ -242,6 +243,65 @@ describe("discordactions", function () { }); }); + describe("deleteGroupRole", function () { + const groupId = "1234"; + const deletedBy = "4321"; + let firestoreOriginal; + + beforeEach(async function () { + firestoreOriginal = admin.firestore; + + const roleRef = admin.firestore().collection("discord-roles").doc(groupId); + await roleRef.set({ + isDeleted: false, + }); + }); + + it("should mark the group role as deleted", async function () { + const result = await deleteGroupRole(groupId, deletedBy); + + const updatedDoc = await admin.firestore().collection("discord-roles").doc(groupId).get(); + + const data = updatedDoc.data(); + expect(data.isDeleted).to.equal(true); + expect(data.deletedBy).to.equal(deletedBy); + expect(data.deletedAt).to.be.an.instanceof(admin.firestore.Timestamp); + expect(result.isSuccess).to.equal(true); + }); + + it("should return isSuccess as false if Firestore update fails", async function () { + delete require.cache[require.resolve("firebase-admin")]; + + const mockFirestore = { + collection: () => ({ + doc: () => ({ + update: async () => { + throw new Error("Database error"); + }, + }), + }), + }; + + Object.defineProperty(admin, "firestore", { + configurable: true, + get: () => () => mockFirestore, + }); + + const result = await deleteGroupRole(groupId, deletedBy); + expect(result.isSuccess).to.equal(false); + }); + + afterEach(async function () { + Object.defineProperty(admin, "firestore", { + configurable: true, + value: firestoreOriginal, + }); + + const roleRef = admin.firestore().collection("discord-roles").doc(groupId); + await roleRef.delete(); + }); + }); + describe("deleteRoleFromMember", function () { let deleteStub; diff --git a/test/unit/services/awsAccess.test.ts b/test/unit/services/awsAccess.test.ts new file mode 100644 index 000000000..4f4a67770 --- /dev/null +++ b/test/unit/services/awsAccess.test.ts @@ -0,0 +1,126 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { + IdentitystoreClient, + ListUsersCommand, + CreateUserCommand, + CreateGroupMembershipCommand, +} from "@aws-sdk/client-identitystore"; +import { + createUser, + addUserToGroup, + fetchAwsUserIdByUsername, +} from '../../../utils/awsFunctions'; + +describe('AWS Identity Store Functions', () => { + let sendStub: sinon.SinonStub; + + beforeEach(() => { + sinon.restore(); + + // Create a stub for the send method + sendStub = sinon.stub(IdentitystoreClient.prototype, 'send'); + }); + + afterEach(() => { + sinon.restore(); + }); + + describe('fetchAwsUserIdByUsername', () => { + it('should return userId when user is found', async () => { + const mockResponse = { + Users: [{ + UserId: 'test-user-id-123', + UserName: 'testuser' + }] + }; + sendStub.resolves(mockResponse); + + const result = await fetchAwsUserIdByUsername('testuser'); + expect(result).to.equal('test-user-id-123'); + expect(sendStub.calledOnce).to.be.true; + expect(sendStub.firstCall.args[0]).to.be.instanceOf(ListUsersCommand); + }); + + it('should return null when no user is found', async () => { + const mockResponse = { Users: [] }; + sendStub.resolves(mockResponse); + + const result = await fetchAwsUserIdByUsername('nonexistentuser'); + expect(result).to.be.null; + }); + + it('should throw error when AWS call fails', async () => { + sendStub.rejects(new Error('AWS Error')); + + try { + await fetchAwsUserIdByUsername('testuser'); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Error while fetching user by username'); + } + }); + }); + describe('createUser', () => { + it('should successfully create a user', async () => { + const mockResponse = { + UserId: 'new-user-id-123', + UserName: 'newuser' + }; + sendStub.resolves(mockResponse); + + const result = await createUser('newuser', 'newuser@example.com'); + expect(result).to.deep.equal(mockResponse); + expect(sendStub.calledOnce).to.be.true; + expect(sendStub.firstCall.args[0]).to.be.instanceOf(CreateUserCommand); + }); + + it('should throw error when user creation fails', async () => { + sendStub.rejects(new Error('Creation Failed')); + + try { + await createUser('newuser', 'newuser@example.com'); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Failed to create user'); + } + }); + }); + describe('addUserToGroup', () => { + it('should successfully add user to group', async () => { + const mockResponse = { + MembershipId: 'membership-123' + }; + sendStub.resolves(mockResponse); + + const result = await addUserToGroup('group-123', 'user-123'); + expect(result).to.deep.equal(mockResponse); + expect(sendStub.calledOnce).to.be.true; + expect(sendStub.firstCall.args[0]).to.be.instanceOf(CreateGroupMembershipCommand); + }); + + it('should handle conflict exception', async () => { + const error = new Error('Conflict'); + (error as any).__type = 'ConflictException'; + sendStub.rejects(error); + + const result = await addUserToGroup('group-123', 'user-123'); + expect(result).to.deep.equal({ conflict: true }); + }); + + it('should throw error for non-conflict failures', async () => { + sendStub.rejects(new Error('Other Error')); + + try { + await addUserToGroup('group-123', 'user-123'); + expect.fail('Should have thrown an error'); + } catch (error) { + expect(error).to.be.instanceOf(Error); + expect((error as Error).message).to.include('Failed to add user to group'); + } + }); + }); +}); + diff --git a/test/unit/services/discordService.test.js b/test/unit/services/discordService.test.js index 69887c58b..5f51955a0 100644 --- a/test/unit/services/discordService.test.js +++ b/test/unit/services/discordService.test.js @@ -6,12 +6,14 @@ const { getDiscordMembers, removeRoleFromUser, setUserDiscordNickname, + deleteGroupRoleFromDiscord, } = require("../../../services/discordService"); const { fetchAllUsers } = require("../../../models/users"); const Sinon = require("sinon"); const userModel = firestore.collection("users"); const userDataArray = require("../../fixtures/user/user")(); const discordMembersArray = require("../../fixtures/discordResponse/discord-response"); +// const { func } = require("joi"); let fetchStub; describe("Discord services", function () { describe("setInDiscordFalseScript", function () { @@ -158,4 +160,55 @@ describe("Discord services", function () { }); }); }); + + describe("delete group role from Discord", function () { + beforeEach(function () { + fetchStub = Sinon.stub(global, "fetch"); + }); + + afterEach(function () { + fetchStub.restore(); + }); + + it("should successfully delete role from discord and return success for 204 response", async function () { + fetchStub.returns( + Promise.resolve({ + ok: true, + status: 204, + }) + ); + + const response = await deleteGroupRoleFromDiscord("123456789"); + expect(response).to.deep.equal({ + success: true, + message: "Role deleted successfully", + }); + expect(fetchStub.calledOnce).to.be.equal(true); + }); + + it("should return failure for non-ok response", async function () { + fetchStub.returns( + Promise.resolve({ + ok: false, + status: 400, + }) + ); + + const response = await deleteGroupRoleFromDiscord("123456789"); + expect(response).to.deep.equal({ + success: false, + message: "Failed to delete role from discord", + }); + }); + + it("should handle unexpected errors", async function () { + fetchStub.rejects(new Error("Network error")); + + const response = await deleteGroupRoleFromDiscord("123456789"); + expect(response).to.deep.equal({ + success: false, + message: "Internal server error", + }); + }); + }); }); diff --git a/test/unit/services/logService.test.ts b/test/unit/services/logService.test.ts new file mode 100644 index 000000000..d6de9c2d9 --- /dev/null +++ b/test/unit/services/logService.test.ts @@ -0,0 +1,47 @@ +import { expect } from "chai"; +const Sinon = require("sinon"); +const cleanDb = require("../../utils/cleanDb"); +const { addLog } = require("../../../services/logService"); +const { INTERNAL_SERVER_ERROR } = require("../../../constants/errorMessages"); + +describe("Logs services", function () { + beforeEach(function () { + Sinon.restore(); + }); + + afterEach(async function () { + await cleanDb(); + }); + + it("should successfully add a log", async function () { + const type = "TEST_LOG"; + const meta = { + userId: "test-user-123", + action: "test-action" + }; + const body = { + details: "test details", + status: "success" + }; + + const result = await addLog(type, meta, body); + + expect(result).to.have.property('id'); + expect(typeof result.id).to.equal('string'); + expect(result.id).to.not.be.empty; + }); + + it("should handle errors when adding log fails", async function () { + + const type = "TEST_LOG"; + const meta = { userId: "test-user-123" }; + const body = { details: "test details" }; + + try { + await addLog(type, meta, body); + expect.fail(INTERNAL_SERVER_ERROR); + } catch (error) { + expect(error.message).to.equal(INTERNAL_SERVER_ERROR); + } + }); +}); diff --git a/utils/awsFunctions.ts b/utils/awsFunctions.ts new file mode 100644 index 000000000..260087b19 --- /dev/null +++ b/utils/awsFunctions.ts @@ -0,0 +1,136 @@ +import { + IdentitystoreClient, + ListUsersCommand, + CreateUserCommand, + CreateGroupMembershipCommand, + ListUsersCommandInput, + CreateUserCommandInput, + CreateGroupMembershipCommandInput, + } from "@aws-sdk/client-identitystore"; + import config from "config"; + + // Define the configuration variables with proper types + const accessKeyId: string = config.get("aws.access_key"); + const secretAccessKey: string = config.get("aws.secret_key"); + const region: string = config.get("aws.region"); + const identityStoreId: string = config.get("aws.identity_store_id"); + + let client: IdentitystoreClient; + + /** + * Configures AWS SDK credentials and returns a singleton IdentitystoreClient instance. + * @returns {IdentitystoreClient} Singleton AWS Identitystore client + */ + function configureAWSCredentials(): IdentitystoreClient { + if (!client) { + if (accessKeyId && secretAccessKey) { + client = new IdentitystoreClient({ + region: region, + credentials: { + accessKeyId: accessKeyId, + secretAccessKey: secretAccessKey, + }, + }); + } + } + return client!; + } + + /** + * Creates the user in the Identity store. + * @param {string} username - The username to create. + * @param {string} email - The email to associate with the user. + * @returns {Promise} - The AWS response or error. + */ + export const createUser = async (username: string, email: string): Promise => { + const client = configureAWSCredentials(); + + const params: CreateUserCommandInput = { + IdentityStoreId: identityStoreId, + UserName: username, + Name: { + Formatted: username, + FamilyName: username, + GivenName: username, + }, + DisplayName: username, + Emails: [ + { + Value: email, + Type: "work", + Primary: true, + }, + ], + }; + + try { + const command = new CreateUserCommand(params); + return (await client.send(command)); + } catch (error) { + console.error(`The error from create user ${error}`); + throw new Error(`Failed to create user: ${error instanceof Error ? error.message : String(error)}`); + } + }; + + /** + * This function adds the user to the IAM group. + * @param {string} groupId - The group ID to add the user to. + * @param {string} userId - The user ID to add to the group. + * @returns {Promise} - The AWS response or error. + */ +export const addUserToGroup = async (groupId: string, awsUserId: string): Promise => { + const client = configureAWSCredentials(); + + const params: CreateGroupMembershipCommandInput = { + IdentityStoreId: identityStoreId, + GroupId: groupId, + MemberId: { + UserId: awsUserId, + }, + }; + + try { + const command = new CreateGroupMembershipCommand(params); + return (await client.send(command)); + } catch (error) { + console.error("Error adding user to group:", error); + if (error.__type === 'ConflictException'){ + return { conflict: true }; + } + + throw new Error(`Failed to add user to group: ${error instanceof Error ? error.message : String(error)}`); + } + }; + + /** + * Function to get UserId by username from AWS Identity Store. + * @param {string} username - The username of the user. + * @returns {Promise} - The UserId if found, otherwise null. + */ + export const fetchAwsUserIdByUsername = async (username: string): Promise => { + const client = configureAWSCredentials(); + + const params: ListUsersCommandInput = { + IdentityStoreId: identityStoreId, + Filters: [ + { + AttributePath: "UserName", + AttributeValue: username, + }, + ], + }; + + let response; + try { + response = await client.send(new ListUsersCommand(params)); + } catch (err) { + throw new Error(`Error while fetching user by username: ${err instanceof Error ? err.message : String(err)}`); + } + if (response.Users && response.Users.length > 0) { + const userId = response.Users[0].UserId; + return userId; + } else { + return null; + } + }; + \ No newline at end of file diff --git a/utils/discord-actions.js b/utils/discord-actions.js index da1f3fe99..1a14f70e1 100644 --- a/utils/discord-actions.js +++ b/utils/discord-actions.js @@ -1,4 +1,5 @@ const jwt = require("jsonwebtoken"); +const config = require("config"); const { getDiscordMemberDetails } = require("../services/discordMembersService"); const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); const RDS_SERVERLESS_PRIVATE_KEY = config.get("rdsServerlessBot.rdsServerLessPrivateKey"); diff --git a/yarn.lock b/yarn.lock index 95acf1406..b230fe3a0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20,6 +20,447 @@ call-me-maybe "^1.0.1" js-yaml "^4.1.0" +"@aws-crypto/sha256-browser@5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz#153895ef1dba6f9fce38af550e0ef58988eb649e" + integrity sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw== + dependencies: + "@aws-crypto/sha256-js" "^5.2.0" + "@aws-crypto/supports-web-crypto" "^5.2.0" + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + "@aws-sdk/util-locate-window" "^3.0.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-crypto/sha256-js@5.2.0", "@aws-crypto/sha256-js@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz#c4fdb773fdbed9a664fc1a95724e206cf3860042" + integrity sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA== + dependencies: + "@aws-crypto/util" "^5.2.0" + "@aws-sdk/types" "^3.222.0" + tslib "^2.6.2" + +"@aws-crypto/supports-web-crypto@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz#a1e399af29269be08e695109aa15da0a07b5b5fb" + integrity sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg== + dependencies: + tslib "^2.6.2" + +"@aws-crypto/util@^5.2.0": + version "5.2.0" + resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-5.2.0.tgz#71284c9cffe7927ddadac793c14f14886d3876da" + integrity sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ== + dependencies: + "@aws-sdk/types" "^3.222.0" + "@smithy/util-utf8" "^2.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-identitystore@^3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-identitystore/-/client-identitystore-3.665.0.tgz#c234ba661ab1cfb4da138b66e595942e588e656e" + integrity sha512-KeTKgvYJyYX0TiRzkmPpTdCZdVnenXMeOd0+OlLcOzk1+LSXXYN/Z3RNezeUR7GzkdZ/mApBJXXP5ZpN0oiDMg== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.665.0" + "@aws-sdk/client-sts" "3.665.0" + "@aws-sdk/core" "3.665.0" + "@aws-sdk/credential-provider-node" "3.665.0" + "@aws-sdk/middleware-host-header" "3.664.0" + "@aws-sdk/middleware-logger" "3.664.0" + "@aws-sdk/middleware-recursion-detection" "3.664.0" + "@aws-sdk/middleware-user-agent" "3.664.0" + "@aws-sdk/region-config-resolver" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@aws-sdk/util-endpoints" "3.664.0" + "@aws-sdk/util-user-agent-browser" "3.664.0" + "@aws-sdk/util-user-agent-node" "3.664.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.7" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.22" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.3.6" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.22" + "@smithy/util-defaults-mode-node" "^3.0.22" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sso-oidc@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.665.0.tgz#d933f79a23aa5afbf9dddfc2685049ebff4b11d1" + integrity sha512-FQ2YyM9/6y3clWkf3d60/W4c/HZy61hbfIsR4KIh8aGOifwfIx/UpZQ61pCr/TXTNqbaAVU2/sK+J1zFkGEoLw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.665.0" + "@aws-sdk/credential-provider-node" "3.665.0" + "@aws-sdk/middleware-host-header" "3.664.0" + "@aws-sdk/middleware-logger" "3.664.0" + "@aws-sdk/middleware-recursion-detection" "3.664.0" + "@aws-sdk/middleware-user-agent" "3.664.0" + "@aws-sdk/region-config-resolver" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@aws-sdk/util-endpoints" "3.664.0" + "@aws-sdk/util-user-agent-browser" "3.664.0" + "@aws-sdk/util-user-agent-node" "3.664.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.7" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.22" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.3.6" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.22" + "@smithy/util-defaults-mode-node" "^3.0.22" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sso@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.665.0.tgz#d8fff846995cfc2d3c0631a0774138395aa57ce2" + integrity sha512-zje+oaIiyviDv5dmBWhGHifPTb0Idq/HatNPy+VEiwo2dxcQBexibD5CQE5e8CWZK123Br/9DHft+iNKdiY5bA== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "3.665.0" + "@aws-sdk/middleware-host-header" "3.664.0" + "@aws-sdk/middleware-logger" "3.664.0" + "@aws-sdk/middleware-recursion-detection" "3.664.0" + "@aws-sdk/middleware-user-agent" "3.664.0" + "@aws-sdk/region-config-resolver" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@aws-sdk/util-endpoints" "3.664.0" + "@aws-sdk/util-user-agent-browser" "3.664.0" + "@aws-sdk/util-user-agent-node" "3.664.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.7" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.22" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.3.6" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.22" + "@smithy/util-defaults-mode-node" "^3.0.22" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/client-sts@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.665.0.tgz#1b2b71841c1da810091b8ac79346c30f01d8897c" + integrity sha512-/OQEaWB1euXhZ/hV+wetDw1tynlrkNKzirzoiFuJ1EQsiIb9Ih/qjUF9KLdF1+/bXbnGu5YvIaAx80YReUchjg== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/client-sso-oidc" "3.665.0" + "@aws-sdk/core" "3.665.0" + "@aws-sdk/credential-provider-node" "3.665.0" + "@aws-sdk/middleware-host-header" "3.664.0" + "@aws-sdk/middleware-logger" "3.664.0" + "@aws-sdk/middleware-recursion-detection" "3.664.0" + "@aws-sdk/middleware-user-agent" "3.664.0" + "@aws-sdk/region-config-resolver" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@aws-sdk/util-endpoints" "3.664.0" + "@aws-sdk/util-user-agent-browser" "3.664.0" + "@aws-sdk/util-user-agent-node" "3.664.0" + "@smithy/config-resolver" "^3.0.9" + "@smithy/core" "^2.4.7" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/hash-node" "^3.0.7" + "@smithy/invalid-dependency" "^3.0.7" + "@smithy/middleware-content-length" "^3.0.9" + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.22" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.3.6" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-body-length-node" "^3.0.0" + "@smithy/util-defaults-mode-browser" "^3.0.22" + "@smithy/util-defaults-mode-node" "^3.0.22" + "@smithy/util-endpoints" "^2.1.3" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@aws-sdk/core@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.665.0.tgz#388249e5114291cec88d07188c6a707957bdd4dc" + integrity sha512-nqmNNf7Ml7qDXTIisDv+OYe/rl3nAW4cmR+HxrOCWdhTHe8xRdR5c45VPoh8nv1KIry5xtd+iqPrzzjydes+Og== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/core" "^2.4.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/property-provider" "^3.1.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/signature-v4" "^4.2.0" + "@smithy/smithy-client" "^3.3.6" + "@smithy/types" "^3.5.0" + "@smithy/util-middleware" "^3.0.7" + fast-xml-parser "4.4.1" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-env@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.664.0.tgz#62e81a883f9b94e593ed31a21f91d6026aba73ee" + integrity sha512-95rE+9Voaco0nmKJrXqfJAxSSkSWqlBy76zomiZrUrv7YuijQtHCW8jte6v6UHAFAaBzgFsY7QqBxs15u9SM7g== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-http@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.664.0.tgz#457e0c081b3f91315f5f1c3ce4f9b625ef085787" + integrity sha512-svaPwVfWV3g/qjd4cYHTUyBtkdOwcVjC+tSj6EjoMrpZwGUXcCbYe04iU0ARZ6tuH/u3vySbTLOGjSa7g8o9Qw== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/property-provider" "^3.1.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.3.6" + "@smithy/types" "^3.5.0" + "@smithy/util-stream" "^3.1.9" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-ini@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.665.0.tgz#a7a40a0867639b1a4dea9a38d6b25fcead660a96" + integrity sha512-CSWBV5GqCkK78TTXq6qx40MWCt90t8rS/O7FIR4nbmoUhG/DysaC1G0om1fSx6k+GWcvIIIsSvD4hdbh8FRWKA== + dependencies: + "@aws-sdk/credential-provider-env" "3.664.0" + "@aws-sdk/credential-provider-http" "3.664.0" + "@aws-sdk/credential-provider-process" "3.664.0" + "@aws-sdk/credential-provider-sso" "3.665.0" + "@aws-sdk/credential-provider-web-identity" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@smithy/credential-provider-imds" "^3.2.4" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-node@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.665.0.tgz#bc8ea541fe0a5e9cbd29564a43abb6a9ea5e3edd" + integrity sha512-cmJfVi4IM0WaKMQvPXhiS5mdIZyCoa04I3D+IEKpD2GAuVZa6tgwqfPyaApFDLjyedGGNFkC4MRgAjCcCl4WFg== + dependencies: + "@aws-sdk/credential-provider-env" "3.664.0" + "@aws-sdk/credential-provider-http" "3.664.0" + "@aws-sdk/credential-provider-ini" "3.665.0" + "@aws-sdk/credential-provider-process" "3.664.0" + "@aws-sdk/credential-provider-sso" "3.665.0" + "@aws-sdk/credential-provider-web-identity" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@smithy/credential-provider-imds" "^3.2.4" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-process@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.664.0.tgz#d5ae17d404440855733a9eb0167ee8db168b7814" + integrity sha512-sQicIw/qWTsmMw8EUQNJXdrWV5SXaZc2zGdCQsQxhR6wwNO2/rZ5JmzdcwUADmleBVyPYk3KGLhcofF/qXT2Ng== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-sso@3.665.0": + version "3.665.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.665.0.tgz#9ba6ea31122d863442fe7c2e9a3004dcb04f15ed" + integrity sha512-Xe8WW4r70bsetGQG3azFeK/gd+Q4OmNiidtRrG64y/V9TIvIqc7Y/yUZNhEgFkpG19o188VmXg/ulnG3E+MvLg== + dependencies: + "@aws-sdk/client-sso" "3.665.0" + "@aws-sdk/token-providers" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/credential-provider-web-identity@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.664.0.tgz#46b79cdae6adb3c7d8da966eeef06124a31e065b" + integrity sha512-10ltP1BfSKRJVXd8Yr5oLbo+VSDskWbps0X3szSsxTk0Dju1xvkz7hoIjylWLvtGbvQ+yb2pmsJYKCudW/4DJg== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-host-header@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.664.0.tgz#14ea7fabe0f5a31ee399bb243981c951ab902560" + integrity sha512-4tCXJ+DZWTq38eLmFgnEmO8X4jfWpgPbWoCyVYpRHCPHq6xbrU65gfwS9jGx25L4YdEce641ChI9TKLryuUgRA== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-logger@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.664.0.tgz#74f47c10732b873c1f097c909b9df46babeacda4" + integrity sha512-eNykMqQuv7eg9pAcaLro44fscIe1VkFfhm+gYnlxd+PH6xqapRki1E68VHehnIptnVBdqnWfEqLUSLGm9suqhg== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-recursion-detection@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.664.0.tgz#0564b857c4501e2de5a2c3d78d3a5f29fad1307b" + integrity sha512-jq27WMZhm+dY8BWZ9Ipy3eXtZj0lJzpaKQE3A3tH5AOIlUV/gqrmnJ9CdqVVef4EJsq9Yil4ZzQjKKmPsxveQg== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/middleware-user-agent@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.664.0.tgz#06827a880095ddf34361662df359bdc97de6f00e" + integrity sha512-Kp5UwXwayO6d472nntiwgrxqay2KS9ozXNmKjQfDrUWbEzvgKI+jgKNMia8MMnjSxYoBGpQ1B8NGh8a6KMEJJg== + dependencies: + "@aws-sdk/types" "3.664.0" + "@aws-sdk/util-endpoints" "3.664.0" + "@smithy/core" "^2.4.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/region-config-resolver@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.664.0.tgz#69e65abae7338e677f6be0c7c43ee622411c1304" + integrity sha512-o/B8dg8K+9714RGYPgMxZgAChPe/MTSMkf/eHXTUFHNik5i1HgVKfac22njV2iictGy/6GhpFsKa1OWNYAkcUg== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/types" "^3.5.0" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.7" + tslib "^2.6.2" + +"@aws-sdk/token-providers@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.664.0.tgz#edeb10bf273960c8ef7172d78c0bb41a0c73d350" + integrity sha512-dBAvXW2/6bAxidvKARFxyCY2uCynYBKRFN00NhS1T5ggxm3sUnuTpWw1DTjl02CVPkacBOocZf10h8pQbHSK8w== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/types@3.664.0", "@aws-sdk/types@^3.222.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.664.0.tgz#e6de1c0a2cdfe4f1e43271223dc0b55e613ced58" + integrity sha512-+GtXktvVgpreM2b+NJL9OqZGsOzHwlCUrO8jgQUvH/yA6Kd8QO2YFhQCp0C9sSzTteZJVqGBu8E0CQurxJHPbw== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@aws-sdk/util-endpoints@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.664.0.tgz#cad1195e9b6af74f61bcad4c71d7b820e7deae8c" + integrity sha512-KrXoHz6zmAahVHkyWMRT+P6xJaxItgmklxEDrT+npsUB4d5C/lhw16Crcp9TDi828fiZK3GYKRAmmNhvmzvBNg== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/types" "^3.5.0" + "@smithy/util-endpoints" "^2.1.3" + tslib "^2.6.2" + +"@aws-sdk/util-locate-window@^3.0.0": + version "3.568.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz#2acc4b2236af0d7494f7e517401ba6b3c4af11ff" + integrity sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig== + dependencies: + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-browser@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.664.0.tgz#d22da782154df1b3d6b60e89103554c07673e3b2" + integrity sha512-c/PV3+f1ss4PpskHbcOxTZ6fntV2oXy/xcDR9nW+kVaz5cM1G702gF0rvGLKPqoBwkj2rWGe6KZvEBeLzynTUQ== + dependencies: + "@aws-sdk/types" "3.664.0" + "@smithy/types" "^3.5.0" + bowser "^2.11.0" + tslib "^2.6.2" + +"@aws-sdk/util-user-agent-node@3.664.0": + version "3.664.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.664.0.tgz#3699b1a959fb6781e627d6303b18cdbd41f1b90d" + integrity sha512-l/m6KkgrTw1p/VTJTk0IoP9I2OnpWp3WbBgzxoNeh9cUcxTufIn++sBxKj5hhDql57LKWsckScG/MhFuH0vZZA== + dependencies: + "@aws-sdk/middleware-user-agent" "3.664.0" + "@aws-sdk/types" "3.664.0" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" @@ -843,6 +1284,399 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz#5981a8db18b56ba38ef0efb7d995b12aa7b51918" integrity sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ== +"@smithy/abort-controller@^3.1.5": + version "3.1.5" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.1.5.tgz#ca7a86a3c6b20fabe59667143f58d9e198616d14" + integrity sha512-DhNPnqTqPoG8aZ5dWkFOgsuY+i0GQ3CI6hMmvCoduNsnU9gUZWZBwGfDQsTTB7NvFPkom1df7jMIJWU90kuXXg== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/config-resolver@^3.0.9": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-3.0.9.tgz#dcf4b7747ca481866f9bfac21469ebe2031a599e" + integrity sha512-5d9oBf40qC7n2xUoHmntKLdqsyTMMo/r49+eqSIjJ73eDfEtljAxEhzIQ3bkgXJtR3xiv7YzMT/3FF3ORkjWdg== + dependencies: + "@smithy/node-config-provider" "^3.1.8" + "@smithy/types" "^3.5.0" + "@smithy/util-config-provider" "^3.0.0" + "@smithy/util-middleware" "^3.0.7" + tslib "^2.6.2" + +"@smithy/core@^2.4.7": + version "2.4.8" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-2.4.8.tgz#397ac17dfa8ad658b77f96f19484f0eeaf22d397" + integrity sha512-x4qWk7p/a4dcf7Vxb2MODIf4OIcqNbK182WxRvZ/3oKPrf/6Fdic5sSElhO1UtXpWKBazWfqg0ZEK9xN1DsuHA== + dependencies: + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-retry" "^3.0.23" + "@smithy/middleware-serde" "^3.0.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/util-body-length-browser" "^3.0.0" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/credential-provider-imds@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.4.tgz#e1a2bfc8a0066f673756ad8735247cf284b9735c" + integrity sha512-S9bb0EIokfYEuar4kEbLta+ivlKCWOCFsLZuilkNy9i0uEUEHSi47IFLPaxqqCl+0ftKmcOTHayY5nQhAuq7+w== + dependencies: + "@smithy/node-config-provider" "^3.1.8" + "@smithy/property-provider" "^3.1.7" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" + tslib "^2.6.2" + +"@smithy/fetch-http-handler@^3.2.9": + version "3.2.9" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.9.tgz#8d5199c162a37caa37a8b6848eefa9ca58221a0b" + integrity sha512-hYNVQOqhFQ6vOpenifFME546f0GfJn2OiQ3M0FDmuUu8V/Uiwy2wej7ZXxFBNqdx0R5DZAqWM1l6VRhGz8oE6A== + dependencies: + "@smithy/protocol-http" "^4.1.4" + "@smithy/querystring-builder" "^3.0.7" + "@smithy/types" "^3.5.0" + "@smithy/util-base64" "^3.0.0" + tslib "^2.6.2" + +"@smithy/hash-node@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-3.0.7.tgz#03b5a382fb588b8c2bac11b4fe7300aaf1661c88" + integrity sha512-SAGHN+QkrwcHFjfWzs/czX94ZEjPJ0CrWJS3M43WswDXVEuP4AVy9gJ3+AF6JQHZD13bojmuf/Ap/ItDeZ+Qfw== + dependencies: + "@smithy/types" "^3.5.0" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/invalid-dependency@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-3.0.7.tgz#b36f258d94498f3c72ab6020091a66fc7cc16eda" + integrity sha512-Bq00GsAhHeYSuZX8Kpu4sbI9agH2BNYnqUmmbTGWOhki9NVsWn2jFr896vvoTMH8KAjNX/ErC/8t5QHuEXG+IA== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/is-array-buffer@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz#f84f0d9f9a36601a9ca9381688bd1b726fd39111" + integrity sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA== + dependencies: + tslib "^2.6.2" + +"@smithy/is-array-buffer@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz#9a95c2d46b8768946a9eec7f935feaddcffa5e7a" + integrity sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ== + dependencies: + tslib "^2.6.2" + +"@smithy/middleware-content-length@^3.0.9": + version "3.0.9" + resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-3.0.9.tgz#fb613d1a6b8c91e828d11c0d7a0a8576dba89b8b" + integrity sha512-t97PidoGElF9hTtLCrof32wfWMqC5g2SEJNxaVH3NjlatuNGsdxXRYO/t+RPnxA15RpYiS0f+zG7FuE2DeGgjA== + dependencies: + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/middleware-endpoint@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.4.tgz#222c9fa49c8af6ebf8bea8ab220d92d9b8c90d3d" + integrity sha512-/ChcVHekAyzUbyPRI8CzPPLj6y8QRAfJngWcLMgsWxKVzw/RzBV69mSOzJYDD3pRwushA1+5tHtPF8fjmzBnrQ== + dependencies: + "@smithy/middleware-serde" "^3.0.7" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + "@smithy/url-parser" "^3.0.7" + "@smithy/util-middleware" "^3.0.7" + tslib "^2.6.2" + +"@smithy/middleware-retry@^3.0.22", "@smithy/middleware-retry@^3.0.23": + version "3.0.23" + resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-3.0.23.tgz#ce5574e278dd14a7995afd5a4ed2a6c9891da8ed" + integrity sha512-x9PbGXxkcXIpm6L26qRSCC+eaYcHwybRmqU8LO/WM2RRlW0g8lz6FIiKbKgGvHuoK3dLZRiQVSQJveiCzwnA5A== + dependencies: + "@smithy/node-config-provider" "^3.1.8" + "@smithy/protocol-http" "^4.1.4" + "@smithy/service-error-classification" "^3.0.7" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-retry" "^3.0.7" + tslib "^2.6.2" + uuid "^9.0.1" + +"@smithy/middleware-serde@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.7.tgz#03f0dda75edffc4cc90ea422349cbfb82368efa7" + integrity sha512-VytaagsQqtH2OugzVTq4qvjkLNbWehHfGcGr0JLJmlDRrNCeZoWkWsSOw1nhS/4hyUUWF/TLGGml4X/OnEep5g== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/middleware-stack@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.7.tgz#813fa7b47895ce0d085eac89c056d21b1e46e771" + integrity sha512-EyTbMCdqS1DoeQsO4gI7z2Gzq1MoRFAeS8GkFYIwbedB7Lp5zlLHJdg+56tllIIG5Hnf9ZWX48YKSHlsKvugGA== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/node-config-provider@^3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.1.8.tgz#2c1092040b4062eae0f7c9e121cc00ac6a77efee" + integrity sha512-E0rU0DglpeJn5ge64mk8wTGEXcQwmpUTY5Zr7IzTpDLmHKiIamINERNZYrPQjg58Ck236sEKSwRSHA4CwshU6Q== + dependencies: + "@smithy/property-provider" "^3.1.7" + "@smithy/shared-ini-file-loader" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/node-http-handler@^3.2.4": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.2.4.tgz#3c57c40d082c3bacac1e49955bd1240e8ccc40b2" + integrity sha512-49reY3+JgLMFNm7uTAKBWiKCA6XSvkNp9FqhVmusm2jpVnHORYFeFZ704LShtqWfjZW/nhX+7Iexyb6zQfXYIQ== + dependencies: + "@smithy/abort-controller" "^3.1.5" + "@smithy/protocol-http" "^4.1.4" + "@smithy/querystring-builder" "^3.0.7" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/property-provider@^3.1.7": + version "3.1.7" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.1.7.tgz#8a304a4b9110a067a93c784e4c11e175f82da379" + integrity sha512-QfzLi1GPMisY7bAM5hOUqBdGYnY5S2JAlr201pghksrQv139f8iiiMalXtjczIP5f6owxFn3MINLNUNvUkgtPw== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/protocol-http@^4.1.4": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.1.4.tgz#6940d652b1825bda2422163ec9baab552669a338" + integrity sha512-MlWK8eqj0JlpZBnWmjQLqmFp71Ug00P+m72/1xQB3YByXD4zZ+y9N4hYrR0EDmrUCZIkyATWHOXFgtavwGDTzQ== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/querystring-builder@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.7.tgz#8c443c65f4249ff1637088db1166d18411d41555" + integrity sha512-65RXGZZ20rzqqxTsChdqSpbhA6tdt5IFNgG6o7e1lnPVLCe6TNWQq4rTl4N87hTDD8mV4IxJJnvyE7brbnRkQw== + dependencies: + "@smithy/types" "^3.5.0" + "@smithy/util-uri-escape" "^3.0.0" + tslib "^2.6.2" + +"@smithy/querystring-parser@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.7.tgz#936206d1e6da9d862384dae730b4bad042d6a948" + integrity sha512-Fouw4KJVWqqUVIu1gZW8BH2HakwLz6dvdrAhXeXfeymOBrZw+hcqaWs+cS1AZPVp4nlbeIujYrKA921ZW2WMPA== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/service-error-classification@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-3.0.7.tgz#5bab4ad802d30bd3fa52b8134f6c171582358226" + integrity sha512-91PRkTfiBf9hxkIchhRKJfl1rsplRDyBnmyFca3y0Z3x/q0JJN480S83LBd8R6sBCkm2bBbqw2FHp0Mbh+ecSA== + dependencies: + "@smithy/types" "^3.5.0" + +"@smithy/shared-ini-file-loader@^3.1.8": + version "3.1.8" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.8.tgz#7a0bf5f20cfe8e0c4a36d8dcab8194d0d2ee958e" + integrity sha512-0NHdQiSkeGl0ICQKcJQ2lCOKH23Nb0EaAa7RDRId6ZqwXkw4LJyIyZ0t3iusD4bnKYDPLGy2/5e2rfUhrt0Acw== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/signature-v4@^4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-4.2.0.tgz#291f5a0e756cc251377e1e8af2a1f494e6173029" + integrity sha512-LafbclHNKnsorMgUkKm7Tk7oJ7xizsZ1VwqhGKqoCIrXh4fqDDp73fK99HOEEgcsQbtemmeY/BPv0vTVYYUNEQ== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-middleware" "^3.0.7" + "@smithy/util-uri-escape" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/smithy-client@^3.3.6", "@smithy/smithy-client@^3.4.0": + version "3.4.0" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.4.0.tgz#ceffb92108a4ad60cbede3baf44ed224dc70b333" + integrity sha512-nOfJ1nVQsxiP6srKt43r2My0Gp5PLWCW2ASqUioxIiGmu6d32v4Nekidiv5qOmmtzIrmaD+ADX5SKHUuhReeBQ== + dependencies: + "@smithy/middleware-endpoint" "^3.1.4" + "@smithy/middleware-stack" "^3.0.7" + "@smithy/protocol-http" "^4.1.4" + "@smithy/types" "^3.5.0" + "@smithy/util-stream" "^3.1.9" + tslib "^2.6.2" + +"@smithy/types@^3.5.0": + version "3.5.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.5.0.tgz#9589e154c50d9c5d00feb7d818112ef8fc285d6e" + integrity sha512-QN0twHNfe8mNJdH9unwsCK13GURU7oEAZqkBI+rsvpv1jrmserO+WnLE7jidR9W/1dxwZ0u/CB01mV2Gms/K2Q== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.7.tgz#9d7d7e4e38514bf75ade6e8a30d2300f3db17d1b" + integrity sha512-70UbSSR8J97c1rHZOWhl+VKiZDqHWxs/iW8ZHrHp5fCCPLSBE7GcUlUvKSle3Ca+J9LLbYCj/A79BxztBvAfpA== + dependencies: + "@smithy/querystring-parser" "^3.0.7" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/util-base64@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017" + integrity sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-body-length-browser@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz#86ec2f6256310b4845a2f064e2f571c1ca164ded" + integrity sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-body-length-node@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz#99a291bae40d8932166907fe981d6a1f54298a6d" + integrity sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA== + dependencies: + tslib "^2.6.2" + +"@smithy/util-buffer-from@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz#6fc88585165ec73f8681d426d96de5d402021e4b" + integrity sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA== + dependencies: + "@smithy/is-array-buffer" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-buffer-from@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz#559fc1c86138a89b2edaefc1e6677780c24594e3" + integrity sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA== + dependencies: + "@smithy/is-array-buffer" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-config-provider@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz#62c6b73b22a430e84888a8f8da4b6029dd5b8efe" + integrity sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-defaults-mode-browser@^3.0.22": + version "3.0.23" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.23.tgz#6920b473126ae8857a04dd6941793bbda12adc8b" + integrity sha512-Y07qslyRtXDP/C5aWKqxTPBl4YxplEELG3xRrz2dnAQ6Lq/FgNrcKWmV561nNaZmFH+EzeGOX3ZRMbU8p1T6Nw== + dependencies: + "@smithy/property-provider" "^3.1.7" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + bowser "^2.11.0" + tslib "^2.6.2" + +"@smithy/util-defaults-mode-node@^3.0.22": + version "3.0.23" + resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.23.tgz#d03d21816e8b2f586ccf4a87cd0b1cc55b4d75e0" + integrity sha512-9Y4WH7f0vnDGuHUa4lGX9e2p+sMwODibsceSV6rfkZOvMC+BY3StB2LdO1NHafpsyHJLpwAgChxQ38tFyd6vkg== + dependencies: + "@smithy/config-resolver" "^3.0.9" + "@smithy/credential-provider-imds" "^3.2.4" + "@smithy/node-config-provider" "^3.1.8" + "@smithy/property-provider" "^3.1.7" + "@smithy/smithy-client" "^3.4.0" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/util-endpoints@^2.1.3": + version "2.1.3" + resolved "https://registry.yarnpkg.com/@smithy/util-endpoints/-/util-endpoints-2.1.3.tgz#7498151e9dc714bdd0c6339314dd2350fa4d250a" + integrity sha512-34eACeKov6jZdHqS5hxBMJ4KyWKztTMulhuQ2UdOoP6vVxMLrOKUqIXAwJe/wiWMhXhydLW664B02CNpQBQ4Aw== + dependencies: + "@smithy/node-config-provider" "^3.1.8" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/util-hex-encoding@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" + integrity sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.7.tgz#770d09749b6d170a1641384a2e961487447446fa" + integrity sha512-OVA6fv/3o7TMJTpTgOi1H5OTwnuUa8hzRzhSFDtZyNxi6OZ70L/FHattSmhE212I7b6WSOJAAmbYnvcjTHOJCA== + dependencies: + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/util-retry@^3.0.7": + version "3.0.7" + resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-3.0.7.tgz#694e0667574ffe9772f620b35d3c7286aced35e9" + integrity sha512-nh1ZO1vTeo2YX1plFPSe/OXaHkLAHza5jpokNiiKX2M5YpNUv6RxGJZhpfmiR4jSvVHCjIDmILjrxKmP+/Ghug== + dependencies: + "@smithy/service-error-classification" "^3.0.7" + "@smithy/types" "^3.5.0" + tslib "^2.6.2" + +"@smithy/util-stream@^3.1.9": + version "3.1.9" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.1.9.tgz#d39656eae27696bdc5a3ec7c2f6b89c32dccd1ca" + integrity sha512-7YAR0Ub3MwTMjDfjnup4qa6W8gygZMxikBhFMPESi6ASsl/rZJhwLpF/0k9TuezScCojsM0FryGdz4LZtjKPPQ== + dependencies: + "@smithy/fetch-http-handler" "^3.2.9" + "@smithy/node-http-handler" "^3.2.4" + "@smithy/types" "^3.5.0" + "@smithy/util-base64" "^3.0.0" + "@smithy/util-buffer-from" "^3.0.0" + "@smithy/util-hex-encoding" "^3.0.0" + "@smithy/util-utf8" "^3.0.0" + tslib "^2.6.2" + +"@smithy/util-uri-escape@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54" + integrity sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-utf8@^2.0.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" + integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + tslib "^2.6.2" + +"@smithy/util-utf8@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-3.0.0.tgz#1a6a823d47cbec1fd6933e5fc87df975286d9d6a" + integrity sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA== + dependencies: + "@smithy/util-buffer-from" "^3.0.0" + tslib "^2.6.2" + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -1716,6 +2550,11 @@ boom@^7.3.x: dependencies: hoek "6.x.x" +bowser@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" + integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + boxen@^5.0.0: version "5.1.2" resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" @@ -3359,6 +4198,13 @@ fast-url-parser@^1.1.3: dependencies: punycode "^1.3.2" +fast-xml-parser@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== + dependencies: + strnum "^1.0.5" + fast-xml-parser@^4.3.0: version "4.4.0" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz#341cc98de71e9ba9e651a67f41f1752d1441a501" @@ -7326,7 +8172,16 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -7391,7 +8246,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -7741,6 +8603,11 @@ tslib@^2.0.1, tslib@^2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== +tslib@^2.6.2: + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -8168,7 +9035,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -8186,6 +9053,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"