From f3c76c79eeaeb7ca2895d991bad09aa61e3b194e Mon Sep 17 00:00:00 2001 From: Andrew Atimapre Date: Mon, 30 Oct 2023 18:23:23 +0100 Subject: [PATCH 01/24] Checkly group level sync support --- .../v1/integrationAuthController.ts | 36 ++++++- backend/src/integrations/groups.ts | 96 +++++++++++++++++++ backend/src/integrations/index.ts | 2 + backend/src/routes/v1/integrationAuth.ts | 8 ++ backend/src/validation/integrationAuth.ts | 6 ++ .../src/hooks/api/integrationAuth/index.tsx | 3 +- .../src/hooks/api/integrationAuth/queries.tsx | 19 +++- .../src/hooks/api/integrationAuth/types.ts | 5 + .../src/pages/integrations/checkly/create.tsx | 48 +++++++++- 9 files changed, 216 insertions(+), 7 deletions(-) create mode 100644 backend/src/integrations/groups.ts diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index 06b245659..5138e4391 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -1,7 +1,7 @@ import { Request, Response } from "express"; import { Types } from "mongoose"; import { standardRequest } from "../../config/request"; -import { getApps, getTeams, revokeAccess } from "../../integrations"; +import { getApps, getTeams, getGroups, revokeAccess } from "../../integrations"; import { Bot, IntegrationAuth, Workspace } from "../../models"; import { EventType } from "../../ee/models"; import { IntegrationService } from "../../services"; @@ -208,6 +208,40 @@ export const saveIntegrationToken = async (req: Request, res: Response) => { }); }; +/** + * Return list of groups allowed for integration with integration authorization id [integrationAuthId] + * @param req + * @param res + * @returns + */ +export const getIntegrationAuthGroups = async (req: Request, res: Response) => { + const { + params: { integrationAuthId } + } = await validateRequest(reqValidator.GetIntegrationAuthGroupsV1, req); + + const { integrationAuth, accessToken } = await getIntegrationAuthAccessHelper({ + integrationAuthId: new ObjectId(integrationAuthId) + }); + + const { permission } = await getUserProjectPermissions( + req.user._id, + integrationAuth.workspace.toString() + ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.Integrations + ); + + const groups = await getGroups({ + integrationAuth: integrationAuth, + accessToken: accessToken + }); + + return res.status(200).send({ + groups + }); +}; + /** * Return list of applications allowed for integration with integration authorization id [integrationAuthId] * @param req diff --git a/backend/src/integrations/groups.ts b/backend/src/integrations/groups.ts new file mode 100644 index 000000000..c69a21bbd --- /dev/null +++ b/backend/src/integrations/groups.ts @@ -0,0 +1,96 @@ +import { + IIntegrationAuth, +} from "../models"; +import { + INTEGRATION_CHECKLY, + INTEGRATION_CHECKLY_API_URL, +} from "../variables"; +import { standardRequest } from "../config/request"; + +interface Group { + name: string; + groupId: string; +} + +/** + * Return list of groups for checkly integration authorization [integrationAuth] + * @param {Object} obj + * @param {String} obj.integrationAuth - integration authorization to get groups + * @param {String} obj.accessToken - access token for integration authorization + * @returns {Object[]} groups - groups for integration authorization + * @returns {String} groups.name - name of group + * @returns {String} groups.groupId - id of group +*/ +const getGroups = async ({ + integrationAuth, + accessToken, +}: { + integrationAuth: IIntegrationAuth; + accessToken: string; +}) => { + + let groups: Group[] = []; + + switch (integrationAuth.integration) { + case INTEGRATION_CHECKLY: + groups = await getGroupsCheckly({ + accessToken, + }); + break; + } + + return groups; +} + +/** + * Return list of groups for Checkly integration + * @param {Object} obj + * @param {String} obj.accessToken - access token for Checkly API + * @returns {Object[]} groups - list of groups in Checkly + * @returns {String} groups.name - name of group + * @returns {String} groups.groupId - id of group +*/ +const getGroupsCheckly = async ({ + accessToken, +}: { + accessToken: string; +}) => { + + let groups: Group[] = []; + + // case: fetch account id + const { data } = await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/accounts`, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json" + } + }); + + const accountId = data.map((a: any) => { + return { + id: a.id, + }; + }); + + // case: fetch list of groups in Checkly + const res = accountId.length > 0 && ( + await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/check-groups`, { + headers: { + Authorization: `Bearer ${accessToken}`, + Accept: "application/json", + "X-Checkly-Account": accountId[0].id, + } + }) + ).data; + + groups = res.map((g: any) => ({ + name: g.name, + groupId: g.id, + })); + + return groups; +} + +export { + getGroups, +} \ No newline at end of file diff --git a/backend/src/integrations/index.ts b/backend/src/integrations/index.ts index e1bf23ba1..e5b6ad0ea 100644 --- a/backend/src/integrations/index.ts +++ b/backend/src/integrations/index.ts @@ -2,6 +2,7 @@ import { exchangeCode } from "./exchange"; import { exchangeRefresh } from "./refresh"; import { getApps } from "./apps"; import { getTeams } from "./teams"; +import { getGroups } from "./groups"; import { revokeAccess } from "./revoke"; export { @@ -9,5 +10,6 @@ export { exchangeRefresh, getApps, getTeams, + getGroups, revokeAccess, } \ No newline at end of file diff --git a/backend/src/routes/v1/integrationAuth.ts b/backend/src/routes/v1/integrationAuth.ts index e28874788..d5d81cf13 100644 --- a/backend/src/routes/v1/integrationAuth.ts +++ b/backend/src/routes/v1/integrationAuth.ts @@ -52,6 +52,14 @@ router.get( integrationAuthController.getIntegrationAuthTeams ); +router.get( + "/:integrationAuthId/groups", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + integrationAuthController.getIntegrationAuthGroups +); + router.get( "/:integrationAuthId/vercel/branches", requireAuth({ diff --git a/backend/src/validation/integrationAuth.ts b/backend/src/validation/integrationAuth.ts index 65eee4477..9f3798ad6 100644 --- a/backend/src/validation/integrationAuth.ts +++ b/backend/src/validation/integrationAuth.ts @@ -117,6 +117,12 @@ export const GetIntegrationAuthVercelBranchesV1 = z.object({ }) }); +export const GetIntegrationAuthGroupsV1 = z.object({ + params: z.object({ + integrationAuthId: z.string().trim() + }) +}); + export const GetIntegrationAuthQoveryOrgsV1 = z.object({ params: z.object({ integrationAuthId: z.string().trim() diff --git a/frontend/src/hooks/api/integrationAuth/index.tsx b/frontend/src/hooks/api/integrationAuth/index.tsx index 7e7c355ca..2a9147ffe 100644 --- a/frontend/src/hooks/api/integrationAuth/index.tsx +++ b/frontend/src/hooks/api/integrationAuth/index.tsx @@ -10,5 +10,6 @@ export { useGetIntegrationAuthTeamCityBuildConfigs, useGetIntegrationAuthTeams, useGetIntegrationAuthVercelBranches, - useSaveIntegrationAccessToken + useSaveIntegrationAccessToken, + useGetIntegrationAuthGroups } from "./queries"; diff --git a/frontend/src/hooks/api/integrationAuth/queries.tsx b/frontend/src/hooks/api/integrationAuth/queries.tsx index 9213919e0..71ee4f535 100644 --- a/frontend/src/hooks/api/integrationAuth/queries.tsx +++ b/frontend/src/hooks/api/integrationAuth/queries.tsx @@ -12,7 +12,8 @@ import { Org, Project, Service, - Team, + Team, + Group, TeamCityBuildConfig} from "./types"; const integrationAuthKeys = { @@ -22,6 +23,8 @@ const integrationAuthKeys = { [{ integrationAuthId, teamId, workspaceSlug }, "integrationAuthApps"] as const, getIntegrationAuthTeams: (integrationAuthId: string) => [{ integrationAuthId }, "integrationAuthTeams"] as const, + getIntegrationAuthGroups: (integrationAuthId: string) => + [{ integrationAuthId }, "integrationAuthGroups"] as const, getIntegrationAuthVercelBranches: ({ integrationAuthId, appId @@ -125,6 +128,12 @@ const fetchIntegrationAuthTeams = async (integrationAuthId: string) => { return data.teams; }; +const fetchIntegrationAuthGroups = async (integrationAuthId: string) => { + const { data } = await apiRequest.get<{ groups: Group[] }>( + `/api/v1/integration-auth/${integrationAuthId}/groups` + ); + return data.groups; +}; const fetchIntegrationAuthVercelBranches = async ({ integrationAuthId, @@ -413,6 +422,14 @@ export const useGetIntegrationAuthVercelBranches = ({ }); }; +export const useGetIntegrationAuthGroups = (integrationAuthId: string) => { + return useQuery({ + queryKey: integrationAuthKeys.getIntegrationAuthGroups(integrationAuthId), + queryFn: () => fetchIntegrationAuthGroups(integrationAuthId), + enabled: true + }); +}; + export const useGetIntegrationAuthQoveryOrgs = (integrationAuthId: string) => { return useQuery({ queryKey: integrationAuthKeys.getIntegrationAuthQoveryOrgs(integrationAuthId), diff --git a/frontend/src/hooks/api/integrationAuth/types.ts b/frontend/src/hooks/api/integrationAuth/types.ts index 2292e3222..e3f9af247 100644 --- a/frontend/src/hooks/api/integrationAuth/types.ts +++ b/frontend/src/hooks/api/integrationAuth/types.ts @@ -26,6 +26,11 @@ export type Environment = { environmentId: string; }; +export type Group = { + name: string; + groupId: string; +}; + export type Container = { name: string; containerId: string; diff --git a/frontend/src/pages/integrations/checkly/create.tsx b/frontend/src/pages/integrations/checkly/create.tsx index 4393dfc0e..9c203bb4c 100644 --- a/frontend/src/pages/integrations/checkly/create.tsx +++ b/frontend/src/pages/integrations/checkly/create.tsx @@ -9,6 +9,8 @@ import { motion } from "framer-motion"; import queryString from "query-string"; import { + Alert, + AlertDescription, Button, Card, CardTitle, @@ -27,7 +29,8 @@ import { import { useGetIntegrationAuthApps, - useGetIntegrationAuthById + useGetIntegrationAuthById, + useGetIntegrationAuthGroups } from "../../../hooks/api/integrationAuth"; import { useGetWorkspaceById } from "../../../hooks/api/workspace"; @@ -47,6 +50,9 @@ export default function ChecklyCreateIntegrationPage() { const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } = useGetIntegrationAuthApps({ integrationAuthId: (integrationAuthId as string) ?? "" }); + const { data: integrationAuthGroups, isLoading: isintegrationAuthGroupsLoading } = useGetIntegrationAuthGroups( + (integrationAuthId as string) ?? "" + ); const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); const [secretPath, setSecretPath] = useState("/"); @@ -55,6 +61,9 @@ export default function ChecklyCreateIntegrationPage() { const [targetApp, setTargetApp] = useState(""); const [targetAppId, setTargetAppId] = useState(""); + const [targetGroup, setTargetGroup] = useState(""); + const [targetGroupId, setTargetGroupId] = useState(""); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { @@ -104,9 +113,10 @@ export default function ChecklyCreateIntegrationPage() { return integrationAuth && workspace && selectedSourceEnvironment && - integrationAuthApps && + integrationAuthApps && + integrationAuthGroups && targetApp ? ( -
+
Set Up Checkly Integration @@ -175,6 +185,36 @@ export default function ChecklyCreateIntegrationPage() { placeholder="Provide a path, default is /" /> + + + + + + By default environment variables are synced to the global level, select a group above to sync at the Group level. + + setTargetGroup(val)} + onValueChange={handleValueChange} className="w-full border border-mineshaft-500" > From ad4513f92601d58d50feb9b8fc969bceb3ad00b2 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 31 Oct 2023 15:03:36 +0200 Subject: [PATCH 03/24] Replace winston with pino --- backend/package-lock.json | 1809 +++++++---------- backend/package.json | 6 +- backend/src/helpers/database.ts | 6 +- backend/src/index.ts | 18 +- backend/src/middleware/requestErrorHandler.ts | 14 +- backend/src/services/TelemetryService.ts | 8 +- backend/src/services/health.ts | 4 +- backend/src/services/smtp.ts | 7 +- backend/src/utils/logger.ts | 67 - backend/src/utils/logging/index.ts | 1 + backend/src/utils/logging/logger.ts | 21 + 11 files changed, 833 insertions(+), 1128 deletions(-) delete mode 100644 backend/src/utils/logger.ts create mode 100644 backend/src/utils/logging/index.ts create mode 100644 backend/src/utils/logging/logger.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index ab57cb687..2000548c9 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -51,6 +51,9 @@ "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", "passport-google-oauth20": "^2.0.0", + "pino": "^8.16.1", + "pino-http": "^8.5.1", + "pino-pretty": "^10.2.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", @@ -61,8 +64,6 @@ "tweetnacl-util": "^0.15.1", "typescript": "^4.9.3", "utility-types": "^3.10.0", - "winston": "^3.8.2", - "winston-loki": "^6.0.6", "zod": "^3.22.3" }, "devDependencies": { @@ -81,6 +82,7 @@ "@types/nodemailer": "^6.4.6", "@types/passport": "^1.0.12", "@types/picomatch": "^2.3.0", + "@types/pino": "^7.0.5", "@types/supertest": "^2.0.12", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", @@ -3436,14 +3438,6 @@ "mongoose": "^6.0.13 || ^7.0.0" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -3466,16 +3460,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4904,65 +4888,54 @@ "node": ">=6" } }, + "node_modules/@probot/pino/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" + }, + "node_modules/@probot/pino/node_modules/jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/@probot/pino/node_modules/pino-pretty": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-6.0.0.tgz", + "integrity": "sha512-jyeR2fXXWc68st1DTTM5NhkHlx8p+1fKZMfm84Jwq+jSw08IwAjNaZBZR6ts69hhPOfOjg/NiE1HYW7vBRPL3A==", + "dependencies": { + "@hapi/bourne": "^2.0.0", + "args": "^5.0.1", + "colorette": "^1.3.0", + "dateformat": "^4.5.1", + "fast-safe-stringify": "^2.0.7", + "jmespath": "^0.15.0", + "joycon": "^3.0.0", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "rfdc": "^1.3.0", + "split2": "^3.1.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/@probot/pino/node_modules/pino-pretty/node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, "node_modules/@probot/pino/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, - "node_modules/@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "node_modules/@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "node_modules/@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "node_modules/@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "node_modules/@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, "node_modules/@sentry-internal/tracing": { "version": "7.59.3", "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.59.3.tgz", @@ -5914,14 +5887,13 @@ "dev": true }, "node_modules/@types/pino": { - "version": "6.3.12", - "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", - "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-7.0.5.tgz", + "integrity": "sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg==", + "deprecated": "This is a stub types definition. pino provides its own type definitions, so you do not need this installed.", + "dev": true, "dependencies": { - "@types/node": "*", - "@types/pino-pretty": "*", - "@types/pino-std-serializers": "*", - "sonic-boom": "^2.1.0" + "pino": "*" } }, "node_modules/@types/pino-http": { @@ -5932,6 +5904,17 @@ "@types/pino": "6.3" } }, + "node_modules/@types/pino-http/node_modules/@types/pino": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", + "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", + "dependencies": { + "@types/node": "*", + "@types/pino-pretty": "*", + "@types/pino-std-serializers": "*", + "sonic-boom": "^2.1.0" + } + }, "node_modules/@types/pino-pretty": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/pino-pretty/-/pino-pretty-5.0.0.tgz", @@ -6032,11 +6015,6 @@ "@types/serve-static": "*" } }, - "node_modules/@types/triple-beam": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", - "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" - }, "node_modules/@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -6323,6 +6301,17 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -6632,19 +6621,6 @@ "node": ">=0.8" } }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7020,17 +6996,6 @@ "node": ">=14.20.1" } }, - "node_modules/btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==", - "bin": { - "btoa": "bin/btoa.js" - }, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", @@ -7311,15 +7276,6 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, - "node_modules/color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "dependencies": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7335,16 +7291,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/color-support": { "version": "1.1.3", @@ -7354,32 +7302,10 @@ "color-support": "bin.js" } }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -7786,11 +7712,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "node_modules/enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -8072,6 +7993,14 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -8289,6 +8218,11 @@ "node >=0.6.0" ] }, + "node_modules/fast-copy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", + "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8395,11 +8329,6 @@ "bser": "2.1.1" } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -8502,11 +8431,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, "node_modules/follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -8661,7 +8585,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -8915,6 +8838,52 @@ "node": ">=12.0.0" } }, + "node_modules/help-me": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", + "dependencies": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/help-me/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/help-me/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/help-me/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -9293,6 +9262,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, "engines": { "node": ">=8" }, @@ -10126,11 +10096,6 @@ "node": ">=6" } }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -10262,24 +10227,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/logform": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", - "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", - "dependencies": { - "@colors/colors": "1.5.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - } - }, - "node_modules/long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -13582,6 +13529,14 @@ "@octokit/openapi-types": "^14.0.0" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13610,14 +13565,6 @@ "wrappy": "1" } }, - "node_modules/one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "dependencies": { - "fn.name": "1.x.x" - } - }, "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -13888,73 +13835,206 @@ } }, "node_modules/pino": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", - "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.16.1.tgz", + "integrity": "sha512-3bKsVhBmgPjGV9pyn4fO/8RtoVDR8ssW1ev819FsRXlRNgW8gR/9Kx+gCK4UPWd4JjrRDLWpzd/pb1AyWm3MGA==", "dependencies": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.8", - "flatstr": "^1.0.12", - "pino-std-serializers": "^3.1.0", - "process-warning": "^1.0.0", + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", "quick-format-unescaped": "^4.0.3", - "sonic-boom": "^1.0.2" + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" }, "bin": { "pino": "bin.js" } }, - "node_modules/pino-http": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-5.8.0.tgz", - "integrity": "sha512-YwXiyRb9y0WCD1P9PcxuJuh3Dc5qmXde/paJE86UGYRdiFOi828hR9iUGmk5gaw6NBT9gLtKANOHFimvh19U5w==", + "node_modules/pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", "dependencies": { - "fast-url-parser": "^1.1.3", - "pino": "^6.13.0", - "pino-std-serializers": "^4.0.0" + "readable-stream": "^4.0.0", + "split2": "^4.0.0" } }, - "node_modules/pino-http/node_modules/pino-std-serializers": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", - "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/pino-abstract-transport/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-http": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-8.5.1.tgz", + "integrity": "sha512-T/3d9YHKBYpv/QHjNy73P5BNYYkRrC2/D6CxKMecG4fKFLN+B2iC6LsKYzGRTRV+Ld3fjxFC1ca4TUGbPdzk+Q==", + "dependencies": { + "get-caller-file": "^2.0.5", + "pino": "^8.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0" + } }, "node_modules/pino-pretty": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-6.0.0.tgz", - "integrity": "sha512-jyeR2fXXWc68st1DTTM5NhkHlx8p+1fKZMfm84Jwq+jSw08IwAjNaZBZR6ts69hhPOfOjg/NiE1HYW7vBRPL3A==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.2.3.tgz", + "integrity": "sha512-4jfIUc8TC1GPUfDyMSlW1STeORqkoxec71yhxIpLDQapUu8WOuoz2TTCoidrIssyz78LZC69whBMPIKCMbi3cw==", "dependencies": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "colorette": "^1.3.0", - "dateformat": "^4.5.1", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^3.0.0", + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "rfdc": "^1.3.0", - "split2": "^3.1.1", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", "strip-json-comments": "^3.1.1" }, "bin": { "pino-pretty": "bin.js" } }, - "node_modules/pino-pretty/node_modules/jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==", - "engines": { - "node": ">= 0.6.0" + "node_modules/pino-pretty/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/pino-pretty/node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "node_modules/pino-pretty/node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/pino-pretty/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/pino-pretty/node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", "dependencies": { - "readable-stream": "^3.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", + "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", + "dependencies": { + "atomic-sleep": "^1.0.0" } }, "node_modules/pino-std-serializers": { @@ -13962,18 +14042,12 @@ "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" }, - "node_modules/pino/node_modules/pino-std-serializers": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", - "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" - }, "node_modules/pino/node_modules/sonic-boom": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", - "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", + "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", "dependencies": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" + "atomic-sleep": "^1.0.0" } }, "node_modules/pirates": { @@ -14448,6 +14522,25 @@ "@octokit/openapi-types": "^14.0.0" } }, + "node_modules/probot/node_modules/@types/pino": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", + "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", + "dependencies": { + "@types/node": "*", + "@types/pino-pretty": "*", + "@types/pino-std-serializers": "*", + "sonic-boom": "^2.1.0" + } + }, + "node_modules/probot/node_modules/@types/pino/node_modules/sonic-boom": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", + "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/probot/node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -14512,15 +14605,74 @@ "node": ">=10" } }, + "node_modules/probot/node_modules/pino": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", + "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", + "dependencies": { + "fast-redact": "^3.0.0", + "fast-safe-stringify": "^2.0.8", + "flatstr": "^1.0.12", + "pino-std-serializers": "^3.1.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "sonic-boom": "^1.0.2" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/probot/node_modules/pino-http": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-5.8.0.tgz", + "integrity": "sha512-YwXiyRb9y0WCD1P9PcxuJuh3Dc5qmXde/paJE86UGYRdiFOi828hR9iUGmk5gaw6NBT9gLtKANOHFimvh19U5w==", + "dependencies": { + "fast-url-parser": "^1.1.3", + "pino": "^6.13.0", + "pino-std-serializers": "^4.0.0" + } + }, + "node_modules/probot/node_modules/pino-http/node_modules/pino-std-serializers": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", + "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" + }, + "node_modules/probot/node_modules/pino-std-serializers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", + "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" + }, + "node_modules/probot/node_modules/process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" + }, + "node_modules/probot/node_modules/sonic-boom": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", + "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, "node_modules/probot/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/process-warning": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", - "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", + "integrity": "sha512-N6mp1+2jpQr3oCFMz6SeHRGbv6Slb20bRhj4v3xR99HqNToAcOe1MFOp4tytyzOfJn+QtN8Rf7U/h2KAn4kC6g==" }, "node_modules/prompts": { "version": "2.4.2", @@ -14535,29 +14687,6 @@ "node": ">= 6" } }, - "node_modules/protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", - "hasInstallScript": true, - "dependencies": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14799,6 +14928,14 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/redis-commands": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", @@ -14990,6 +15127,11 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -15141,19 +15283,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - }, "node_modules/simple-update-notifier": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", @@ -15328,14 +15457,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "node_modules/stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", - "engines": { - "node": "*" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -15647,17 +15768,20 @@ "node": ">=8" } }, - "node_modules/text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tiny-lru": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.0.1.tgz", @@ -15748,14 +15872,6 @@ "node": ">=6" } }, - "node_modules/triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", - "engines": { - "node": ">= 14.0.0" - } - }, "node_modules/ts-jest": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", @@ -16063,11 +16179,6 @@ "querystring": "0.2.0" } }, - "node_modules/url-polyfill": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.12.tgz", - "integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==" - }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -16240,278 +16351,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/winston": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", - "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", - "dependencies": { - "@colors/colors": "1.5.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - }, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/winston-loki": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/winston-loki/-/winston-loki-6.0.7.tgz", - "integrity": "sha512-6psEy428ckTXP/ni+DrA4vas2Ldt/43TFm7beoVExKLlTyEwu7zu2c8GBGbnxrdzRQAxXUfom2sK7zr4QP8y5g==", - "dependencies": { - "async-exit-hook": "2.0.1", - "btoa": "^1.2.1", - "protobufjs": "^7.2.4", - "url-polyfill": "^1.1.12", - "winston-transport": "^4.3.0" - }, - "optionalDependencies": { - "snappy": "7.1.1" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-android-arm-eabi": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.1.1.tgz", - "integrity": "sha512-NKd/ztuVEgQaAaNVQ5zZaCB9VV+7+uBXBHqhaE5iSapQhLc41szTlT0s68FCee75OoT3vhqdA6Jp5TrzZ2WOaw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-android-arm64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.1.1.tgz", - "integrity": "sha512-DktruMAO0K0toTnxNHg2GWNIAPJqdvIchCsdsRaKyuEnG101qBg0mYiRCAhxHgbT6RJlOGbUPKkbA9KKRhEJUg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-darwin-arm64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.1.1.tgz", - "integrity": "sha512-3LZyoAw3Qa5F7sCCTkSkhmGlydwUKU6L3Jl46eKHO2Ctm8Gcjxww6T7MfwlwGZ6JqAM6d1d++WLzUZPCGXVmag==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-darwin-x64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.1.1.tgz", - "integrity": "sha512-X1D2F67bQkPwr5iSR29/RnOrLwAkB55YO6t41toABzla3mflLDpzZcakz6FokIukykf7ey31/t73v/4pbgaBkg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-freebsd-x64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.1.1.tgz", - "integrity": "sha512-vSeuf+An8jFVHPAn5IbWE9hTGU9PFAaZLj/X7rKTQQtZstnDsHgWe6u4g7FHLuOdwQ8TvhcxAEpNlYIXIk4AJg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-linux-arm-gnueabihf": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.1.1.tgz", - "integrity": "sha512-/yyN6QsnOs3D1+jI3SfRX+gtnD86rbixdfmgxv9g40+FrDaDTLAu/3VuZIqH02qqq/xiWbDnkO+42RGxXDzTCw==", - "cpu": [ - "arm" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-linux-arm64-gnu": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.1.1.tgz", - "integrity": "sha512-StEeUCSwUoajgrBtiCQPTkHu+0Q4QlYndghGZNdbN1zJ1ny70YzPpevaFBUyjI/eJ+FN9uICKtwTPtQNSILS5g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-linux-arm64-musl": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.1.1.tgz", - "integrity": "sha512-jWEBRzj+lswZVYf0b5eY0fjMlBL9L9yqjmTuv2UIMjJNHPuR282LK/s3Fm9sYIXQtKkiCo5JyhmIcoghZ3v0Eg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-linux-x64-gnu": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.1.1.tgz", - "integrity": "sha512-41DPoAUFAU9VNrj/96qKfStH2Xq88ZYIsSz8BlITDm2ScoeDGOGbmaWguCXU7I+bC2uKWTmUVMXKqk6tVY6LEg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-linux-x64-musl": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.1.1.tgz", - "integrity": "sha512-xR4hzFQqVq6J8Zf6XyUVtFJBaRgDyAQYUoBsCr92tZ7gI/0RlWCV6Q6JMO/wP5CSsvyFJIAtSUXXqlzIpw0GPA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-win32-arm64-msvc": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.1.1.tgz", - "integrity": "sha512-2mHPadctsaYtrfSV5Na8ooTdI5rflPxP1pceY4us6vbjeWrfgB+KQCuEFOHsGXqFNfsi6L9nWH8nB9swnxnSyw==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-win32-ia32-msvc": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.1.1.tgz", - "integrity": "sha512-FOMgs9W71hdgjyl3T9F7b/WEIuoryfgBqsyhtHjAaa/98R0BUHl0bOoHg8ka0b5GgnhLBHkX2Yd6VD+Si9Q2ww==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/@napi-rs/snappy-win32-x64-msvc": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.1.1.tgz", - "integrity": "sha512-Mu3yELySvzhBcNTVCq+hYxVh+lH3/KjoQ5HIEb3DDPoX0AGRTm3XZa+usq8pFWjl91Cgp9nWK+9lVSkCCIRaKA==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/winston-loki/node_modules/snappy": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.1.1.tgz", - "integrity": "sha512-mL7GGPJ+WdsaFT5aR/uEqCq8cPg2VbhyifDEP7AeqIVDsAC8LBGYbZP1Qzoa2Ym84OW7JEQXqIpwqFp1EQw5BA==", - "optional": true, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/snappy-android-arm-eabi": "7.1.1", - "@napi-rs/snappy-android-arm64": "7.1.1", - "@napi-rs/snappy-darwin-arm64": "7.1.1", - "@napi-rs/snappy-darwin-x64": "7.1.1", - "@napi-rs/snappy-freebsd-x64": "7.1.1", - "@napi-rs/snappy-linux-arm-gnueabihf": "7.1.1", - "@napi-rs/snappy-linux-arm64-gnu": "7.1.1", - "@napi-rs/snappy-linux-arm64-musl": "7.1.1", - "@napi-rs/snappy-linux-x64-gnu": "7.1.1", - "@napi-rs/snappy-linux-x64-musl": "7.1.1", - "@napi-rs/snappy-win32-arm64-msvc": "7.1.1", - "@napi-rs/snappy-win32-ia32-msvc": "7.1.1", - "@napi-rs/snappy-win32-x64-msvc": "7.1.1" - } - }, - "node_modules/winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", - "dependencies": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - }, - "engines": { - "node": ">= 6.4.0" - } - }, "node_modules/wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -19476,11 +19315,6 @@ "integrity": "sha512-pojgSWYKNIwFM6wWDNct1YD0+8nIxhe2jp5jBbK8JGU60dEs2o0Yw3mCo2y7nBwbvRC2oEots/BlLMVb1Wdo8A==", "requires": {} }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" - }, "@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -19502,16 +19336,6 @@ } } }, - "@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", - "requires": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" - } - }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -20578,6 +20402,45 @@ "tslib": "^1.9.3" } }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==" + }, + "pino-pretty": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-6.0.0.tgz", + "integrity": "sha512-jyeR2fXXWc68st1DTTM5NhkHlx8p+1fKZMfm84Jwq+jSw08IwAjNaZBZR6ts69hhPOfOjg/NiE1HYW7vBRPL3A==", + "requires": { + "@hapi/bourne": "^2.0.0", + "args": "^5.0.1", + "colorette": "^1.3.0", + "dateformat": "^4.5.1", + "fast-safe-stringify": "^2.0.7", + "jmespath": "^0.15.0", + "joycon": "^3.0.0", + "pump": "^3.0.0", + "readable-stream": "^3.6.0", + "rfdc": "^1.3.0", + "split2": "^3.1.1", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + } + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -20585,60 +20448,6 @@ } } }, - "@protobufjs/aspromise": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" - }, - "@protobufjs/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" - }, - "@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" - }, - "@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" - }, - "@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" - } - }, - "@protobufjs/float": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" - }, - "@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" - }, - "@protobufjs/path": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" - }, - "@protobufjs/pool": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" - }, - "@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" - }, "@sentry-internal/tracing": { "version": "7.59.3", "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.59.3.tgz", @@ -21461,14 +21270,12 @@ "dev": true }, "@types/pino": { - "version": "6.3.12", - "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", - "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-7.0.5.tgz", + "integrity": "sha512-wKoab31pknvILkxAF8ss+v9iNyhw5Iu/0jLtRkUD74cNfOOLJNnqfFKAv0r7wVaTQxRZtWrMpGfShwwBjOcgcg==", + "dev": true, "requires": { - "@types/node": "*", - "@types/pino-pretty": "*", - "@types/pino-std-serializers": "*", - "sonic-boom": "^2.1.0" + "pino": "*" } }, "@types/pino-http": { @@ -21477,6 +21284,19 @@ "integrity": "sha512-A9MW6VCnx5ii7s+Fs5aFIw+aSZcBCpsZ/atpxamu8tTsvWFacxSf2Hrn1Ohn1jkVRB/LiPGOapRXcFawDBnDnA==", "requires": { "@types/pino": "6.3" + }, + "dependencies": { + "@types/pino": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", + "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", + "requires": { + "@types/node": "*", + "@types/pino-pretty": "*", + "@types/pino-std-serializers": "*", + "sonic-boom": "^2.1.0" + } + } } }, "@types/pino-pretty": { @@ -21577,11 +21397,6 @@ "@types/serve-static": "*" } }, - "@types/triple-beam": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", - "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" - }, "@types/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -21776,6 +21591,14 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -22004,16 +21827,6 @@ "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" - }, - "async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==" - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -22299,11 +22112,6 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==" }, - "btoa": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz", - "integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==" - }, "btoa-lite": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", @@ -22497,30 +22305,6 @@ "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", "dev": true }, - "color": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", - "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", - "requires": { - "color-convert": "^1.9.3", - "color-string": "^1.6.0" - }, - "dependencies": { - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - } - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -22533,16 +22317,8 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "color-support": { "version": "1.1.3", @@ -22550,18 +22326,9 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" }, "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==" - }, - "colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "requires": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" }, "combined-stream": { "version": "1.0.8", @@ -22867,11 +22634,6 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "enabled": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", - "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" - }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -23071,6 +22833,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -23243,6 +23010,11 @@ "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true }, + "fast-copy": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.1.tgz", + "integrity": "sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -23329,11 +23101,6 @@ "bser": "2.1.1" } }, - "fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -23417,11 +23184,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "fn.name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", - "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" - }, "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", @@ -23532,8 +23294,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { "version": "1.2.1", @@ -23700,6 +23461,45 @@ "resolved": "https://registry.npmjs.org/helmet/-/helmet-5.1.1.tgz", "integrity": "sha512-/yX0oVZBggA9cLJh8aw3PPCfedBnbd7J2aowjzsaWwZh7/UFY0nccn/aHAggIgWUFfnykX8GKd3a1pSbrmlcVQ==" }, + "help-me": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-4.2.0.tgz", + "integrity": "sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==", + "requires": { + "glob": "^8.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "hexoid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", @@ -23969,7 +23769,8 @@ "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true }, "is-typed-array": { "version": "1.1.12", @@ -24613,11 +24414,6 @@ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true }, - "kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" - }, "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -24727,24 +24523,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "logform": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", - "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", - "requires": { - "@colors/colors": "1.5.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" - } - }, - "long": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", - "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" - }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -27076,6 +26854,11 @@ } } }, + "on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -27098,14 +26881,6 @@ "wrappy": "1" } }, - "one-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", - "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", - "requires": { - "fn.name": "1.x.x" - } - }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", @@ -27290,82 +27065,144 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pino": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", - "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", + "version": "8.16.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.16.1.tgz", + "integrity": "sha512-3bKsVhBmgPjGV9pyn4fO/8RtoVDR8ssW1ev819FsRXlRNgW8gR/9Kx+gCK4UPWd4JjrRDLWpzd/pb1AyWm3MGA==", "requires": { - "fast-redact": "^3.0.0", - "fast-safe-stringify": "^2.0.8", - "flatstr": "^1.0.12", - "pino-std-serializers": "^3.1.0", - "process-warning": "^1.0.0", + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.1.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", "quick-format-unescaped": "^4.0.3", - "sonic-boom": "^1.0.2" + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.7.0", + "thread-stream": "^2.0.0" }, "dependencies": { - "pino-std-serializers": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", - "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" - }, "sonic-boom": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", - "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", + "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", "requires": { - "atomic-sleep": "^1.0.0", - "flatstr": "^1.0.12" + "atomic-sleep": "^1.0.0" + } + } + } + }, + "pino-abstract-transport": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz", + "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==", + "requires": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } } } }, "pino-http": { - "version": "5.8.0", - "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-5.8.0.tgz", - "integrity": "sha512-YwXiyRb9y0WCD1P9PcxuJuh3Dc5qmXde/paJE86UGYRdiFOi828hR9iUGmk5gaw6NBT9gLtKANOHFimvh19U5w==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-8.5.1.tgz", + "integrity": "sha512-T/3d9YHKBYpv/QHjNy73P5BNYYkRrC2/D6CxKMecG4fKFLN+B2iC6LsKYzGRTRV+Ld3fjxFC1ca4TUGbPdzk+Q==", "requires": { - "fast-url-parser": "^1.1.3", - "pino": "^6.13.0", - "pino-std-serializers": "^4.0.0" - }, - "dependencies": { - "pino-std-serializers": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", - "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" - } + "get-caller-file": "^2.0.5", + "pino": "^8.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0" } }, "pino-pretty": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-6.0.0.tgz", - "integrity": "sha512-jyeR2fXXWc68st1DTTM5NhkHlx8p+1fKZMfm84Jwq+jSw08IwAjNaZBZR6ts69hhPOfOjg/NiE1HYW7vBRPL3A==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-10.2.3.tgz", + "integrity": "sha512-4jfIUc8TC1GPUfDyMSlW1STeORqkoxec71yhxIpLDQapUu8WOuoz2TTCoidrIssyz78LZC69whBMPIKCMbi3cw==", "requires": { - "@hapi/bourne": "^2.0.0", - "args": "^5.0.1", - "colorette": "^1.3.0", - "dateformat": "^4.5.1", - "fast-safe-stringify": "^2.0.7", - "jmespath": "^0.15.0", - "joycon": "^3.0.0", + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.0", + "fast-safe-stringify": "^2.1.1", + "help-me": "^4.0.1", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", "pump": "^3.0.0", - "readable-stream": "^3.6.0", - "rfdc": "^1.3.0", - "split2": "^3.1.1", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^3.0.0", "strip-json-comments": "^3.1.1" }, "dependencies": { - "jmespath": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", - "integrity": "sha512-+kHj8HXArPfpPEKGLZ+kB5ONRTCiGQXo8RQYL0hH8t6pWXUBBK5KkkQmTNOwKK4LEsd0yTsgtjJVm4UBSZea4w==" - }, - "split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "requires": { - "readable-stream": "^3.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "sonic-boom": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.7.0.tgz", + "integrity": "sha512-IudtNvSqA/ObjN97tfgNmOKyDOs4dNcg4cUUsHDebqsgb8wGBBwb31LIgShNO8fye0dFI52X1+tFoKKI6Rq1Gg==", + "requires": { + "atomic-sleep": "^1.0.0" } } } @@ -27801,6 +27638,27 @@ "@octokit/openapi-types": "^14.0.0" } }, + "@types/pino": { + "version": "6.3.12", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-6.3.12.tgz", + "integrity": "sha512-dsLRTq8/4UtVSpJgl9aeqHvbh6pzdmjYD3C092SYgLD2TyoCqHpTJk6vp8DvCTGGc7iowZ2MoiYiVUUCcu7muw==", + "requires": { + "@types/node": "*", + "@types/pino-pretty": "*", + "@types/pino-std-serializers": "*", + "sonic-boom": "^2.1.0" + }, + "dependencies": { + "sonic-boom": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", + "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", + "requires": { + "atomic-sleep": "^1.0.0" + } + } + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -27849,6 +27707,56 @@ "yallist": "^4.0.0" } }, + "pino": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-6.14.0.tgz", + "integrity": "sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==", + "requires": { + "fast-redact": "^3.0.0", + "fast-safe-stringify": "^2.0.8", + "flatstr": "^1.0.12", + "pino-std-serializers": "^3.1.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "sonic-boom": "^1.0.2" + } + }, + "pino-http": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/pino-http/-/pino-http-5.8.0.tgz", + "integrity": "sha512-YwXiyRb9y0WCD1P9PcxuJuh3Dc5qmXde/paJE86UGYRdiFOi828hR9iUGmk5gaw6NBT9gLtKANOHFimvh19U5w==", + "requires": { + "fast-url-parser": "^1.1.3", + "pino": "^6.13.0", + "pino-std-serializers": "^4.0.0" + }, + "dependencies": { + "pino-std-serializers": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", + "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==" + } + } + }, + "pino-std-serializers": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-3.2.0.tgz", + "integrity": "sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==" + }, + "process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" + }, + "sonic-boom": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-1.4.1.tgz", + "integrity": "sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==", + "requires": { + "atomic-sleep": "^1.0.0", + "flatstr": "^1.0.12" + } + }, "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -27856,10 +27764,15 @@ } } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "process-warning": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", - "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", + "integrity": "sha512-N6mp1+2jpQr3oCFMz6SeHRGbv6Slb20bRhj4v3xR99HqNToAcOe1MFOp4tytyzOfJn+QtN8Rf7U/h2KAn4kC6g==" }, "prompts": { "version": "2.4.2", @@ -27871,25 +27784,6 @@ "sisteransi": "^1.0.5" } }, - "protobufjs": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", - "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", - "requires": { - "@protobufjs/aspromise": "^1.1.2", - "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", - "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", - "@protobufjs/path": "^1.1.2", - "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", - "@types/node": ">=13.7.0", - "long": "^5.0.0" - } - }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -28043,6 +27937,11 @@ "picomatch": "^2.2.1" } }, + "real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==" + }, "redis-commands": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", @@ -28168,6 +28067,11 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==" }, + "secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, "semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -28298,21 +28202,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "requires": { - "is-arrayish": "^0.3.1" - }, - "dependencies": { - "is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" - } - } - }, "simple-update-notifier": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", @@ -28452,11 +28341,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "stack-trace": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", - "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" - }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -28693,17 +28577,20 @@ "minimatch": "^3.0.4" } }, - "text-hex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", - "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "thread-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz", + "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==", + "requires": { + "real-require": "^0.2.0" + } + }, "tiny-lru": { "version": "11.0.1", "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.0.1.tgz", @@ -28771,11 +28658,6 @@ } } }, - "triple-beam": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", - "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==" - }, "ts-jest": { "version": "29.1.1", "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", @@ -28985,11 +28867,6 @@ } } }, - "url-polyfill": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.12.tgz", - "integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==" - }, "util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -29120,148 +28997,6 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "winston": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.10.0.tgz", - "integrity": "sha512-nT6SIDaE9B7ZRO0u3UvdrimG0HkB7dSTAgInQnNR2SOPJ4bvq5q79+pXLftKmP52lJGW15+H5MCK0nM9D3KB/g==", - "requires": { - "@colors/colors": "1.5.0", - "@dabh/diagnostics": "^2.0.2", - "async": "^3.2.3", - "is-stream": "^2.0.0", - "logform": "^2.4.0", - "one-time": "^1.0.0", - "readable-stream": "^3.4.0", - "safe-stable-stringify": "^2.3.1", - "stack-trace": "0.0.x", - "triple-beam": "^1.3.0", - "winston-transport": "^4.5.0" - } - }, - "winston-loki": { - "version": "6.0.7", - "resolved": "https://registry.npmjs.org/winston-loki/-/winston-loki-6.0.7.tgz", - "integrity": "sha512-6psEy428ckTXP/ni+DrA4vas2Ldt/43TFm7beoVExKLlTyEwu7zu2c8GBGbnxrdzRQAxXUfom2sK7zr4QP8y5g==", - "requires": { - "async-exit-hook": "2.0.1", - "btoa": "^1.2.1", - "protobufjs": "^7.2.4", - "snappy": "7.1.1", - "url-polyfill": "^1.1.12", - "winston-transport": "^4.3.0" - }, - "dependencies": { - "@napi-rs/snappy-android-arm-eabi": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.1.1.tgz", - "integrity": "sha512-NKd/ztuVEgQaAaNVQ5zZaCB9VV+7+uBXBHqhaE5iSapQhLc41szTlT0s68FCee75OoT3vhqdA6Jp5TrzZ2WOaw==", - "optional": true - }, - "@napi-rs/snappy-android-arm64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.1.1.tgz", - "integrity": "sha512-DktruMAO0K0toTnxNHg2GWNIAPJqdvIchCsdsRaKyuEnG101qBg0mYiRCAhxHgbT6RJlOGbUPKkbA9KKRhEJUg==", - "optional": true - }, - "@napi-rs/snappy-darwin-arm64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.1.1.tgz", - "integrity": "sha512-3LZyoAw3Qa5F7sCCTkSkhmGlydwUKU6L3Jl46eKHO2Ctm8Gcjxww6T7MfwlwGZ6JqAM6d1d++WLzUZPCGXVmag==", - "optional": true - }, - "@napi-rs/snappy-darwin-x64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.1.1.tgz", - "integrity": "sha512-X1D2F67bQkPwr5iSR29/RnOrLwAkB55YO6t41toABzla3mflLDpzZcakz6FokIukykf7ey31/t73v/4pbgaBkg==", - "optional": true - }, - "@napi-rs/snappy-freebsd-x64": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.1.1.tgz", - "integrity": "sha512-vSeuf+An8jFVHPAn5IbWE9hTGU9PFAaZLj/X7rKTQQtZstnDsHgWe6u4g7FHLuOdwQ8TvhcxAEpNlYIXIk4AJg==", - "optional": true - }, - "@napi-rs/snappy-linux-arm-gnueabihf": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.1.1.tgz", - "integrity": "sha512-/yyN6QsnOs3D1+jI3SfRX+gtnD86rbixdfmgxv9g40+FrDaDTLAu/3VuZIqH02qqq/xiWbDnkO+42RGxXDzTCw==", - "optional": true - }, - "@napi-rs/snappy-linux-arm64-gnu": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.1.1.tgz", - "integrity": "sha512-StEeUCSwUoajgrBtiCQPTkHu+0Q4QlYndghGZNdbN1zJ1ny70YzPpevaFBUyjI/eJ+FN9uICKtwTPtQNSILS5g==", - "optional": true - }, - "@napi-rs/snappy-linux-arm64-musl": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.1.1.tgz", - "integrity": "sha512-jWEBRzj+lswZVYf0b5eY0fjMlBL9L9yqjmTuv2UIMjJNHPuR282LK/s3Fm9sYIXQtKkiCo5JyhmIcoghZ3v0Eg==", - "optional": true - }, - "@napi-rs/snappy-linux-x64-gnu": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.1.1.tgz", - "integrity": "sha512-41DPoAUFAU9VNrj/96qKfStH2Xq88ZYIsSz8BlITDm2ScoeDGOGbmaWguCXU7I+bC2uKWTmUVMXKqk6tVY6LEg==", - "optional": true - }, - "@napi-rs/snappy-linux-x64-musl": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.1.1.tgz", - "integrity": "sha512-xR4hzFQqVq6J8Zf6XyUVtFJBaRgDyAQYUoBsCr92tZ7gI/0RlWCV6Q6JMO/wP5CSsvyFJIAtSUXXqlzIpw0GPA==", - "optional": true - }, - "@napi-rs/snappy-win32-arm64-msvc": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.1.1.tgz", - "integrity": "sha512-2mHPadctsaYtrfSV5Na8ooTdI5rflPxP1pceY4us6vbjeWrfgB+KQCuEFOHsGXqFNfsi6L9nWH8nB9swnxnSyw==", - "optional": true - }, - "@napi-rs/snappy-win32-ia32-msvc": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.1.1.tgz", - "integrity": "sha512-FOMgs9W71hdgjyl3T9F7b/WEIuoryfgBqsyhtHjAaa/98R0BUHl0bOoHg8ka0b5GgnhLBHkX2Yd6VD+Si9Q2ww==", - "optional": true - }, - "@napi-rs/snappy-win32-x64-msvc": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.1.1.tgz", - "integrity": "sha512-Mu3yELySvzhBcNTVCq+hYxVh+lH3/KjoQ5HIEb3DDPoX0AGRTm3XZa+usq8pFWjl91Cgp9nWK+9lVSkCCIRaKA==", - "optional": true - }, - "snappy": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.1.1.tgz", - "integrity": "sha512-mL7GGPJ+WdsaFT5aR/uEqCq8cPg2VbhyifDEP7AeqIVDsAC8LBGYbZP1Qzoa2Ym84OW7JEQXqIpwqFp1EQw5BA==", - "optional": true, - "requires": { - "@napi-rs/snappy-android-arm-eabi": "7.1.1", - "@napi-rs/snappy-android-arm64": "7.1.1", - "@napi-rs/snappy-darwin-arm64": "7.1.1", - "@napi-rs/snappy-darwin-x64": "7.1.1", - "@napi-rs/snappy-freebsd-x64": "7.1.1", - "@napi-rs/snappy-linux-arm-gnueabihf": "7.1.1", - "@napi-rs/snappy-linux-arm64-gnu": "7.1.1", - "@napi-rs/snappy-linux-arm64-musl": "7.1.1", - "@napi-rs/snappy-linux-x64-gnu": "7.1.1", - "@napi-rs/snappy-linux-x64-musl": "7.1.1", - "@napi-rs/snappy-win32-arm64-msvc": "7.1.1", - "@napi-rs/snappy-win32-ia32-msvc": "7.1.1", - "@napi-rs/snappy-win32-x64-msvc": "7.1.1" - } - } - } - }, - "winston-transport": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", - "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", - "requires": { - "logform": "^2.3.2", - "readable-stream": "^3.6.0", - "triple-beam": "^1.3.0" - } - }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index f2c616671..77a90f87d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -42,6 +42,9 @@ "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", "passport-google-oauth20": "^2.0.0", + "pino": "^8.16.1", + "pino-http": "^8.5.1", + "pino-pretty": "^10.2.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", @@ -52,8 +55,6 @@ "tweetnacl-util": "^0.15.1", "typescript": "^4.9.3", "utility-types": "^3.10.0", - "winston": "^3.8.2", - "winston-loki": "^6.0.6", "zod": "^3.22.3" }, "overrides": { @@ -104,6 +105,7 @@ "@types/nodemailer": "^6.4.6", "@types/passport": "^1.0.12", "@types/picomatch": "^2.3.0", + "@types/pino": "^7.0.5", "@types/supertest": "^2.0.12", "@types/swagger-jsdoc": "^6.0.1", "@types/swagger-ui-express": "^4.1.3", diff --git a/backend/src/helpers/database.ts b/backend/src/helpers/database.ts index 611e941f5..dc6d2faa6 100644 --- a/backend/src/helpers/database.ts +++ b/backend/src/helpers/database.ts @@ -1,5 +1,5 @@ import mongoose from "mongoose"; -import { getLogger } from "../utils/logger"; +import { logger } from "../utils/logging"; /** * Initialize database connection @@ -18,10 +18,10 @@ export const initDatabaseHelper = async ({ // allow empty strings to pass the required validator mongoose.Schema.Types.String.checkRequired(v => typeof v === "string"); - (await getLogger("database")).info("Database connection established"); + logger.info("Database connection established"); } catch (err) { - (await getLogger("database")).error(`Unable to establish Database connection due to the error.\n${err}`); + logger.error(err, "Unable to establish database connection"); } return mongoose.connection; diff --git a/backend/src/index.ts b/backend/src/index.ts index aa4ac2cd5..b1a7d80f5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -5,6 +5,8 @@ import express from "express"; require("express-async-errors"); import helmet from "helmet"; import cors from "cors"; +import { logger } from "./utils/logging"; +import httpLogger from "pino-http"; import { DatabaseService } from "./services"; import { EELicenseService, GithubSecretScanningService } from "./ee/services"; import { setUpHealthEndpoint } from "./services/health"; @@ -73,7 +75,7 @@ import { workspaces as v3WorkspacesRouter } from "./routes/v3"; import { healthCheck } from "./routes/status"; -import { getLogger } from "./utils/logger"; +// import { getLogger } from "./utils/logger"; import { RouteNotFoundError } from "./utils/errors"; import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { @@ -94,12 +96,20 @@ import path from "path"; let handler: null | any = null; const main = async () => { + const port = await getPort(); + await setup(); await EELicenseService.initGlobalFeatureSet(); const app = express(); app.enable("trust proxy"); + + app.use(httpLogger({ + logger, + autoLogging: false + })); + app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); @@ -164,7 +174,7 @@ const main = async () => { const nextApp = new NextServer({ dev: false, dir: nextJsBuildPath, - port: await getPort(), + port, conf, hostname: "local", customServer: false @@ -255,8 +265,8 @@ const main = async () => { app.use(requestErrorHandler); - const server = app.listen(await getPort(), async () => { - (await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`); + const server = app.listen(port, async () => { + logger.info(`Server started listening at port ${port}`); }); // await createTestUserForDevelopment(); diff --git a/backend/src/middleware/requestErrorHandler.ts b/backend/src/middleware/requestErrorHandler.ts index 01b37bf3d..f7e964630 100644 --- a/backend/src/middleware/requestErrorHandler.ts +++ b/backend/src/middleware/requestErrorHandler.ts @@ -2,7 +2,7 @@ import * as Sentry from "@sentry/node"; import { ErrorRequestHandler } from "express"; import { TokenExpiredError } from "jsonwebtoken"; import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; -import { getLogger } from "../utils/logger"; +import { logger } from "../utils/logging"; import RequestError from "../utils/requestError"; import { ForbiddenError } from "@casl/ability"; @@ -15,10 +15,14 @@ export const requestErrorHandler: ErrorRequestHandler = async ( if (res.headersSent) return next(); const logAndCaptureException = async (error: RequestError) => { - (await getLogger("backend-main")).log( - (error).levelName.toLowerCase(), - `${error.stack}\n${error.message}` - ); + // TODO: tie pino error-handling to error types/levels + + // (await getLogger("backend-main")).log( + // (error).levelName.toLowerCase(), + // `${error.stack}\n${error.message}` + // ); + + logger.error(error.stack, error.message); //* Set Sentry user identification if req.user is populated if (req.user !== undefined && req.user !== null) { diff --git a/backend/src/services/TelemetryService.ts b/backend/src/services/TelemetryService.ts index 3db2fdb89..63e4f0c12 100644 --- a/backend/src/services/TelemetryService.ts +++ b/backend/src/services/TelemetryService.ts @@ -1,5 +1,5 @@ import { PostHog } from "posthog-node"; -import { getLogger } from "../utils/logger"; +import { logger } from "../utils/logging"; import { AuthData } from "../interfaces/middleware"; import { getNodeEnv, @@ -22,13 +22,13 @@ class Telemetry { * Logs telemetry enable/disable notice. */ static logTelemetryMessage = async () => { + if(!(await getTelemetryEnabled())){ - (await getLogger("backend-main")).info([ - "", + [ "To improve, Infisical collects telemetry data about general usage.", "This helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth as we support Infisical as open-source software.", "To opt into telemetry, you can set `TELEMETRY_ENABLED=true` within the environment variables.", - ].join("\n")) + ].forEach(line => logger.info(line)); } } diff --git a/backend/src/services/health.ts b/backend/src/services/health.ts index 5ddfb8171..b9cb90fa2 100644 --- a/backend/src/services/health.ts +++ b/backend/src/services/health.ts @@ -1,10 +1,10 @@ import mongoose from "mongoose"; import { createTerminus } from "@godaddy/terminus"; -import { getLogger } from "../utils/logger"; +import { logger } from "../utils/logging"; export const setUpHealthEndpoint = (server: T) => { const onSignal = async () => { - (await getLogger("backend-main")).info("Server is starting clean-up"); + logger.info("Server is starting clean-up"); return Promise.all([ new Promise((resolve) => { if (mongoose.connection && mongoose.connection.readyState == 1) { diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index b067d499a..cf688f801 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -16,7 +16,7 @@ import { getSmtpSecure, getSmtpUsername, } from "../config"; -import { getLogger } from "../utils/logger"; +import { logger } from "../utils/logging"; export const initSmtp = async () => { const mailOpts: SMTPConnection.Options = { @@ -84,15 +84,14 @@ export const initSmtp = async () => { .then(async () => { Sentry.setUser(null); Sentry.captureMessage("SMTP - Successfully connected"); - (await getLogger("backend-main")).info( - "SMTP - Successfully connected" - ); + logger.info("SMTP - Successfully connected"); }) .catch(async (err) => { Sentry.setUser(null); Sentry.captureException( `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}` ); + logger.error(err, `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`); }); return transporter; diff --git a/backend/src/utils/logger.ts b/backend/src/utils/logger.ts deleted file mode 100644 index 9673f1115..000000000 --- a/backend/src/utils/logger.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable no-console */ -import { createLogger, format, transports } from "winston"; -import LokiTransport from "winston-loki"; -import { getLokiHost, getNodeEnv } from "../config"; - -const { combine, colorize, label, printf, splat, timestamp } = format; - -const logFormat = (prefix: string) => combine( - timestamp(), - splat(), - label({ label: prefix }), - printf((info) => `${info.timestamp} ${info.label} ${info.level}: ${info.message}`) -); - -const createLoggerWithLabel = async (level: string, label: string) => { - const _level = level.toLowerCase() || "info" - //* Always add Console output to transports - const _transports: any[] = [ - new transports.Console({ - format: combine( - colorize(), - logFormat(label), - // format.json() - ), - }), - ] - //* Add LokiTransport if it's enabled - if((await getLokiHost()) !== undefined){ - _transports.push( - new LokiTransport({ - host: await getLokiHost(), - handleExceptions: true, - handleRejections: true, - batching: true, - level: _level, - timeout: 30000, - format: format.combine( - format.json() - ), - labels: { - app: process.env.npm_package_name, - version: process.env.npm_package_version, - environment: await getNodeEnv(), - }, - onConnectionError: (err: Error)=> console.error("Connection error while connecting to Loki Server.\n", err), - }) - ) - } - - - return createLogger({ - level: _level, - transports: _transports, - format: format.combine( - logFormat(label), - format.metadata({ fillExcept: ["message", "level", "timestamp", "label"] }) - ), - }); -} - -export const getLogger = async (loggerName: "backend-main" | "database") => { - const logger = { - "backend-main": await createLoggerWithLabel("info", "[IFSC:backend-main]"), - "database": await createLoggerWithLabel("info", "[IFSC:database]"), - } - return logger[loggerName] -} diff --git a/backend/src/utils/logging/index.ts b/backend/src/utils/logging/index.ts new file mode 100644 index 000000000..ef89344c7 --- /dev/null +++ b/backend/src/utils/logging/index.ts @@ -0,0 +1 @@ +export { logger } from "./logger"; \ No newline at end of file diff --git a/backend/src/utils/logging/logger.ts b/backend/src/utils/logging/logger.ts new file mode 100644 index 000000000..2cef739ed --- /dev/null +++ b/backend/src/utils/logging/logger.ts @@ -0,0 +1,21 @@ +import pino from "pino"; + +export const logger = pino({ + level: process.env.PINO_LOG_LEVEL || "trace", + timestamp: pino.stdTimeFunctions.isoTime, + formatters: { + bindings: (bindings) => { + return { + pid: bindings.pid, + hostname: bindings.hostname + // node_version: process.version + }; + }, + }, + transport: { + target: "pino-pretty", + options: { + colorize: true + } + } +}); \ No newline at end of file From 3b0bd362c9abf1ba6d0b6d7013e623c675fd6fb1 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 1 Nov 2023 10:10:39 +0200 Subject: [PATCH 04/24] Refactor requestErrorHandler, adjust request errors to appropriate pino log level --- backend/package-lock.json | 118 ++++++++++++++---- backend/package.json | 2 +- backend/src/middleware/requestErrorHandler.ts | 58 ++++----- backend/src/utils/errors.ts | 2 +- backend/src/utils/logging/logger.ts | 11 +- backend/src/utils/requestError.ts | 47 +++---- backend/src/utils/setup/backfillData.ts | 26 ++-- docs/self-hosting/configuration/envars.mdx | 29 ++--- 8 files changed, 175 insertions(+), 118 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 2000548c9..ccd86213d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -15,7 +15,7 @@ "@godaddy/terminus": "^4.12.0", "@node-saml/passport-saml": "^4.0.4", "@octokit/rest": "^19.0.5", - "@sentry/node": "^7.49.0", + "@sentry/node": "^7.77.0", "@sentry/tracing": "^7.48.0", "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", @@ -5028,18 +5028,59 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@sentry/node": { - "version": "7.59.3", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.59.3.tgz", - "integrity": "sha512-5dG90YzmKjuy5TK04qDuc9LBxnGfqsZw8Oh9giwOBfQVCaFp7R14AXyQ1F8k3iUF+4sGeTvoqi9I/GKAItVmlA==", + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.77.0.tgz", + "integrity": "sha512-Ob5tgaJOj0OYMwnocc6G/CDLWC7hXfVvKX/ofkF98+BbN/tQa5poL+OwgFn9BA8ud8xKzyGPxGU6LdZ8Oh3z/g==", "dependencies": { - "@sentry-internal/tracing": "7.59.3", - "@sentry/core": "7.59.3", - "@sentry/types": "7.59.3", - "@sentry/utils": "7.59.3", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^2.4.1 || ^1.9.3" + "@sentry-internal/tracing": "7.77.0", + "@sentry/core": "7.77.0", + "@sentry/types": "7.77.0", + "@sentry/utils": "7.77.0", + "https-proxy-agent": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/@sentry-internal/tracing": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz", + "integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==", + "dependencies": { + "@sentry/core": "7.77.0", + "@sentry/types": "7.77.0", + "@sentry/utils": "7.77.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/core": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz", + "integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==", + "dependencies": { + "@sentry/types": "7.77.0", + "@sentry/utils": "7.77.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/types": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz", + "integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/utils": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz", + "integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==", + "dependencies": { + "@sentry/types": "7.77.0" }, "engines": { "node": ">=8" @@ -20523,18 +20564,49 @@ } }, "@sentry/node": { - "version": "7.59.3", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.59.3.tgz", - "integrity": "sha512-5dG90YzmKjuy5TK04qDuc9LBxnGfqsZw8Oh9giwOBfQVCaFp7R14AXyQ1F8k3iUF+4sGeTvoqi9I/GKAItVmlA==", + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.77.0.tgz", + "integrity": "sha512-Ob5tgaJOj0OYMwnocc6G/CDLWC7hXfVvKX/ofkF98+BbN/tQa5poL+OwgFn9BA8ud8xKzyGPxGU6LdZ8Oh3z/g==", "requires": { - "@sentry-internal/tracing": "7.59.3", - "@sentry/core": "7.59.3", - "@sentry/types": "7.59.3", - "@sentry/utils": "7.59.3", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^2.4.1 || ^1.9.3" + "@sentry-internal/tracing": "7.77.0", + "@sentry/core": "7.77.0", + "@sentry/types": "7.77.0", + "@sentry/utils": "7.77.0", + "https-proxy-agent": "^5.0.0" + }, + "dependencies": { + "@sentry-internal/tracing": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz", + "integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==", + "requires": { + "@sentry/core": "7.77.0", + "@sentry/types": "7.77.0", + "@sentry/utils": "7.77.0" + } + }, + "@sentry/core": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz", + "integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==", + "requires": { + "@sentry/types": "7.77.0", + "@sentry/utils": "7.77.0" + } + }, + "@sentry/types": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz", + "integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==" + }, + "@sentry/utils": { + "version": "7.77.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz", + "integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==", + "requires": { + "@sentry/types": "7.77.0" + } + } } }, "@sentry/tracing": { diff --git a/backend/package.json b/backend/package.json index 77a90f87d..f950b1d9c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,7 +6,7 @@ "@godaddy/terminus": "^4.12.0", "@node-saml/passport-saml": "^4.0.4", "@octokit/rest": "^19.0.5", - "@sentry/node": "^7.49.0", + "@sentry/node": "^7.77.0", "@sentry/tracing": "^7.48.0", "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", diff --git a/backend/src/middleware/requestErrorHandler.ts b/backend/src/middleware/requestErrorHandler.ts index f7e964630..3bcea0d98 100644 --- a/backend/src/middleware/requestErrorHandler.ts +++ b/backend/src/middleware/requestErrorHandler.ts @@ -3,52 +3,44 @@ import { ErrorRequestHandler } from "express"; import { TokenExpiredError } from "jsonwebtoken"; import { InternalServerError, UnauthorizedRequestError } from "../utils/errors"; import { logger } from "../utils/logging"; -import RequestError from "../utils/requestError"; +import RequestError, { mapToPinoLogLevel } from "../utils/requestError"; import { ForbiddenError } from "@casl/ability"; export const requestErrorHandler: ErrorRequestHandler = async ( - error: RequestError | Error, + err: RequestError | Error, req, res, next ) => { if (res.headersSent) return next(); - const logAndCaptureException = async (error: RequestError) => { - // TODO: tie pino error-handling to error types/levels + let error: RequestError; - // (await getLogger("backend-main")).log( - // (error).levelName.toLowerCase(), - // `${error.stack}\n${error.message}` - // ); - - logger.error(error.stack, error.message); - - //* Set Sentry user identification if req.user is populated - if (req.user !== undefined && req.user !== null) { - Sentry.setUser({ email: (req.user as any).email }); - } - - Sentry.captureException(error); - }; - - if (error instanceof RequestError) { - if (error instanceof TokenExpiredError) { - error = UnauthorizedRequestError({ stack: error.stack, message: "Token expired" }); - } - await logAndCaptureException((error)); - } else { - if (error instanceof ForbiddenError) { - error = UnauthorizedRequestError({ context: { exception: error.message }, stack: error.stack }) - } else { - error = InternalServerError({ context: { exception: error.message }, stack: error.stack }); - } - - await logAndCaptureException((error)); + switch (true) { + case err instanceof TokenExpiredError: + error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" }); + break; + case err instanceof ForbiddenError: + error = UnauthorizedRequestError({ context: { exception: err.message }, stack: err.stack }) + break; + case err instanceof RequestError: + error = err as RequestError; + break; + default: + error = InternalServerError({ context: { exception: err.message }, stack: err.stack }); + break; } + logger[mapToPinoLogLevel(error.level)](error); + + if (req.user) { + Sentry.setUser({ email: (req.user as any).email }); + } + + Sentry.captureException(error); + delete (error).stacktrace // remove stack trace from being sent to client - res.status((error).statusCode).json(error); + res.status((error).statusCode).json(error); // revise json part here next(); }; diff --git a/backend/src/utils/errors.ts b/backend/src/utils/errors.ts index 42cc20509..1751dcd92 100644 --- a/backend/src/utils/errors.ts +++ b/backend/src/utils/errors.ts @@ -29,7 +29,7 @@ export const UnauthorizedRequestError = (error?: Partial) = }); export const ForbiddenRequestError = (error?: Partial) => new RequestError({ - logLevel: error?.logLevel ?? LogLevel.INFO, + logLevel: error?.logLevel ?? LogLevel.WARN, statusCode: error?.statusCode ?? 403, type: error?.type ?? "forbidden", message: error?.message ?? "You are not allowed to access this resource", diff --git a/backend/src/utils/logging/logger.ts b/backend/src/utils/logging/logger.ts index 2cef739ed..37f2c0ecd 100644 --- a/backend/src/utils/logging/logger.ts +++ b/backend/src/utils/logging/logger.ts @@ -13,9 +13,12 @@ export const logger = pino({ }, }, transport: { - target: "pino-pretty", - options: { - colorize: true - } + targets: [ + { + target: "pino-pretty", + level: process.env.PINO_LOG_LEVEL || "trace", + options: { colorize: true } + } + ] } }); \ No newline at end of file diff --git a/backend/src/utils/requestError.ts b/backend/src/utils/requestError.ts index 73d29dbb8..f5f320b8d 100644 --- a/backend/src/utils/requestError.ts +++ b/backend/src/utils/requestError.ts @@ -2,34 +2,30 @@ import { Request } from "express" import { getVerboseErrorOutput } from "../config"; export enum LogLevel { - DEBUG = 100, - INFO = 200, - NOTICE = 250, - WARNING = 300, - ERROR = 400, - CRITICAL = 500, - ALERT = 550, - EMERGENCY = 600, + TRACE = 10, + DEBUG = 20, + INFO = 30, + WARN = 40, + ERROR = 50, + FATAL = 60 } -export const mapToWinstonLogLevel = (customLogLevel: LogLevel): string => { +type PinoLogLevel = "trace" | "debug" | "info" | "warn" | "error" | "fatal"; + +export const mapToPinoLogLevel = (customLogLevel: LogLevel): PinoLogLevel => { switch (customLogLevel) { + case LogLevel.TRACE: + return "trace"; case LogLevel.DEBUG: return "debug"; case LogLevel.INFO: return "info"; - case LogLevel.NOTICE: - return "notice"; - case LogLevel.WARNING: + case LogLevel.WARN: return "warn"; case LogLevel.ERROR: return "error"; - case LogLevel.CRITICAL: - return "crit"; - case LogLevel.ALERT: - return "alert"; - case LogLevel.EMERGENCY: - return "emerg"; + case LogLevel.FATAL: + return "fatal"; } } @@ -42,10 +38,10 @@ export type RequestErrorContext = { stack?: string|undefined } -export default class RequestError extends Error{ +export default class RequestError extends Error { private _logLevel: LogLevel - private _logName: string + private _logName: string; statusCode: number type: string context: Record @@ -55,9 +51,10 @@ export default class RequestError extends Error{ constructor( {logLevel, statusCode, type, message, context, stack} : RequestErrorContext ){ + super(message) this._logLevel = logLevel || LogLevel.INFO - this._logName = LogLevel[this._logLevel] + this._logName = LogLevel[this._logLevel]; this.statusCode = statusCode this.type = type this.context = context || {} @@ -83,8 +80,12 @@ export default class RequestError extends Error{ }) } - get level(){ return this._logLevel } - get levelName(){ return this._logName } + get level(){ + return this._logLevel + } + get levelName(){ + return this._logName + } withTags(...tags: string[]|number[]){ this.context["tags"] = Object.assign(tags, this.context["tags"]) diff --git a/backend/src/utils/setup/backfillData.ts b/backend/src/utils/setup/backfillData.ts index 3339d8927..319c2605b 100644 --- a/backend/src/utils/setup/backfillData.ts +++ b/backend/src/utils/setup/backfillData.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import crypto from "crypto"; import { Types } from "mongoose"; import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto"; @@ -47,6 +46,7 @@ import { ProjectPermissionSub, memberProjectPermissions } from "../../ee/services/ProjectRoleService"; +import { logger } from "../logging"; /** * Backfill secrets to ensure that they're all versioned and have @@ -88,7 +88,7 @@ export const backfillSecretVersions = async () => { ) }); } - console.log("Migration: Secret version migration v1 complete"); + logger.info("Migration: Secret version migration v1 complete"); }; /** @@ -518,7 +518,7 @@ export const backfillSecretFolders = async () => { .limit(50); } - console.log("Migration: Folder migration v1 complete"); + logger.info("Migration: Folder migration v1 complete"); }; export const backfillServiceToken = async () => { @@ -534,7 +534,7 @@ export const backfillServiceToken = async () => { } } ); - console.log("Migration: Service token migration v1 complete"); + logger.info("Migration: Service token migration v1 complete"); }; export const backfillIntegration = async () => { @@ -550,7 +550,7 @@ export const backfillIntegration = async () => { } } ); - console.log("Migration: Integration migration v1 complete"); + logger.info("Migration: Integration migration v1 complete"); }; export const backfillServiceTokenMultiScope = async () => { @@ -575,7 +575,7 @@ export const backfillServiceTokenMultiScope = async () => { } } - console.log("Migration: Service token migration v2 complete"); + logger.info("Migration: Service token migration v2 complete"); }; /** @@ -650,7 +650,7 @@ export const backfillTrustedIps = async () => { }); await TrustedIP.bulkWrite(operations); - console.log("Backfill: Trusted IPs complete"); + logger.info("Backfill: Trusted IPs complete"); } }; @@ -698,7 +698,7 @@ export const backfillPermission = async () => { if (lock) { try { - console.info("Lock acquired for script [backfillPermission]"); + logger.info("Lock acquired for script [backfillPermission]"); const memberships = await Membership.find({ deniedPermissions: { @@ -801,7 +801,7 @@ export const backfillPermission = async () => { } } - console.info("Backfill: Finished converting old denied permission in workspace to viewers"); + logger.info("Backfill: Finished converting old denied permission in workspace to viewers"); await MembershipOrg.updateMany( { @@ -814,14 +814,14 @@ export const backfillPermission = async () => { } ); - console.info("Backfill: Finished converting owner role to member"); + logger.info("Backfill: Finished converting owner role to member"); } catch (error) { - console.error("An error occurred when running script [backfillPermission]:", error); + logger.error(error, "An error occurred when running script [backfillPermission]"); } } else { - console.info("Could not acquire lock for script [backfillPermission], skipping"); + logger.info("Could not acquire lock for script [backfillPermission], skipping"); } }; @@ -837,5 +837,5 @@ export const migrateRoleFromOwnerToAdmin = async () => { } ); - console.info("Backfill: Finished converting owner role to member"); + logger.info("Backfill: Finished converting owner role to member"); } \ No newline at end of file diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 9b054e376..c05647627 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -153,29 +153,18 @@ Other environment variables are listed below to increase the functionality of yo JWT token lifetime expressed in seconds or a string describing a time span -{" "} - - - -{" "} - - - -#### Error logging +#### Logging Infisical uses Sentry to report error logs -{" "} + + The minimum log level for application logging; can be one of `trace`, `debug`, `info`, `warn`, `error`, or `fatal`. + Date: Wed, 1 Nov 2023 11:06:36 +0200 Subject: [PATCH 05/24] Remove transactions from delete organization, workspace, user --- backend/src/helpers/organization.ts | 504 +++++++++++----------------- backend/src/helpers/user.ts | 201 +++++------ backend/src/helpers/workspace.ts | 249 +++++--------- 3 files changed, 366 insertions(+), 588 deletions(-) diff --git a/backend/src/helpers/organization.ts b/backend/src/helpers/organization.ts index 2e5ee6ebb..a527c2c21 100644 --- a/backend/src/helpers/organization.ts +++ b/backend/src/helpers/organization.ts @@ -1,4 +1,4 @@ -import mongoose, { Types, mongo } from "mongoose"; +import { Types } from "mongoose"; import { Bot, BotKey, @@ -55,7 +55,7 @@ import { import { createBotOrg } from "./botOrg"; -import { InternalServerError, ResourceNotFoundError } from "../utils/errors"; +import { ResourceNotFoundError } from "../utils/errors"; /** * Create an organization with name [name] @@ -111,311 +111,215 @@ export const createOrganization = async ({ * @returns */ export const deleteOrganization = async ({ - organizationId, - existingSession + organizationId }: { organizationId: Types.ObjectId; - existingSession?: mongo.ClientSession; }) => { - let session; - - if (existingSession) { - session = existingSession; - } else { - session = await mongoose.startSession(); - session.startTransaction(); - } + const organization = await Organization.findByIdAndDelete( + organizationId + ); - try { - const organization = await Organization.findByIdAndDelete( - organizationId, - { - session - } + if (!organization) throw ResourceNotFoundError(); + + await MembershipOrg.deleteMany({ + organization: organization._id + }); + + await BotOrg.deleteMany({ + organization: organization._id + }); + + await SSOConfig.deleteMany({ + organization: organization._id + }); + + await Role.deleteMany({ + organization: organization._id + }); + + await IncidentContactOrg.deleteMany({ + organization: organization._id + }); + + await GitRisks.deleteMany({ + organization: organization._id + }); + + await GitAppInstallationSession.deleteMany({ + organization: organization._id + }); + + await GitAppOrganizationInstallation.deleteMany({ + organization: organization._id + }); + + const workspaceIds = await Workspace.distinct("_id", { + organization: organization._id + }); + + await Workspace.deleteMany({ + organization: organization._id + }); + + await Membership.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Key.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Bot.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await BotKey.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await SecretBlindIndexData.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Secret.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await SecretVersion.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await SecretSnapshot.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await SecretImport.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Folder.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await FolderVersion.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Webhook.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await TrustedIP.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Tag.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await IntegrationAuth.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Integration.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await ServiceToken.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await ServiceTokenData.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await ServiceTokenDataV3.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await ServiceTokenDataV3Key.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await AuditLog.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Log.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await Action.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await SecretApprovalPolicy.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + await SecretApprovalRequest.deleteMany({ + workspace: { + $in: workspaceIds + } + }); + + if (organization.customerId) { + // delete from stripe here + await licenseServerKeyRequest.delete( + `${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}` ); - - if (!organization) throw ResourceNotFoundError(); - - await MembershipOrg.deleteMany({ - organization: organization._id - }, { - session - }); - - await BotOrg.deleteMany({ - organization: organization._id - }, { - session - }); - - await SSOConfig.deleteMany({ - organization: organization._id - }, { - session - }); - - await Role.deleteMany({ - organization: organization._id - }, { - session - }); - - await IncidentContactOrg.deleteMany({ - organization: organization._id - }, { - session - }); - - await GitRisks.deleteMany({ - organization: organization._id - }, { - session - }); - - await GitAppInstallationSession.deleteMany({ - organization: organization._id - }, { - session - }); - - await GitAppOrganizationInstallation.deleteMany({ - organization: organization._id - }, { - session - }); - - const workspaceIds = await Workspace.distinct("_id", { - organization: organization._id - }); - - await Workspace.deleteMany({ - organization: organization._id - }, { - session - }); - - await Membership.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Key.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Bot.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await BotKey.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await SecretBlindIndexData.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Secret.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await SecretVersion.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await SecretSnapshot.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await SecretImport.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Folder.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await FolderVersion.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Webhook.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await TrustedIP.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Tag.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await IntegrationAuth.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Integration.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await ServiceToken.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await ServiceTokenData.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await ServiceTokenDataV3.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await ServiceTokenDataV3Key.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await AuditLog.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Log.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await Action.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await SecretApprovalPolicy.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - await SecretApprovalRequest.deleteMany({ - workspace: { - $in: workspaceIds - } - }, { - session - }); - - if (organization.customerId) { - // delete from stripe here - await licenseServerKeyRequest.delete( - `${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}` - ); - } - - return organization; - } catch (err) { - if (!existingSession) { - await session.abortTransaction(); - } - throw InternalServerError({ - message: "Failed to delete organization" - }); - } finally { - if (!existingSession) { - await session.commitTransaction(); - session.endSession(); - } } + + return organization; } /** diff --git a/backend/src/helpers/user.ts b/backend/src/helpers/user.ts index b78f471f1..5c840e906 100644 --- a/backend/src/helpers/user.ts +++ b/backend/src/helpers/user.ts @@ -1,4 +1,4 @@ -import mongoose, { Types, mongo } from "mongoose"; +import { Types } from "mongoose"; import { APIKeyData, BackupPrivateKey, @@ -222,141 +222,92 @@ const checkDeleteUserConditions = async ({ * @returns {User} user - deleted user */ export const deleteUser = async ({ - userId, - existingSession + userId }: { userId: Types.ObjectId; - existingSession?: mongo.ClientSession; }) => { + + const user = await User.findByIdAndDelete(userId); - let session; + if (!user) throw ResourceNotFoundError(); + + await checkDeleteUserConditions({ + userId: user._id + }); - if (existingSession) { - session = existingSession; - } else { - session = await mongoose.startSession(); - session.startTransaction(); + await UserAction.deleteMany({ + user: user._id + }); + + await BackupPrivateKey.deleteMany({ + user: user._id + }); + + await APIKeyData.deleteMany({ + user: user._id + }); + + await Action.deleteMany({ + user: user._id + }); + + await Log.deleteMany({ + user: user._id + }); + + await TokenVersion.deleteMany({ + user: user._id + }); + + await Key.deleteMany({ + receiver: user._id + }); + + const membershipOrgs = await MembershipOrg.find({ + user: userId + }); + + // delete organizations where user is only member + for await (const membershipOrg of membershipOrgs) { + const memberCount = await MembershipOrg.countDocuments({ + organization: membershipOrg.organization + }); + + if (memberCount === 1) { + // organization only has 1 member (the current user) + + await deleteOrganization({ + organizationId: membershipOrg.organization + }); + } } - try { - const user = await User.findByIdAndDelete(userId, { - session + const memberships = await Membership.find({ + user: userId + }); + + // delete workspaces where user is only member + for await (const membership of memberships) { + const memberCount = await Membership.countDocuments({ + workspace: membership.workspace }); - if (!user) throw ResourceNotFoundError(); + if (memberCount === 1) { + // workspace only has 1 member (the current user) -> delete workspace - await checkDeleteUserConditions({ - userId: user._id - }); - - await UserAction.deleteMany({ - user: user._id - }, { - session - }); - - await BackupPrivateKey.deleteMany({ - user: user._id - }, { - session - }); - - await APIKeyData.deleteMany({ - user: user._id - }, { - session - }); - - await Action.deleteMany({ - user: user._id - }, { - session - }); - - await Log.deleteMany({ - user: user._id - }, { - session - }); - - await TokenVersion.deleteMany({ - user: user._id - }); - - await Key.deleteMany({ - receiver: user._id - }, { - session - }); - - const membershipOrgs = await MembershipOrg.find({ - user: userId - }, null, { - session - }); - - // delete organizations where user is only member - for await (const membershipOrg of membershipOrgs) { - const memberCount = await MembershipOrg.countDocuments({ - organization: membershipOrg.organization + await deleteWorkspace({ + workspaceId: membership.workspace }); - - if (memberCount === 1) { - // organization only has 1 member (the current user) - - await deleteOrganization({ - organizationId: membershipOrg.organization, - existingSession: session - }); - } - } - - const memberships = await Membership.find({ - user: userId - }, null, { - session - }); - - // delete workspaces where user is only member - for await (const membership of memberships) { - const memberCount = await Membership.countDocuments({ - workspace: membership.workspace - }); - - if (memberCount === 1) { - // workspace only has 1 member (the current user) -> delete workspace - - await deleteWorkspace({ - workspaceId: membership.workspace, - existingSession: session - }); - } - } - - await MembershipOrg.deleteMany({ - user: userId - }, { - session - }); - - await Membership.deleteMany({ - user: userId - }, { - session - }); - - return user; - } catch (err) { - if (!existingSession) { - await session.abortTransaction(); - } - throw InternalServerError({ - message: "Failed to delete account" - }) - } finally { - if (!existingSession) { - await session.commitTransaction(); - session.endSession(); } } + + await MembershipOrg.deleteMany({ + user: userId + }); + + await Membership.deleteMany({ + user: userId + }); + + return user; } \ No newline at end of file diff --git a/backend/src/helpers/workspace.ts b/backend/src/helpers/workspace.ts index 71d945e78..413b187d1 100644 --- a/backend/src/helpers/workspace.ts +++ b/backend/src/helpers/workspace.ts @@ -1,4 +1,4 @@ -import mongoose, { Types, mongo } from "mongoose"; +import { Types } from "mongoose"; import { Bot, BotKey, @@ -33,8 +33,7 @@ import { import { createBot } from "../helpers/bot"; import { EELicenseService } from "../ee/services"; import { SecretService } from "../services"; -import { - InternalServerError, +import { ResourceNotFoundError } from "../utils/errors"; @@ -102,189 +101,113 @@ export const createWorkspace = async ({ * @param {String} obj.id - id of workspace to delete */ export const deleteWorkspace = async ({ - workspaceId, - existingSession + workspaceId }: { workspaceId: Types.ObjectId; - existingSession?: mongo.ClientSession; }) => { - - let session; - - if (existingSession) { - session = existingSession; - } else { - session = await mongoose.startSession(); - session.startTransaction(); - } + const workspace = await Workspace.findByIdAndDelete(workspaceId); - try { - const workspace = await Workspace.findByIdAndDelete(workspaceId, { session }); - - if (!workspace) throw ResourceNotFoundError(); - - await Membership.deleteMany({ - workspace: workspace._id - }, { - session - }); - - await Key.deleteMany({ - workspace: workspace._id - }, { - session - }); - - await Bot.deleteMany({ - workspace: workspace._id - }, { - session - }); + if (!workspace) throw ResourceNotFoundError(); + + await Membership.deleteMany({ + workspace: workspace._id + }); + + await Key.deleteMany({ + workspace: workspace._id + }); + + await Bot.deleteMany({ + workspace: workspace._id + }); - await BotKey.deleteMany({ - workspace: workspace._id - }, { - session - }); + await BotKey.deleteMany({ + workspace: workspace._id + }); - await SecretBlindIndexData.deleteMany({ - workspace: workspace._id - }, { - session - }); + await SecretBlindIndexData.deleteMany({ + workspace: workspace._id + }); - await Secret.deleteMany({ - workspace: workspace._id - }, { - session - }); - - await SecretVersion.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Secret.deleteMany({ + workspace: workspace._id + }); + + await SecretVersion.deleteMany({ + workspace: workspace._id + }); - await SecretSnapshot.deleteMany({ - workspace: workspace._id - }, { - session - }); + await SecretSnapshot.deleteMany({ + workspace: workspace._id + }); - await SecretImport.deleteMany({ - workspace: workspace._id - }, { - session - }); + await SecretImport.deleteMany({ + workspace: workspace._id + }); - await Folder.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Folder.deleteMany({ + workspace: workspace._id + }); - await FolderVersion.deleteMany({ - workspace: workspace._id - }, { - session - }); + await FolderVersion.deleteMany({ + workspace: workspace._id + }); - await Webhook.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Webhook.deleteMany({ + workspace: workspace._id + }); - await TrustedIP.deleteMany({ - workspace: workspace._id - }, { - session - }); + await TrustedIP.deleteMany({ + workspace: workspace._id + }); - await Tag.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Tag.deleteMany({ + workspace: workspace._id + }); - await IntegrationAuth.deleteMany({ - workspace: workspace._id - }, { - session - }); + await IntegrationAuth.deleteMany({ + workspace: workspace._id + }); - await Integration.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Integration.deleteMany({ + workspace: workspace._id + }); - await ServiceToken.deleteMany({ - workspace: workspace._id - }, { - session - }); + await ServiceToken.deleteMany({ + workspace: workspace._id + }); - await ServiceTokenData.deleteMany({ - workspace: workspace._id - }, { - session - }); + await ServiceTokenData.deleteMany({ + workspace: workspace._id + }); - await ServiceTokenDataV3.deleteMany({ - workspace: workspace._id - }, { - session - }); + await ServiceTokenDataV3.deleteMany({ + workspace: workspace._id + }); - await ServiceTokenDataV3Key.deleteMany({ - workspace: workspace._id - }, { - session - }); + await ServiceTokenDataV3Key.deleteMany({ + workspace: workspace._id + }); - await AuditLog.deleteMany({ - workspace: workspace._id - }, { - session - }); + await AuditLog.deleteMany({ + workspace: workspace._id + }); - await Log.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Log.deleteMany({ + workspace: workspace._id + }); - await Action.deleteMany({ - workspace: workspace._id - }, { - session - }); + await Action.deleteMany({ + workspace: workspace._id + }); - await SecretApprovalPolicy.deleteMany({ - workspace: workspace._id - }, { - session - }); + await SecretApprovalPolicy.deleteMany({ + workspace: workspace._id + }); - await SecretApprovalRequest.deleteMany({ - workspace: workspace._id - }, { - session - }); - - return workspace; - } catch (err) { - if (!existingSession) { - await session.abortTransaction(); - } - throw InternalServerError({ - message: "Failed to delete organization" - }); - } finally { - if (!existingSession) { - await session.commitTransaction(); - session.endSession(); - } - } + await SecretApprovalRequest.deleteMany({ + workspace: workspace._id + }); + + return workspace; }; From 20d4f16d33aba1d99806473469d41f607f6223e5 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 1 Nov 2023 13:20:10 +0200 Subject: [PATCH 06/24] Move pino-pretty to dev-dep, dev script --- backend/package-lock.json | 2 +- backend/package.json | 4 ++-- backend/src/utils/logging/logger.ts | 9 --------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index e71257bec..62426ad22 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -53,7 +53,6 @@ "passport-google-oauth20": "^2.0.0", "pino": "^8.16.1", "pino-http": "^8.5.1", - "pino-pretty": "^10.2.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", @@ -96,6 +95,7 @@ "jest-junit": "^15.0.0", "nodemon": "^2.0.19", "npm": "^8.19.3", + "pino-pretty": "^10.2.3", "smee-client": "^1.2.3", "supertest": "^6.3.3", "swagger-autogen": "^2.23.5", diff --git a/backend/package.json b/backend/package.json index f8d25f929..24b6699f7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -44,7 +44,6 @@ "passport-google-oauth20": "^2.0.0", "pino": "^8.16.1", "pino-http": "^8.5.1", - "pino-pretty": "^10.2.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", @@ -67,7 +66,7 @@ "main": "src/index.js", "scripts": { "start": "node build/index.js", - "dev": "nodemon", + "dev": "nodemon index.js | npx pino-pretty --colorize", "swagger-autogen": "node ./swagger/index.ts", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build", "lint": "eslint . --ext .ts", @@ -119,6 +118,7 @@ "jest-junit": "^15.0.0", "nodemon": "^2.0.19", "npm": "^8.19.3", + "pino-pretty": "^10.2.3", "smee-client": "^1.2.3", "supertest": "^6.3.3", "swagger-autogen": "^2.23.5", diff --git a/backend/src/utils/logging/logger.ts b/backend/src/utils/logging/logger.ts index 37f2c0ecd..ecfe24b69 100644 --- a/backend/src/utils/logging/logger.ts +++ b/backend/src/utils/logging/logger.ts @@ -11,14 +11,5 @@ export const logger = pino({ // node_version: process.version }; }, - }, - transport: { - targets: [ - { - target: "pino-pretty", - level: process.env.PINO_LOG_LEVEL || "trace", - options: { colorize: true } - } - ] } }); \ No newline at end of file From 28a2aebe67f8d5b9c5398fbc9a790f1ab4f14e10 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 1 Nov 2023 20:06:02 +0530 Subject: [PATCH 07/24] chore: removed npx from pino-pretty --- backend/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/package.json b/backend/package.json index 24b6699f7..768f3d3af 100644 --- a/backend/package.json +++ b/backend/package.json @@ -66,7 +66,7 @@ "main": "src/index.js", "scripts": { "start": "node build/index.js", - "dev": "nodemon index.js | npx pino-pretty --colorize", + "dev": "nodemon index.js | pino-pretty --colorize", "swagger-autogen": "node ./swagger/index.ts", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build", "lint": "eslint . --ext .ts", From 82e924bafffffe26da08fe9a16c23aec63b313df Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 25 Oct 2023 12:15:57 +0530 Subject: [PATCH 08/24] feat(secret-rotation): implemented api and queue for secret rotation --- backend/package-lock.json | 704 +++++++++++++++++- backend/package.json | 7 + backend/src/ee/controllers/v1/index.ts | 6 +- .../v1/secretRotationController.ts | 91 +++ .../v1/secretRotationProviderController.ts | 28 + backend/src/ee/models/auditLog/enums.ts | 7 +- backend/src/ee/models/auditLog/types.ts | 89 ++- backend/src/ee/routes/v1/index.ts | 6 +- backend/src/ee/routes/v1/secretRotation.ts | 41 + .../ee/routes/v1/secretRotationProvider.ts | 17 + backend/src/ee/secretRotation/db.ts | 0 backend/src/ee/secretRotation/models.ts | 91 +++ .../ee/secretRotation/providerTemplates.ts | 215 ++++++ backend/src/ee/secretRotation/queue.ts | 417 +++++++++++ backend/src/ee/secretRotation/service.ts | 116 +++ backend/src/ee/secretRotation/types.ts | 131 ++++ backend/src/ee/services/ProjectRoleService.ts | 11 +- backend/src/ee/validation/secretRotation.ts | 32 + .../ee/validation/secretRotationProvider.ts | 7 + backend/src/index.ts | 12 +- backend/src/interfaces/middleware/index.ts | 38 +- 21 files changed, 1951 insertions(+), 115 deletions(-) create mode 100644 backend/src/ee/controllers/v1/secretRotationController.ts create mode 100644 backend/src/ee/controllers/v1/secretRotationProviderController.ts create mode 100644 backend/src/ee/routes/v1/secretRotation.ts create mode 100644 backend/src/ee/routes/v1/secretRotationProvider.ts create mode 100644 backend/src/ee/secretRotation/db.ts create mode 100644 backend/src/ee/secretRotation/models.ts create mode 100644 backend/src/ee/secretRotation/providerTemplates.ts create mode 100644 backend/src/ee/secretRotation/queue.ts create mode 100644 backend/src/ee/secretRotation/service.ts create mode 100644 backend/src/ee/secretRotation/types.ts create mode 100644 backend/src/ee/validation/secretRotation.ts create mode 100644 backend/src/ee/validation/secretRotationProvider.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index 62426ad22..bf0abb3ed 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -20,6 +20,7 @@ "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", "@ucast/mongo2js": "^1.3.4", + "ajv": "^8.12.0", "argon2": "^0.30.3", "aws-sdk": "^2.1364.0", "axios": "^1.3.5", @@ -38,12 +39,14 @@ "helmet": "^5.1.1", "infisical-node": "^1.2.1", "ioredis": "^5.3.2", + "jmespath": "^0.16.0", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", "libsodium-wrappers": "^0.7.10", "lodash": "^4.17.21", "mongoose": "^7.4.1", + "mysql": "^2.18.1", "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", @@ -51,6 +54,7 @@ "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", "passport-google-oauth20": "^2.0.0", + "pg": "^8.11.3", "pino": "^8.16.1", "pino-http": "^8.5.1", "posthog-node": "^2.6.0", @@ -75,11 +79,14 @@ "@types/cors": "^2.8.12", "@types/express": "^4.17.14", "@types/jest": "^29.5.0", + "@types/jmespath": "^0.15.1", "@types/jsonwebtoken": "^8.5.9", "@types/lodash": "^4.14.191", + "@types/mysql": "^2.15.23", "@types/node": "^18.11.3", "@types/nodemailer": "^6.4.6", "@types/passport": "^1.0.12", + "@types/pg": "^8.10.7", "@types/picomatch": "^2.3.0", "@types/pino": "^7.0.5", "@types/supertest": "^2.0.12", @@ -3507,6 +3514,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/@eslint/js": { "version": "8.44.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", @@ -5849,6 +5878,12 @@ "pretty-format": "^29.0.0" } }, + "node_modules/@types/jmespath": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.1.tgz", + "integrity": "sha512-RWN1HQ71Hjl2ixw4a8s7/Bcz6S9uaBTaoCQ5cJB7OsjgHBFi3GaWMy0vRgZBPSYXdsMKFNxGLUUEh9uRf00Spw==", + "dev": true + }, "node_modules/@types/js-yaml": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", @@ -5890,6 +5925,15 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "node_modules/@types/mysql": { + "version": "2.15.23", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.23.tgz", + "integrity": "sha512-l3mEJpU1VNkt7uJP2vYWZrfjxlzQtwJNKSWe+7sgChuDjFoyRfu263f9pBDlhFPmyda23o8GNGq6FL5CHSd/QA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "18.16.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", @@ -5921,6 +5965,74 @@ "@types/passport": "*" } }, + "node_modules/@types/pg": { + "version": "8.10.7", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.7.tgz", + "integrity": "sha512-ksJqHipwYaSEHz9e1fr6H6erjoEdNNaOxwyJgPx9bNeaqOW3iWBQgVHfpwiSAoqGzchfc+ZyRLwEfeCcyYD3uQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + } + }, + "node_modules/@types/pg/node_modules/pg-types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.1.tgz", + "integrity": "sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==", + "dev": true, + "dependencies": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.0.1", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/pg/node_modules/postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "dependencies": { + "obuf": "~1.1.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/pg/node_modules/postgres-date": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.0.1.tgz", + "integrity": "sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@types/pg/node_modules/postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/@types/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.0.tgz", @@ -6419,14 +6531,13 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dependencies": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" }, "funding": { @@ -6900,6 +7011,14 @@ "@juanelas/base64": "^1.1.2" } }, + "node_modules/bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -7063,6 +7182,14 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/bull": { "version": "4.10.4", "resolved": "https://registry.npmjs.org/bull/-/bull-4.10.4.tgz", @@ -7450,8 +7577,7 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/cors": { "version": "2.8.5", @@ -7912,6 +8038,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/eslint-scope": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", @@ -7937,6 +8079,12 @@ "node": ">=4.0" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -8267,8 +8415,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.0", @@ -10038,10 +10185,9 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -10715,6 +10861,47 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" } }, + "node_modules/mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "dependencies": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/mysql/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/mysql/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -13543,6 +13730,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, "node_modules/octokit-auth-probot": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/octokit-auth-probot/-/octokit-auth-probot-1.2.9.tgz", @@ -13684,6 +13877,11 @@ "node": ">=6" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -13849,6 +14047,98 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -14232,6 +14522,47 @@ "node": ">=8" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-range": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", + "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==", + "dev": true + }, "node_modules/posthog-node": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.6.0.tgz", @@ -14710,6 +15041,11 @@ "node": ">= 0.6.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/process-warning": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", @@ -15015,6 +15351,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -15498,6 +15842,14 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "node_modules/sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -16197,7 +16549,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -16206,7 +16557,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } @@ -16515,6 +16865,14 @@ "node": ">=0.6.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -19407,6 +19765,26 @@ "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } } }, "@eslint/js": { @@ -21263,6 +21641,12 @@ "pretty-format": "^29.0.0" } }, + "@types/jmespath": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@types/jmespath/-/jmespath-0.15.1.tgz", + "integrity": "sha512-RWN1HQ71Hjl2ixw4a8s7/Bcz6S9uaBTaoCQ5cJB7OsjgHBFi3GaWMy0vRgZBPSYXdsMKFNxGLUUEh9uRf00Spw==", + "dev": true + }, "@types/js-yaml": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", @@ -21304,6 +21688,15 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "@types/mysql": { + "version": "2.15.23", + "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.23.tgz", + "integrity": "sha512-l3mEJpU1VNkt7uJP2vYWZrfjxlzQtwJNKSWe+7sgChuDjFoyRfu263f9pBDlhFPmyda23o8GNGq6FL5CHSd/QA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "18.16.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", @@ -21335,6 +21728,61 @@ "@types/passport": "*" } }, + "@types/pg": { + "version": "8.10.7", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.10.7.tgz", + "integrity": "sha512-ksJqHipwYaSEHz9e1fr6H6erjoEdNNaOxwyJgPx9bNeaqOW3iWBQgVHfpwiSAoqGzchfc+ZyRLwEfeCcyYD3uQ==", + "dev": true, + "requires": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^4.0.1" + }, + "dependencies": { + "pg-types": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.0.1.tgz", + "integrity": "sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==", + "dev": true, + "requires": { + "pg-int8": "1.0.1", + "pg-numeric": "1.0.2", + "postgres-array": "~3.0.1", + "postgres-bytea": "~3.0.0", + "postgres-date": "~2.0.1", + "postgres-interval": "^3.0.0", + "postgres-range": "^1.1.1" + } + }, + "postgres-array": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.2.tgz", + "integrity": "sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==", + "dev": true + }, + "postgres-bytea": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz", + "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==", + "dev": true, + "requires": { + "obuf": "~1.1.2" + } + }, + "postgres-date": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.0.1.tgz", + "integrity": "sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==", + "dev": true + }, + "postgres-interval": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz", + "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==", + "dev": true + } + } + }, "@types/picomatch": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.3.0.tgz", @@ -21717,14 +22165,13 @@ } }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "requires": { "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, @@ -22081,6 +22528,11 @@ "@juanelas/base64": "^1.1.2" } }, + "bignumber.js": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", + "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -22210,6 +22662,11 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "bull": { "version": "4.10.4", "resolved": "https://registry.npmjs.org/bull/-/bull-4.10.4.tgz", @@ -22485,8 +22942,7 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "cors": { "version": "2.8.5", @@ -22789,6 +23245,18 @@ "text-table": "^0.2.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "eslint-scope": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", @@ -22804,6 +23272,12 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true } } }, @@ -23090,8 +23564,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { "version": "3.3.0", @@ -24406,10 +24879,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -24919,6 +25391,46 @@ "node-gyp-build-optional-packages": "5.0.7" } }, + "mysql": { + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", + "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "requires": { + "bignumber.js": "9.0.0", + "readable-stream": "2.3.7", + "safe-buffer": "5.1.2", + "sqlstring": "2.3.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -26900,6 +27412,12 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" }, + "obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, "octokit-auth-probot": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/octokit-auth-probot/-/octokit-auth-probot-1.2.9.tgz", @@ -27004,6 +27522,11 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -27119,6 +27642,74 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-cloudflare": "^1.1.1", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-numeric": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz", + "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==", + "dev": true + }, + "pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==", + "requires": {} + }, + "pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -27387,6 +27978,35 @@ } } }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "postgres-range": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.3.tgz", + "integrity": "sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==", + "dev": true + }, "posthog-node": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.6.0.tgz", @@ -27841,6 +28461,11 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "process-warning": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", @@ -28043,6 +28668,11 @@ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -28413,6 +29043,11 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "sqlstring": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", + "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" + }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -28910,7 +29545,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, "requires": { "punycode": "^2.1.0" }, @@ -28918,8 +29552,7 @@ "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" } } }, @@ -29165,6 +29798,11 @@ "resolved": "https://registry.npmjs.org/xpath/-/xpath-0.0.27.tgz", "integrity": "sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==" }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/backend/package.json b/backend/package.json index 768f3d3af..8d8f0228c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -11,6 +11,7 @@ "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", "@ucast/mongo2js": "^1.3.4", + "ajv": "^8.12.0", "argon2": "^0.30.3", "aws-sdk": "^2.1364.0", "axios": "^1.3.5", @@ -29,12 +30,14 @@ "helmet": "^5.1.1", "infisical-node": "^1.2.1", "ioredis": "^5.3.2", + "jmespath": "^0.16.0", "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.0", "jsrp": "^0.2.4", "libsodium-wrappers": "^0.7.10", "lodash": "^4.17.21", "mongoose": "^7.4.1", + "mysql": "^2.18.1", "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", @@ -44,6 +47,7 @@ "passport-google-oauth20": "^2.0.0", "pino": "^8.16.1", "pino-http": "^8.5.1", + "pg": "^8.11.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", @@ -98,11 +102,14 @@ "@types/cors": "^2.8.12", "@types/express": "^4.17.14", "@types/jest": "^29.5.0", + "@types/jmespath": "^0.15.1", "@types/jsonwebtoken": "^8.5.9", "@types/lodash": "^4.14.191", + "@types/mysql": "^2.15.23", "@types/node": "^18.11.3", "@types/nodemailer": "^6.4.6", "@types/passport": "^1.0.12", + "@types/pg": "^8.10.7", "@types/picomatch": "^2.3.0", "@types/pino": "^7.0.5", "@types/supertest": "^2.0.12", diff --git a/backend/src/ee/controllers/v1/index.ts b/backend/src/ee/controllers/v1/index.ts index b023599e7..dc161c271 100644 --- a/backend/src/ee/controllers/v1/index.ts +++ b/backend/src/ee/controllers/v1/index.ts @@ -10,6 +10,8 @@ import * as cloudProductsController from "./cloudProductsController"; import * as roleController from "./roleController"; import * as secretApprovalPolicyController from "./secretApprovalPolicyController"; import * as secretApprovalRequestController from "./secretApprovalRequestsController"; +import * as secretRotationProviderController from "./secretRotationProviderController"; +import * as secretRotationController from "./secretRotationController"; export { secretController, @@ -23,5 +25,7 @@ export { cloudProductsController, roleController, secretApprovalPolicyController, - secretApprovalRequestController + secretApprovalRequestController, + secretRotationProviderController, + secretRotationController }; diff --git a/backend/src/ee/controllers/v1/secretRotationController.ts b/backend/src/ee/controllers/v1/secretRotationController.ts new file mode 100644 index 000000000..a8a2fd2dd --- /dev/null +++ b/backend/src/ee/controllers/v1/secretRotationController.ts @@ -0,0 +1,91 @@ +import { Request, Response } from "express"; +import { validateRequest } from "../../../helpers/validation"; +import * as reqValidator from "../../validation/secretRotation"; +import * as secretRotationService from "../../secretRotation/service"; +import { + getUserProjectPermissions, + ProjectPermissionActions, + ProjectPermissionSub +} from "../../services/ProjectRoleService"; +import { ForbiddenError } from "@casl/ability"; + +export const createSecretRotation = async (req: Request, res: Response) => { + const { + body: { + provider, + customProvider, + interval, + outputs, + secretPath, + environment, + workspaceId, + inputs + } + } = await validateRequest(reqValidator.createSecretRotationV1, req); + + const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Create, + ProjectPermissionSub.SecretRotation + ); + + const secretRotation = await secretRotationService.createSecretRotation({ + workspaceId, + inputs, + environment, + secretPath, + outputs, + interval, + customProvider, + provider + }); + + return res.send({ secretRotation }); +}; + +export const restartSecretRotations = async (req: Request, res: Response) => { + const { + body: { id } + } = await validateRequest(reqValidator.restartSecretRotationV1, req); + + const doc = await secretRotationService.getSecretRotationById({ id }); + const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString()); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Edit, + ProjectPermissionSub.SecretRotation + ); + + const secretRotation = await secretRotationService.restartSecretRotation({ id }); + return res.send({ secretRotation }); +}; + +export const deleteSecretRotations = async (req: Request, res: Response) => { + const { + params: { id } + } = await validateRequest(reqValidator.removeSecretRotationV1, req); + + const doc = await secretRotationService.getSecretRotationById({ id }); + const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString()); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Delete, + ProjectPermissionSub.SecretRotation + ); + + const secretRotations = await secretRotationService.deleteSecretRotation({ id }); + return res.send({ secretRotations }); +}; + +export const getSecretRotations = async (req: Request, res: Response) => { + const { + query: { workspaceId } + } = await validateRequest(reqValidator.getSecretRotationV1, req); + + const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.SecretRotation + ); + + const secretRotations = await secretRotationService.getSecretRotationOfWorkspace(workspaceId); + return res.send({ secretRotations }); +}; diff --git a/backend/src/ee/controllers/v1/secretRotationProviderController.ts b/backend/src/ee/controllers/v1/secretRotationProviderController.ts new file mode 100644 index 000000000..7d62ed5f5 --- /dev/null +++ b/backend/src/ee/controllers/v1/secretRotationProviderController.ts @@ -0,0 +1,28 @@ +import { Request, Response } from "express"; +import { validateRequest } from "../../../helpers/validation"; +import * as reqValidator from "../../validation/secretRotationProvider"; +import * as secretRotationProviderService from "../../secretRotation/service"; +import { + getUserProjectPermissions, + ProjectPermissionActions, + ProjectPermissionSub +} from "../../services/ProjectRoleService"; +import { ForbiddenError } from "@casl/ability"; + +export const getProviderTemplates = async (req: Request, res: Response) => { + const { + params: { workspaceId } + } = await validateRequest(reqValidator.getSecretRotationProvidersV1, req); + + const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + ProjectPermissionSub.SecretRotation + ); + + const rotationProviderList = await secretRotationProviderService.getProviderTemplate({ + workspaceId + }); + + return res.send(rotationProviderList); +}; diff --git a/backend/src/ee/models/auditLog/enums.ts b/backend/src/ee/models/auditLog/enums.ts index a7be37a65..45ba56345 100644 --- a/backend/src/ee/models/auditLog/enums.ts +++ b/backend/src/ee/models/auditLog/enums.ts @@ -1,7 +1,8 @@ export enum ActorType { - USER = "user", - SERVICE = "service", - SERVICE_V3 = "service-v3" + USER = "user", + SERVICE = "service", + SERVICE_V3 = "service-v3", + Machine = "machine" } export enum UserAgentType { diff --git a/backend/src/ee/models/auditLog/types.ts b/backend/src/ee/models/auditLog/types.ts index 579a02ccb..24796c70a 100644 --- a/backend/src/ee/models/auditLog/types.ts +++ b/backend/src/ee/models/auditLog/types.ts @@ -1,11 +1,5 @@ -import { - ActorType, - EventType -} from "./enums"; -import { - IServiceTokenV3Scope, - IServiceTokenV3TrustedIp -} from "../../../models/serviceTokenDataV3"; +import { ActorType, EventType } from "./enums"; +import { IServiceTokenV3Scope, IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3"; interface UserActorMetadata { userId: string; @@ -28,14 +22,15 @@ export interface ServiceActor { } export interface ServiceActorV3 { - type: ActorType.SERVICE_V3; - metadata: ServiceActorMetadata; + type: ActorType.SERVICE_V3; + metadata: ServiceActorMetadata; } -export type Actor = - | UserActor - | ServiceActor - | ServiceActorV3; +export interface MachineActor { + type: ActorType.Machine; +} + +export type Actor = UserActor | ServiceActor | ServiceActorV3 | MachineActor; interface GetSecretsEvent { type: EventType.GET_SECRETS; @@ -226,36 +221,36 @@ interface DeleteServiceTokenEvent { } interface CreateServiceTokenV3Event { - type: EventType.CREATE_SERVICE_TOKEN_V3; - metadata: { - name: string; - isActive: boolean; - scopes: Array; - trustedIps: Array; - expiresAt?: Date; - } + type: EventType.CREATE_SERVICE_TOKEN_V3; + metadata: { + name: string; + isActive: boolean; + scopes: Array; + trustedIps: Array; + expiresAt?: Date; + }; } interface UpdateServiceTokenV3Event { - type: EventType.UPDATE_SERVICE_TOKEN_V3; - metadata: { - name?: string; - isActive?: boolean; - scopes?: Array; - trustedIps?: Array; - expiresAt?: Date; - } + type: EventType.UPDATE_SERVICE_TOKEN_V3; + metadata: { + name?: string; + isActive?: boolean; + scopes?: Array; + trustedIps?: Array; + expiresAt?: Date; + }; } interface DeleteServiceTokenV3Event { - type: EventType.DELETE_SERVICE_TOKEN_V3; - metadata: { - name: string; - isActive: boolean; - scopes: Array; - expiresAt?: Date; - trustedIps: Array; - } + type: EventType.DELETE_SERVICE_TOKEN_V3; + metadata: { + name: string; + isActive: boolean; + scopes: Array; + expiresAt?: Date; + trustedIps: Array; + }; } interface CreateEnvironmentEvent { @@ -427,15 +422,15 @@ interface UpdateUserRole { } interface UpdateUserDeniedPermissions { - type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS, - metadata: { - userId: string; - email: string; - deniedPermissions: { - environmentSlug: string; - ability: string; - }[] - } + type: EventType.UPDATE_USER_WORKSPACE_DENIED_PERMISSIONS; + metadata: { + userId: string; + email: string; + deniedPermissions: { + environmentSlug: string; + ability: string; + }[]; + }; } interface SecretApprovalMerge { type: EventType.SECRET_APPROVAL_MERGED; diff --git a/backend/src/ee/routes/v1/index.ts b/backend/src/ee/routes/v1/index.ts index 34477cf72..40145e81f 100644 --- a/backend/src/ee/routes/v1/index.ts +++ b/backend/src/ee/routes/v1/index.ts @@ -10,6 +10,8 @@ import secretScanning from "./secretScanning"; import roles from "./role"; import secretApprovalPolicy from "./secretApprovalPolicy"; import secretApprovalRequest from "./secretApprovalRequest"; +import secretRotationProvider from "./secretRotationProvider"; +import secretRotation from "./secretRotation"; export { secret, @@ -23,5 +25,7 @@ export { secretScanning, roles, secretApprovalPolicy, - secretApprovalRequest + secretApprovalRequest, + secretRotationProvider, + secretRotation }; diff --git a/backend/src/ee/routes/v1/secretRotation.ts b/backend/src/ee/routes/v1/secretRotation.ts new file mode 100644 index 000000000..a4da8a72f --- /dev/null +++ b/backend/src/ee/routes/v1/secretRotation.ts @@ -0,0 +1,41 @@ +import express from "express"; + +import { AuthMode } from "../../../variables"; +import { requireAuth } from "../../../middleware"; +import { secretRotationController } from "../../controllers/v1"; + +const router = express.Router(); + +router.post( + "/", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + secretRotationController.createSecretRotation +); + +router.post( + "/restart", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + secretRotationController.restartSecretRotations +); + +router.get( + "/", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + secretRotationController.getSecretRotations +); + +router.delete( + "/:id", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + secretRotationController.deleteSecretRotations +); + +export default router; diff --git a/backend/src/ee/routes/v1/secretRotationProvider.ts b/backend/src/ee/routes/v1/secretRotationProvider.ts new file mode 100644 index 000000000..16ab17184 --- /dev/null +++ b/backend/src/ee/routes/v1/secretRotationProvider.ts @@ -0,0 +1,17 @@ +import express from "express"; + +import { AuthMode } from "../../../variables"; +import { requireAuth } from "../../../middleware"; +import { secretRotationProviderController } from "../../controllers/v1"; + +const router = express.Router(); + +router.get( + "/:workspaceId", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT] + }), + secretRotationProviderController.getProviderTemplates +); + +export default router; diff --git a/backend/src/ee/secretRotation/db.ts b/backend/src/ee/secretRotation/db.ts new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/ee/secretRotation/models.ts b/backend/src/ee/secretRotation/models.ts new file mode 100644 index 000000000..0ddde5d83 --- /dev/null +++ b/backend/src/ee/secretRotation/models.ts @@ -0,0 +1,91 @@ +import { Schema, model } from "mongoose"; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_BASE64, + ENCODING_SCHEME_UTF8 +} from "../../variables"; +import { ISecretRotation } from "./types"; + +const secretRotationSchema = new Schema( + { + workspace: { + type: Schema.Types.ObjectId, + ref: "Workspace" + }, + provider: { + type: String, + required: true + }, + customProvider: { + type: Schema.Types.ObjectId, + ref: "SecretRotationProvider" + }, + environment: { + type: String, + required: true + }, + secretPath: { + type: String, + required: true + }, + interval: { + type: Number, + required: true + }, + lastRotatedAt: { + type: String + }, + status: { + type: String, + enum: ["success", "failed"] + }, + statusMessage: { + type: String + }, + // encrypted data on input keys and secrets got + encryptedData: { + type: String, + select: false + }, + encryptedDataIV: { + type: String, + select: false + }, + encryptedDataTag: { + type: String, + select: false + }, + algorithm: { + // the encryption algorithm used + type: String, + enum: [ALGORITHM_AES_256_GCM], + required: true, + select: false, + default: ALGORITHM_AES_256_GCM + }, + keyEncoding: { + type: String, + enum: [ENCODING_SCHEME_UTF8, ENCODING_SCHEME_BASE64], + required: true, + select: false, + default: ENCODING_SCHEME_UTF8 + }, + outputs: [ + { + key: { + type: String, + required: true + }, + secret: { + type: Schema.Types.ObjectId, + ref: "Secret" + } + } + ] + }, + { + timestamps: true + } +); + +export const SecretRotation = model("SecretRotation", secretRotationSchema); diff --git a/backend/src/ee/secretRotation/providerTemplates.ts b/backend/src/ee/secretRotation/providerTemplates.ts new file mode 100644 index 000000000..4becc2ccd --- /dev/null +++ b/backend/src/ee/secretRotation/providerTemplates.ts @@ -0,0 +1,215 @@ +import { + ISecretRotationProviderTemplate, + TProviderFunctionTypes, + TDbProviderClients, + TAssignOp +} from "./types"; + +const SENDGRID_TEMPLATE = { + inputs: { + type: "object" as const, + properties: { + admin_api_key: { type: "string" as const }, + scopes: { type: "array", items: { type: "string" as const } } + }, + required: ["admin_api_key", "scopes"], + additionalProperties: false + }, + outputs: { + api_key: { type: "string" } + }, + internal: { + api_key_id: { type: "string" } + }, + functions: { + set: { + type: TProviderFunctionTypes.HTTP as const, + url: "https://api.sendgrid.com/v3/api_keys", + method: "POST", + header: { + Authorization: "Bearer ${inputs.admin_api_key}" + }, + body: { + name: "infisical-${random | 16}", + scopes: { ref: "inputs.scopes" } + }, + setter: { + "outputs.api_key": { + assign: TAssignOp.JmesPath as const, + path: "api_key" + }, + "internal.api_key_id": { + assign: TAssignOp.JmesPath as const, + path: "api_key_id" + } + } + }, + remove: { + type: TProviderFunctionTypes.HTTP as const, + url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", + header: { + Authorization: "Bearer ${inputs.admin_api_key}" + }, + method: "DELETE" + }, + test: { + type: TProviderFunctionTypes.HTTP as const, + url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", + header: { + Authorization: "Bearer ${inputs.admin_api_key}" + }, + method: "GET" + } + } +}; + +const POSTGRES_TEMPLATE = { + inputs: { + type: "object" as const, + properties: { + admin_username: { type: "string" as const }, + admin_password: { type: "string" as const }, + host: { type: "string" as const }, + database: { type: "string" as const }, + port: { type: "integer" as const, default: "5432" }, + username1: { type: "string", default: "infisical-pg-user1" }, + username2: { type: "string", default: "infisical-pg-user2" } + }, + required: ["admin_username", "admin_password", "host", "database"], + additionalProperties: false + }, + outputs: { + db_username: { type: "string" }, + db_password: { type: "string" } + }, + internal: { + rotated_password: { type: "string" }, + username: { type: "string" } + }, + functions: { + set: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Pg, + username: "${inputs.admin_username}", + password: "${inputs.admin_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + query: "ALTER USER ${internal.username} WITH PASSWORD '${internal.rotated_password}'", + setter: { + "outputs.db_username": { + assign: TAssignOp.Direct as const, + value: "${internal.username}" + }, + "outputs.db_password": { + assign: TAssignOp.Direct as const, + value: "${internal.rotated_password}" + } + }, + pre: { + "internal.rotated_password": { + assign: TAssignOp.Direct as const, + value: "${random | 32}" + } + } + }, + test: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Pg, + username: "${internal.username}", + password: "${internal.rotated_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + query: "SELECT NOW()" + } + } +}; + +const MYSQL_TEMPLATE = { + inputs: { + type: "object" as const, + properties: { + admin_username: { type: "string" as const }, + admin_password: { type: "string" as const }, + host: { type: "string" as const }, + database: { type: "string" as const }, + port: { type: "integer" as const, default: "3306" }, + username1: { type: "string", default: "infisical-sql-user1" }, + username2: { type: "string", default: "infisical-sql-user2" } + }, + required: ["admin_username", "admin_password", "host", "database"], + additionalProperties: false + }, + outputs: { + db_username: { type: "string" }, + db_password: { type: "string" } + }, + internal: { + rotated_password: { type: "string" }, + username: { type: "string" } + }, + functions: { + set: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Sql, + username: "${inputs.admin_username}", + password: "${inputs.admin_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + query: + "ALTER USER ${internal.username} IDENTIFIED WITH mysql_native_password BY '${internal.rotated_password}'", + setter: { + "outputs.db_username": { + assign: TAssignOp.Direct as const, + value: "${internal.username}" + }, + "outputs.db_password": { + assign: TAssignOp.Direct as const, + value: "${internal.rotated_password}" + } + }, + pre: { + "internal.rotated_password": { + assign: TAssignOp.Direct as const, + value: "${random | 32}" + } + } + }, + test: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Sql, + username: "${internal.username}", + password: "${internal.rotated_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + query: "SELECT NOW()" + } + } +}; + +export const providerRotationTemplates: ISecretRotationProviderTemplate[] = [ + { + name: "sendgrid", + title: "Twilio Sendgrid", + image: "sendgrid.png", + description: "Rotate Twilio Sendgrid API keys", + template: SENDGRID_TEMPLATE + }, + { + name: "postgres", + title: "PostgreSQL", + image: "postgres.png", + description: "Rotate PostgreSQL/CockroachDB user credentials", + template: POSTGRES_TEMPLATE + }, + { + name: "mysql", + title: "MySQL", + image: "mysql.png", + description: "Rotate MySQL@7/MariaDB user credentials", + template: MYSQL_TEMPLATE + } +]; diff --git a/backend/src/ee/secretRotation/queue.ts b/backend/src/ee/secretRotation/queue.ts new file mode 100644 index 000000000..a5fb368ec --- /dev/null +++ b/backend/src/ee/secretRotation/queue.ts @@ -0,0 +1,417 @@ +import axios from "axios"; +import Queue, { Job } from "bull"; +import jmespath from "jmespath"; +import { customAlphabet } from "nanoid"; +import { Client as PgClient } from "pg"; +import mysql from "mysql"; + +import { client, getRootEncryptionKey } from "../../config"; +import { BotService, TelemetryService } from "../../services"; +import { SecretRotation } from "./models"; +import { providerRotationTemplates } from "./providerTemplates"; +import { + ISecretRotationData, + ISecretRotationEncData, + ISecretRotationProviderTemplate, + TAssignOp, + TDbProviderClients, + TDbProviderFunction, + TDirectAssignOp, + THttpProviderFunction, + TProviderFunction, + TProviderFunctionTypes +} from "./types"; +import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; +import { ISecret, Secret } from "../../models"; +import { SECRET_SHARED } from "../../variables"; +import { EESecretService } from "../services"; +import { SecretVersion } from "../models"; + +const REGEX = /\${([^}]+)}/g; +const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +const nanoId = customAlphabet(SLUG_ALPHABETS, 10); + +const secretRotationQueue = new Queue("secret-rotation-service", process.env.REDIS_URL as string); + +const interpolate = (data: any, getValue: (key: string) => unknown) => { + if (!data) return; + + if (typeof data === "number") return data; + + if (typeof data === "string") { + return data.replace(REGEX, (_a, b) => getValue(b) as string); + } + + if (typeof data === "object" && Array.isArray(data)) { + data.forEach((el, index) => { + data[index] = interpolate(el, getValue); + }); + } + + if (typeof data === "object") { + if ((data as { ref: string })?.ref) return getValue((data as { ref: string }).ref); + const temp = data as Record; // for converting ts object to record type + Object.keys(temp).forEach((key) => { + temp[key as keyof typeof temp] = interpolate(data[key as keyof typeof temp], getValue); + }); + } + return data; +}; + +const getInterpolationValue = (variables: ISecretRotationData) => (key: string) => { + if (key.includes("|")) { + const [keyword, ...arg] = key.split("|").map((el) => el.trim()); + switch (keyword) { + case "random": { + return nanoId(parseInt(arg[0], 10)); + } + default: { + throw Error(`Interpolation key not found - ${key}`); + } + } + } + const [type, keyName] = key.split(".").map((el) => el.trim()); + return variables[type as keyof ISecretRotationData][keyName]; +}; + +const secretRotationHttpFn = async ( + func: THttpProviderFunction, + variables: ISecretRotationData +) => { + // string interpolation + const headers = interpolate(func.header, getInterpolationValue(variables)); + const url = interpolate(func.url, getInterpolationValue(variables)); + const body = interpolate(func.body, getInterpolationValue(variables)); + // axios will automatically throw error if req status is not between 2xx range + return axios({ method: func.method, url, headers, data: body }); +}; + +const secretRotationDbFn = async (func: TDbProviderFunction, variables: ISecretRotationData) => { + const { type, client, pre, ...dbConnection } = func; + const { username, password, host, database, port, query } = interpolate( + dbConnection, + getInterpolationValue(variables) + ); + if (host === "localhost" || host === "127.0.0.1") throw new Error("Invalid db host"); + if (client === TDbProviderClients.Pg) { + const pgClient = new PgClient({ user: username, password, host, database, port }); + await pgClient.connect(); + const res = await pgClient.query(query); + await pgClient.end(); + return res.rows[0]; + } else if (client === TDbProviderClients.Sql) { + const sqlClient = mysql.createPool({ + user: username, + password, + host, + database, + port, + connectionLimit: 1 + }); + const res = await new Promise((resolve, reject) => { + sqlClient.query(query, (err, data) => { + if (err) return reject(err); + resolve(data); + }); + }); + await new Promise((resolve, reject) => { + sqlClient.end(function (err) { + if (err) return reject(err); + return resolve({}); + }); + }); + return (res as any)?.[0]; + } +}; + +const secretRotationPreSetFn = ( + op: Record, + variables: ISecretRotationData +) => { + const getValFn = getInterpolationValue(variables); + Object.entries(op || {}).forEach(([key, assignFn]) => { + const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; + variables[type][keyName] = interpolate(assignFn.value, getValFn); + }); +}; + +const secretRotationSetFn = async (func: TProviderFunction, variables: ISecretRotationData) => { + const getValFn = getInterpolationValue(variables); + // http setter + if (func.type === TProviderFunctionTypes.HTTP) { + const res = await secretRotationHttpFn(func, variables); + Object.entries(func.setter || {}).forEach(([key, assignFn]) => { + const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; + if (assignFn.assign === TAssignOp.JmesPath) { + variables[type][keyName] = jmespath.search(res.data, assignFn.path); + } else if (assignFn.value) { + variables[type][keyName] = interpolate(assignFn.value, getValFn); + } + }); + // db setter + } else if (func.type === TProviderFunctionTypes.DB) { + const data = await secretRotationDbFn(func, variables); + Object.entries(func.setter || {}).forEach(([key, assignFn]) => { + const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; + if (assignFn.assign === TAssignOp.JmesPath) { + if (typeof data === "object") { + variables[type][keyName] = jmespath.search(data, assignFn.path); + } + } else if (assignFn.value) { + variables[type][keyName] = interpolate(assignFn.value, getValFn); + } + }); + } +}; + +const secretRotationTestFn = async (func: TProviderFunction, variables: ISecretRotationData) => { + if (func.type === TProviderFunctionTypes.HTTP) { + await secretRotationHttpFn(func, variables); + } else if (func.type === TProviderFunctionTypes.DB) { + await secretRotationDbFn(func, variables); + } +}; + +const secretRotationRemoveFn = async (func: TProviderFunction, variables: ISecretRotationData) => { + if (!func) return; + if (func.type === TProviderFunctionTypes.HTTP) { + // string interpolation + return await secretRotationHttpFn(func, variables); + } +}; + +secretRotationQueue.process(async (job: Job) => { + const rotationStratDocId = job.data.rotationDocId; + const secretRotation = await SecretRotation.findById(rotationStratDocId) + .select("+encryptedData +encryptedDataTag +encryptedDataIV") + .populate<{ + outputs: [ + { + key: string; + secret: ISecret; + } + ]; + }>("outputs.secret"); + + const infisicalRotationProvider = providerRotationTemplates.find( + ({ name }) => name === secretRotation?.provider + ); + + try { + if (!infisicalRotationProvider || !secretRotation) + throw new Error("Failed to find rotation strategy"); + + if (secretRotation.outputs.some(({ secret }) => !secret)) + throw new Error("Secrets not found in dashboard"); + + const workspaceId = secretRotation.workspace; + + // deep copy + const provider = JSON.parse( + JSON.stringify(infisicalRotationProvider) + ) as ISecretRotationProviderTemplate; + + // decrypt user provided inputs for secret rotation + const rootEncryptionKey = await getRootEncryptionKey(); + const decryptedData = client.decryptSymmetric( + secretRotation.encryptedData, + rootEncryptionKey, + secretRotation.encryptedDataIV, + secretRotation.encryptedDataTag + ); + + const variables = JSON.parse(decryptedData) as ISecretRotationEncData; + + // rotation set cycle + const newCredential: ISecretRotationData = { + inputs: variables.inputs, + outputs: {}, + internal: {} + }; + // special glue code for database + if (provider.template.functions.set.type === TProviderFunctionTypes.DB) { + const lastCred = variables.creds.at(-1); + if (lastCred && variables.creds.length === 1) { + newCredential.internal.username = + lastCred.internal.username === variables.inputs.username1 + ? variables.inputs.username2 + : variables.inputs.username1; + } else { + newCredential.internal.username = lastCred + ? lastCred.internal.username + : variables.inputs.username1; + } + } + if (provider.template.functions.set?.pre) { + secretRotationPreSetFn(provider.template.functions.set.pre, newCredential); + } + await secretRotationSetFn(provider.template.functions.set, newCredential); + await secretRotationTestFn(provider.template.functions.test, newCredential); + + if (variables.creds.length === 2) { + const deleteCycleCred = variables.creds.pop(); + if (deleteCycleCred && provider.template.functions.remove) { + const deleteCycleVar = { inputs: variables.inputs, ...deleteCycleCred }; + await secretRotationRemoveFn(provider.template.functions.remove, deleteCycleVar); + } + } + variables.creds.unshift({ outputs: newCredential.outputs, internal: newCredential.internal }); + const { ciphertext, iv, tag } = client.encryptSymmetric( + JSON.stringify(variables), + rootEncryptionKey + ); + + // save the rotation state + await SecretRotation.findByIdAndUpdate(rotationStratDocId, { + encryptedData: ciphertext, + encryptedDataIV: iv, + encryptedDataTag: tag, + status: "success", + statusMessage: "Rotated successfully", + lastRotatedAt: new Date().toUTCString() + }); + + const key = await BotService.getWorkspaceKeyWithBot({ + workspaceId: secretRotation.workspace + }); + + const encryptedSecrets = secretRotation.outputs.map(({ key: outputKey, secret }) => ({ + secret, + value: encryptSymmetric128BitHexKeyUTF8({ + plaintext: + typeof newCredential.outputs[outputKey] === "object" + ? JSON.stringify(newCredential.outputs[outputKey]) + : String(newCredential.outputs[outputKey]), + key + }) + })); + + // now save the secret do a bulk update + // can't use the updateSecret function due to various parameter required issue + // REFACTOR(akhilmhdh): secret module should be lot more flexible. Ability to update bulk or individually by blindIndex, by id etc + await Secret.bulkWrite( + encryptedSecrets.map(({ secret, value }) => ({ + updateOne: { + filter: { + workspace: workspaceId, + environment: secretRotation.environment, + _id: secret._id, + type: SECRET_SHARED + }, + update: { + $inc: { + version: 1 + }, + secretValueCiphertext: value.ciphertext, + secretValueIV: value.iv, + secretValueTag: value.tag + } + } + })) + ); + + await EESecretService.addSecretVersions({ + secretVersions: encryptedSecrets.map(({ secret, value }) => { + const { + _id, + version, + workspace, + type, + folder, + secretBlindIndex, + secretKeyIV, + secretKeyTag, + secretKeyCiphertext, + skipMultilineEncoding, + environment, + algorithm, + keyEncoding + } = secret; + + return new SecretVersion({ + secret: _id, + version: version + 1, + workspace: workspace, + type, + folder, + environment, + isDeleted: false, + secretBlindIndex: secretBlindIndex, + secretKeyCiphertext: secretKeyCiphertext, + secretKeyIV: secretKeyIV, + secretKeyTag: secretKeyTag, + secretValueCiphertext: value.ciphertext, + secretValueIV: value.iv, + secretValueTag: value.tag, + algorithm, + keyEncoding, + skipMultilineEncoding + }); + }) + }); + + // akhilmhdh: @tony need to do something about this as its depend on authData which is not possibile in here + // await EEAuditLogService.createAuditLog( + // {actor:ActorType.Machine}, + // { + // type: EventType.UPDATE_SECRETS, + // metadata: { + // environment, + // secretPath, + // secrets: secretsToBeUpdated.map(({ _id, version, secretBlindIndex }) => ({ + // secretId: _id.toString(), + // secretKey: secretBlindIndexToKey[secretBlindIndex || ""], + // secretVersion: version + 1 + // })) + // } + // }, + // { + // workspaceId + // } + // ); + + const folderId = encryptedSecrets?.[0]?.secret?.folder; + // (EE) take a secret snapshot + await EESecretService.takeSecretSnapshot({ + workspaceId, + environment: secretRotation.environment, + folderId + }); + + const postHogClient = await TelemetryService.getPostHogClient(); + + if (postHogClient) { + postHogClient.capture({ + event: "secrets rotated", + properties: { + numberOfSecrets: encryptedSecrets.length, + environment: secretRotation.environment, + workspaceId, + folderId + } + }); + } + } catch (err) { + console.error(err); + await SecretRotation.findByIdAndUpdate(rotationStratDocId, { + status: "failed", + statusMessage: (err as Error).message, + lastRotatedAt: new Date().toUTCString() + }); + } + return Promise.resolve(); +}); + +const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000; +export const startSecretRotationQueue = async (rotationDocId: string, interval: number) => { + // when migration to bull mq just use the option immedite to trigger repeatable immediately + secretRotationQueue.add({ rotationDocId }, { jobId: rotationDocId, removeOnComplete: true }); + return secretRotationQueue.add( + { rotationDocId }, + { repeat: { every: daysToMillisecond(interval) }, jobId: rotationDocId } + ); +}; + +export const removeSecretRotationQueue = async (rotationDocId: string, interval: number) => { + return secretRotationQueue.removeRepeatable({ every: interval * 1000, jobId: rotationDocId }); +}; diff --git a/backend/src/ee/secretRotation/service.ts b/backend/src/ee/secretRotation/service.ts new file mode 100644 index 000000000..7e02dc860 --- /dev/null +++ b/backend/src/ee/secretRotation/service.ts @@ -0,0 +1,116 @@ +import { + ISecretRotationEncData, + ISecretRotationProviderTemplate, + TCreateSecretRotation, + TGetProviderTemplates +} from "./types"; +import { + providerRotationTemplates as infisicalRotationTemplates, + providerRotationTemplates +} from "./providerTemplates"; +import { SecretRotation } from "./models"; +import { client, getRootEncryptionKey } from "../../config"; +import { BadRequestError } from "../../utils/errors"; +import Ajv from "ajv"; +import { removeSecretRotationQueue, startSecretRotationQueue } from "./queue"; + +const ajv = new Ajv(); + +export const getProviderTemplate = async ({ workspaceId }: TGetProviderTemplates) => { + return { + custom: [], + providers: infisicalRotationTemplates + }; +}; + +export const createSecretRotation = async ({ + workspaceId, + secretPath, + environment, + provider, + interval, + inputs, + outputs +}: TCreateSecretRotation) => { + const rotationTemplate = providerRotationTemplates.find(({ name }) => name === provider); + if (!rotationTemplate) throw BadRequestError({ message: "Provider not found" }); + + const formattedInputs: Record = {}; + Object.entries(inputs).forEach(([key, value]) => { + const type = rotationTemplate.template.inputs.properties[key].type; + if (type === "string") { + formattedInputs[key] = value; + return; + } + if (type === "integer") { + formattedInputs[key] = parseInt(value as string, 10); + return; + } + formattedInputs[key] = JSON.parse(value as string); + }); + // ensure input one follows the correct schema + const valid = ajv.validate(rotationTemplate.template.inputs, formattedInputs); + if (!valid) { + throw BadRequestError({ message: ajv.errors?.[0].message }); + } + + const encData: Partial = { + inputs: formattedInputs, + creds: [] + }; + + const rootEncryptionKey = await getRootEncryptionKey(); + const { ciphertext, iv, tag } = client.encryptSymmetric( + JSON.stringify(encData), + rootEncryptionKey + ); + + const secretRotation = new SecretRotation({ + workspace: workspaceId, + provider, + environment, + secretPath, + interval, + outputs: Object.entries(outputs).map(([key, secret]) => ({ key, secret })), + encryptedData: ciphertext, + encryptedDataIV: iv, + encryptedDataTag: tag + }); + + await secretRotation.save(); + await startSecretRotationQueue(secretRotation._id.toString(), interval); + + return secretRotation; +}; + +export const deleteSecretRotation = async ({ id }: { id: string }) => { + const doc = await SecretRotation.findByIdAndRemove(id); + if (!doc) throw BadRequestError({ message: "Rotation not found" }); + + await removeSecretRotationQueue(doc._id.toString(), doc.interval); + return doc; +}; + +export const restartSecretRotation = async ({ id }: { id: string }) => { + const secretRotation = await SecretRotation.findById(id); + if (!secretRotation) throw BadRequestError({ message: "Rotation not found" }); + + await removeSecretRotationQueue(secretRotation._id.toString(), secretRotation.interval); + await startSecretRotationQueue(secretRotation._id.toString(), secretRotation.interval); + + return secretRotation; +}; + +export const getSecretRotationById = async ({ id }: { id: string }) => { + const doc = await SecretRotation.findById(id); + if (!doc) throw BadRequestError({ message: "Rotation not found" }); + return doc; +}; + +export const getSecretRotationOfWorkspace = async (workspaceId: string) => { + const secretRotations = await SecretRotation.find({ + workspace: workspaceId + }).populate("outputs.secret"); + + return secretRotations; +}; diff --git a/backend/src/ee/secretRotation/types.ts b/backend/src/ee/secretRotation/types.ts new file mode 100644 index 000000000..4aa5975de --- /dev/null +++ b/backend/src/ee/secretRotation/types.ts @@ -0,0 +1,131 @@ +import { Document, Types } from "mongoose"; + +export interface ISecretRotation extends Document { + _id: Types.ObjectId; + name: string; + interval: number; + provider: string; + customProvider: Types.ObjectId; + workspace: Types.ObjectId; + environment: string; + secretPath: string; + outputs: Array<{ + key: string; + secret: Types.ObjectId; + }>; + status?: "success" | "failed"; + lastRotatedAt?: string; + statusMessage?: string; + encryptedData: string; + encryptedDataIV: string; + encryptedDataTag: string; + algorithm: string; + keyEncoding: string; +} + +export type ISecretRotationEncData = { + inputs: Record; + creds: Array<{ + outputs: Record; + internal: Record; + }>; +}; + +export type ISecretRotationData = { + inputs: Record; + outputs: Record; + internal: Record; +}; + +export type ISecretRotationProviderTemplate = { + name: string; + title: string; + image?: string; + description?: string; + template: TProviderTemplate; +}; + +export enum TProviderFunctionTypes { + HTTP = "http", + DB = "database" +} + +export enum TDbProviderClients { + // postgres, cockroack db, amazon red shift + Pg = "pg", + // mysql and maria db + Sql = "sql" +} + +export enum TAssignOp { + Direct = "direct", + JmesPath = "jmesopath" +} + +export type TJmesPathAssignOp = { + assign: TAssignOp.JmesPath; + path: string; +}; + +export type TDirectAssignOp = { + assign: TAssignOp.Direct; + value: string; +}; + +export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp; + +export type THttpProviderFunction = { + type: TProviderFunctionTypes.HTTP; + url: string; + method: string; + header?: Record; + query?: Record; + body?: Record; + setter?: Record; + pre?: Record; +}; + +export type TDbProviderFunction = { + type: TProviderFunctionTypes.DB; + client: TDbProviderClients; + username: string; + password: string; + host: string; + database: string; + port: string; + query: string; + setter?: Record; + pre?: Record; +}; + +export type TProviderFunction = THttpProviderFunction | TDbProviderFunction; + +export type TProviderTemplate = { + inputs: { + type: "object"; + properties: Record; + required?: string[]; + }; + outputs: Record; + functions: { + set: TProviderFunction; + remove?: TProviderFunction; + test: TProviderFunction; + }; +}; + +// function type args +export type TGetProviderTemplates = { + workspaceId: string; +}; + +export type TCreateSecretRotation = { + provider: string; + customProvider?: string; + workspaceId: string; + secretPath: string; + environment: string; + interval: number; + inputs: Record; + outputs: Record; +}; diff --git a/backend/src/ee/services/ProjectRoleService.ts b/backend/src/ee/services/ProjectRoleService.ts index 867edb7e5..ac71121de 100644 --- a/backend/src/ee/services/ProjectRoleService.ts +++ b/backend/src/ee/services/ProjectRoleService.ts @@ -50,7 +50,8 @@ export enum ProjectPermissionSub { Workspace = "workspace", Secrets = "secrets", SecretRollback = "secret-rollback", - SecretApproval = "secret-approval" + SecretApproval = "secret-approval", + SecretRotation = "secret-rotation" } type SubjectFields = { @@ -74,6 +75,7 @@ export type ProjectPermissionSet = | [ProjectPermissionActions, ProjectPermissionSub.Settings] | [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens] | [ProjectPermissionActions, ProjectPermissionSub.SecretApproval] + | [ProjectPermissionActions, ProjectPermissionSub.SecretRotation] | [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace] | [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace] | [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback] @@ -92,6 +94,11 @@ const buildAdminPermission = () => { can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); + can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation); + can(ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation); + can(ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); @@ -162,6 +169,7 @@ const buildMemberPermission = () => { can(ProjectPermissionActions.Delete, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); can(ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback); @@ -214,6 +222,7 @@ const buildViewerPermission = () => { can(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval); can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback); + can(ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation); can(ProjectPermissionActions.Read, ProjectPermissionSub.Member); can(ProjectPermissionActions.Read, ProjectPermissionSub.Role); can(ProjectPermissionActions.Read, ProjectPermissionSub.Integrations); diff --git a/backend/src/ee/validation/secretRotation.ts b/backend/src/ee/validation/secretRotation.ts new file mode 100644 index 000000000..616844aaf --- /dev/null +++ b/backend/src/ee/validation/secretRotation.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; + +export const createSecretRotationV1 = z.object({ + body: z.object({ + workspaceId: z.string().trim(), + secretPath: z.string().trim(), + environment: z.string().trim(), + interval: z.number().min(1), + provider: z.string().trim(), + customProvider: z.string().trim().optional(), + inputs: z.record(z.unknown()), + outputs: z.record(z.string()) + }) +}); + +export const restartSecretRotationV1 = z.object({ + body: z.object({ + id: z.string().trim() + }) +}); + +export const getSecretRotationV1 = z.object({ + query: z.object({ + workspaceId: z.string().trim() + }) +}); + +export const removeSecretRotationV1 = z.object({ + params: z.object({ + id: z.string().trim() + }) +}); diff --git a/backend/src/ee/validation/secretRotationProvider.ts b/backend/src/ee/validation/secretRotationProvider.ts new file mode 100644 index 000000000..d322939bb --- /dev/null +++ b/backend/src/ee/validation/secretRotationProvider.ts @@ -0,0 +1,7 @@ +import { z } from "zod"; + +export const getSecretRotationProvidersV1 = z.object({ + params: z.object({ + workspaceId: z.string() + }) +}); diff --git a/backend/src/index.ts b/backend/src/index.ts index b1a7d80f5..c1e0baab9 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -27,8 +27,10 @@ import { users as eeUsersRouter, workspace as eeWorkspaceRouter, roles as v1RoleRouter, - secretApprovalPolicy as v1SecretApprovalPolicy, - secretApprovalRequest as v1SecretApprovalRequest, + secretApprovalPolicy as v1SecretApprovalPolicyRouter, + secretApprovalRequest as v1SecretApprovalRequestRouter, + secretRotation as v1SecretRotation, + secretRotationProvider as v1SecretRotationProviderRouter, secretScanning as v1SecretScanningRouter } from "./ee/routes/v1"; import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3"; @@ -194,6 +196,8 @@ const main = async () => { app.use("/api/v1/cloud-products", eeCloudProductsRouter); app.use("/api/v3/api-key", v3apiKeyDataRouter); // new app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // new + app.use("/api/v1/secret-rotation-providers", v1SecretRotationProviderRouter); + app.use("/api/v1/secret-rotations", v1SecretRotation); // v1 routes app.use("/api/v1/signup", v1SignupRouter); @@ -217,9 +221,9 @@ const main = async () => { app.use("/api/v1/webhooks", v1WebhooksRouter); app.use("/api/v1/secret-imports", v1SecretImpsRouter); app.use("/api/v1/roles", v1RoleRouter); - app.use("/api/v1/secret-approvals", v1SecretApprovalPolicy); + app.use("/api/v1/secret-approvals", v1SecretApprovalPolicyRouter); app.use("/api/v1/sso", v1SSORouter); - app.use("/api/v1/secret-approval-requests", v1SecretApprovalRequest); + app.use("/api/v1/secret-approval-requests", v1SecretApprovalRequestRouter); // v2 routes (improvements) app.use("/api/v2/signup", v2SignupRouter); diff --git a/backend/src/interfaces/middleware/index.ts b/backend/src/interfaces/middleware/index.ts index 5146d25cd..0100d3ff6 100644 --- a/backend/src/interfaces/middleware/index.ts +++ b/backend/src/interfaces/middleware/index.ts @@ -1,39 +1,27 @@ import { Types } from "mongoose"; -import { - IServiceTokenData, - IServiceTokenDataV3, - IUser, -} from "../../models"; -import { - ServiceActor, - ServiceActorV3, - UserActor, - UserAgentType -} from "../../ee/models"; +import { IServiceTokenData, IServiceTokenDataV3, IUser } from "../../models"; +import { ServiceActor, ServiceActorV3, UserActor, UserAgentType } from "../../ee/models"; interface BaseAuthData { - ipAddress: string; - userAgent: string; - userAgentType: UserAgentType; - tokenVersionId?: Types.ObjectId; + ipAddress: string; + userAgent: string; + userAgentType: UserAgentType; + tokenVersionId?: Types.ObjectId; } export interface UserAuthData extends BaseAuthData { - actor: UserActor; - authPayload: IUser; + actor: UserActor; + authPayload: IUser; } export interface ServiceTokenV3AuthData extends BaseAuthData { - actor: ServiceActorV3; - authPayload: IServiceTokenDataV3; + actor: ServiceActorV3; + authPayload: IServiceTokenDataV3; } export interface ServiceTokenAuthData extends BaseAuthData { - actor: ServiceActor; - authPayload: IServiceTokenData; + actor: ServiceActor; + authPayload: IServiceTokenData; } -export type AuthData = - | UserAuthData - | ServiceTokenV3AuthData - | ServiceTokenAuthData; \ No newline at end of file +export type AuthData = UserAuthData | ServiceTokenV3AuthData | ServiceTokenAuthData; From 97e4338335e3aeeda1879865d4d504d4c99573de Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 25 Oct 2023 12:17:31 +0530 Subject: [PATCH 09/24] feat(secret-rotation): implemented frontend ui for secret rotation --- .../public/images/secretRotation/mysql.png | Bin 0 -> 13007 bytes .../public/images/secretRotation/postgres.png | Bin 0 -> 69342 bytes .../public/images/secretRotation/sendgrid.png | Bin 0 -> 4708 bytes .../src/components/v2/Stepper/Stepper.tsx | 78 ++++ frontend/src/components/v2/Stepper/index.tsx | 2 + frontend/src/components/v2/index.tsx | 1 + .../context/ProjectPermissionContext/types.ts | 4 +- frontend/src/hooks/api/index.tsx | 1 + .../src/hooks/api/secretRotation/index.ts | 6 + .../src/hooks/api/secretRotation/mutation.tsx | 52 +++ .../src/hooks/api/secretRotation/queries.tsx | 110 +++++ .../src/hooks/api/secretRotation/types.ts | 133 ++++++ frontend/src/hooks/api/subscriptions/types.ts | 1 + frontend/src/hooks/api/types.ts | 6 + frontend/src/layouts/AppLayout/AppLayout.tsx | 12 + .../project/[id]/secret-rotation/index.tsx | 23 + .../SecretRotationPage/SecretRotationPage.tsx | 407 ++++++++++++++++++ .../SecretRotationPage.utils.ts | 30 ++ .../CreateRotationForm/CreateRotationForm.tsx | 149 +++++++ .../components/CreateRotationForm/index.tsx | 1 + .../steps/GeneralDetailsForm.tsx | 93 ++++ .../steps/RotationInputForm.tsx | 61 +++ .../steps/RotationOutputForm.tsx | 80 ++++ .../src/views/SecretRotationPage/index.tsx | 1 + 24 files changed, 1250 insertions(+), 1 deletion(-) create mode 100644 frontend/public/images/secretRotation/mysql.png create mode 100644 frontend/public/images/secretRotation/postgres.png create mode 100644 frontend/public/images/secretRotation/sendgrid.png create mode 100644 frontend/src/components/v2/Stepper/Stepper.tsx create mode 100644 frontend/src/components/v2/Stepper/index.tsx create mode 100644 frontend/src/hooks/api/secretRotation/index.ts create mode 100644 frontend/src/hooks/api/secretRotation/mutation.tsx create mode 100644 frontend/src/hooks/api/secretRotation/queries.tsx create mode 100644 frontend/src/hooks/api/secretRotation/types.ts create mode 100644 frontend/src/pages/project/[id]/secret-rotation/index.tsx create mode 100644 frontend/src/views/SecretRotationPage/SecretRotationPage.tsx create mode 100644 frontend/src/views/SecretRotationPage/SecretRotationPage.utils.ts create mode 100644 frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx create mode 100644 frontend/src/views/SecretRotationPage/components/CreateRotationForm/index.tsx create mode 100644 frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx create mode 100644 frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx create mode 100644 frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx create mode 100644 frontend/src/views/SecretRotationPage/index.tsx diff --git a/frontend/public/images/secretRotation/mysql.png b/frontend/public/images/secretRotation/mysql.png new file mode 100644 index 0000000000000000000000000000000000000000..d92befdbcc726ba83c1f8cb57b51f0e982f7e7b6 GIT binary patch literal 13007 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 Vfb-MthI7ynapO}s&9Rx~e*<*xtlj_s literal 0 HcmV?d00001 diff --git a/frontend/src/components/v2/Stepper/Stepper.tsx b/frontend/src/components/v2/Stepper/Stepper.tsx new file mode 100644 index 000000000..8eabddaf6 --- /dev/null +++ b/frontend/src/components/v2/Stepper/Stepper.tsx @@ -0,0 +1,78 @@ +import { Children, cloneElement, ReactElement, ReactNode } from "react"; +import { faCheck } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + +export type StepperProps = { + activeStep: number; + children: ReactNode; + direction: "vertical" | "horizontal"; + className?: string; +}; + +export const Stepper = ({ activeStep, children, direction, className }: StepperProps) => { + return ( +
+ {Children.map(children as ReactNode, (child: ReactNode, index) => { + const isCompleted = activeStep > index; + const isActive = index === activeStep; + const isNotLast = index + 1 !== (children as Array).length; + return ( +
+
+
+ {isCompleted ? : index + 1} +
+ {cloneElement(child as ReactElement, { + direction, + activeStep, + isCompleted, + isActive + })} +
+ {isNotLast && ( +
+ )} +
+ ); + })} +
+ ); +}; + +export type StepProps = { + title: string; + description?: ReactNode; + // isActive?: boolean; + // isCompleted?: boolean; + // activeStep?: number; + // direction?: "vertical" | "horizontal"; +}; + +export const Step = ({ title, description }: StepProps) => { + return ( +
+
{title}
+ {description &&
{description}
} +
+ ); +}; diff --git a/frontend/src/components/v2/Stepper/index.tsx b/frontend/src/components/v2/Stepper/index.tsx new file mode 100644 index 000000000..b90544cdd --- /dev/null +++ b/frontend/src/components/v2/Stepper/index.tsx @@ -0,0 +1,2 @@ +export type { StepperProps,StepProps } from "./Stepper"; +export { Step,Stepper } from "./Stepper"; diff --git a/frontend/src/components/v2/index.tsx b/frontend/src/components/v2/index.tsx index 31e2b4e57..26af93f37 100644 --- a/frontend/src/components/v2/index.tsx +++ b/frontend/src/components/v2/index.tsx @@ -22,6 +22,7 @@ export * from "./SecretInput"; export * from "./Select"; export * from "./Skeleton"; export * from "./Spinner"; +export * from "./Stepper"; export * from "./Switch"; export * from "./Table"; export * from "./Tabs"; diff --git a/frontend/src/context/ProjectPermissionContext/types.ts b/frontend/src/context/ProjectPermissionContext/types.ts index ae4b49d63..5ae91e756 100644 --- a/frontend/src/context/ProjectPermissionContext/types.ts +++ b/frontend/src/context/ProjectPermissionContext/types.ts @@ -21,7 +21,8 @@ export enum ProjectPermissionSub { Workspace = "workspace", Secrets = "secrets", SecretRollback = "secret-rollback", - SecretApproval = "secret-approval" + SecretApproval = "secret-approval", + SecretRotation = "secret-rotation" } type SubjectFields = { @@ -45,6 +46,7 @@ export type ProjectPermissionSet = | [ProjectPermissionActions, ProjectPermissionSub.Settings] | [ProjectPermissionActions, ProjectPermissionSub.ServiceTokens] | [ProjectPermissionActions, ProjectPermissionSub.SecretApproval] + | [ProjectPermissionActions, ProjectPermissionSub.SecretRotation] | [ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace] | [ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace] | [ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback] diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 008f1e2fb..c707fe33f 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -12,6 +12,7 @@ export * from "./secretApproval"; export * from "./secretApprovalRequest"; export * from "./secretFolders"; export * from "./secretImports"; +export * from "./secretRotation"; export * from "./secrets"; export * from "./secretSnapshots"; export * from "./serviceAccounts"; diff --git a/frontend/src/hooks/api/secretRotation/index.ts b/frontend/src/hooks/api/secretRotation/index.ts new file mode 100644 index 000000000..906c8b498 --- /dev/null +++ b/frontend/src/hooks/api/secretRotation/index.ts @@ -0,0 +1,6 @@ +export { + useCreateSecretRotation, + useDeleteSecretRotation, + useRestartSecretRotation +} from "./mutation"; +export { useGetSecretRotationProviders, useGetSecretRotations } from "./queries"; diff --git a/frontend/src/hooks/api/secretRotation/mutation.tsx b/frontend/src/hooks/api/secretRotation/mutation.tsx new file mode 100644 index 000000000..cda4f8074 --- /dev/null +++ b/frontend/src/hooks/api/secretRotation/mutation.tsx @@ -0,0 +1,52 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { secretRotationKeys } from "./queries"; +import { + TCreateSecretRotationDTO, + TDeleteSecretRotationDTO, + TRestartSecretRotationDTO +} from "./types"; + +export const useCreateSecretRotation = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TCreateSecretRotationDTO>({ + mutationFn: async (dto) => { + const { data } = await apiRequest.post("/api/v1/secret-rotations", dto); + return data; + }, + onSuccess: (_, { workspaceId }) => { + queryClient.invalidateQueries(secretRotationKeys.list({ workspaceId })); + } + }); +}; + +export const useDeleteSecretRotation = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TDeleteSecretRotationDTO>({ + mutationFn: async (dto) => { + const { data } = await apiRequest.delete(`/api/v1/secret-rotations/${dto.id}`); + return data; + }, + onSuccess: (_, { workspaceId }) => { + queryClient.invalidateQueries(secretRotationKeys.list({ workspaceId })); + } + }); +}; + +export const useRestartSecretRotation = () => { + const queryClient = useQueryClient(); + + return useMutation<{}, {}, TRestartSecretRotationDTO>({ + mutationFn: async (dto) => { + const { data } = await apiRequest.post("/api/v1/secret-rotations/restart", { id: dto.id }); + return data; + }, + onSuccess: (_, { workspaceId }) => { + queryClient.invalidateQueries(secretRotationKeys.list({ workspaceId })); + } + }); +}; diff --git a/frontend/src/hooks/api/secretRotation/queries.tsx b/frontend/src/hooks/api/secretRotation/queries.tsx new file mode 100644 index 000000000..a3c8e270e --- /dev/null +++ b/frontend/src/hooks/api/secretRotation/queries.tsx @@ -0,0 +1,110 @@ +import { useCallback } from "react"; +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { + decryptAssymmetric, + decryptSymmetric +} from "@app/components/utilities/cryptography/crypto"; +import { apiRequest } from "@app/config/request"; + +import { + TGetSecretRotationList, + TGetSecretRotationProviders, + TSecretRotation, + TSecretRotationProviderList +} from "./types"; + +export const secretRotationKeys = { + listProviders: ({ workspaceId }: TGetSecretRotationProviders) => [ + { workspaceId }, + "secret-rotation-providers" + ], + list: ({ workspaceId }: Omit) => + [{ workspaceId }, "secret-rotations"] as const +}; + +const fetchSecretRotationProviders = async ({ workspaceId }: TGetSecretRotationProviders) => { + const { data } = await apiRequest.get( + `/api/v1/secret-rotation-providers/${workspaceId}` + ); + return data; +}; + +export const useGetSecretRotationProviders = ({ + workspaceId, + options = {} +}: TGetSecretRotationProviders & { + options?: Omit< + UseQueryOptions< + TSecretRotationProviderList, + unknown, + TSecretRotationProviderList, + ReturnType + >, + "queryKey" | "queryFn" + >; +}) => + useQuery({ + ...options, + queryKey: secretRotationKeys.listProviders({ workspaceId }), + enabled: Boolean(workspaceId) && (options?.enabled ?? true), + queryFn: async () => fetchSecretRotationProviders({ workspaceId }) + }); + +const fetchSecretRotations = async ({ + workspaceId +}: Omit) => { + const { data } = await apiRequest.get<{ secretRotations: TSecretRotation[] }>( + "/api/v1/secret-rotations", + { params: { workspaceId } } + ); + return data.secretRotations; +}; + +export const useGetSecretRotations = ({ + workspaceId, + decryptFileKey, + options = {} +}: TGetSecretRotationList & { + options?: Omit< + UseQueryOptions< + TSecretRotation[], + unknown, + TSecretRotation<{ key: string }>[], + ReturnType + >, + "queryKey" | "queryFn" + >; +}) => + useQuery({ + ...options, + queryKey: secretRotationKeys.list({ workspaceId }), + enabled: Boolean(workspaceId) && (options?.enabled ?? true), + queryFn: async () => fetchSecretRotations({ workspaceId }), + select: useCallback( + (data: TSecretRotation[]) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + const decryptKey = decryptAssymmetric({ + ciphertext: decryptFileKey.encryptedKey, + nonce: decryptFileKey.nonce, + publicKey: decryptFileKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); + return data.map((el) => ({ + ...el, + outputs: el.outputs.map(({ key, secret }) => ({ + key, + secret: { + key: decryptSymmetric({ + ciphertext: secret.secretValueCiphertext, + iv: secret.secretValueIV, + tag: secret.secretValueTag, + key: decryptKey + }) + } + })) + })); + }, + [decryptFileKey] + ) + }); diff --git a/frontend/src/hooks/api/secretRotation/types.ts b/frontend/src/hooks/api/secretRotation/types.ts new file mode 100644 index 000000000..76d3b5174 --- /dev/null +++ b/frontend/src/hooks/api/secretRotation/types.ts @@ -0,0 +1,133 @@ +import { UserWsKeyPair } from "../keys/types"; +import { EncryptedSecret } from "../secrets/types"; + +export enum TProviderFunctionTypes { + HTTP = "http", + DB = "database" +} + +export enum TDbProviderClients { + // postgres, cockroack db, amazon red shift + Pg = "pg", + // mysql and maria db + Sql = "sql" +} + +export enum TAssignOp { + Direct = "direct", + JmesPath = "jmesopath" +} + +export type TJmesPathAssignOp = { + assign: TAssignOp.JmesPath; + path: string; +}; + +export type TDirectAssignOp = { + assign: TAssignOp.Direct; + value: string; +}; + +export type TAssignFunction = TJmesPathAssignOp | TDirectAssignOp; + +export type THttpProviderFunction = { + type: TProviderFunctionTypes.HTTP; + url: string; + method: string; + header?: Record; + query?: Record; + body?: Record; + setter?: Record; + pre?: Record; +}; + +export type TDbProviderFunction = { + type: TProviderFunctionTypes.DB; + client: TDbProviderClients; + username: string; + password: string; + host: string; + database: string; + port: string; + query: string; + setter?: Record; + pre?: Record; +}; + +export type TProviderFunction = THttpProviderFunction | TDbProviderFunction; + +export type TProviderTemplate = { + inputs: { + properties: Record; + type: "object"; + required: string[]; + }; + outputs: Record; + functions: { + set: TProviderFunction; + remove?: TProviderFunction; + test: TProviderFunction; + }; +}; + +export type TSecretRotation = { + _id: string; + interval: number; + provider: string; + customProvider: string; + workspace: string; + environment: string; + secretPath: string; + outputs: Array<{ + key: string; + secret: T; + }>; + status?: "success" | "failed"; + lastRotatedAt?: string; + statusMessage?: string; + algorithm: string; + keyEncoding: string; +}; + +export type TSecretRotationProvider = { + name: string; + image: string; + title: string; + description: string; + template: TProviderTemplate; +}; + +export type TSecretRotationProviderList = { + custom: TSecretRotationProvider[]; + providers: TSecretRotationProvider[]; +}; + +export type TGetSecretRotationProviders = { + workspaceId: string; +}; + +export type TGetSecretRotationList = { + workspaceId: string; + decryptFileKey: UserWsKeyPair; +}; + +export type TCreateSecretRotationDTO = { + workspaceId: string; + secretPath: string; + environment: string; + interval: number; + provider: string; + customProvider?: string; + inputs: Record; + outputs: Record; +}; + +export type TDeleteSecretRotationDTO = { + id: string; + workspaceId: string; +}; + +export type TRestartSecretRotationDTO = { + id: string; + workspaceId: string; +}; diff --git a/frontend/src/hooks/api/subscriptions/types.ts b/frontend/src/hooks/api/subscriptions/types.ts index c61967eec..e38a7527f 100644 --- a/frontend/src/hooks/api/subscriptions/types.ts +++ b/frontend/src/hooks/api/subscriptions/types.ts @@ -12,6 +12,7 @@ export type SubscriptionPlan = { secretVersioning: boolean; slug: string; secretApproval: string; + secretRotation: string; tier: number; workspaceLimit: number; workspacesUsed: number; diff --git a/frontend/src/hooks/api/types.ts b/frontend/src/hooks/api/types.ts index f6131e5b2..422219adf 100644 --- a/frontend/src/hooks/api/types.ts +++ b/frontend/src/hooks/api/types.ts @@ -13,6 +13,12 @@ export type { export { ApprovalStatus, CommitType } from "./secretApprovalRequest/types"; export type { TSecretFolder } from "./secretFolders/types"; export type { TImportedSecrets, TSecretImports } from "./secretImports/types"; +export type { + TGetSecretRotationProviders, + TProviderTemplate, + TSecretRotationProvider, + TSecretRotationProviderList +} from "./secretRotation/types"; export * from "./secrets/types"; export type { CreateServiceTokenDTO, ServiceToken } from "./serviceTokens/types"; export type { SubscriptionPlan } from "./subscriptions/types"; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 04302548e..74f289209 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -497,6 +497,18 @@ export const AppLayout = ({ children }: LayoutProps) => { + + + + Secret rotation + + + { + const { t } = useTranslation(); + + return ( +
+ + {t("common.head-title", { title: t("settings.project.title") })} + + + + +
+ ); +}; + +export default SecretRotation; + +SecretRotation.requireAuth = true; diff --git a/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx new file mode 100644 index 000000000..b83e30bde --- /dev/null +++ b/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx @@ -0,0 +1,407 @@ +import { useTranslation } from "react-i18next"; +import { + faArrowsSpin, + faExclamationTriangle, + faFolder, + faInfoCircle, + faRotate, + faTrash +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { formatDistance } from "date-fns"; + +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { ProjectPermissionCan } from "@app/components/permissions"; +import { + Button, + DeleteActionModal, + EmptyState, + IconButton, + Modal, + ModalContent, + Skeleton, + Spinner, + Table, + TableContainer, + TableSkeleton, + TBody, + Td, + Th, + THead, + Tooltip, + Tr, + UpgradePlanModal +} from "@app/components/v2"; +import { + ProjectPermissionActions, + ProjectPermissionSub, + useProjectPermission, + useSubscription, + useWorkspace +} from "@app/context"; +import { withProjectPermission } from "@app/hoc"; +import { usePopUp } from "@app/hooks"; +import { + useDeleteSecretRotation, + useGetSecretRotationProviders, + useGetSecretRotations, + useGetUserWsKey, + useGetWorkspaceBot, + useRestartSecretRotation, + useUpdateBotActiveStatus +} from "@app/hooks/api"; +import { TSecretRotationProvider } from "@app/hooks/api/types"; + +import { CreateRotationForm } from "./components/CreateRotationForm"; +import { generateBotKey } from "./SecretRotationPage.utils"; + +export const SecretRotationPage = withProjectPermission( + () => { + const { currentWorkspace } = useWorkspace(); + const { t } = useTranslation(); + const permission = useProjectPermission(); + const { createNotification } = useNotificationContext(); + const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([ + "createRotation", + "activeBot", + "deleteRotation", + "upgradePlan" + ] as const); + const workspaceId = currentWorkspace?._id || ""; + const canCreateRotation = permission.can( + ProjectPermissionActions.Create, + ProjectPermissionSub.SecretRotation + ); + const { subscription } = useSubscription(); + + const { data: userWsKey } = useGetUserWsKey(workspaceId); + + const { data: secretRotationProviders, isLoading: isRotationProviderLoading } = + useGetSecretRotationProviders({ workspaceId }); + const { data: secretRotations, isLoading: isRotationLoading } = useGetSecretRotations({ + workspaceId, + decryptFileKey: userWsKey! + }); + + const { + mutateAsync: deleteSecretRotation, + variables: deleteSecretRotationVars, + isLoading: isDeletingRotation + } = useDeleteSecretRotation(); + const { + mutateAsync: restartSecretRotation, + variables: restartSecretRotationVar, + isLoading: isRestartingRotation + } = useRestartSecretRotation(); + + const { data: bot } = useGetWorkspaceBot(workspaceId); + const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus(); + + const isBotActive = Boolean(bot?.isActive); + + const handleDeleteRotation = async () => { + const { id } = popUp.deleteRotation.data as { id: string }; + try { + await deleteSecretRotation({ + id, + workspaceId + }); + handlePopUpClose("deleteRotation"); + createNotification({ + type: "success", + text: "Successfully removed rotation" + }); + } catch (error) { + console.log(error); + createNotification({ + type: "error", + text: "Failed to remove rotation" + }); + } + }; + + const handleRestartRotation = async (id: string) => { + try { + await restartSecretRotation({ + id, + workspaceId + }); + createNotification({ + type: "success", + text: "Secret rotation initiated" + }); + } catch (error) { + console.log(error); + createNotification({ + type: "error", + text: "Failed to restart rotation" + }); + } + }; + + const handleUserAcceptBotCondition = async () => { + const provider = popUp.activeBot?.data as TSecretRotationProvider; + try { + if (bot?._id) { + const botKey = generateBotKey(bot.publicKey, userWsKey!); + await updateBotActiveStatus({ + isActive: true, + botId: bot._id, + workspaceId, + botKey + }); + } + handlePopUpOpen("createRotation", provider); + handlePopUpClose("activeBot"); + } catch (error) { + console.log(error); + createNotification({ + type: "error", + text: "Failed to create bot" + }); + } + }; + + const handleCreateRotation = async (provider: TSecretRotationProvider) => { + if (subscription && !subscription?.secretRotation) { + handlePopUpOpen("upgradePlan"); + return; + } + if (!canCreateRotation) { + createNotification({ type: "error", text: "Access permission denied!!" }); + return; + } + if (isBotActive) { + handlePopUpOpen("createRotation", provider); + } else { + handlePopUpOpen("activeBot", provider); + } + }; + + return ( +
+
+

Secret Rotation

+

Auto rotate secrets for better security

+
+
+
Rotated Secrets
+
+ + + + + + + + + + + + + + {isRotationLoading && ( + + )} + {!isRotationLoading && secretRotations?.length === 0 && ( + + + + )} + {secretRotations?.map( + ({ + environment, + secretPath, + outputs, + provider, + _id, + lastRotatedAt, + status, + statusMessage + }) => { + const isDeleting = deleteSecretRotationVars?.id === _id && isDeletingRotation; + const isRestarting = + restartSecretRotationVar?.id === _id && isRestartingRotation; + return ( + + + + + + + + + ); + } + )} + +
Secret NameEnvironmentProviderStatusLast RotationAction
+ +
+ {outputs + .map(({ key }) => key) + .join(",") + .toUpperCase()} + +
+
{environment}
+
+ + {secretPath} +
+
+
{provider} +
+ {status} + {status === "failed" && ( + + + + )} +
+
+ {lastRotatedAt + ? formatDistance(new Date(lastRotatedAt), new Date()) + : "-"} + +
+ + {(isAllowed) => ( + handleRestartRotation(_id)} + > + {isRestarting ? ( + + ) : ( + + )} + + )} + + + {(isAllowed) => ( + handlePopUpOpen("deleteRotation", { id: _id })} + > + {isDeleting ? ( + + ) : ( + + )} + + )} + +
+
+
+
+
+
Infisical Rotation Providers
+
+ {isRotationProviderLoading && + Array.from({ length: 12 }).map((_, index) => ( + + ))} + {!isRotationProviderLoading && + secretRotationProviders?.providers.map((provider) => ( +
{ + if (evt.key === "Enter") handlePopUpOpen("createRotation", provider); + }} + onClick={() => handleCreateRotation(provider)} + > + rotation provider logo +
+ {provider.title} +
+
+ + + +
+
+ ))} +
+ handlePopUpToggle("createRotation", isOpen)} + provider={(popUp.createRotation.data as TSecretRotationProvider) || {}} + /> + handlePopUpToggle("activeBot", isOpen)} + > + + + +
+ } + > + {t("integrations.why-infisical-needs-access")} + + + handlePopUpToggle("deleteRotation", isOpen)} + deleteKey="confirm" + onDeleteApproved={handleDeleteRotation} + /> + handlePopUpToggle("upgradePlan", isOpen)} + text="You can add secret rotation if you switch to Infisical's Team plan." + /> +
+ ); + }, + { action: ProjectPermissionActions.Read, subject: ProjectPermissionSub.SecretRotation } +); diff --git a/frontend/src/views/SecretRotationPage/SecretRotationPage.utils.ts b/frontend/src/views/SecretRotationPage/SecretRotationPage.utils.ts new file mode 100644 index 000000000..6dd346f8e --- /dev/null +++ b/frontend/src/views/SecretRotationPage/SecretRotationPage.utils.ts @@ -0,0 +1,30 @@ +import { UserWsKeyPair } from "@app/hooks/api/types"; + +import { + decryptAssymmetric, + encryptAssymmetric +} from "../../components/utilities/cryptography/crypto"; + +// refactor these to common function in frontend +export const generateBotKey = (botPublicKey: string, latestKey: UserWsKeyPair) => { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY"); + + if (!PRIVATE_KEY) { + throw new Error("Private Key missing"); + } + + const WORKSPACE_KEY = decryptAssymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); + + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: WORKSPACE_KEY, + publicKey: botPublicKey, + privateKey: PRIVATE_KEY + }); + + return { encryptedKey: ciphertext, nonce }; +}; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx new file mode 100644 index 000000000..c5493159e --- /dev/null +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx @@ -0,0 +1,149 @@ +import { useRef, useState } from "react"; +import { AnimatePresence, motion } from "framer-motion"; + +import { Modal, ModalContent, Step, Stepper } from "@app/components/v2"; +import { useCreateSecretRotation } from "@app/hooks/api"; +import { TSecretRotationProvider } from "@app/hooks/api/types"; + +import { useNotificationContext } from "~/components/context/Notifications/NotificationProvider"; + +import { GeneralDetailsForm, TFormSchema as TGeneralFormSchema } from "./steps/GeneralDetailsForm"; +import { RotationInputForm } from "./steps/RotationInputForm"; +import { + RotationOutputForm, + TFormSchema as TRotationOutputSchema +} from "./steps/RotationOutputForm"; + +const WIZARD_STEPS = [ + { + title: "General" + }, + { + title: "Inputs" + }, + { + title: "Secret Mapping" + } +]; + +type Props = { + isOpen?: boolean; + onToggle: (isOpen: boolean) => void; + customProvider?: string; + workspaceId: string; + provider: TSecretRotationProvider; +}; + +export const CreateRotationForm = ({ + isOpen, + onToggle, + provider, + workspaceId, + customProvider +}: Props) => { + const [wizardStep, setWizardStep] = useState(0); + const wizardData = useRef<{ + general?: TGeneralFormSchema; + input?: Record; + output?: TRotationOutputSchema; + }>({}); + const { createNotification } = useNotificationContext(); + + const { mutateAsync: createSecretRotation } = useCreateSecretRotation(); + + const handleFormCancel = () => { + onToggle(false); + setWizardStep(0); + wizardData.current = {}; + }; + + const handleFormSubmit = async () => { + if (!wizardData.current.general || !wizardData.current.input || !wizardData.current.output) + return; + try { + await createSecretRotation({ + workspaceId, + provider: provider.name, + customProvider, + secretPath: wizardData.current.general.secretPath, + environment: wizardData.current.general.environment, + interval: wizardData.current.general.interval, + inputs: wizardData.current.input, + outputs: wizardData.current.output + }); + setWizardStep(0); + onToggle(false); + wizardData.current = {}; + } catch (error) { + console.log(error); + createNotification({ + type: "error", + text: "Failed to create secret rotation" + }); + } + }; + + return ( + { + onToggle(state); + setWizardStep(0); + wizardData.current = {}; + }} + > + + + {WIZARD_STEPS.map(({ title }, index) => ( + + ))} + + + {wizardStep === 0 && ( + + { + wizardData.current.general = data; + setWizardStep((state) => state + 1); + }} + /> + + )} + {wizardStep === 1 && ( + { + wizardData.current.input = data; + setWizardStep((state) => state + 1); + }} + inputSchema={provider.template?.inputs || {}} + /> + )} + {wizardStep === 2 && ( + { + wizardData.current.output = data; + await handleFormSubmit(); + }} + /> + )} + + + + ); +}; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/index.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/index.tsx new file mode 100644 index 000000000..8392c4cdb --- /dev/null +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/index.tsx @@ -0,0 +1 @@ +export { CreateRotationForm } from "./CreateRotationForm"; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx new file mode 100644 index 000000000..b83b19974 --- /dev/null +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx @@ -0,0 +1,93 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; + +const formSchema = z.object({ + environment: z.string().trim(), + secretPath: z.string().trim().default("/"), + interval: z.number() +}); + +export type TFormSchema = z.infer; +type Props = { + onSubmit: (data: TFormSchema) => void; + onCancel: () => void; +}; + +export const GeneralDetailsForm = ({ onSubmit, onCancel }: Props) => { + const { currentWorkspace } = useWorkspace(); + const environments = currentWorkspace?.environments || []; + const { + control, + handleSubmit, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(formSchema) + }); + + return ( +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + field.onChange(parseInt(evt.target.value, 10))} + /> + + )} + /> +
+ + +
+ + ); +}; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx new file mode 100644 index 000000000..baacfc2a5 --- /dev/null +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx @@ -0,0 +1,61 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { Button, FormControl, SecretInput } from "@app/components/v2"; + +type Props = { + onSubmit: (data: Record) => void; + onCancel: () => void; + inputSchema: { + properties: Record; + required: string[]; + }; +}; + +const formSchema = z.record(z.string().trim().optional()); + +export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) => { + const { + control, + handleSubmit, + formState: { isSubmitting } + } = useForm>({ + resolver: zodResolver(formSchema) + }); + + return ( +
+ {Object.keys(inputSchema.properties || {}).map((inputName) => ( + ( + + + + )} + /> + ))} +
+ + +
+ + ); +}; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx new file mode 100644 index 000000000..ed60ba912 --- /dev/null +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationOutputForm.tsx @@ -0,0 +1,80 @@ +import { Controller, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { Button, FormControl, Select, SelectItem } from "@app/components/v2"; +import { useWorkspace } from "@app/context"; +import { useGetProjectSecrets, useGetUserWsKey } from "@app/hooks/api"; + +const formSchema = z.record(z.string()); + +export type TFormSchema = z.infer; +type Props = { + environment: string; + secretPath: string; + outputSchema: Record; + onSubmit: (data: TFormSchema) => void; + onCancel: () => void; +}; + +export const RotationOutputForm = ({ + onSubmit, + onCancel, + environment, + secretPath, + outputSchema = {} +}: Props) => { + const { currentWorkspace } = useWorkspace(); + const workspaceId = currentWorkspace?._id || ""; + const { + control, + handleSubmit, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(formSchema) + }); + + const { data: userWsKey } = useGetUserWsKey(workspaceId); + const { data: secrets } = useGetProjectSecrets({ + workspaceId, + environment, + secretPath, + decryptFileKey: userWsKey! + }); + + return ( +
+ {Object.keys(outputSchema).map((outputName) => ( + ( + + + + )} + /> + ))} +
+ + +
+ + ); +}; diff --git a/frontend/src/views/SecretRotationPage/index.tsx b/frontend/src/views/SecretRotationPage/index.tsx new file mode 100644 index 000000000..c2f41e76e --- /dev/null +++ b/frontend/src/views/SecretRotationPage/index.tsx @@ -0,0 +1 @@ +export { SecretRotationPage } from "./SecretRotationPage"; From c9c40521b258c6f5c624aa23601e957b6b4b7f32 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 26 Oct 2023 14:12:39 +0530 Subject: [PATCH 10/24] feat(secret-rotation): added db ssl support --- .../src/controllers/v3/secretsController.ts | 20 +- .../ee/secretRotation/providerTemplates.ts | 215 ------------------ backend/src/ee/secretRotation/queue.ts | 12 +- backend/src/ee/secretRotation/service.ts | 16 +- .../src/ee/secretRotation/templates/index.ts | 28 +++ .../src/ee/secretRotation/templates/mysql.ts | 75 ++++++ .../ee/secretRotation/templates/postgres.ts | 74 ++++++ .../ee/secretRotation/templates/sendgrid.ts | 59 +++++ backend/src/helpers/secrets.ts | 14 +- .../services/SecretService/index.ts | 1 - backend/src/validation/secrets.ts | 2 - frontend/public/lotties/rotation.json | 1 + .../src/components/v2/Stepper/Stepper.tsx | 4 +- frontend/src/layouts/AppLayout/AppLayout.tsx | 2 +- .../SecretRotationPage/SecretRotationPage.tsx | 8 +- .../CreateRotationForm/CreateRotationForm.tsx | 73 +++--- .../steps/GeneralDetailsForm.tsx | 93 -------- .../steps/RotationInputForm.tsx | 1 - .../steps/RotationOutputForm.tsx | 101 ++++++-- 19 files changed, 387 insertions(+), 412 deletions(-) delete mode 100644 backend/src/ee/secretRotation/providerTemplates.ts create mode 100644 backend/src/ee/secretRotation/templates/index.ts create mode 100644 backend/src/ee/secretRotation/templates/mysql.ts create mode 100644 backend/src/ee/secretRotation/templates/postgres.ts create mode 100644 backend/src/ee/secretRotation/templates/sendgrid.ts create mode 100644 frontend/public/lotties/rotation.json delete mode 100644 frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index 2e0c8514a..31b645432 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -140,7 +140,7 @@ export const getSecretsRaw = async (req: Request, res: Response) => { query: { secretPath, environment, workspaceId } } = validatedData; const { - query: { folderId, include_imports: includeImports } + query: { include_imports: includeImports } } = validatedData; // if the service token has single scope, it will get all secrets for that scope by default @@ -156,13 +156,6 @@ export const getSecretsRaw = async (req: Request, res: Response) => { workspaceId = serviceTokenDetails.workspace.toString(); } - if (folderId && folderId !== "root") { - const folder = await Folder.findOne({ workspace: workspaceId, environment }); - if (!folder) throw BadRequestError({ message: "Folder not found" }); - - secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; - } - if (!environment || !workspaceId) throw BadRequestError({ message: "Missing environment or workspace id" }); @@ -177,7 +170,6 @@ export const getSecretsRaw = async (req: Request, res: Response) => { const secrets = await SecretService.getSecrets({ workspaceId: new Types.ObjectId(workspaceId), environment, - folderId, secretPath, authData: req.authData }); @@ -467,20 +459,13 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => { export const getSecrets = async (req: Request, res: Response) => { const validatedData = await validateRequest(reqValidator.GetSecretsV3, req); const { - query: { environment, workspaceId, include_imports: includeImports, folderId } + query: { environment, workspaceId, include_imports: includeImports } } = validatedData; let { query: { secretPath } } = validatedData; - if (folderId && folderId !== "root") { - const folder = await Folder.findOne({ workspace: workspaceId, environment }); - if (!folder) return res.send({ secrets: [] }); - - secretPath = getFolderWithPathFromId(folder.nodes, folderId).folderPath; - } - const { authVerifier: permissionCheckFn } = await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -492,7 +477,6 @@ export const getSecrets = async (req: Request, res: Response) => { const secrets = await SecretService.getSecrets({ workspaceId: new Types.ObjectId(workspaceId), environment, - folderId, secretPath, authData: req.authData }); diff --git a/backend/src/ee/secretRotation/providerTemplates.ts b/backend/src/ee/secretRotation/providerTemplates.ts deleted file mode 100644 index 4becc2ccd..000000000 --- a/backend/src/ee/secretRotation/providerTemplates.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - ISecretRotationProviderTemplate, - TProviderFunctionTypes, - TDbProviderClients, - TAssignOp -} from "./types"; - -const SENDGRID_TEMPLATE = { - inputs: { - type: "object" as const, - properties: { - admin_api_key: { type: "string" as const }, - scopes: { type: "array", items: { type: "string" as const } } - }, - required: ["admin_api_key", "scopes"], - additionalProperties: false - }, - outputs: { - api_key: { type: "string" } - }, - internal: { - api_key_id: { type: "string" } - }, - functions: { - set: { - type: TProviderFunctionTypes.HTTP as const, - url: "https://api.sendgrid.com/v3/api_keys", - method: "POST", - header: { - Authorization: "Bearer ${inputs.admin_api_key}" - }, - body: { - name: "infisical-${random | 16}", - scopes: { ref: "inputs.scopes" } - }, - setter: { - "outputs.api_key": { - assign: TAssignOp.JmesPath as const, - path: "api_key" - }, - "internal.api_key_id": { - assign: TAssignOp.JmesPath as const, - path: "api_key_id" - } - } - }, - remove: { - type: TProviderFunctionTypes.HTTP as const, - url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", - header: { - Authorization: "Bearer ${inputs.admin_api_key}" - }, - method: "DELETE" - }, - test: { - type: TProviderFunctionTypes.HTTP as const, - url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", - header: { - Authorization: "Bearer ${inputs.admin_api_key}" - }, - method: "GET" - } - } -}; - -const POSTGRES_TEMPLATE = { - inputs: { - type: "object" as const, - properties: { - admin_username: { type: "string" as const }, - admin_password: { type: "string" as const }, - host: { type: "string" as const }, - database: { type: "string" as const }, - port: { type: "integer" as const, default: "5432" }, - username1: { type: "string", default: "infisical-pg-user1" }, - username2: { type: "string", default: "infisical-pg-user2" } - }, - required: ["admin_username", "admin_password", "host", "database"], - additionalProperties: false - }, - outputs: { - db_username: { type: "string" }, - db_password: { type: "string" } - }, - internal: { - rotated_password: { type: "string" }, - username: { type: "string" } - }, - functions: { - set: { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.Pg, - username: "${inputs.admin_username}", - password: "${inputs.admin_password}", - host: "${inputs.host}", - database: "${inputs.database}", - port: "${inputs.port}", - query: "ALTER USER ${internal.username} WITH PASSWORD '${internal.rotated_password}'", - setter: { - "outputs.db_username": { - assign: TAssignOp.Direct as const, - value: "${internal.username}" - }, - "outputs.db_password": { - assign: TAssignOp.Direct as const, - value: "${internal.rotated_password}" - } - }, - pre: { - "internal.rotated_password": { - assign: TAssignOp.Direct as const, - value: "${random | 32}" - } - } - }, - test: { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.Pg, - username: "${internal.username}", - password: "${internal.rotated_password}", - host: "${inputs.host}", - database: "${inputs.database}", - port: "${inputs.port}", - query: "SELECT NOW()" - } - } -}; - -const MYSQL_TEMPLATE = { - inputs: { - type: "object" as const, - properties: { - admin_username: { type: "string" as const }, - admin_password: { type: "string" as const }, - host: { type: "string" as const }, - database: { type: "string" as const }, - port: { type: "integer" as const, default: "3306" }, - username1: { type: "string", default: "infisical-sql-user1" }, - username2: { type: "string", default: "infisical-sql-user2" } - }, - required: ["admin_username", "admin_password", "host", "database"], - additionalProperties: false - }, - outputs: { - db_username: { type: "string" }, - db_password: { type: "string" } - }, - internal: { - rotated_password: { type: "string" }, - username: { type: "string" } - }, - functions: { - set: { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.Sql, - username: "${inputs.admin_username}", - password: "${inputs.admin_password}", - host: "${inputs.host}", - database: "${inputs.database}", - port: "${inputs.port}", - query: - "ALTER USER ${internal.username} IDENTIFIED WITH mysql_native_password BY '${internal.rotated_password}'", - setter: { - "outputs.db_username": { - assign: TAssignOp.Direct as const, - value: "${internal.username}" - }, - "outputs.db_password": { - assign: TAssignOp.Direct as const, - value: "${internal.rotated_password}" - } - }, - pre: { - "internal.rotated_password": { - assign: TAssignOp.Direct as const, - value: "${random | 32}" - } - } - }, - test: { - type: TProviderFunctionTypes.DB as const, - client: TDbProviderClients.Sql, - username: "${internal.username}", - password: "${internal.rotated_password}", - host: "${inputs.host}", - database: "${inputs.database}", - port: "${inputs.port}", - query: "SELECT NOW()" - } - } -}; - -export const providerRotationTemplates: ISecretRotationProviderTemplate[] = [ - { - name: "sendgrid", - title: "Twilio Sendgrid", - image: "sendgrid.png", - description: "Rotate Twilio Sendgrid API keys", - template: SENDGRID_TEMPLATE - }, - { - name: "postgres", - title: "PostgreSQL", - image: "postgres.png", - description: "Rotate PostgreSQL/CockroachDB user credentials", - template: POSTGRES_TEMPLATE - }, - { - name: "mysql", - title: "MySQL", - image: "mysql.png", - description: "Rotate MySQL@7/MariaDB user credentials", - template: MYSQL_TEMPLATE - } -]; diff --git a/backend/src/ee/secretRotation/queue.ts b/backend/src/ee/secretRotation/queue.ts index a5fb368ec..e85cb88ec 100644 --- a/backend/src/ee/secretRotation/queue.ts +++ b/backend/src/ee/secretRotation/queue.ts @@ -8,7 +8,7 @@ import mysql from "mysql"; import { client, getRootEncryptionKey } from "../../config"; import { BotService, TelemetryService } from "../../services"; import { SecretRotation } from "./models"; -import { providerRotationTemplates } from "./providerTemplates"; +import { rotationTemplates } from "./templates"; import { ISecretRotationData, ISecretRotationEncData, @@ -88,13 +88,14 @@ const secretRotationHttpFn = async ( const secretRotationDbFn = async (func: TDbProviderFunction, variables: ISecretRotationData) => { const { type, client, pre, ...dbConnection } = func; - const { username, password, host, database, port, query } = interpolate( + const { username, password, host, database, port, query, ca } = interpolate( dbConnection, getInterpolationValue(variables) ); + const ssl = ca ? { rejectUnauthorized: false, ca } : undefined; if (host === "localhost" || host === "127.0.0.1") throw new Error("Invalid db host"); if (client === TDbProviderClients.Pg) { - const pgClient = new PgClient({ user: username, password, host, database, port }); + const pgClient = new PgClient({ user: username, password, host, database, port, ssl }); await pgClient.connect(); const res = await pgClient.query(query); await pgClient.end(); @@ -106,7 +107,8 @@ const secretRotationDbFn = async (func: TDbProviderFunction, variables: ISecretR host, database, port, - connectionLimit: 1 + connectionLimit: 1, + ssl }); const res = await new Promise((resolve, reject) => { sqlClient.query(query, (err, data) => { @@ -193,7 +195,7 @@ secretRotationQueue.process(async (job: Job) => { ]; }>("outputs.secret"); - const infisicalRotationProvider = providerRotationTemplates.find( + const infisicalRotationProvider = rotationTemplates.find( ({ name }) => name === secretRotation?.provider ); diff --git a/backend/src/ee/secretRotation/service.ts b/backend/src/ee/secretRotation/service.ts index 7e02dc860..8476dcbb8 100644 --- a/backend/src/ee/secretRotation/service.ts +++ b/backend/src/ee/secretRotation/service.ts @@ -1,13 +1,5 @@ -import { - ISecretRotationEncData, - ISecretRotationProviderTemplate, - TCreateSecretRotation, - TGetProviderTemplates -} from "./types"; -import { - providerRotationTemplates as infisicalRotationTemplates, - providerRotationTemplates -} from "./providerTemplates"; +import { ISecretRotationEncData, TCreateSecretRotation, TGetProviderTemplates } from "./types"; +import { rotationTemplates } from "./templates"; import { SecretRotation } from "./models"; import { client, getRootEncryptionKey } from "../../config"; import { BadRequestError } from "../../utils/errors"; @@ -19,7 +11,7 @@ const ajv = new Ajv(); export const getProviderTemplate = async ({ workspaceId }: TGetProviderTemplates) => { return { custom: [], - providers: infisicalRotationTemplates + providers: rotationTemplates }; }; @@ -32,7 +24,7 @@ export const createSecretRotation = async ({ inputs, outputs }: TCreateSecretRotation) => { - const rotationTemplate = providerRotationTemplates.find(({ name }) => name === provider); + const rotationTemplate = rotationTemplates.find(({ name }) => name === provider); if (!rotationTemplate) throw BadRequestError({ message: "Provider not found" }); const formattedInputs: Record = {}; diff --git a/backend/src/ee/secretRotation/templates/index.ts b/backend/src/ee/secretRotation/templates/index.ts new file mode 100644 index 000000000..063d5149f --- /dev/null +++ b/backend/src/ee/secretRotation/templates/index.ts @@ -0,0 +1,28 @@ +import { ISecretRotationProviderTemplate } from "../types"; +import { MYSQL_TEMPLATE } from "./mysql"; +import { POSTGRES_TEMPLATE } from "./postgres"; +import { SENDGRID_TEMPLATE } from "./sendgrid"; + +export const rotationTemplates: ISecretRotationProviderTemplate[] = [ + { + name: "sendgrid", + title: "Twilio Sendgrid", + image: "sendgrid.png", + description: "Rotate Twilio Sendgrid API keys", + template: SENDGRID_TEMPLATE + }, + { + name: "postgres", + title: "PostgreSQL", + image: "postgres.png", + description: "Rotate PostgreSQL/CockroachDB user credentials", + template: POSTGRES_TEMPLATE + }, + { + name: "mysql", + title: "MySQL", + image: "mysql.png", + description: "Rotate MySQL@7/MariaDB user credentials", + template: MYSQL_TEMPLATE + } +]; diff --git a/backend/src/ee/secretRotation/templates/mysql.ts b/backend/src/ee/secretRotation/templates/mysql.ts new file mode 100644 index 000000000..fa730b881 --- /dev/null +++ b/backend/src/ee/secretRotation/templates/mysql.ts @@ -0,0 +1,75 @@ +import { TProviderFunctionTypes, TDbProviderClients, TAssignOp } from "../types"; + +export const MYSQL_TEMPLATE = { + inputs: { + type: "object" as const, + properties: { + admin_username: { type: "string" as const }, + admin_password: { type: "string" as const }, + host: { type: "string" as const }, + database: { type: "string" as const }, + port: { type: "integer" as const, default: "3306" }, + username1: { type: "string", default: "infisical-sql-user1" }, + username2: { type: "string", default: "infisical-sql-user2" }, + ca: { type: "string" } + }, + required: [ + "admin_username", + "admin_password", + "host", + "database", + "username1", + "username2", + "port" + ], + additionalProperties: false + }, + outputs: { + db_username: { type: "string" }, + db_password: { type: "string" } + }, + internal: { + rotated_password: { type: "string" }, + username: { type: "string" } + }, + functions: { + set: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Sql, + username: "${inputs.admin_username}", + password: "${inputs.admin_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + ca: "${inputs.ca}", + query: + "ALTER USER ${internal.username} IDENTIFIED WITH mysql_native_password BY '${internal.rotated_password}'", + setter: { + "outputs.db_username": { + assign: TAssignOp.Direct as const, + value: "${internal.username}" + }, + "outputs.db_password": { + assign: TAssignOp.Direct as const, + value: "${internal.rotated_password}" + } + }, + pre: { + "internal.rotated_password": { + assign: TAssignOp.Direct as const, + value: "${random | 32}" + } + } + }, + test: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Sql, + username: "${internal.username}", + password: "${internal.rotated_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + query: "SELECT NOW()" + } + } +}; diff --git a/backend/src/ee/secretRotation/templates/postgres.ts b/backend/src/ee/secretRotation/templates/postgres.ts new file mode 100644 index 000000000..ca489570b --- /dev/null +++ b/backend/src/ee/secretRotation/templates/postgres.ts @@ -0,0 +1,74 @@ +import { TProviderFunctionTypes, TDbProviderClients, TAssignOp } from "../types"; + +export const POSTGRES_TEMPLATE = { + inputs: { + type: "object" as const, + properties: { + admin_username: { type: "string" as const }, + admin_password: { type: "string" as const }, + host: { type: "string" as const }, + database: { type: "string" as const }, + port: { type: "integer" as const, default: "5432" }, + username1: { type: "string", default: "infisical-pg-user1" }, + username2: { type: "string", default: "infisical-pg-user2" }, + ca: { type: "string" } + }, + required: [ + "admin_username", + "admin_password", + "host", + "database", + "username1", + "username2", + "port" + ], + additionalProperties: false + }, + outputs: { + db_username: { type: "string" }, + db_password: { type: "string" } + }, + internal: { + rotated_password: { type: "string" }, + username: { type: "string" } + }, + functions: { + set: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Pg, + username: "${inputs.admin_username}", + password: "${inputs.admin_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + ca: "${inputs.ca}", + query: "ALTER USER ${internal.username} WITH PASSWORD '${internal.rotated_password}'", + setter: { + "outputs.db_username": { + assign: TAssignOp.Direct as const, + value: "${internal.username}" + }, + "outputs.db_password": { + assign: TAssignOp.Direct as const, + value: "${internal.rotated_password}" + } + }, + pre: { + "internal.rotated_password": { + assign: TAssignOp.Direct as const, + value: "${random | 32}" + } + } + }, + test: { + type: TProviderFunctionTypes.DB as const, + client: TDbProviderClients.Pg, + username: "${internal.username}", + password: "${internal.rotated_password}", + host: "${inputs.host}", + database: "${inputs.database}", + port: "${inputs.port}", + query: "SELECT NOW()" + } + } +}; diff --git a/backend/src/ee/secretRotation/templates/sendgrid.ts b/backend/src/ee/secretRotation/templates/sendgrid.ts new file mode 100644 index 000000000..454c9a145 --- /dev/null +++ b/backend/src/ee/secretRotation/templates/sendgrid.ts @@ -0,0 +1,59 @@ +import { TProviderFunctionTypes, TAssignOp } from "../types"; + +export const SENDGRID_TEMPLATE = { + inputs: { + type: "object" as const, + properties: { + admin_api_key: { type: "string" as const }, + scopes: { type: "array", items: { type: "string" as const } } + }, + required: ["admin_api_key", "scopes"], + additionalProperties: false + }, + outputs: { + api_key: { type: "string" } + }, + internal: { + api_key_id: { type: "string" } + }, + functions: { + set: { + type: TProviderFunctionTypes.HTTP as const, + url: "https://api.sendgrid.com/v3/api_keys", + method: "POST", + header: { + Authorization: "Bearer ${inputs.admin_api_key}" + }, + body: { + name: "infisical-${random | 16}", + scopes: { ref: "inputs.scopes" } + }, + setter: { + "outputs.api_key": { + assign: TAssignOp.JmesPath as const, + path: "api_key" + }, + "internal.api_key_id": { + assign: TAssignOp.JmesPath as const, + path: "api_key_id" + } + } + }, + remove: { + type: TProviderFunctionTypes.HTTP as const, + url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", + header: { + Authorization: "Bearer ${inputs.admin_api_key}" + }, + method: "DELETE" + }, + test: { + type: TProviderFunctionTypes.HTTP as const, + url: "https://api.sendgrid.com/v3/api_keys/${internal.api_key_id}", + header: { + Authorization: "Bearer ${inputs.admin_api_key}" + }, + method: "GET" + } + } +}; diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 82a98d1e9..4b38079c1 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -553,14 +553,22 @@ export const getSecretsHelper = async ({ workspaceId, environment, authData, - folderId, secretPath = "/" }: GetSecretsParams) => { let secrets: ISecret[] = []; // if using service token filter towards the folderId by secretpath - if (!folderId) { - folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); + const folders = await Folder.findOne({ + workspace: workspaceId, + environment + }); + let folderId = "root"; + if (!folders && folderId !== "root") return []; + // get folder from folder tree + if (folders) { + const folder = getFolderByPath(folders.nodes, secretPath); + if (!folder) return []; + folderId = folder?.id; } // get personal secrets first diff --git a/backend/src/interfaces/services/SecretService/index.ts b/backend/src/interfaces/services/SecretService/index.ts index a2d9c5bb5..631114a79 100644 --- a/backend/src/interfaces/services/SecretService/index.ts +++ b/backend/src/interfaces/services/SecretService/index.ts @@ -26,7 +26,6 @@ export interface CreateSecretParams { export interface GetSecretsParams { workspaceId: Types.ObjectId; environment: string; - folderId?: string; secretPath: string; authData: AuthData; } diff --git a/backend/src/validation/secrets.ts b/backend/src/validation/secrets.ts index 174ffa791..799e9531d 100644 --- a/backend/src/validation/secrets.ts +++ b/backend/src/validation/secrets.ts @@ -228,7 +228,6 @@ export const GetSecretsRawV3 = z.object({ workspaceId: z.string().trim().optional(), environment: z.string().trim().optional(), secretPath: z.string().trim().default("/"), - folderId: z.string().trim().optional(), include_imports: z .enum(["true", "false"]) .default("false") @@ -302,7 +301,6 @@ export const GetSecretsV3 = z.object({ workspaceId: z.string().trim(), environment: z.string().trim(), secretPath: z.string().trim().default("/"), - folderId: z.string().trim().optional(), include_imports: z .enum(["true", "false"]) .default("false") diff --git a/frontend/public/lotties/rotation.json b/frontend/public/lotties/rotation.json new file mode 100644 index 000000000..5ea9f1551 --- /dev/null +++ b/frontend/public/lotties/rotation.json @@ -0,0 +1 @@ +{"v":"5.8.1","fr":60,"ip":130,"op":250,"w":430,"h":430,"nm":"232-arrow-21","ddd":0,"assets":[{"id":"comp_0","nm":"in-reveal","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.724,"y":0.959},"o":{"x":0.249,"y":0.15},"t":0,"s":[254.591,233.94,0],"to":[91.728,-89.288,0],"ti":[15.825,17.58,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.212,"y":0.03},"t":20,"s":[371.54,52.516,0],"to":[-13.998,-15.55,0],"ti":[108.509,-104.074,0]},{"i":{"x":0.777,"y":0.968},"o":{"x":0.167,"y":0.166},"t":38.854,"s":[190.63,169.978,0],"to":[-116.05,111.307,0],"ti":[-17.451,-15.403,0]},{"i":{"x":0.34,"y":1},"o":{"x":0.092,"y":0.037},"t":60,"s":[52.25,371.162,0],"to":[17.313,15.282,0],"ti":[-111.385,109.594,0]},{"t":120,"s":[254.591,233.94,0]}],"ix":2,"l":2},"a":{"a":0,"k":[17.55,17.625,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.27,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[16.71,16.977],[15.715,15.982]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.73,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.218,17.05],[-5.447,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.962,17.05],[46.065,-17.05]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":43,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.091,17.05],[54.956,-17.05]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.27,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[15.7,18.006],[16.725,16.982]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.73,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5.779,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.477,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":43,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[55.496,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[212.41,212.41,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.8,0],[12.6,-0.5],[-0.1,-18.6],[-83.4,-4.2],[-15.5,0],[-4.133,0],[0,0],[0,15.1],[87.4,3.5]],"o":[[-13.3,0],[-86.5,3.6],[0,18.2],[14.5,0.7],[4.1,0],[41.4,0],[62.1,-6.4],[0,-18.8],[-12,-0.5]],"v":[[-0.05,-38.75],[-38.95,-37.95],[-190.45,-0.05],[-45.15,37.65],[-0.05,38.75],[12.35,38.65],[85.75,34.55],[190.45,-0.05],[37.15,-38.05]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.21],"y":[1]},"o":{"x":[0.28],"y":[0]},"t":0,"s":[0]},{"t":120,"s":[10]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.838],"y":[0.938]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[0.862]},"o":{"x":[0.333],"y":[0.122]},"t":20,"s":[24]},{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.233],"y":[0.277]},"t":60,"s":[74]},{"t":120,"s":[100]}],"ix":2},"o":{"a":0,"k":184,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":600,"st":0,"bm":0}]},{"id":"comp_1","nm":"hover-roll","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.454,"y":0.663},"o":{"x":0.333,"y":0},"t":0,"s":[254.591,233.94,0],"to":[91.728,-89.288,0],"ti":[15.825,17.58,0]},{"i":{"x":0.833,"y":0.839},"o":{"x":0.349,"y":0.126},"t":33,"s":[371.54,52.516,0],"to":[-13.998,-15.55,0],"ti":[108.509,-104.074,0]},{"i":{"x":0.609,"y":0.905},"o":{"x":0.167,"y":0.172},"t":52.439,"s":[190.63,169.978,0],"to":[-116.05,111.307,0],"ti":[-17.451,-15.403,0]},{"i":{"x":0.36,"y":1},"o":{"x":0.241,"y":0.111},"t":76,"s":[52.25,371.162,0],"to":[17.313,15.282,0],"ti":[-111.385,109.594,0]},{"t":120,"s":[254.591,233.94,0]}],"ix":2,"l":2},"a":{"a":0,"k":[17.55,17.625,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.962,17.05],[46.065,-17.05]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.091,17.05],[54.956,-17.05]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":76,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.477,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[55.496,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":76,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[212.41,212.41,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.3,0],[-12,-0.5],[0,-18.8],[62.1,-6.4],[41.4,0],[4.1,0],[14.5,0.7],[0,18.2],[-86.5,3.6]],"o":[[12.8,0],[87.4,3.5],[0,15.1],[0,0],[-4.133,0],[-15.5,0],[-83.4,-4.2],[-0.1,-18.6],[12.6,-0.5]],"v":[[-0.05,-38.75],[37.15,-38.05],[190.45,-0.05],[85.75,34.55],[12.35,38.65],[-0.05,38.75],[-45.15,37.65],[-190.45,-0.05],[-38.95,-37.95]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":90,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.669],"y":[0.508]},"o":{"x":[0.357],"y":[0]},"t":0,"s":[177]},{"i":{"x":[0.647],"y":[0.648]},"o":{"x":[0.319],"y":[0.372]},"t":15,"s":[140.786]},{"i":{"x":[0.69],"y":[0.756]},"o":{"x":[0.353],"y":[0.325]},"t":23,"s":[116.18]},{"i":{"x":[0.787],"y":[0.738]},"o":{"x":[0.432],"y":[0.53]},"t":27,"s":[102.846]},{"i":{"x":[0.678],"y":[0.533]},"o":{"x":[0.345],"y":[0.448]},"t":33,"s":[90]},{"i":{"x":[0.686],"y":[0.714]},"o":{"x":[0.353],"y":[0.284]},"t":37,"s":[82.015]},{"i":{"x":[0.631],"y":[0.643]},"o":{"x":[0.286],"y":[0.172]},"t":43,"s":[60.409]},{"i":{"x":[0.664],"y":[0.74]},"o":{"x":[0.322],"y":[0.327]},"t":54,"s":[0.454]},{"i":{"x":[0.673],"y":[0.789]},"o":{"x":[0.338],"y":[0.369]},"t":66,"s":[-61.886]},{"i":{"x":[0.679],"y":[0.543]},"o":{"x":[0.343],"y":[0.528]},"t":71,"s":[-80.306]},{"i":{"x":[0.587],"y":[0.409]},"o":{"x":[0.243],"y":[0.215]},"t":76,"s":[-88]},{"i":{"x":[0.627],"y":[0.73]},"o":{"x":[0.279],"y":[0.281]},"t":83,"s":[-105.245]},{"i":{"x":[0.68],"y":[1]},"o":{"x":[0.326],"y":[0.726]},"t":98,"s":[-157.904]},{"t":120,"s":[-183]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]},{"id":"comp_2","nm":"loop-roll","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.724,"y":0.939},"o":{"x":0.54,"y":0.486},"t":0,"s":[254.591,233.94,0],"to":[91.728,-89.288,0],"ti":[15.825,17.58,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.212,"y":0.043},"t":30,"s":[371.54,52.516,0],"to":[-13.998,-15.55,0],"ti":[108.509,-104.074,0]},{"i":{"x":0.777,"y":0.954},"o":{"x":0.167,"y":0.166},"t":57.334,"s":[190.63,169.978,0],"to":[-116.05,111.307,0],"ti":[-17.451,-15.403,0]},{"i":{"x":0.833,"y":0.856},"o":{"x":0.248,"y":0.053},"t":88,"s":[52.25,371.162,0],"to":[17.313,15.282,0],"ti":[-111.385,109.594,0]},{"t":120,"s":[254.591,233.94,0]}],"ix":2,"l":2},"a":{"a":0,"k":[17.55,17.625,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.962,17.05],[46.065,-17.05]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.091,17.05],[54.956,-17.05]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.477,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[55.496,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[212.41,212.41,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.3,0],[-12,-0.5],[0,-18.8],[62.1,-6.4],[41.4,0],[4.1,0],[14.5,0.7],[0,18.2],[-86.5,3.6]],"o":[[12.8,0],[87.4,3.5],[0,15.1],[0,0],[-4.133,0],[-15.5,0],[-83.4,-4.2],[-0.1,-18.6],[12.6,-0.5]],"v":[[-0.05,-38.75],[37.15,-38.05],[190.45,-0.05],[85.75,34.55],[12.35,38.65],[-0.05,38.75],[-45.15,37.65],[-190.45,-0.05],[-38.95,-37.95]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":90,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.571],"y":[0.529]},"o":{"x":[0.185],"y":[0.176]},"t":0,"s":[177]},{"i":{"x":[0.763],"y":[0.944]},"o":{"x":[0.359],"y":[0.427]},"t":12,"s":[139.995]},{"i":{"x":[0.771],"y":[0.949]},"o":{"x":[0.25],"y":[0.057]},"t":30,"s":[88.727]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.306],"y":[0.068]},"t":89,"s":[-88.215]},{"t":120,"s":[-183]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"control","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"stroke","np":3,"mn":"Pseudo/@@CfAoPsZIQ6KIGy9s+DWp8w","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@CfAoPsZIQ6KIGy9s+DWp8w-0001","ix":1,"v":{"a":0,"k":2,"ix":1}}]},{"ty":5,"nm":"primary","np":3,"mn":"ADBE Color Control","ix":2,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[1,1,1],"ix":1}}]}],"ip":0,"op":381,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"in-reveal","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":0,"op":130,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"hover-roll","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":130,"op":260,"st":130,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"loop-roll","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":260,"op":390,"st":260,"bm":0}],"markers":[{"tm":0,"cm":"in-reveal","dr":120},{"tm":130,"cm":"default:hover-roll","dr":120},{"tm":260,"cm":"loop-roll","dr":120}]} \ No newline at end of file diff --git a/frontend/src/components/v2/Stepper/Stepper.tsx b/frontend/src/components/v2/Stepper/Stepper.tsx index 8eabddaf6..8389121c0 100644 --- a/frontend/src/components/v2/Stepper/Stepper.tsx +++ b/frontend/src/components/v2/Stepper/Stepper.tsx @@ -32,7 +32,7 @@ export const Stepper = ({ activeStep, children, direction, className }: StepperP
{ return ( -
+
{title}
{description &&
{description}
}
diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 74f289209..aefe6ef62 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -503,7 +503,7 @@ export const AppLayout = ({ children }: LayoutProps) => { isSelected={ router.asPath === `/project/${currentWorkspace?._id}/secret-rotation` } - icon="system-outline-189-domain-verification" + icon="rotation" > Secret rotation diff --git a/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx index b83e30bde..e36f8d791 100644 --- a/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx +++ b/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx @@ -163,10 +163,10 @@ export const SecretRotationPage = withProjectPermission( }; const handleCreateRotation = async (provider: TSecretRotationProvider) => { - if (subscription && !subscription?.secretRotation) { - handlePopUpOpen("upgradePlan"); - return; - } + // if (subscription && !subscription?.secretRotation) { + // handlePopUpOpen("upgradePlan"); + // return; + // } if (!canCreateRotation) { createNotification({ type: "error", text: "Access permission denied!!" }); return; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx index c5493159e..3576cc0f9 100644 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/CreateRotationForm.tsx @@ -7,7 +7,6 @@ import { TSecretRotationProvider } from "@app/hooks/api/types"; import { useNotificationContext } from "~/components/context/Notifications/NotificationProvider"; -import { GeneralDetailsForm, TFormSchema as TGeneralFormSchema } from "./steps/GeneralDetailsForm"; import { RotationInputForm } from "./steps/RotationInputForm"; import { RotationOutputForm, @@ -16,13 +15,12 @@ import { const WIZARD_STEPS = [ { - title: "General" + title: "Inputs", + description: "Provider secrets" }, { - title: "Inputs" - }, - { - title: "Secret Mapping" + title: "Outputs", + description: "Map rotated secrets to keys" } ]; @@ -43,7 +41,6 @@ export const CreateRotationForm = ({ }: Props) => { const [wizardStep, setWizardStep] = useState(0); const wizardData = useRef<{ - general?: TGeneralFormSchema; input?: Record; output?: TRotationOutputSchema; }>({}); @@ -58,18 +55,17 @@ export const CreateRotationForm = ({ }; const handleFormSubmit = async () => { - if (!wizardData.current.general || !wizardData.current.input || !wizardData.current.output) - return; + if (!wizardData.current.input || !wizardData.current.output) return; try { await createSecretRotation({ workspaceId, provider: provider.name, customProvider, - secretPath: wizardData.current.general.secretPath, - environment: wizardData.current.general.environment, - interval: wizardData.current.general.interval, + secretPath: wizardData.current.output.secretPath, + environment: wizardData.current.output.environment, + interval: wizardData.current.output.interval, inputs: wizardData.current.input, - outputs: wizardData.current.output + outputs: wizardData.current.output.secrets }); setWizardStep(0); onToggle(false); @@ -98,49 +94,50 @@ export const CreateRotationForm = ({ className="max-w-2xl" > - {WIZARD_STEPS.map(({ title }, index) => ( - + {WIZARD_STEPS.map(({ title, description }, index) => ( + ))} {wizardStep === 0 && ( - { - wizardData.current.general = data; + wizardData.current.input = data; setWizardStep((state) => state + 1); }} + inputSchema={provider.template?.inputs || {}} /> )} {wizardStep === 1 && ( - { - wizardData.current.input = data; - setWizardStep((state) => state + 1); - }} - inputSchema={provider.template?.inputs || {}} - /> - )} - {wizardStep === 2 && ( - { - wizardData.current.output = data; - await handleFormSubmit(); - }} - /> + + { + wizardData.current.output = data; + await handleFormSubmit(); + }} + /> + )} diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx deleted file mode 100644 index b83b19974..000000000 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/GeneralDetailsForm.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { Controller, useForm } from "react-hook-form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { z } from "zod"; - -import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; - -const formSchema = z.object({ - environment: z.string().trim(), - secretPath: z.string().trim().default("/"), - interval: z.number() -}); - -export type TFormSchema = z.infer; -type Props = { - onSubmit: (data: TFormSchema) => void; - onCancel: () => void; -}; - -export const GeneralDetailsForm = ({ onSubmit, onCancel }: Props) => { - const { currentWorkspace } = useWorkspace(); - const environments = currentWorkspace?.environments || []; - const { - control, - handleSubmit, - formState: { isSubmitting } - } = useForm({ - resolver: zodResolver(formSchema) - }); - - return ( -
- ( - - - - )} - /> - ( - - - - )} - /> - ( - - field.onChange(parseInt(evt.target.value, 10))} - /> - - )} - /> -
- - -
- - ); -}; diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx index baacfc2a5..8c50687d1 100644 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx @@ -34,7 +34,6 @@ export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) => defaultValue={inputSchema.properties[inputName]?.default} render={({ field }) => ( ; type Props = { - environment: string; - secretPath: string; outputSchema: Record; onSubmit: (data: TFormSchema) => void; onCancel: () => void; }; -export const RotationOutputForm = ({ - onSubmit, - onCancel, - environment, - secretPath, - outputSchema = {} -}: Props) => { +export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Props) => { const { currentWorkspace } = useWorkspace(); + const environments = currentWorkspace?.environments || []; const workspaceId = currentWorkspace?._id || ""; const { control, handleSubmit, + watch, formState: { isSubmitting } } = useForm({ resolver: zodResolver(formSchema) }); + const environment = watch("environment", environments?.[0]?.slug); + const secretPath = watch("secretPath"); + const { data: userWsKey } = useGetUserWsKey(workspaceId); - const { data: secrets } = useGetProjectSecrets({ + const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({ workspaceId, environment, secretPath, @@ -44,11 +46,65 @@ export const RotationOutputForm = ({ return (
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + field.onChange(parseInt(evt.target.value, 10))} + /> + + )} + /> +
+
Mapping
+
Select keys for rotated value to get saved
+
{Object.keys(outputSchema).map((outputName) => ( ( )} From 9248f36edb6c8b4b911f0aa1f02ee74c13ba73b0 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Thu, 26 Oct 2023 20:17:18 +0530 Subject: [PATCH 11/24] feat(secret-rotation): added db ssl option in test function --- backend/src/ee/secretRotation/templates/mysql.ts | 1 + backend/src/ee/secretRotation/templates/postgres.ts | 1 + .../views/SecretRotationPage/SecretRotationPage.tsx | 10 +++++----- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/backend/src/ee/secretRotation/templates/mysql.ts b/backend/src/ee/secretRotation/templates/mysql.ts index fa730b881..b57666596 100644 --- a/backend/src/ee/secretRotation/templates/mysql.ts +++ b/backend/src/ee/secretRotation/templates/mysql.ts @@ -69,6 +69,7 @@ export const MYSQL_TEMPLATE = { host: "${inputs.host}", database: "${inputs.database}", port: "${inputs.port}", + ca: "${inputs.ca}", query: "SELECT NOW()" } } diff --git a/backend/src/ee/secretRotation/templates/postgres.ts b/backend/src/ee/secretRotation/templates/postgres.ts index ca489570b..c4981cf29 100644 --- a/backend/src/ee/secretRotation/templates/postgres.ts +++ b/backend/src/ee/secretRotation/templates/postgres.ts @@ -68,6 +68,7 @@ export const POSTGRES_TEMPLATE = { host: "${inputs.host}", database: "${inputs.database}", port: "${inputs.port}", + ca: "${inputs.ca}", query: "SELECT NOW()" } } diff --git a/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx b/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx index e36f8d791..0cb179db3 100644 --- a/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx +++ b/frontend/src/views/SecretRotationPage/SecretRotationPage.tsx @@ -163,10 +163,10 @@ export const SecretRotationPage = withProjectPermission( }; const handleCreateRotation = async (provider: TSecretRotationProvider) => { - // if (subscription && !subscription?.secretRotation) { - // handlePopUpOpen("upgradePlan"); - // return; - // } + if (subscription && !subscription?.secretRotation) { + handlePopUpOpen("upgradePlan"); + return; + } if (!canCreateRotation) { createNotification({ type: "error", text: "Access permission denied!!" }); return; @@ -392,7 +392,7 @@ export const SecretRotationPage = withProjectPermission( title="Are you sure want to delete this rotation?" subTitle="This will stop the rotation from dynamically changing. Secret won't be deleted" onChange={(isOpen) => handlePopUpToggle("deleteRotation", isOpen)} - deleteKey="confirm" + deleteKey="delete" onDeleteApproved={handleDeleteRotation} /> Date: Thu, 26 Oct 2023 12:35:44 -0400 Subject: [PATCH 12/24] add secretRotation to feature set --- backend/src/ee/services/EELicenseService.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/ee/services/EELicenseService.ts b/backend/src/ee/services/EELicenseService.ts index f403f55d6..6baea04dc 100644 --- a/backend/src/ee/services/EELicenseService.ts +++ b/backend/src/ee/services/EELicenseService.ts @@ -38,6 +38,7 @@ interface FeatureSet { trial_end: number | null; has_used_trial: boolean; secretApproval: boolean; + secretRotation: boolean; } /** @@ -74,7 +75,8 @@ class EELicenseService { status: null, trial_end: null, has_used_trial: true, - secretApproval: false + secretApproval: false, + secretRotation: true, } public localFeatureSet: NodeCache; From bc68a002659045703cd9c3279f0298305bfb5e9d Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Fri, 27 Oct 2023 15:00:51 +0530 Subject: [PATCH 13/24] feat(secret-rotation): updated lottie and added side effect on successfully --- backend/src/controllers/v3/secretsController.ts | 16 ++++++++++++++++ backend/src/ee/secretRotation/queue.ts | 13 +++++++++++-- frontend/public/lotties/rotation.json | 2 +- frontend/src/layouts/AppLayout/AppLayout.tsx | 4 ++-- 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index 31b645432..dedb15b65 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -859,6 +859,14 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => { authData: req.authData }); + await EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: new Types.ObjectId(workspaceId), + environment, + secretPath + }) + }); + return res.status(200).send({ secrets: createdSecrets }); @@ -903,6 +911,14 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => { authData: req.authData }); + await EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: new Types.ObjectId(workspaceId), + environment, + secretPath + }) + }); + return res.status(200).send({ secrets: updatedSecrets }); diff --git a/backend/src/ee/secretRotation/queue.ts b/backend/src/ee/secretRotation/queue.ts index e85cb88ec..c545a9ac2 100644 --- a/backend/src/ee/secretRotation/queue.ts +++ b/backend/src/ee/secretRotation/queue.ts @@ -6,7 +6,7 @@ import { Client as PgClient } from "pg"; import mysql from "mysql"; import { client, getRootEncryptionKey } from "../../config"; -import { BotService, TelemetryService } from "../../services"; +import { BotService, EventService, TelemetryService } from "../../services"; import { SecretRotation } from "./models"; import { rotationTemplates } from "./templates"; import { @@ -26,6 +26,7 @@ import { ISecret, Secret } from "../../models"; import { SECRET_SHARED } from "../../variables"; import { EESecretService } from "../services"; import { SecretVersion } from "../models"; +import { eventPushSecrets } from "../../events"; const REGEX = /\${([^}]+)}/g; const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; @@ -380,8 +381,15 @@ secretRotationQueue.process(async (job: Job) => { folderId }); - const postHogClient = await TelemetryService.getPostHogClient(); + await EventService.handleEvent({ + event: eventPushSecrets({ + workspaceId: secretRotation.workspace, + environment: secretRotation.environment, + secretPath: secretRotation.secretPath + }) + }); + const postHogClient = await TelemetryService.getPostHogClient(); if (postHogClient) { postHogClient.capture({ event: "secrets rotated", @@ -401,6 +409,7 @@ secretRotationQueue.process(async (job: Job) => { lastRotatedAt: new Date().toUTCString() }); } + return Promise.resolve(); }); diff --git a/frontend/public/lotties/rotation.json b/frontend/public/lotties/rotation.json index 5ea9f1551..d05d254a4 100644 --- a/frontend/public/lotties/rotation.json +++ b/frontend/public/lotties/rotation.json @@ -1 +1 @@ -{"v":"5.8.1","fr":60,"ip":130,"op":250,"w":430,"h":430,"nm":"232-arrow-21","ddd":0,"assets":[{"id":"comp_0","nm":"in-reveal","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.724,"y":0.959},"o":{"x":0.249,"y":0.15},"t":0,"s":[254.591,233.94,0],"to":[91.728,-89.288,0],"ti":[15.825,17.58,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.212,"y":0.03},"t":20,"s":[371.54,52.516,0],"to":[-13.998,-15.55,0],"ti":[108.509,-104.074,0]},{"i":{"x":0.777,"y":0.968},"o":{"x":0.167,"y":0.166},"t":38.854,"s":[190.63,169.978,0],"to":[-116.05,111.307,0],"ti":[-17.451,-15.403,0]},{"i":{"x":0.34,"y":1},"o":{"x":0.092,"y":0.037},"t":60,"s":[52.25,371.162,0],"to":[17.313,15.282,0],"ti":[-111.385,109.594,0]},{"t":120,"s":[254.591,233.94,0]}],"ix":2,"l":2},"a":{"a":0,"k":[17.55,17.625,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.27,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[16.71,16.977],[15.715,15.982]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.73,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.218,17.05],[-5.447,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.962,17.05],[46.065,-17.05]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":43,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.091,17.05],[54.956,-17.05]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.27,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[15.7,18.006],[16.725,16.982]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.73,"y":0},"t":10,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-5.779,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.477,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":43,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[55.496,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":60,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"t":100,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[212.41,212.41,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.8,0],[12.6,-0.5],[-0.1,-18.6],[-83.4,-4.2],[-15.5,0],[-4.133,0],[0,0],[0,15.1],[87.4,3.5]],"o":[[-13.3,0],[-86.5,3.6],[0,18.2],[14.5,0.7],[4.1,0],[41.4,0],[62.1,-6.4],[0,-18.8],[-12,-0.5]],"v":[[-0.05,-38.75],[-38.95,-37.95],[-190.45,-0.05],[-45.15,37.65],[-0.05,38.75],[12.35,38.65],[85.75,34.55],[190.45,-0.05],[37.15,-38.05]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.21],"y":[1]},"o":{"x":[0.28],"y":[0]},"t":0,"s":[0]},{"t":120,"s":[10]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.838],"y":[0.938]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[0.862]},"o":{"x":[0.333],"y":[0.122]},"t":20,"s":[24]},{"i":{"x":[0.38],"y":[1]},"o":{"x":[0.233],"y":[0.277]},"t":60,"s":[74]},{"t":120,"s":[100]}],"ix":2},"o":{"a":0,"k":184,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":600,"st":0,"bm":0}]},{"id":"comp_1","nm":"hover-roll","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.454,"y":0.663},"o":{"x":0.333,"y":0},"t":0,"s":[254.591,233.94,0],"to":[91.728,-89.288,0],"ti":[15.825,17.58,0]},{"i":{"x":0.833,"y":0.839},"o":{"x":0.349,"y":0.126},"t":33,"s":[371.54,52.516,0],"to":[-13.998,-15.55,0],"ti":[108.509,-104.074,0]},{"i":{"x":0.609,"y":0.905},"o":{"x":0.167,"y":0.172},"t":52.439,"s":[190.63,169.978,0],"to":[-116.05,111.307,0],"ti":[-17.451,-15.403,0]},{"i":{"x":0.36,"y":1},"o":{"x":0.241,"y":0.111},"t":76,"s":[52.25,371.162,0],"to":[17.313,15.282,0],"ti":[-111.385,109.594,0]},{"t":120,"s":[254.591,233.94,0]}],"ix":2,"l":2},"a":{"a":0,"k":[17.55,17.625,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.962,17.05],[46.065,-17.05]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.091,17.05],[54.956,-17.05]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":76,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":33,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.477,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[55.496,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":76,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[212.41,212.41,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.3,0],[-12,-0.5],[0,-18.8],[62.1,-6.4],[41.4,0],[4.1,0],[14.5,0.7],[0,18.2],[-86.5,3.6]],"o":[[12.8,0],[87.4,3.5],[0,15.1],[0,0],[-4.133,0],[-15.5,0],[-83.4,-4.2],[-0.1,-18.6],[12.6,-0.5]],"v":[[-0.05,-38.75],[37.15,-38.05],[190.45,-0.05],[85.75,34.55],[12.35,38.65],[-0.05,38.75],[-45.15,37.65],[-190.45,-0.05],[-38.95,-37.95]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":90,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.669],"y":[0.508]},"o":{"x":[0.357],"y":[0]},"t":0,"s":[177]},{"i":{"x":[0.647],"y":[0.648]},"o":{"x":[0.319],"y":[0.372]},"t":15,"s":[140.786]},{"i":{"x":[0.69],"y":[0.756]},"o":{"x":[0.353],"y":[0.325]},"t":23,"s":[116.18]},{"i":{"x":[0.787],"y":[0.738]},"o":{"x":[0.432],"y":[0.53]},"t":27,"s":[102.846]},{"i":{"x":[0.678],"y":[0.533]},"o":{"x":[0.345],"y":[0.448]},"t":33,"s":[90]},{"i":{"x":[0.686],"y":[0.714]},"o":{"x":[0.353],"y":[0.284]},"t":37,"s":[82.015]},{"i":{"x":[0.631],"y":[0.643]},"o":{"x":[0.286],"y":[0.172]},"t":43,"s":[60.409]},{"i":{"x":[0.664],"y":[0.74]},"o":{"x":[0.322],"y":[0.327]},"t":54,"s":[0.454]},{"i":{"x":[0.673],"y":[0.789]},"o":{"x":[0.338],"y":[0.369]},"t":66,"s":[-61.886]},{"i":{"x":[0.679],"y":[0.543]},"o":{"x":[0.343],"y":[0.528]},"t":71,"s":[-80.306]},{"i":{"x":[0.587],"y":[0.409]},"o":{"x":[0.243],"y":[0.215]},"t":76,"s":[-88]},{"i":{"x":[0.627],"y":[0.73]},"o":{"x":[0.279],"y":[0.281]},"t":83,"s":[-105.245]},{"i":{"x":[0.68],"y":[1]},"o":{"x":[0.326],"y":[0.726]},"t":98,"s":[-157.904]},{"t":120,"s":[-183]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]},{"id":"comp_2","nm":"loop-roll","fr":60,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Vector 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.724,"y":0.939},"o":{"x":0.54,"y":0.486},"t":0,"s":[254.591,233.94,0],"to":[91.728,-89.288,0],"ti":[15.825,17.58,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.212,"y":0.043},"t":30,"s":[371.54,52.516,0],"to":[-13.998,-15.55,0],"ti":[108.509,-104.074,0]},{"i":{"x":0.777,"y":0.954},"o":{"x":0.167,"y":0.166},"t":57.334,"s":[190.63,169.978,0],"to":[-116.05,111.307,0],"ti":[-17.451,-15.403,0]},{"i":{"x":0.833,"y":0.856},"o":{"x":0.248,"y":0.053},"t":88,"s":[52.25,371.162,0],"to":[17.313,15.282,0],"ti":[-111.385,109.594,0]},{"t":120,"s":[254.591,233.94,0]}],"ix":2,"l":2},"a":{"a":0,"k":[17.55,17.625,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.962,17.05],[46.065,-17.05]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[18.091,17.05],[54.956,-17.05]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.546,17.05],[17.273,-17.05]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.05,17.05],[-17.05,-17.05]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":30,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":47,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[46.477,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[55.496,52.3],[17.55,17.2]],"c":false}]},{"i":{"x":0.36,"y":1},"o":{"x":0.167,"y":0.167},"t":90,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[17.269,52.3],[17.55,17.2]],"c":false}]},{"t":103,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-17.55,52.3],[17.55,17.2]],"c":false}]}],"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Vector","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[212.41,212.41,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[118.243,118.243,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.3,0],[-12,-0.5],[0,-18.8],[62.1,-6.4],[41.4,0],[4.1,0],[14.5,0.7],[0,18.2],[-86.5,3.6]],"o":[[12.8,0],[87.4,3.5],[0,15.1],[0,0],[-4.133,0],[-15.5,0],[-83.4,-4.2],[-0.1,-18.6],[12.6,-0.5]],"v":[[-0.05,-38.75],[37.15,-38.05],[190.45,-0.05],[85.75,34.55],[12.35,38.65],[-0.05,38.75],[-45.15,37.65],[-190.45,-0.05],[-38.95,-37.95]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":90,"ix":2},"o":{"a":1,"k":[{"i":{"x":[0.571],"y":[0.529]},"o":{"x":[0.185],"y":[0.176]},"t":0,"s":[177]},{"i":{"x":[0.763],"y":[0.944]},"o":{"x":[0.359],"y":[0.427]},"t":12,"s":[139.995]},{"i":{"x":[0.771],"y":[0.949]},"o":{"x":[0.25],"y":[0.057]},"t":30,"s":[88.727]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.306],"y":[0.068]},"t":89,"s":[-88.215]},{"t":120,"s":[-183]}],"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('232-arrow-21').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":10,"ix":5,"x":"var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 2), comp('232-arrow-21').layer('control').effect('stroke')('Menu'));"},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"control","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"stroke","np":3,"mn":"Pseudo/@@CfAoPsZIQ6KIGy9s+DWp8w","ix":1,"en":1,"ef":[{"ty":7,"nm":"Menu","mn":"Pseudo/@@CfAoPsZIQ6KIGy9s+DWp8w-0001","ix":1,"v":{"a":0,"k":2,"ix":1}}]},{"ty":5,"nm":"primary","np":3,"mn":"ADBE Color Control","ix":2,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[1,1,1],"ix":1}}]}],"ip":0,"op":381,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"in-reveal","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":0,"op":130,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"hover-roll","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":130,"op":260,"st":130,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"loop-roll","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[215,215,0],"ix":2,"l":2},"a":{"a":0,"k":[215,215,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":430,"h":430,"ip":260,"op":390,"st":260,"bm":0}],"markers":[{"tm":0,"cm":"in-reveal","dr":120},{"tm":130,"cm":"default:hover-roll","dr":120},{"tm":260,"cm":"loop-roll","dr":120}]} \ No newline at end of file +{"v":"5.12.1","fr":60,"ip":70,"op":130,"w":500,"h":500,"nm":"system-regular-18-autorenew","ddd":0,"assets":[{"id":"comp_0","nm":"in-autorenew","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL ","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.121],"y":[1]},"o":{"x":[0.089],"y":[0.223]},"t":0,"s":[0]},{"t":59,"s":[-360]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary.design","cl":"primary design","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[56.365,-32.706,0],"ix":2,"l":2},"a":{"a":0,"k":[256.365,167.294,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[36.203,36.206],[-36.201,36.206],[-36.203,-36.206]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[149.137,136.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-54.792,0],[-7.083,-76.459]],"o":[[0,0],[26.458,-43.959],[78.542,0],[0,0]],"v":[[-139.584,5.208],[-139.584,5],[-10.208,-68.125],[139.584,68.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"t":9,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[260.212,167.294],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":1,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primary.design","cl":"primary design","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.319,132.712,0],"ix":2,"l":2},"a":{"a":0,"k":[243.319,332.712,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-36.203,-36.206],[36.201,-36.206],[36.203,36.206]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[350.223,363.801],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[54.792,0],[7.083,76.459]],"o":[[0,0],[-26.458,43.959],[-78.542,0],[0,0]],"v":[[139.584,-5.208],[139.584,-5],[10.208,68.125],[-139.584,-68.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":1,"s":[0]},{"t":9,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[239.795,332.712],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":1,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.002,250.002,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[2083,2083,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.41,0],[0,0],[0,-0.41],[-0.42,0],[0,0],[1.83,0],[0.31,3.35],[0.41,-0.05],[-0.04,-0.42],[-4.17,0],[-1.5,1.58],[0,0],[-0.42,0],[0,0.41],[0,0]],"o":[[0,0],[-0.42,0],[0,0.41],[0,0],[-1.22,1.28],[-3.39,0],[-0.04,-0.41],[-0.42,0.04],[0.38,4.12],[2.22,0],[0,0],[0,0.41],[0.42,0],[0,0],[-0.01,-0.42]],"v":[[6.917,-0.992],[3.417,-0.992],[2.657,-0.242],[3.417,0.508],[5.047,0.508],[0.317,2.528],[-6.173,-3.342],[-6.993,-4.022],[-7.673,-3.202],[0.317,4.028],[6.157,1.508],[6.157,3.238],[6.917,3.988],[7.677,3.238],[7.677,-0.242]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[249.683,253.972],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.17,0],[1.51,-1.59],[0,0],[0.42,0],[0,-0.41],[0,0],[-0.42,0],[0,0],[0,0.41],[0.42,0],[0,0],[-1.83,0],[-0.31,-3.35],[-0.38,0],[-0.02,0],[0.04,0.42]],"o":[[-2.24,0],[0,0],[0,-0.41],[-0.42,0],[0,0],[0,0.41],[0,0],[0.42,0],[0,-0.41],[0,0],[1.21,-1.28],[3.39,0],[0.04,0.39],[0.02,0],[0.42,-0.04],[-0.38,-4.12]],"v":[[-0.307,-4.025],[-6.177,-1.475],[-6.177,-3.235],[-6.937,-3.985],[-7.687,-3.235],[-7.687,0.245],[-6.927,0.995],[-3.427,0.995],[-2.667,0.245],[-3.427,-0.505],[-5.027,-0.505],[-0.307,-2.525],[6.183,3.345],[6.933,4.025],[7.003,4.025],[7.683,3.205]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[250.307,246.025],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[250.307,246.025],"ix":2},"a":{"a":0,"k":[250.307,246.025],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":300,"st":0,"ct":1,"bm":0}]},{"id":"comp_1","nm":"hover-autorenew","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL ","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.15],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[0]},{"t":60,"s":[-360]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":1,"s":[101,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":4,"s":[100,100,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":40,"s":[100,100,100]},{"t":60,"s":[101,100,100]}],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary.design","cl":"primary design","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[56.365,-32.706,0],"ix":2,"l":2},"a":{"a":0,"k":[256.365,167.294,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[36.203,36.206],[-36.201,36.206],[-36.203,-36.206]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[149.137,136.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-54.792,0],[-7.083,-76.459]],"o":[[0,0],[26.458,-43.959],[78.542,0],[0,0]],"v":[[-139.584,5.208],[-139.584,5],[-10.208,-68.125],[139.584,68.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[260.212,167.294],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":1,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primary.design","cl":"primary design","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.319,132.712,0],"ix":2,"l":2},"a":{"a":0,"k":[243.319,332.712,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-36.203,-36.206],[36.201,-36.206],[36.203,36.206]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[350.223,363.801],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[54.792,0],[7.083,76.459]],"o":[[0,0],[-26.458,43.959],[-78.542,0],[0,0]],"v":[[139.584,-5.208],[139.584,-5],[10.208,68.125],[-139.584,-68.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[239.795,332.712],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":1,"op":60,"st":1,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.002,250.002,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[2083,2083,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.41,0],[0,0],[0,-0.41],[-0.42,0],[0,0],[1.83,0],[0.31,3.35],[0.41,-0.05],[-0.04,-0.42],[-4.17,0],[-1.5,1.58],[0,0],[-0.42,0],[0,0.41],[0,0]],"o":[[0,0],[-0.42,0],[0,0.41],[0,0],[-1.22,1.28],[-3.39,0],[-0.04,-0.41],[-0.42,0.04],[0.38,4.12],[2.22,0],[0,0],[0,0.41],[0.42,0],[0,0],[-0.01,-0.42]],"v":[[6.917,-0.992],[3.417,-0.992],[2.657,-0.242],[3.417,0.508],[5.047,0.508],[0.317,2.528],[-6.173,-3.342],[-6.993,-4.022],[-7.673,-3.202],[0.317,4.028],[6.157,1.508],[6.157,3.238],[6.917,3.988],[7.677,3.238],[7.677,-0.242]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[249.683,253.972],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.17,0],[1.51,-1.59],[0,0],[0.42,0],[0,-0.41],[0,0],[-0.42,0],[0,0],[0,0.41],[0.42,0],[0,0],[-1.83,0],[-0.31,-3.35],[-0.38,0],[-0.02,0],[0.04,0.42]],"o":[[-2.24,0],[0,0],[0,-0.41],[-0.42,0],[0,0],[0,0.41],[0,0],[0.42,0],[0,-0.41],[0,0],[1.21,-1.28],[3.39,0],[0.04,0.39],[0.02,0],[0.42,-0.04],[-0.38,-4.12]],"v":[[-0.307,-4.025],[-6.177,-1.475],[-6.177,-3.235],[-6.937,-3.985],[-7.687,-3.235],[-7.687,0.245],[-6.927,0.995],[-3.427,0.995],[-2.667,0.245],[-3.427,-0.505],[-5.027,-0.505],[-0.307,-2.525],[6.183,3.345],[6.933,4.025],[7.003,4.025],[7.683,3.205]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[250.307,246.025],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[250.307,246.025],"ix":2},"a":{"a":0,"k":[250.307,246.025],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1,"st":-60,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".primary.design","cl":"primary design","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250.002,250.002,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[2083,2083,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.41,0],[0,0],[0,-0.41],[-0.42,0],[0,0],[1.83,0],[0.31,3.35],[0.41,-0.05],[-0.04,-0.42],[-4.17,0],[-1.5,1.58],[0,0],[-0.42,0],[0,0.41],[0,0]],"o":[[0,0],[-0.42,0],[0,0.41],[0,0],[-1.22,1.28],[-3.39,0],[-0.04,-0.41],[-0.42,0.04],[0.38,4.12],[2.22,0],[0,0],[0,0.41],[0.42,0],[0,0],[-0.01,-0.42]],"v":[[6.917,-0.992],[3.417,-0.992],[2.657,-0.242],[3.417,0.508],[5.047,0.508],[0.317,2.528],[-6.173,-3.342],[-6.993,-4.022],[-7.673,-3.202],[0.317,4.028],[6.157,1.508],[6.157,3.238],[6.917,3.988],[7.677,3.238],[7.677,-0.242]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[249.683,253.972],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.17,0],[1.51,-1.59],[0,0],[0.42,0],[0,-0.41],[0,0],[-0.42,0],[0,0],[0,0.41],[0.42,0],[0,0],[-1.83,0],[-0.31,-3.35],[-0.38,0],[-0.02,0],[0.04,0.42]],"o":[[-2.24,0],[0,0],[0,-0.41],[-0.42,0],[0,0],[0,0.41],[0,0],[0.42,0],[0,-0.41],[0,0],[1.21,-1.28],[3.39,0],[0.04,0.39],[0.02,0],[0.42,-0.04],[-0.38,-4.12]],"v":[[-0.307,-4.025],[-6.177,-1.475],[-6.177,-3.235],[-6.937,-3.985],[-7.687,-3.235],[-7.687,0.245],[-6.927,0.995],[-3.427,0.995],[-2.667,0.245],[-3.427,-0.505],[-5.027,-0.505],[-0.307,-2.525],[6.183,3.345],[6.933,4.025],[7.003,4.025],[7.683,3.205]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Fill","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[250.307,246.025],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[250.307,246.025],"ix":2},"a":{"a":0,"k":[250.307,246.025],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":300,"st":0,"ct":1,"bm":0}]},{"id":"comp_2","nm":"loop-autorenew","fr":60,"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"NULL ","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":60,"s":[-360]}],"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[50,50,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".primary.design","cl":"primary design","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[56.365,-32.706,0],"ix":2,"l":2},"a":{"a":0,"k":[256.365,167.294,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[36.203,36.206],[-36.201,36.206],[-36.203,-36.206]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[149.137,136.205],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-54.792,0],[-7.083,-76.459]],"o":[[0,0],[26.458,-43.959],[78.542,0],[0,0]],"v":[[-139.584,5.208],[-139.584,5],[-10.208,-68.125],[139.584,68.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[260.212,167.294],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":1,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".primary.design","cl":"primary design","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[43.319,132.712,0],"ix":2,"l":2},"a":{"a":0,"k":[243.319,332.712,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-36.203,-36.206],[36.201,-36.206],[36.203,36.206]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tr","p":{"a":0,"k":[350.223,363.801],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[54.792,0],[7.083,76.459]],"o":[[0,0],[-26.458,43.959],[-78.542,0],[0,0]],"v":[[139.584,-5.208],[139.584,-5],[10.208,68.125],[-139.584,-68.125]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3,"x":"var $bm_rt;\n$bm_rt = comp('system-regular-18-autorenew').layer('control').effect('primary')('Color');"},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":31.3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":".primary","mn":"ADBE Vector Graphic - Stroke","hd":false,"cl":"primary"},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":3,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"a":0,"k":[239.795,332.712],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":75,"st":1,"ct":1,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"control","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"ef":[{"ty":5,"nm":"primary","np":3,"mn":"ADBE Color Control","ix":1,"en":1,"ef":[{"ty":2,"nm":"Color","mn":"ADBE Color Control-0001","ix":1,"v":{"a":0,"k":[1,1,1],"ix":1}}]}],"ip":0,"op":201,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"in-autorenew","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":0,"op":70,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"hover-autorenew","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":70,"op":140,"st":70,"bm":0},{"ddd":0,"ind":4,"ty":0,"nm":"loop-autorenew","refId":"comp_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2,"l":2},"a":{"a":0,"k":[250,250,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"w":500,"h":500,"ip":140,"op":210,"st":140,"bm":0}],"markers":[{"tm":0,"cm":"in-autorenew","dr":60},{"tm":70,"cm":"default:hover-autorenew","dr":60},{"tm":140,"cm":"loop-autorenew","dr":60}],"props":{}} \ No newline at end of file diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index aefe6ef62..44b3964bc 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -505,7 +505,7 @@ export const AppLayout = ({ children }: LayoutProps) => { } icon="rotation" > - Secret rotation + Secret Rotation
@@ -517,7 +517,7 @@ export const AppLayout = ({ children }: LayoutProps) => { } icon="system-outline-189-domain-verification" > - Secret approvals + Secret Approvals {Boolean(secretApprovalReqCount?.open) && ( {secretApprovalReqCount?.open} From 2de898fdbd891d32c3e477e10fc197b183960b81 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Mon, 30 Oct 2023 22:19:12 +0530 Subject: [PATCH 14/24] feat: backward compatiable enc key --- backend/src/ee/secretRotation/queue.ts | 34 +++++++++++++----- backend/src/ee/secretRotation/service.ts | 44 ++++++++++++++++++------ 2 files changed, 58 insertions(+), 20 deletions(-) diff --git a/backend/src/ee/secretRotation/queue.ts b/backend/src/ee/secretRotation/queue.ts index c545a9ac2..501e4d748 100644 --- a/backend/src/ee/secretRotation/queue.ts +++ b/backend/src/ee/secretRotation/queue.ts @@ -5,7 +5,7 @@ import { customAlphabet } from "nanoid"; import { Client as PgClient } from "pg"; import mysql from "mysql"; -import { client, getRootEncryptionKey } from "../../config"; +import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; import { BotService, EventService, TelemetryService } from "../../services"; import { SecretRotation } from "./models"; import { rotationTemplates } from "./templates"; @@ -21,9 +21,12 @@ import { TProviderFunction, TProviderFunctionTypes } from "./types"; -import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; +import { + decryptSymmetric128BitHexKeyUTF8, + encryptSymmetric128BitHexKeyUTF8 +} from "../../utils/crypto"; import { ISecret, Secret } from "../../models"; -import { SECRET_SHARED } from "../../variables"; +import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8, SECRET_SHARED } from "../../variables"; import { EESecretService } from "../services"; import { SecretVersion } from "../models"; import { eventPushSecrets } from "../../events"; @@ -215,13 +218,26 @@ secretRotationQueue.process(async (job: Job) => { ) as ISecretRotationProviderTemplate; // decrypt user provided inputs for secret rotation + const encryptionKey = await getEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey(); - const decryptedData = client.decryptSymmetric( - secretRotation.encryptedData, - rootEncryptionKey, - secretRotation.encryptedDataIV, - secretRotation.encryptedDataTag - ); + let decryptedData = ""; + if (rootEncryptionKey && secretRotation.keyEncoding === ENCODING_SCHEME_BASE64) { + // case: encoding scheme is base64 + decryptedData = client.decryptSymmetric( + secretRotation.encryptedData, + rootEncryptionKey, + secretRotation.encryptedDataIV, + secretRotation.encryptedDataTag + ); + } else if (encryptionKey && secretRotation.keyEncoding === ENCODING_SCHEME_UTF8) { + // case: encoding scheme is utf8 + decryptedData = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secretRotation.encryptedData, + iv: secretRotation.encryptedDataIV, + tag: secretRotation.encryptedDataTag, + key: encryptionKey + }); + } const variables = JSON.parse(decryptedData) as ISecretRotationEncData; diff --git a/backend/src/ee/secretRotation/service.ts b/backend/src/ee/secretRotation/service.ts index 8476dcbb8..2ed68d646 100644 --- a/backend/src/ee/secretRotation/service.ts +++ b/backend/src/ee/secretRotation/service.ts @@ -1,10 +1,16 @@ import { ISecretRotationEncData, TCreateSecretRotation, TGetProviderTemplates } from "./types"; import { rotationTemplates } from "./templates"; import { SecretRotation } from "./models"; -import { client, getRootEncryptionKey } from "../../config"; +import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; import { BadRequestError } from "../../utils/errors"; import Ajv from "ajv"; import { removeSecretRotationQueue, startSecretRotationQueue } from "./queue"; +import { + ALGORITHM_AES_256_GCM, + ENCODING_SCHEME_BASE64, + ENCODING_SCHEME_UTF8 +} from "../../variables"; +import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; const ajv = new Ajv(); @@ -51,24 +57,40 @@ export const createSecretRotation = async ({ creds: [] }; - const rootEncryptionKey = await getRootEncryptionKey(); - const { ciphertext, iv, tag } = client.encryptSymmetric( - JSON.stringify(encData), - rootEncryptionKey - ); - const secretRotation = new SecretRotation({ workspace: workspaceId, provider, environment, secretPath, interval, - outputs: Object.entries(outputs).map(([key, secret]) => ({ key, secret })), - encryptedData: ciphertext, - encryptedDataIV: iv, - encryptedDataTag: tag + outputs: Object.entries(outputs).map(([key, secret]) => ({ key, secret })) }); + const encryptionKey = await getEncryptionKey(); + const rootEncryptionKey = await getRootEncryptionKey(); + + if (rootEncryptionKey) { + const { ciphertext, iv, tag } = client.encryptSymmetric( + JSON.stringify(encData), + rootEncryptionKey + ); + secretRotation.encryptedDataIV = iv; + secretRotation.encryptedDataTag = tag; + secretRotation.encryptedData = ciphertext; + secretRotation.algorithm = ALGORITHM_AES_256_GCM; + secretRotation.keyEncoding = ENCODING_SCHEME_BASE64; + } else if (encryptionKey) { + const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ + plaintext: JSON.stringify(encData), + key: encryptionKey + }); + secretRotation.encryptedDataIV = iv; + secretRotation.encryptedDataTag = tag; + secretRotation.encryptedData = ciphertext; + secretRotation.algorithm = ALGORITHM_AES_256_GCM; + secretRotation.keyEncoding = ENCODING_SCHEME_UTF8; + } + await secretRotation.save(); await startSecretRotationQueue(secretRotation._id.toString(), interval); From c36352f05fc4c2e4ed3ae9d28a3c8810188b69f4 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 31 Oct 2023 15:44:05 +0530 Subject: [PATCH 15/24] feat(secret-rotation): updated helper text for options --- .../src/ee/secretRotation/templates/mysql.ts | 16 +++++++++--- .../ee/secretRotation/templates/postgres.ts | 16 +++++++++--- .../ee/secretRotation/templates/sendgrid.ts | 10 ++++--- backend/src/ee/secretRotation/types.ts | 2 +- .../components/v2/FormControl/FormControl.tsx | 14 +++++++--- .../steps/RotationInputForm.tsx | 26 ++++++++++++++++--- .../steps/RotationOutputForm.tsx | 16 ++++++++---- 7 files changed, 76 insertions(+), 24 deletions(-) diff --git a/backend/src/ee/secretRotation/templates/mysql.ts b/backend/src/ee/secretRotation/templates/mysql.ts index b57666596..357de6919 100644 --- a/backend/src/ee/secretRotation/templates/mysql.ts +++ b/backend/src/ee/secretRotation/templates/mysql.ts @@ -1,4 +1,4 @@ -import { TProviderFunctionTypes, TDbProviderClients, TAssignOp } from "../types"; +import { TAssignOp, TDbProviderClients, TProviderFunctionTypes } from "../types"; export const MYSQL_TEMPLATE = { inputs: { @@ -9,9 +9,17 @@ export const MYSQL_TEMPLATE = { host: { type: "string" as const }, database: { type: "string" as const }, port: { type: "integer" as const, default: "3306" }, - username1: { type: "string", default: "infisical-sql-user1" }, - username2: { type: "string", default: "infisical-sql-user2" }, - ca: { type: "string" } + username1: { + type: "string", + default: "infisical-sql-user1", + desc: "This user must be created in your database" + }, + username2: { + type: "string", + default: "infisical-sql-user2", + desc: "This user must be created in your database" + }, + ca: { type: "string", desc: "SSL certificate for db auth(string)" } }, required: [ "admin_username", diff --git a/backend/src/ee/secretRotation/templates/postgres.ts b/backend/src/ee/secretRotation/templates/postgres.ts index c4981cf29..3b3153be1 100644 --- a/backend/src/ee/secretRotation/templates/postgres.ts +++ b/backend/src/ee/secretRotation/templates/postgres.ts @@ -1,4 +1,4 @@ -import { TProviderFunctionTypes, TDbProviderClients, TAssignOp } from "../types"; +import { TAssignOp, TDbProviderClients, TProviderFunctionTypes } from "../types"; export const POSTGRES_TEMPLATE = { inputs: { @@ -9,9 +9,17 @@ export const POSTGRES_TEMPLATE = { host: { type: "string" as const }, database: { type: "string" as const }, port: { type: "integer" as const, default: "5432" }, - username1: { type: "string", default: "infisical-pg-user1" }, - username2: { type: "string", default: "infisical-pg-user2" }, - ca: { type: "string" } + username1: { + type: "string", + default: "infisical-pg-user1", + desc: "This user must be created in your database" + }, + username2: { + type: "string", + default: "infisical-pg-user2", + desc: "This user must be created in your database" + }, + ca: { type: "string", desc: "SSL certificate for db auth(string)" } }, required: [ "admin_username", diff --git a/backend/src/ee/secretRotation/templates/sendgrid.ts b/backend/src/ee/secretRotation/templates/sendgrid.ts index 454c9a145..ada62a0e6 100644 --- a/backend/src/ee/secretRotation/templates/sendgrid.ts +++ b/backend/src/ee/secretRotation/templates/sendgrid.ts @@ -1,11 +1,15 @@ -import { TProviderFunctionTypes, TAssignOp } from "../types"; +import { TAssignOp, TProviderFunctionTypes } from "../types"; export const SENDGRID_TEMPLATE = { inputs: { type: "object" as const, properties: { - admin_api_key: { type: "string" as const }, - scopes: { type: "array", items: { type: "string" as const } } + admin_api_key: { type: "string" as const, desc: "Sendgrid admin api key to create new keys" }, + scopes: { + type: "array", + items: { type: "string" as const }, + desc: "Scopes for created tokens by rotation(Array)" + } }, required: ["admin_api_key", "scopes"], additionalProperties: false diff --git a/backend/src/ee/secretRotation/types.ts b/backend/src/ee/secretRotation/types.ts index 4aa5975de..36ad36798 100644 --- a/backend/src/ee/secretRotation/types.ts +++ b/backend/src/ee/secretRotation/types.ts @@ -103,7 +103,7 @@ export type TProviderFunction = THttpProviderFunction | TDbProviderFunction; export type TProviderTemplate = { inputs: { type: "object"; - properties: Record; + properties: Record; required?: string[]; }; outputs: Record; diff --git a/frontend/src/components/v2/FormControl/FormControl.tsx b/frontend/src/components/v2/FormControl/FormControl.tsx index 91dfd4139..8f2e57f48 100644 --- a/frontend/src/components/v2/FormControl/FormControl.tsx +++ b/frontend/src/components/v2/FormControl/FormControl.tsx @@ -9,16 +9,24 @@ export type FormLabelProps = { isRequired?: boolean; label?: ReactNode; icon?: ReactNode; + className?: string; }; -export const FormLabel = ({ id, label, isRequired, icon }: FormLabelProps) => ( +export const FormLabel = ({ id, label, isRequired, icon, className }: FormLabelProps) => ( {label} {isRequired && *} - {icon && {icon}} + {icon && ( + + {icon} + + )} ); diff --git a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx index 8c50687d1..125515bf1 100644 --- a/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx +++ b/frontend/src/views/SecretRotationPage/components/CreateRotationForm/steps/RotationInputForm.tsx @@ -1,14 +1,16 @@ import { Controller, useForm } from "react-hook-form"; +import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; -import { Button, FormControl, SecretInput } from "@app/components/v2"; +import { Button, FormControl, FormLabel, SecretInput, Tooltip } from "@app/components/v2"; type Props = { onSubmit: (data: Record) => void; onCancel: () => void; inputSchema: { - properties: Record; + properties: Record; required: string[]; }; }; @@ -35,8 +37,24 @@ export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) => render={({ field }) => ( + + {Boolean(inputSchema.properties[inputName]?.desc) && ( + + + + )} +
+ } > {!isSecretsLoading && - secrets?.map(({ key, _id }) => ( - - {key} - - ))} + secrets + ?.filter( + ({ _id }) => + value === _id || !Object.values(selectedSecrets || {}).includes(_id) + ) + ?.map(({ key, _id }) => ( + + {key} + + ))} {isSecretsLoading && ( From 8b522a3fb525008b438dc85b19b139c9f2508d78 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Tue, 31 Oct 2023 16:49:59 +0530 Subject: [PATCH 16/24] feat(secret-rotation): updated docs for secret rotation --- .../ee/secretRotation/templates/sendgrid.ts | 6 +-- .../platform/secret-rotation/mysql.mdx | 37 +++++++++++++++++++ .../platform/secret-rotation/overview.mdx | 37 +++++++++++++++++++ .../platform/secret-rotation/postgres.mdx | 37 +++++++++++++++++++ .../platform/secret-rotation/sendgrid.mdx | 31 ++++++++++++++++ docs/mint.json | 14 ++++++- 6 files changed, 158 insertions(+), 4 deletions(-) create mode 100644 docs/documentation/platform/secret-rotation/mysql.mdx create mode 100644 docs/documentation/platform/secret-rotation/overview.mdx create mode 100644 docs/documentation/platform/secret-rotation/postgres.mdx create mode 100644 docs/documentation/platform/secret-rotation/sendgrid.mdx diff --git a/backend/src/ee/secretRotation/templates/sendgrid.ts b/backend/src/ee/secretRotation/templates/sendgrid.ts index ada62a0e6..b600f3e0c 100644 --- a/backend/src/ee/secretRotation/templates/sendgrid.ts +++ b/backend/src/ee/secretRotation/templates/sendgrid.ts @@ -5,13 +5,13 @@ export const SENDGRID_TEMPLATE = { type: "object" as const, properties: { admin_api_key: { type: "string" as const, desc: "Sendgrid admin api key to create new keys" }, - scopes: { + api_key_scopes: { type: "array", items: { type: "string" as const }, desc: "Scopes for created tokens by rotation(Array)" } }, - required: ["admin_api_key", "scopes"], + required: ["admin_api_key", "api_key_scopes"], additionalProperties: false }, outputs: { @@ -30,7 +30,7 @@ export const SENDGRID_TEMPLATE = { }, body: { name: "infisical-${random | 16}", - scopes: { ref: "inputs.scopes" } + scopes: { ref: "inputs.api_key_scopes" } }, setter: { "outputs.api_key": { diff --git a/docs/documentation/platform/secret-rotation/mysql.mdx b/docs/documentation/platform/secret-rotation/mysql.mdx new file mode 100644 index 000000000..b630e349a --- /dev/null +++ b/docs/documentation/platform/secret-rotation/mysql.mdx @@ -0,0 +1,37 @@ +--- +title: "MySQL/MariaDB" +description: "Rotated database user password of a MySQL or MariaDB" +--- + +Infisical will update periodically the provided database user's password. + + + At present Infisical do require access to your database. We will soon be released Infisical agent based rotation which would help you rotate without direct database access from Infisical cloud. + + +## Working + +1. User's has to create the two user's for Infisical to rotate and provide them required database access +2. Infisical will connect with your database with admin access +3. If last rotated one was username1, then username2 is chosen to be rotated +5. Update it's password with random value +6. After testing it gets saved to the provided secret mapping + +## Rotation Configuration + +1. Head over to Secret Rotation configuration page of your project by clicking on side bar `Secret Rotation` +2. Click on `MySQL` +3. Provide the inputs + - Admin Username: DB admin username + - Admin Password: DB admin password + - Host: DB host + - Port: DB port(number) + - Username1: The first username in two to rotate + - Username2: The second username in two to rotate + - CA: Certificate to connect with database(string) +4. Final step + - Select `Environment`, `Secret Path` and `Interval` to rotate the secrets + - Finally select the secrets in your provided board to replace with new secret after each rotation + - Your done and good to go. + +Congrats. You have 10x your MySQL/MariaDB access security. diff --git a/docs/documentation/platform/secret-rotation/overview.mdx b/docs/documentation/platform/secret-rotation/overview.mdx new file mode 100644 index 000000000..a11142106 --- /dev/null +++ b/docs/documentation/platform/secret-rotation/overview.mdx @@ -0,0 +1,37 @@ +--- +title: "Secret Rotation Overview" +description: "Keep your credentials safe by rotation" +--- + +Secret rotation is the process of periodically changing the values of secrets. This is done to reduce the risk of secrets being compromised and used to gain unauthorized access to systems or data. + +Rotated secrets can be +1. API key for an external service +2. Database credentials + +## How does the rotation happen? + +There are four phases in secret rotation and its triggered periodically in an internval. + +1. Creation + +System will create secret by calling an external service like an API call, or randomly generate a value. +Now there exist three valid secrets. + +2. Test + +Test the new secret key by some check to ensure its working one. Thus only two will be considered active and the other is considered inactive. + +3. Deletion + +System will remove the inactive secret and now there exist two valid secrets + +4. Finish + +System will switch the secret value from the rotated ones and trigger side effects like webhooks and events. + +## Infisical Secret Rotation Strategies + +1. [SendGrid](./sendgrid) +2. [PostgreSQL/CockroachDB](./postgres) +3. [MySQL/MariaDB](./mysql) diff --git a/docs/documentation/platform/secret-rotation/postgres.mdx b/docs/documentation/platform/secret-rotation/postgres.mdx new file mode 100644 index 000000000..167b60fcd --- /dev/null +++ b/docs/documentation/platform/secret-rotation/postgres.mdx @@ -0,0 +1,37 @@ +--- +title: "PostgreSQL/CockroachDB" +description: "Rotated database user password of a postgreSQL or cochroach db" +--- + +Infisical will update periodically the provided database user's password. + + + At present Infisical do require access to your database. We will soon be released Infisical agent based rotation which would help you rotate without direct database access from Infisical cloud. + + +## Working + +1. User's has to create the two user's for Infisical to rotate and provide them required database access +2. Infisical will connect with your database with admin access +3. If last rotated one was username1, then username2 is chosen to be rotated +5. Update it's password with random value +6. After testing it gets saved to the provided secret mapping + +## Rotation Configuration + +1. Head over to Secret Rotation configuration page of your project by clicking on side bar `Secret Rotation` +2. Click on `PostgreSQL` +3. Provide the inputs + - Admin Username: DB admin username + - Admin Password: DB admin password + - Host: DB host + - Port: DB port(number) + - Username1: The first username in two to rotate + - Username2: The second username in two to rotate + - CA: Certificate to connect with database(string) +4. Final step + - Select `Environment`, `Secret Path` and `Interval` to rotate the secrets + - Finally select the secrets in your provided board to replace with new secret after each rotation + - Your done and good to go. + +Congrats. You have 10x your PostgreSQL/CockroachDB access security. diff --git a/docs/documentation/platform/secret-rotation/sendgrid.mdx b/docs/documentation/platform/secret-rotation/sendgrid.mdx new file mode 100644 index 000000000..c4dd2797f --- /dev/null +++ b/docs/documentation/platform/secret-rotation/sendgrid.mdx @@ -0,0 +1,31 @@ +--- +title: "Twilio SendGrid" +description: "Rotate Twilio SendGrid API keys" +--- + +Twilio SendGrid is a cloud-based email delivery platform that helps businesses send transactional and marketing emails. +It uses an API key to do various operations. Using Infisical you can easily dynamically change the keys. + +## Working + +1. Infisical will need an admin token of SendGrid to create API keys dynamically. +2. Using the given admin token and scope by user Infisical will create and rotate API keys periodically +3. Under the hood infisical uses [SendGrid API](https://docs.sendgrid.com/api-reference/api-keys/create-api-keys) + +## Rotation Configuration + +1. Head over to Secret Rotation configuration page of your project by clicking on side bar `Secret Rotation` +2. Click on `Twilio SendGrid Card` +3. Provide the inputs + - Admin API Key: + SendGrid admin key to create lower scoped API keys. + - API Key Scopes + SendGrid generated API Key's scopes. For more info refer [this doc](https://docs.sendgrid.com/api-reference/api-key-permissions/api-key-permissions) + +4. Final step + - Select `Environment`, `Secret Path` and `Interval` to rotate the secrets + - Finally select the secrets in your provided board to replace with new secret after each rotation + - Your done and good to go. + +Now your output mapped secret value will be replaced periodically by SendGrid. + diff --git a/docs/mint.json b/docs/mint.json index 92876198d..05904b56b 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -34,7 +34,10 @@ } }, "topbarLinks": [ - { "name": "Log In", "url": "https://app.infisical.com/login" } + { + "name": "Log In", + "url": "https://app.infisical.com/login" + } ], "topbarCtaButton": { "name": "Start for Free", @@ -120,6 +123,15 @@ "documentation/platform/audit-logs", "documentation/platform/token", "documentation/platform/mfa", + { + "group": "Secret Rotation", + "pages": [ + "documentation/platform/secret-rotation/overview", + "documentation/platform/secret-rotation/sendgrid", + "documentation/platform/secret-rotation/postgres", + "documentation/platform/secret-rotation/mysql" + ] + }, { "group": "SSO", "pages": [ From c1ea441e3a6628d4ec5279f7b666852beb1b9a75 Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 1 Nov 2023 01:08:15 -0400 Subject: [PATCH 17/24] AJV set strict to false --- backend/src/ee/secretRotation/queue.ts | 2 +- backend/src/ee/secretRotation/service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/ee/secretRotation/queue.ts b/backend/src/ee/secretRotation/queue.ts index 501e4d748..132627221 100644 --- a/backend/src/ee/secretRotation/queue.ts +++ b/backend/src/ee/secretRotation/queue.ts @@ -189,7 +189,7 @@ const secretRotationRemoveFn = async (func: TProviderFunction, variables: ISecre secretRotationQueue.process(async (job: Job) => { const rotationStratDocId = job.data.rotationDocId; const secretRotation = await SecretRotation.findById(rotationStratDocId) - .select("+encryptedData +encryptedDataTag +encryptedDataIV") + .select("+encryptedData +encryptedDataTag +encryptedDataIV +keyEncoding") .populate<{ outputs: [ { diff --git a/backend/src/ee/secretRotation/service.ts b/backend/src/ee/secretRotation/service.ts index 2ed68d646..3770c520c 100644 --- a/backend/src/ee/secretRotation/service.ts +++ b/backend/src/ee/secretRotation/service.ts @@ -12,7 +12,7 @@ import { } from "../../variables"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; -const ajv = new Ajv(); +const ajv = new Ajv({ strict: false }); export const getProviderTemplate = async ({ workspaceId }: TGetProviderTemplates) => { return { From 75eeda4278d8183659f28eeb3a6ddc14f9460916 Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 1 Nov 2023 19:49:49 +0530 Subject: [PATCH 18/24] feat(secret-rotation): changed to mysql2 client and refactored queue util functions --- backend/package-lock.json | 236 +++++++++++------- backend/package.json | 3 +- .../ee/secretRotation/{ => queue}/queue.ts | 192 ++------------ .../ee/secretRotation/queue/queue.utils.ts | 179 +++++++++++++ backend/src/ee/secretRotation/service.ts | 2 +- .../src/ee/secretRotation/templates/mysql.ts | 3 +- 6 files changed, 342 insertions(+), 273 deletions(-) rename backend/src/ee/secretRotation/{ => queue}/queue.ts (59%) create mode 100644 backend/src/ee/secretRotation/queue/queue.utils.ts diff --git a/backend/package-lock.json b/backend/package-lock.json index bf0abb3ed..28b45c4df 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -46,7 +46,7 @@ "libsodium-wrappers": "^0.7.10", "lodash": "^4.17.21", "mongoose": "^7.4.1", - "mysql": "^2.18.1", + "mysql2": "^3.6.2", "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", @@ -82,7 +82,6 @@ "@types/jmespath": "^0.15.1", "@types/jsonwebtoken": "^8.5.9", "@types/lodash": "^4.14.191", - "@types/mysql": "^2.15.23", "@types/node": "^18.11.3", "@types/nodemailer": "^6.4.6", "@types/passport": "^1.0.12", @@ -5925,15 +5924,6 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, - "node_modules/@types/mysql": { - "version": "2.15.23", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.23.tgz", - "integrity": "sha512-l3mEJpU1VNkt7uJP2vYWZrfjxlzQtwJNKSWe+7sgChuDjFoyRfu263f9pBDlhFPmyda23o8GNGq6FL5CHSd/QA==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { "version": "18.16.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", @@ -7011,14 +7001,6 @@ "@juanelas/base64": "^1.1.2" } }, - "node_modules/bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -8760,6 +8742,14 @@ "node": ">=10" } }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9435,6 +9425,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "node_modules/is-retry-allowed": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", @@ -10861,45 +10856,76 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" } }, - "node_modules/mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "node_modules/mysql2": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.2.tgz", + "integrity": "sha512-m5erE6bMoWfPXW1D5UrVwlT8PowAoSX69KcZzPuARQ3wY1RJ52NW9PdvdPo076XiSIkQ5IBTis7hxdlrQTlyug==", "dependencies": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mysql2/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "engines": { + "node": ">=16.14" + } + }, + "node_modules/mysql2/node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", "engines": { "node": ">= 0.6" } }, - "node_modules/mysql/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" } }, - "node_modules/mysql/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/mysql/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dependencies": { - "safe-buffer": "~5.1.0" + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" } }, "node_modules/nanoid": { @@ -15588,6 +15614,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "node_modules/serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", @@ -21688,15 +21719,6 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, - "@types/mysql": { - "version": "2.15.23", - "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.23.tgz", - "integrity": "sha512-l3mEJpU1VNkt7uJP2vYWZrfjxlzQtwJNKSWe+7sgChuDjFoyRfu263f9pBDlhFPmyda23o8GNGq6FL5CHSd/QA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/node": { "version": "18.16.19", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.19.tgz", @@ -22528,11 +22550,6 @@ "@juanelas/base64": "^1.1.2" } }, - "bignumber.js": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", - "integrity": "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A==" - }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -23830,6 +23847,14 @@ "wide-align": "^1.1.2" } }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -24306,6 +24331,11 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, "is-retry-allowed": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", @@ -25391,43 +25421,58 @@ "node-gyp-build-optional-packages": "5.0.7" } }, - "mysql": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.18.1.tgz", - "integrity": "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig==", + "mysql2": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.6.2.tgz", + "integrity": "sha512-m5erE6bMoWfPXW1D5UrVwlT8PowAoSX69KcZzPuARQ3wY1RJ52NW9PdvdPo076XiSIkQ5IBTis7hxdlrQTlyug==", "requires": { - "bignumber.js": "9.0.0", - "readable-stream": "2.3.7", - "safe-buffer": "5.1.2", - "sqlstring": "2.3.1" + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" }, "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } + "sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" + } + } + }, + "named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "requires": { + "lru-cache": "^7.14.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" } } }, @@ -28839,6 +28884,11 @@ } } }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, "serve-static": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", diff --git a/backend/package.json b/backend/package.json index 8d8f0228c..9768762e8 100644 --- a/backend/package.json +++ b/backend/package.json @@ -37,7 +37,7 @@ "libsodium-wrappers": "^0.7.10", "lodash": "^4.17.21", "mongoose": "^7.4.1", - "mysql": "^2.18.1", + "mysql2": "^3.6.2", "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", @@ -105,7 +105,6 @@ "@types/jmespath": "^0.15.1", "@types/jsonwebtoken": "^8.5.9", "@types/lodash": "^4.14.191", - "@types/mysql": "^2.15.23", "@types/node": "^18.11.3", "@types/nodemailer": "^6.4.6", "@types/passport": "^1.0.12", diff --git a/backend/src/ee/secretRotation/queue.ts b/backend/src/ee/secretRotation/queue/queue.ts similarity index 59% rename from backend/src/ee/secretRotation/queue.ts rename to backend/src/ee/secretRotation/queue/queue.ts index 132627221..0a29c7a6c 100644 --- a/backend/src/ee/secretRotation/queue.ts +++ b/backend/src/ee/secretRotation/queue/queue.ts @@ -1,191 +1,33 @@ -import axios from "axios"; import Queue, { Job } from "bull"; -import jmespath from "jmespath"; -import { customAlphabet } from "nanoid"; -import { Client as PgClient } from "pg"; -import mysql from "mysql"; - -import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; -import { BotService, EventService, TelemetryService } from "../../services"; -import { SecretRotation } from "./models"; -import { rotationTemplates } from "./templates"; +import { client, getEncryptionKey, getRootEncryptionKey } from "../../../config"; +import { BotService, EventService, TelemetryService } from "../../../services"; +import { SecretRotation } from "../models"; +import { rotationTemplates } from "../templates"; import { ISecretRotationData, ISecretRotationEncData, ISecretRotationProviderTemplate, - TAssignOp, - TDbProviderClients, - TDbProviderFunction, - TDirectAssignOp, - THttpProviderFunction, - TProviderFunction, TProviderFunctionTypes -} from "./types"; +} from "../types"; import { decryptSymmetric128BitHexKeyUTF8, encryptSymmetric128BitHexKeyUTF8 -} from "../../utils/crypto"; -import { ISecret, Secret } from "../../models"; -import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8, SECRET_SHARED } from "../../variables"; -import { EESecretService } from "../services"; -import { SecretVersion } from "../models"; -import { eventPushSecrets } from "../../events"; +} from "../../../utils/crypto"; +import { ISecret, Secret } from "../../../models"; +import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8, SECRET_SHARED } from "../../../variables"; +import { EESecretService } from "../../services"; +import { SecretVersion } from "../../models"; +import { eventPushSecrets } from "../../../events"; -const REGEX = /\${([^}]+)}/g; -const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; -const nanoId = customAlphabet(SLUG_ALPHABETS, 10); +import { + secretRotationPreSetFn, + secretRotationRemoveFn, + secretRotationSetFn, + secretRotationTestFn +} from "./queue.utils"; const secretRotationQueue = new Queue("secret-rotation-service", process.env.REDIS_URL as string); -const interpolate = (data: any, getValue: (key: string) => unknown) => { - if (!data) return; - - if (typeof data === "number") return data; - - if (typeof data === "string") { - return data.replace(REGEX, (_a, b) => getValue(b) as string); - } - - if (typeof data === "object" && Array.isArray(data)) { - data.forEach((el, index) => { - data[index] = interpolate(el, getValue); - }); - } - - if (typeof data === "object") { - if ((data as { ref: string })?.ref) return getValue((data as { ref: string }).ref); - const temp = data as Record; // for converting ts object to record type - Object.keys(temp).forEach((key) => { - temp[key as keyof typeof temp] = interpolate(data[key as keyof typeof temp], getValue); - }); - } - return data; -}; - -const getInterpolationValue = (variables: ISecretRotationData) => (key: string) => { - if (key.includes("|")) { - const [keyword, ...arg] = key.split("|").map((el) => el.trim()); - switch (keyword) { - case "random": { - return nanoId(parseInt(arg[0], 10)); - } - default: { - throw Error(`Interpolation key not found - ${key}`); - } - } - } - const [type, keyName] = key.split(".").map((el) => el.trim()); - return variables[type as keyof ISecretRotationData][keyName]; -}; - -const secretRotationHttpFn = async ( - func: THttpProviderFunction, - variables: ISecretRotationData -) => { - // string interpolation - const headers = interpolate(func.header, getInterpolationValue(variables)); - const url = interpolate(func.url, getInterpolationValue(variables)); - const body = interpolate(func.body, getInterpolationValue(variables)); - // axios will automatically throw error if req status is not between 2xx range - return axios({ method: func.method, url, headers, data: body }); -}; - -const secretRotationDbFn = async (func: TDbProviderFunction, variables: ISecretRotationData) => { - const { type, client, pre, ...dbConnection } = func; - const { username, password, host, database, port, query, ca } = interpolate( - dbConnection, - getInterpolationValue(variables) - ); - const ssl = ca ? { rejectUnauthorized: false, ca } : undefined; - if (host === "localhost" || host === "127.0.0.1") throw new Error("Invalid db host"); - if (client === TDbProviderClients.Pg) { - const pgClient = new PgClient({ user: username, password, host, database, port, ssl }); - await pgClient.connect(); - const res = await pgClient.query(query); - await pgClient.end(); - return res.rows[0]; - } else if (client === TDbProviderClients.Sql) { - const sqlClient = mysql.createPool({ - user: username, - password, - host, - database, - port, - connectionLimit: 1, - ssl - }); - const res = await new Promise((resolve, reject) => { - sqlClient.query(query, (err, data) => { - if (err) return reject(err); - resolve(data); - }); - }); - await new Promise((resolve, reject) => { - sqlClient.end(function (err) { - if (err) return reject(err); - return resolve({}); - }); - }); - return (res as any)?.[0]; - } -}; - -const secretRotationPreSetFn = ( - op: Record, - variables: ISecretRotationData -) => { - const getValFn = getInterpolationValue(variables); - Object.entries(op || {}).forEach(([key, assignFn]) => { - const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; - variables[type][keyName] = interpolate(assignFn.value, getValFn); - }); -}; - -const secretRotationSetFn = async (func: TProviderFunction, variables: ISecretRotationData) => { - const getValFn = getInterpolationValue(variables); - // http setter - if (func.type === TProviderFunctionTypes.HTTP) { - const res = await secretRotationHttpFn(func, variables); - Object.entries(func.setter || {}).forEach(([key, assignFn]) => { - const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; - if (assignFn.assign === TAssignOp.JmesPath) { - variables[type][keyName] = jmespath.search(res.data, assignFn.path); - } else if (assignFn.value) { - variables[type][keyName] = interpolate(assignFn.value, getValFn); - } - }); - // db setter - } else if (func.type === TProviderFunctionTypes.DB) { - const data = await secretRotationDbFn(func, variables); - Object.entries(func.setter || {}).forEach(([key, assignFn]) => { - const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; - if (assignFn.assign === TAssignOp.JmesPath) { - if (typeof data === "object") { - variables[type][keyName] = jmespath.search(data, assignFn.path); - } - } else if (assignFn.value) { - variables[type][keyName] = interpolate(assignFn.value, getValFn); - } - }); - } -}; - -const secretRotationTestFn = async (func: TProviderFunction, variables: ISecretRotationData) => { - if (func.type === TProviderFunctionTypes.HTTP) { - await secretRotationHttpFn(func, variables); - } else if (func.type === TProviderFunctionTypes.DB) { - await secretRotationDbFn(func, variables); - } -}; - -const secretRotationRemoveFn = async (func: TProviderFunction, variables: ISecretRotationData) => { - if (!func) return; - if (func.type === TProviderFunctionTypes.HTTP) { - // string interpolation - return await secretRotationHttpFn(func, variables); - } -}; - secretRotationQueue.process(async (job: Job) => { const rotationStratDocId = job.data.rotationDocId; const secretRotation = await SecretRotation.findById(rotationStratDocId) diff --git a/backend/src/ee/secretRotation/queue/queue.utils.ts b/backend/src/ee/secretRotation/queue/queue.utils.ts new file mode 100644 index 000000000..c1ddbefc1 --- /dev/null +++ b/backend/src/ee/secretRotation/queue/queue.utils.ts @@ -0,0 +1,179 @@ +import axios from "axios"; +import jmespath from "jmespath"; +import { customAlphabet } from "nanoid"; +import { Client as PgClient } from "pg"; +import mysql from "mysql2"; +import { + ISecretRotationData, + TAssignOp, + TDbProviderClients, + TDbProviderFunction, + TDirectAssignOp, + THttpProviderFunction, + TProviderFunction, + TProviderFunctionTypes +} from "../types"; +const REGEX = /\${([^}]+)}/g; +const SLUG_ALPHABETS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; +const nanoId = customAlphabet(SLUG_ALPHABETS, 10); + +export const interpolate = (data: any, getValue: (key: string) => unknown) => { + if (!data) return; + + if (typeof data === "number") return data; + + if (typeof data === "string") { + return data.replace(REGEX, (_a, b) => getValue(b) as string); + } + + if (typeof data === "object" && Array.isArray(data)) { + data.forEach((el, index) => { + data[index] = interpolate(el, getValue); + }); + } + + if (typeof data === "object") { + if ((data as { ref: string })?.ref) return getValue((data as { ref: string }).ref); + const temp = data as Record; // for converting ts object to record type + Object.keys(temp).forEach((key) => { + temp[key as keyof typeof temp] = interpolate(data[key as keyof typeof temp], getValue); + }); + } + return data; +}; + +const getInterpolationValue = (variables: ISecretRotationData) => (key: string) => { + if (key.includes("|")) { + const [keyword, ...arg] = key.split("|").map((el) => el.trim()); + switch (keyword) { + case "random": { + return nanoId(parseInt(arg[0], 10)); + } + default: { + throw Error(`Interpolation key not found - ${key}`); + } + } + } + const [type, keyName] = key.split(".").map((el) => el.trim()); + return variables[type as keyof ISecretRotationData][keyName]; +}; + +export const secretRotationHttpFn = async ( + func: THttpProviderFunction, + variables: ISecretRotationData +) => { + // string interpolation + const headers = interpolate(func.header, getInterpolationValue(variables)); + const url = interpolate(func.url, getInterpolationValue(variables)); + const body = interpolate(func.body, getInterpolationValue(variables)); + // axios will automatically throw error if req status is not between 2xx range + return axios({ method: func.method, url, headers, data: body }); +}; + +export const secretRotationDbFn = async ( + func: TDbProviderFunction, + variables: ISecretRotationData +) => { + const { type, client, pre, ...dbConnection } = func; + const { username, password, host, database, port, query, ca } = interpolate( + dbConnection, + getInterpolationValue(variables) + ); + const ssl = ca ? { rejectUnauthorized: false, ca } : undefined; + if (host === "localhost" || host === "127.0.0.1") throw new Error("Invalid db host"); + if (client === TDbProviderClients.Pg) { + const pgClient = new PgClient({ user: username, password, host, database, port, ssl }); + await pgClient.connect(); + const res = await pgClient.query(query); + await pgClient.end(); + return res.rows[0]; + } else if (client === TDbProviderClients.Sql) { + const sqlClient = mysql.createPool({ + user: username, + password, + host, + database, + port, + connectionLimit: 1, + ssl + }); + const res = await new Promise((resolve, reject) => { + sqlClient.query(query, (err, data) => { + if (err) return reject(err); + resolve(data); + }); + }); + await new Promise((resolve, reject) => { + sqlClient.end(function (err) { + if (err) return reject(err); + return resolve({}); + }); + }); + return (res as any)?.[0]; + } +}; + +export const secretRotationPreSetFn = ( + op: Record, + variables: ISecretRotationData +) => { + const getValFn = getInterpolationValue(variables); + Object.entries(op || {}).forEach(([key, assignFn]) => { + const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; + variables[type][keyName] = interpolate(assignFn.value, getValFn); + }); +}; + +export const secretRotationSetFn = async ( + func: TProviderFunction, + variables: ISecretRotationData +) => { + const getValFn = getInterpolationValue(variables); + // http setter + if (func.type === TProviderFunctionTypes.HTTP) { + const res = await secretRotationHttpFn(func, variables); + Object.entries(func.setter || {}).forEach(([key, assignFn]) => { + const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; + if (assignFn.assign === TAssignOp.JmesPath) { + variables[type][keyName] = jmespath.search(res.data, assignFn.path); + } else if (assignFn.value) { + variables[type][keyName] = interpolate(assignFn.value, getValFn); + } + }); + // db setter + } else if (func.type === TProviderFunctionTypes.DB) { + const data = await secretRotationDbFn(func, variables); + Object.entries(func.setter || {}).forEach(([key, assignFn]) => { + const [type, keyName] = key.split(".") as [keyof ISecretRotationData, string]; + if (assignFn.assign === TAssignOp.JmesPath) { + if (typeof data === "object") { + variables[type][keyName] = jmespath.search(data, assignFn.path); + } + } else if (assignFn.value) { + variables[type][keyName] = interpolate(assignFn.value, getValFn); + } + }); + } +}; + +export const secretRotationTestFn = async ( + func: TProviderFunction, + variables: ISecretRotationData +) => { + if (func.type === TProviderFunctionTypes.HTTP) { + await secretRotationHttpFn(func, variables); + } else if (func.type === TProviderFunctionTypes.DB) { + await secretRotationDbFn(func, variables); + } +}; + +export const secretRotationRemoveFn = async ( + func: TProviderFunction, + variables: ISecretRotationData +) => { + if (!func) return; + if (func.type === TProviderFunctionTypes.HTTP) { + // string interpolation + return await secretRotationHttpFn(func, variables); + } +}; diff --git a/backend/src/ee/secretRotation/service.ts b/backend/src/ee/secretRotation/service.ts index 3770c520c..9e00f20e1 100644 --- a/backend/src/ee/secretRotation/service.ts +++ b/backend/src/ee/secretRotation/service.ts @@ -4,7 +4,7 @@ import { SecretRotation } from "./models"; import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; import { BadRequestError } from "../../utils/errors"; import Ajv from "ajv"; -import { removeSecretRotationQueue, startSecretRotationQueue } from "./queue"; +import { removeSecretRotationQueue, startSecretRotationQueue } from "./queue/queue"; import { ALGORITHM_AES_256_GCM, ENCODING_SCHEME_BASE64, diff --git a/backend/src/ee/secretRotation/templates/mysql.ts b/backend/src/ee/secretRotation/templates/mysql.ts index 357de6919..ce44c753f 100644 --- a/backend/src/ee/secretRotation/templates/mysql.ts +++ b/backend/src/ee/secretRotation/templates/mysql.ts @@ -50,8 +50,7 @@ export const MYSQL_TEMPLATE = { database: "${inputs.database}", port: "${inputs.port}", ca: "${inputs.ca}", - query: - "ALTER USER ${internal.username} IDENTIFIED WITH mysql_native_password BY '${internal.rotated_password}'", + query: "ALTER USER ${internal.username} IDENTIFIED BY '${internal.rotated_password}'", setter: { "outputs.db_username": { assign: TAssignOp.Direct as const, From 40238788e5d30a1bb21f534898086f61dfb590ac Mon Sep 17 00:00:00 2001 From: Akhil Mohan Date: Wed, 1 Nov 2023 22:24:58 +0530 Subject: [PATCH 19/24] feat(secret-rotation): changed queue logging to pino --- backend/package-lock.json | 39 +++++++------------- backend/src/ee/secretRotation/queue/queue.ts | 3 +- 2 files changed, 16 insertions(+), 26 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 28b45c4df..c743979e6 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -7559,7 +7559,8 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "node_modules/cors": { "version": "2.8.5", @@ -10409,6 +10410,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "node_modules/lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -15067,11 +15073,6 @@ "node": ">= 0.6.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "node_modules/process-warning": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", @@ -15873,14 +15874,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "node_modules/sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -22959,7 +22952,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true }, "cors": { "version": "2.8.5", @@ -25097,6 +25091,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, "lru_map": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", @@ -28506,11 +28505,6 @@ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, "process-warning": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.3.0.tgz", @@ -29093,11 +29087,6 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, - "sqlstring": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", - "integrity": "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ==" - }, "stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", diff --git a/backend/src/ee/secretRotation/queue/queue.ts b/backend/src/ee/secretRotation/queue/queue.ts index 0a29c7a6c..ff63d32c7 100644 --- a/backend/src/ee/secretRotation/queue/queue.ts +++ b/backend/src/ee/secretRotation/queue/queue.ts @@ -18,6 +18,7 @@ import { ENCODING_SCHEME_BASE64, ENCODING_SCHEME_UTF8, SECRET_SHARED } from "../ import { EESecretService } from "../../services"; import { SecretVersion } from "../../models"; import { eventPushSecrets } from "../../../events"; +import { logger } from "../../../utils/logging"; import { secretRotationPreSetFn, @@ -260,7 +261,7 @@ secretRotationQueue.process(async (job: Job) => { }); } } catch (err) { - console.error(err); + logger.error(err); await SecretRotation.findByIdAndUpdate(rotationStratDocId, { status: "failed", statusMessage: (err as Error).message, From a07bd5ad40b0c6876877ac4a05f6b6a6f39a34ba Mon Sep 17 00:00:00 2001 From: Maidul Islam Date: Wed, 1 Nov 2023 16:49:31 -0400 Subject: [PATCH 20/24] add log to secretRotationQueue process --- backend/src/ee/secretRotation/queue/queue.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/ee/secretRotation/queue/queue.ts b/backend/src/ee/secretRotation/queue/queue.ts index ff63d32c7..0127bbd9c 100644 --- a/backend/src/ee/secretRotation/queue/queue.ts +++ b/backend/src/ee/secretRotation/queue/queue.ts @@ -30,6 +30,7 @@ import { const secretRotationQueue = new Queue("secret-rotation-service", process.env.REDIS_URL as string); secretRotationQueue.process(async (job: Job) => { + logger.info(`secretRotationQueue.process: [rotationDocument=${job.data.rotationDocId}]`); const rotationStratDocId = job.data.rotationDocId; const secretRotation = await SecretRotation.findById(rotationStratDocId) .select("+encryptedData +encryptedDataTag +encryptedDataIV +keyEncoding") From 079a09a3d1c364833a0e8f37ae8ba65b5e846b1b Mon Sep 17 00:00:00 2001 From: Vladyslav Matsiiako Date: Wed, 1 Nov 2023 14:27:46 -0700 Subject: [PATCH 21/24] Remove create new org --- frontend/src/layouts/AppLayout/AppLayout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 44b3964bc..632449273 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -319,7 +319,7 @@ export const AppLayout = ({ children }: LayoutProps) => { ))} - + {/*
- + */}
-
-
-
Pro Tips
- After creating an integration, your secrets will start syncing immediately. This might cause an unexpected override of current secrets in Checkly with secrets from Infisical. - If you have multiple Checkly integrations and are using suffixes for at least one of them, you will have to add suffixes for all the active Checkly integrations – otherwise you might run into rare unexpected behavior. -
) : (
diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 9e23e7909..34d28ec14 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -163,12 +163,22 @@ export const IntegrationsSection = ({
)} {((integration.integration === "checkly") || (integration.integration === "github")) && ( -
- -
- {integration?.metadata?.secretSuffix || "-"} + <> + {integration.targetService && ( +
+ +
+ {integration.targetService} +
+
+ )} +
+ +
+ {integration?.metadata?.secretSuffix || "-"} +
-
+ )}
From d4a5eb12e862f0c4c0f860ea7a7311dc5c48d09b Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 2 Nov 2023 11:54:01 +0200 Subject: [PATCH 24/24] Patch checkly integration --- frontend/src/pages/integrations/checkly/create.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/integrations/checkly/create.tsx b/frontend/src/pages/integrations/checkly/create.tsx index 4db24b846..cd5ae71b7 100644 --- a/frontend/src/pages/integrations/checkly/create.tsx +++ b/frontend/src/pages/integrations/checkly/create.tsx @@ -100,7 +100,7 @@ export default function ChecklyCreateIntegrationPage() { appId: targetApp?.appId, sourceEnvironment: selectedSourceEnvironment, targetService: targetGroup?.name, - targetServiceId: String(targetGroup?.groupId), + targetServiceId: targetGroup?.groupId ? String(targetGroup?.groupId) : undefined, secretPath, metadata: { secretSuffix