From c4b4d2a312dcd30d7c5aae91ecbcdff6486cf584 Mon Sep 17 00:00:00 2001 From: Shubham Palriwala Date: Mon, 2 Oct 2023 16:33:49 +0530 Subject: [PATCH] chore: add authorization to server actions for Actions (#868) * poc: use server session and api key validation on deletion * feat: use server session and api key validation on deletion and creation * feat: packages/lib/apiKey for apiKey services and auth * shubham/auth-for-api-key * fix: caching * feat: handle authorization for action creation, update, delete * feat: use cached method across and wrapper for authzn check * fix: club caching methods and use authzn errors * feat: add caching in canUserAccessApiKey * feat: add caching in canUserAccessAction and use Authzn error * fix: rename action to actionClass wherever needed * fix: use cache getActionClass * fix: make changes * fix: import --------- Co-authored-by: Matthias Nannt --- .../actions/ActionSettingsTab.tsx | 10 +- .../actions/AddNoCodeActionModal.tsx | 4 +- .../(actionsAndAttributes)/actions/actions.ts | 66 +++++++++++++ .../(actionsAndAttributes)/actions/page.tsx | 2 +- .../settings/api-keys/actions.ts | 20 ++-- .../surveys/[surveyId]/edit/page.tsx | 2 +- apps/web/app/api/v1/js/sync/lib/sync.ts | 4 +- .../action-classes/[actionClassId]/route.ts | 2 +- .../api/v1/management/action-classes/route.ts | 2 +- packages/lib/actionClass/auth.ts | 24 +++++ .../actionClass.ts => actionClass/service.ts} | 59 +++++------- packages/lib/apiKey/auth.ts | 5 +- packages/lib/services/actions.ts | 2 +- pnpm-lock.yaml | 93 ++++++++----------- 14 files changed, 179 insertions(+), 116 deletions(-) create mode 100644 packages/lib/actionClass/auth.ts rename packages/lib/{services/actionClass.ts => actionClass/service.ts} (75%) diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionSettingsTab.tsx b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionSettingsTab.tsx index ea4f37db2b..da87f38a60 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionSettingsTab.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionSettingsTab.tsx @@ -9,11 +9,14 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; import { toast } from "react-hot-toast"; import { testURLmatch } from "./testURLmatch"; -import { deleteActionClass, updateActionClass } from "@formbricks/lib/services/actionClass"; import { TActionClassInput, TActionClassNoCodeConfig } from "@formbricks/types/v1/actionClasses"; import { CssSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/CssSelector"; import { PageUrlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/PageUrlSelector"; import { InnerHtmlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/InnerHtmlSelector"; +import { + deleteActionClassAction, + updateActionClassAction, +} from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions"; interface ActionSettingsTabProps { environmentId: string; @@ -77,10 +80,11 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen const filteredNoCodeConfig = filterNoCodeConfig(data.noCodeConfig as NoCodeConfig); const updatedData: TActionClassInput = { ...data, + environmentId, noCodeConfig: filteredNoCodeConfig, type: "noCode", } as TActionClassInput; - await updateActionClass(environmentId, actionClass.id, updatedData); + await updateActionClassAction(environmentId, actionClass.id, updatedData); setOpen(false); router.refresh(); toast.success("Action updated successfully"); @@ -94,7 +98,7 @@ export default function ActionSettingsTab({ environmentId, actionClass, setOpen const handleDeleteAction = async () => { try { setIsDeletingAction(true); - await deleteActionClass(environmentId, actionClass.id); + await deleteActionClassAction(environmentId, actionClass.id); router.refresh(); toast.success("Action deleted successfully"); setOpen(false); diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/AddNoCodeActionModal.tsx b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/AddNoCodeActionModal.tsx index ff08847f2e..ab39bb7279 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/AddNoCodeActionModal.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/AddNoCodeActionModal.tsx @@ -7,7 +7,6 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; import toast from "react-hot-toast"; import { testURLmatch } from "./testURLmatch"; -import { createActionClass } from "@formbricks/lib/services/actionClass"; import { TActionClassInput, TActionClassNoCodeConfig, @@ -16,6 +15,7 @@ import { import { CssSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/CssSelector"; import { PageUrlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/PageUrlSelector"; import { InnerHtmlSelector } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/(selectors)/InnerHtmlSelector"; +import { createActionClassAction } from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions"; interface AddNoCodeActionModalProps { environmentId: string; @@ -84,7 +84,7 @@ export default function AddNoCodeActionModal({ type: "noCode", } as TActionClassInput; - const newActionClass: TActionClass = await createActionClass(environmentId, updatedData); + const newActionClass: TActionClass = await createActionClassAction(updatedData); if (setActionClassArray) { setActionClassArray((prevActionClassArray: TActionClass[]) => [ ...prevActionClassArray, diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts index 844156de4e..11dd1b590f 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/actions.ts @@ -1,27 +1,93 @@ "use server"; +import { authOptions } from "@/app/api/auth/[...nextauth]/authOptions"; +import { hasUserEnvironmentAccess } from "@formbricks/lib/environment/auth"; +import { createActionClass, deleteActionClass, updateActionClass } from "@formbricks/lib/actionClass/service"; +import { canUserAccessActionClass } from "@formbricks/lib/actionClass/auth"; +import { getServerSession } from "next-auth"; +import { TActionClassInput } from "@formbricks/types/v1/actionClasses"; + import { getActionCountInLast24Hours, getActionCountInLast7Days, getActionCountInLastHour, } from "@formbricks/lib/services/actions"; import { getSurveysByActionClassId } from "@formbricks/lib/services/survey"; +import { AuthorizationError } from "@formbricks/types/v1/errors"; + +export async function deleteActionClassAction(environmentId, actionClassId: string) { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + + await deleteActionClass(environmentId, actionClassId); +} + +export async function updateActionClassAction( + environmentId: string, + actionClassId: string, + updatedAction: Partial +) { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + + return await updateActionClass(environmentId, actionClassId, updatedAction); +} + +export async function createActionClassAction(action: TActionClassInput) { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await hasUserEnvironmentAccess(session.user.id, action.environmentId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + + return await createActionClass(action.environmentId, action); +} export const getActionCountInLastHourAction = async (actionClassId: string) => { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + return await getActionCountInLastHour(actionClassId); }; export const getActionCountInLast24HoursAction = async (actionClassId: string) => { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + return await getActionCountInLast24Hours(actionClassId); }; export const getActionCountInLast7DaysAction = async (actionClassId: string) => { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + return await getActionCountInLast7Days(actionClassId); }; export const GetActiveInactiveSurveysAction = async ( actionClassId: string ): Promise<{ activeSurveys: string[]; inactiveSurveys: string[] }> => { + const session = await getServerSession(authOptions); + if (!session) throw new AuthorizationError("Not authorized"); + + const isAuthorized = await canUserAccessActionClass(session.user.id, actionClassId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + const surveys = await getSurveysByActionClassId(actionClassId); const response = { activeSurveys: surveys.filter((s) => s.status === "inProgress").map((survey) => survey.name), diff --git a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/page.tsx b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/page.tsx index 27dbd8caf6..2e39e09f4a 100644 --- a/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/page.tsx @@ -4,7 +4,7 @@ import ActionClassesTable from "@/app/(app)/environments/[environmentId]/(action import ActionClassDataRow from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionRowData"; import ActionTableHeading from "@/app/(app)/environments/[environmentId]/(actionsAndAttributes)/actions/ActionTableHeading"; import { REVALIDATION_INTERVAL } from "@formbricks/lib/constants"; -import { getActionClasses } from "@formbricks/lib/services/actionClass"; +import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { Metadata } from "next"; export const metadata: Metadata = { diff --git a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/actions.ts b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/actions.ts index 49d513804e..1db54a5815 100644 --- a/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/actions.ts +++ b/apps/web/app/(app)/environments/[environmentId]/settings/api-keys/actions.ts @@ -11,22 +11,18 @@ import { AuthorizationError } from "@formbricks/types/v1/errors"; export async function deleteApiKeyAction(id: string) { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const isAuthorized = await canUserAccessApiKey(session.user.id, id); - if (isAuthorized) { - return await deleteApiKey(id); - } else { - throw new AuthorizationError("Not authorized"); - } + const isAuthorized = await canUserAccessApiKey(session.user.id, id); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + + return await deleteApiKey(id); } export async function createApiKeyAction(environmentId: string, apiKeyData: TApiKeyCreateInput) { const session = await getServerSession(authOptions); if (!session) throw new AuthorizationError("Not authorized"); - const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId); - if (isAuthorized) { - return await createApiKey(environmentId, apiKeyData); - } else { - throw new AuthorizationError("Not authorized"); - } + const isAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId); + if (!isAuthorized) throw new AuthorizationError("Not authorized"); + + return await createApiKey(environmentId, apiKeyData); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx index e150dd0ef8..1223a5a437 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/edit/page.tsx @@ -5,7 +5,7 @@ import SurveyEditor from "./SurveyEditor"; import { getSurveyWithAnalytics } from "@formbricks/lib/services/survey"; import { getProductByEnvironmentId } from "@formbricks/lib/services/product"; import { getEnvironment } from "@formbricks/lib/services/environment"; -import { getActionClasses } from "@formbricks/lib/services/actionClass"; +import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { getAttributeClasses } from "@formbricks/lib/services/attributeClass"; import { ErrorComponent } from "@formbricks/ui"; diff --git a/apps/web/app/api/v1/js/sync/lib/sync.ts b/apps/web/app/api/v1/js/sync/lib/sync.ts index a9ee6f7ec2..e865231f6e 100644 --- a/apps/web/app/api/v1/js/sync/lib/sync.ts +++ b/apps/web/app/api/v1/js/sync/lib/sync.ts @@ -1,6 +1,6 @@ import { getSurveysCached } from "@/app/api/v1/js/surveys"; import { MAU_LIMIT } from "@formbricks/lib/constants"; -import { getActionClassesCached } from "@formbricks/lib/services/actionClass"; +import { getActionClasses } from "@formbricks/lib/actionClass/service"; import { getEnvironment } from "@formbricks/lib/services/environment"; import { createPerson, getMonthlyActivePeopleCount, getPersonCached } from "@formbricks/lib/services/person"; import { getProductByEnvironmentIdCached } from "@formbricks/lib/services/product"; @@ -101,7 +101,7 @@ export const getUpdatedState = async ( // get/create rest of the state const [surveys, noCodeActionClasses, product] = await Promise.all([ getSurveysCached(environmentId, person), - getActionClassesCached(environmentId), + getActionClasses(environmentId), getProductByEnvironmentIdCached(environmentId), ]); diff --git a/apps/web/app/api/v1/management/action-classes/[actionClassId]/route.ts b/apps/web/app/api/v1/management/action-classes/[actionClassId]/route.ts index 8702de259a..8c1d26eb11 100644 --- a/apps/web/app/api/v1/management/action-classes/[actionClassId]/route.ts +++ b/apps/web/app/api/v1/management/action-classes/[actionClassId]/route.ts @@ -1,6 +1,6 @@ import { responses } from "@/lib/api/response"; import { NextResponse } from "next/server"; -import { deleteActionClass, getActionClass, updateActionClass } from "@formbricks/lib/services/actionClass"; +import { deleteActionClass, getActionClass, updateActionClass } from "@formbricks/lib/actionClass/service"; import { TActionClass, ZActionClassInput } from "@formbricks/types/v1/actionClasses"; import { authenticateRequest } from "@/app/api/v1/auth"; import { transformErrorToDetails } from "@/lib/api/validator"; diff --git a/apps/web/app/api/v1/management/action-classes/route.ts b/apps/web/app/api/v1/management/action-classes/route.ts index 89d0fe72bf..ac8d492c06 100644 --- a/apps/web/app/api/v1/management/action-classes/route.ts +++ b/apps/web/app/api/v1/management/action-classes/route.ts @@ -3,7 +3,7 @@ import { DatabaseError } from "@formbricks/types/v1/errors"; import { authenticateRequest } from "@/app/api/v1/auth"; import { NextResponse } from "next/server"; import { TActionClass, ZActionClassInput } from "@formbricks/types/v1/actionClasses"; -import { createActionClass, getActionClasses } from "@formbricks/lib/services/actionClass"; +import { createActionClass, getActionClasses } from "@formbricks/lib/actionClass/service"; import { transformErrorToDetails } from "@/lib/api/validator"; export async function GET(request: Request) { diff --git a/packages/lib/actionClass/auth.ts b/packages/lib/actionClass/auth.ts new file mode 100644 index 0000000000..1d7a431361 --- /dev/null +++ b/packages/lib/actionClass/auth.ts @@ -0,0 +1,24 @@ +import { ZId } from "@formbricks/types/v1/environment"; +import { validateInputs } from "../utils/validate"; +import { hasUserEnvironmentAccess } from "../environment/auth"; +import { getActionClass } from "./service"; +import { unstable_cache } from "next/cache"; + +export const canUserAccessActionClass = async (userId: string, actionClassId: string): Promise => + await unstable_cache( + async () => { + validateInputs([userId, ZId], [actionClassId, ZId]); + if (!userId) return false; + + const actionClass = await getActionClass(actionClassId); + if (!actionClass) return false; + + const hasAccessToEnvironment = await hasUserEnvironmentAccess(userId, actionClass.environmentId); + if (!hasAccessToEnvironment) return false; + + return true; + }, + + [`users-${userId}-actionClasses-${actionClassId}`], + { revalidate: 30 * 60, tags: [`actionClasses-${actionClassId}`] } + )(); // 30 minutes diff --git a/packages/lib/services/actionClass.ts b/packages/lib/actionClass/service.ts similarity index 75% rename from packages/lib/services/actionClass.ts rename to packages/lib/actionClass/service.ts index f6e02c7db5..f69cb1bb05 100644 --- a/packages/lib/services/actionClass.ts +++ b/packages/lib/actionClass/service.ts @@ -6,22 +6,15 @@ import { TActionClass, TActionClassInput, ZActionClassInput } from "@formbricks/ import { ZId } from "@formbricks/types/v1/environment"; import { DatabaseError, ResourceNotFoundError } from "@formbricks/types/v1/errors"; import { revalidateTag, unstable_cache } from "next/cache"; -import { cache } from "react"; import { validateInputs } from "../utils/validate"; const halfHourInSeconds = 60 * 30; export const getActionClassCacheTag = (name: string, environmentId: string): string => `environments-${environmentId}-actionClass-${name}`; -const getActionClassCacheKey = (name: string, environmentId: string): string[] => [ - getActionClassCacheTag(name, environmentId), -]; const getActionClassesCacheTag = (environmentId: string): string => `environments-${environmentId}-actionClasses`; -const getActionClassesCacheKey = (environmentId: string): string[] => [ - getActionClassesCacheTag(environmentId), -]; const select = { id: true, @@ -34,33 +27,29 @@ const select = { environmentId: true, }; -export const getActionClasses = cache(async (environmentId: string): Promise => { - validateInputs([environmentId, ZId]); - try { - let actionClasses = await prisma.eventClass.findMany({ - where: { - environmentId: environmentId, - }, - select, - orderBy: { - createdAt: "asc", - }, - }); - - return actionClasses; - } catch (error) { - throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`); - } -}); - -export const getActionClassesCached = (environmentId: string) => +export const getActionClasses = (environmentId: string): Promise => unstable_cache( async () => { - return await getActionClasses(environmentId); + validateInputs([environmentId, ZId]); + try { + let actionClasses = await prisma.eventClass.findMany({ + where: { + environmentId: environmentId, + }, + select, + orderBy: { + createdAt: "asc", + }, + }); + + return actionClasses; + } catch (error) { + throw new DatabaseError(`Database error when fetching actions for environment ${environmentId}`); + } }, - getActionClassesCacheKey(environmentId), + [`environments-${environmentId}-actionClasses`], { - tags: getActionClassesCacheKey(environmentId), + tags: [getActionClassesCacheTag(environmentId)], revalidate: halfHourInSeconds, } )(); @@ -96,7 +85,7 @@ export const deleteActionClass = async ( if (result === null) throw new ResourceNotFoundError("Action", actionClassId); // revalidate cache - revalidateTag(getActionClassesCacheTag(environmentId)); + revalidateTag(getActionClassesCacheTag(result.environmentId)); return result; } catch (error) { @@ -157,8 +146,8 @@ export const updateActionClass = async ( }); // revalidate cache - revalidateTag(getActionClassCacheTag(result.name, environmentId)); - revalidateTag(getActionClassesCacheTag(environmentId)); + revalidateTag(getActionClassCacheTag(result.name, result.environmentId)); + revalidateTag(getActionClassesCacheTag(result.environmentId)); return result; } catch (error) { @@ -176,9 +165,9 @@ export const getActionClassCached = async (name: string, environmentId: string) }, }); }, - getActionClassCacheKey(name, environmentId), + [`environments-${environmentId}-actionClasses-${name}`], { - tags: getActionClassCacheKey(name, environmentId), + tags: [getActionClassesCacheTag(environmentId)], revalidate: halfHourInSeconds, } )(); diff --git a/packages/lib/apiKey/auth.ts b/packages/lib/apiKey/auth.ts index 58be5678cb..6f565c7a98 100644 --- a/packages/lib/apiKey/auth.ts +++ b/packages/lib/apiKey/auth.ts @@ -2,8 +2,8 @@ import { hasUserEnvironmentAccess } from "../environment/auth"; import { getApiKey } from "./service"; import { unstable_cache } from "next/cache"; -export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise => { - return await unstable_cache( +export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Promise => + await unstable_cache( async () => { if (!userId) return false; @@ -19,4 +19,3 @@ export const canUserAccessApiKey = async (userId: string, apiKeyId: string): Pro [`users-${userId}-apiKeys-${apiKeyId}`], { revalidate: 30 * 60, tags: [`apiKeys-${apiKeyId}`] } )(); // 30 minutes -}; diff --git a/packages/lib/services/actions.ts b/packages/lib/services/actions.ts index 0f934a1b67..ffa8b2475a 100644 --- a/packages/lib/services/actions.ts +++ b/packages/lib/services/actions.ts @@ -11,7 +11,7 @@ import { validateInputs } from "../utils/validate"; import { TJsActionInput } from "@formbricks/types/v1/js"; import { revalidateTag } from "next/cache"; import { EventType } from "@prisma/client"; -import { getActionClassCacheTag, getActionClassCached } from "../services/actionClass"; +import { getActionClassCacheTag, getActionClassCached } from "../actionClass/service"; import { getSessionCached } from "../services/session"; export const getActionsByEnvironmentId = cache( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d06effa37..1da5426d6d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -28,7 +28,7 @@ importers: version: 3.12.7 turbo: specifier: latest - version: 1.10.12 + version: 1.10.13 apps/demo: dependencies: @@ -438,7 +438,7 @@ importers: version: 9.0.0(eslint@8.50.0) eslint-config-turbo: specifier: latest - version: 1.10.12(eslint@8.50.0) + version: 1.8.8(eslint@8.50.0) eslint-plugin-react: specifier: 7.33.2 version: 7.33.2(eslint@8.50.0) @@ -1151,7 +1151,7 @@ packages: '@babel/helper-plugin-utils': 7.22.5 debug: 4.3.4 lodash.debounce: 4.0.8 - resolve: 1.22.2 + resolve: 1.22.6 transitivePeerDependencies: - supports-color dev: true @@ -10677,13 +10677,13 @@ packages: resolution: {integrity: sha512-NB/L/1Y30qyJcG5xZxCJKW/+bqyj+llbcCwo9DEz8bESIP0SLTOQ8T1DWCCFc+wJ61AMEstj4511PSScqMMfCw==} dev: true - /eslint-config-turbo@1.10.12(eslint@8.50.0): - resolution: {integrity: sha512-z3jfh+D7UGYlzMWGh+Kqz++hf8LOE96q3o5R8X4HTjmxaBWlLAWG+0Ounr38h+JLR2TJno0hU9zfzoPNkR9BdA==} + /eslint-config-turbo@1.8.8(eslint@8.50.0): + resolution: {integrity: sha512-+yT22sHOT5iC1sbBXfLIdXfbZuiv9bAyOXsxTxFCWelTeFFnANqmuKB3x274CFvf7WRuZ/vYP/VMjzU9xnFnxA==} peerDependencies: eslint: '>6.6.0' dependencies: eslint: 8.50.0 - eslint-plugin-turbo: 1.10.12(eslint@8.50.0) + eslint-plugin-turbo: 1.8.8(eslint@8.50.0) dev: true /eslint-import-resolver-node@0.3.9: @@ -10889,12 +10889,11 @@ packages: semver: 6.3.1 string.prototype.matchall: 4.0.8 - /eslint-plugin-turbo@1.10.12(eslint@8.50.0): - resolution: {integrity: sha512-uNbdj+ohZaYo4tFJ6dStRXu2FZigwulR1b3URPXe0Q8YaE7thuekKNP+54CHtZPH9Zey9dmDx5btAQl9mfzGOw==} + /eslint-plugin-turbo@1.8.8(eslint@8.50.0): + resolution: {integrity: sha512-zqyTIvveOY4YU5jviDWw9GXHd4RiKmfEgwsjBrV/a965w0PpDwJgEUoSMB/C/dU310Sv9mF3DSdEjxjJLaw6rA==} peerDependencies: eslint: '>6.6.0' dependencies: - dotenv: 16.0.3 eslint: 8.50.0 dev: true @@ -13163,11 +13162,6 @@ packages: rgba-regex: 1.0.0 dev: true - /is-core-module@2.11.0: - resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} - dependencies: - has: 1.0.3 - /is-core-module@2.13.0: resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: @@ -14722,7 +14716,7 @@ packages: is-plain-object: 2.0.4 object.map: 1.0.1 rechoir: 0.6.2 - resolve: 1.22.2 + resolve: 1.22.6 transitivePeerDependencies: - supports-color dev: true @@ -15145,7 +15139,7 @@ packages: dependencies: findup-sync: 2.0.0 micromatch: 3.1.10 - resolve: 1.22.2 + resolve: 1.22.6 stack-trace: 0.0.10 transitivePeerDependencies: - supports-color @@ -16561,7 +16555,7 @@ packages: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.2 + resolve: 1.22.6 semver: 5.7.2 validate-npm-package-license: 3.0.4 dev: true @@ -17625,7 +17619,7 @@ packages: postcss: 8.4.27 postcss-value-parser: 4.2.0 read-cache: 1.0.0 - resolve: 1.22.2 + resolve: 1.22.6 /postcss-js@4.0.1(postcss@8.4.27): resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} @@ -19921,7 +19915,7 @@ packages: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -19930,7 +19924,7 @@ packages: resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -19946,7 +19940,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.11.0 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -20121,16 +20115,8 @@ packages: fsevents: 2.3.2 dev: true - /rollup@3.28.1: - resolution: {integrity: sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /rollup@3.5.1: - resolution: {integrity: sha512-hdQWTvPeiAbM6SUkxV70HdGUVxsgsc+CLy5fuh4KdgUBJ0SowXiix8gANgXoG3wEuLwfoJhCT2V+WwxfWq9Ikw==} + /rollup@3.29.4: + resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -21999,7 +21985,7 @@ packages: joycon: 3.1.1 postcss-load-config: 4.0.1(postcss@8.4.27) resolve-from: 5.0.0 - rollup: 3.5.1 + rollup: 3.29.4 source-map: 0.8.0-beta.0 sucrase: 3.32.0 tree-kill: 1.2.2 @@ -22053,65 +22039,64 @@ packages: dependencies: safe-buffer: 5.2.1 - /turbo-darwin-64@1.10.12: - resolution: {integrity: sha512-vmDfGVPl5/aFenAbOj3eOx3ePNcWVUyZwYr7taRl0ZBbmv2TzjRiFotO4vrKCiTVnbqjQqAFQWY2ugbqCI1kOQ==} + /turbo-darwin-64@1.10.13: + resolution: {integrity: sha512-vmngGfa2dlYvX7UFVncsNDMuT4X2KPyPJ2Jj+xvf5nvQnZR/3IeDEGleGVuMi/hRzdinoxwXqgk9flEmAYp0Xw==} cpu: [x64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-darwin-arm64@1.10.12: - resolution: {integrity: sha512-3JliEESLNX2s7g54SOBqqkqJ7UhcOGkS0ywMr5SNuvF6kWVTbuUq7uBU/sVbGq8RwvK1ONlhPvJne5MUqBCTCQ==} + /turbo-darwin-arm64@1.10.13: + resolution: {integrity: sha512-eMoJC+k7gIS4i2qL6rKmrIQGP6Wr9nN4odzzgHFngLTMimok2cGLK3qbJs5O5F/XAtEeRAmuxeRnzQwTl/iuAw==} cpu: [arm64] os: [darwin] requiresBuild: true dev: true optional: true - /turbo-linux-64@1.10.12: - resolution: {integrity: sha512-siYhgeX0DidIfHSgCR95b8xPee9enKSOjCzx7EjTLmPqPaCiVebRYvbOIYdQWRqiaKh9yfhUtFmtMOMScUf1gg==} + /turbo-linux-64@1.10.13: + resolution: {integrity: sha512-0CyYmnKTs6kcx7+JRH3nPEqCnzWduM0hj8GP/aodhaIkLNSAGAa+RiYZz6C7IXN+xUVh5rrWTnU2f1SkIy7Gdg==} cpu: [x64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-linux-arm64@1.10.12: - resolution: {integrity: sha512-K/ZhvD9l4SslclaMkTiIrnfcACgos79YcAo4kwc8bnMQaKuUeRpM15sxLpZp3xDjDg8EY93vsKyjaOhdFG2UbA==} + /turbo-linux-arm64@1.10.13: + resolution: {integrity: sha512-0iBKviSGQQlh2OjZgBsGjkPXoxvRIxrrLLbLObwJo3sOjIH0loGmVIimGS5E323soMfi/o+sidjk2wU1kFfD7Q==} cpu: [arm64] os: [linux] requiresBuild: true dev: true optional: true - /turbo-windows-64@1.10.12: - resolution: {integrity: sha512-7FSgSwvktWDNOqV65l9AbZwcoueAILeE4L7JvjauNASAjjbuzXGCEq5uN8AQU3U5BOFj4TdXrVmO2dX+lLu8Zg==} + /turbo-windows-64@1.10.13: + resolution: {integrity: sha512-S5XySRfW2AmnTeY1IT+Jdr6Goq7mxWganVFfrmqU+qqq3Om/nr0GkcUX+KTIo9mPrN0D3p5QViBRzulwB5iuUQ==} cpu: [x64] os: [win32] requiresBuild: true dev: true optional: true - /turbo-windows-arm64@1.10.12: - resolution: {integrity: sha512-gCNXF52dwom1HLY9ry/cneBPOKTBHhzpqhMylcyvJP0vp9zeMQQkt6yjYv+6QdnmELC92CtKNp2FsNZo+z0pyw==} + /turbo-windows-arm64@1.10.13: + resolution: {integrity: sha512-nKol6+CyiExJIuoIc3exUQPIBjP9nIq5SkMJgJuxsot2hkgGrafAg/izVDRDrRduQcXj2s8LdtxJHvvnbI8hEQ==} cpu: [arm64] os: [win32] requiresBuild: true dev: true optional: true - /turbo@1.10.12: - resolution: {integrity: sha512-WM3+jTfQWnB9W208pmP4oeehZcC6JQNlydb/ZHMRrhmQa+htGhWLCzd6Q9rLe0MwZLPpSPFV2/bN5egCLyoKjQ==} + /turbo@1.10.13: + resolution: {integrity: sha512-vOF5IPytgQPIsgGtT0n2uGZizR2N3kKuPIn4b5p5DdeLoI0BV7uNiydT7eSzdkPRpdXNnO8UwS658VaI4+YSzQ==} hasBin: true - requiresBuild: true optionalDependencies: - turbo-darwin-64: 1.10.12 - turbo-darwin-arm64: 1.10.12 - turbo-linux-64: 1.10.12 - turbo-linux-arm64: 1.10.12 - turbo-windows-64: 1.10.12 - turbo-windows-arm64: 1.10.12 + turbo-darwin-64: 1.10.13 + turbo-darwin-arm64: 1.10.13 + turbo-linux-64: 1.10.13 + turbo-linux-arm64: 1.10.13 + turbo-windows-64: 1.10.13 + turbo-windows-arm64: 1.10.13 dev: true /tween-functions@1.2.0: @@ -22985,7 +22970,7 @@ packages: dependencies: esbuild: 0.18.10 postcss: 8.4.30 - rollup: 3.28.1 + rollup: 3.29.4 terser: 5.20.0 optionalDependencies: fsevents: 2.3.2