fix(results): 🔒️ Improve delete security checks
This commit is contained in:
@ -4,7 +4,6 @@ import { SubmissionsTable } from 'components/results/SubmissionsTable'
|
|||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
convertResultsToTableData,
|
convertResultsToTableData,
|
||||||
deleteAllResults,
|
|
||||||
deleteResults,
|
deleteResults,
|
||||||
getAllResults,
|
getAllResults,
|
||||||
useResults,
|
useResults,
|
||||||
@ -70,10 +69,11 @@ export const SubmissionsContent = ({
|
|||||||
const selectedIds = (results ?? [])
|
const selectedIds = (results ?? [])
|
||||||
.filter((_, idx) => selectedIndices.includes(idx))
|
.filter((_, idx) => selectedIndices.includes(idx))
|
||||||
.map((result) => result.id)
|
.map((result) => result.id)
|
||||||
const { error } =
|
const { error } = await deleteResults(
|
||||||
totalSelected === totalResults
|
workspaceId,
|
||||||
? await deleteAllResults(typebotId)
|
typebotId,
|
||||||
: await deleteResults(typebotId, selectedIds)
|
totalSelected === totalResults ? [] : selectedIds
|
||||||
|
)
|
||||||
if (error) showToast({ description: error.message, title: error.name })
|
if (error) showToast({ description: error.message, title: error.name })
|
||||||
else {
|
else {
|
||||||
mutate(
|
mutate(
|
||||||
|
@ -2,7 +2,7 @@ import { rest, setupWorker } from 'msw'
|
|||||||
import { setupServer } from 'msw/node'
|
import { setupServer } from 'msw/node'
|
||||||
|
|
||||||
const handlers = () => [
|
const handlers = () => [
|
||||||
rest.get('/api/auth/session', (req, res, ctx) => {
|
rest.get('http://localhost:3000/api/auth/session', (req, res, ctx) => {
|
||||||
const authenticatedUser = JSON.parse(
|
const authenticatedUser = JSON.parse(
|
||||||
typeof localStorage !== 'undefined'
|
typeof localStorage !== 'undefined'
|
||||||
? (localStorage.getItem('authenticatedUser') as string)
|
? (localStorage.getItem('authenticatedUser') as string)
|
||||||
|
@ -15,13 +15,13 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const user = await getAuthenticatedUser(req)
|
const user = await getAuthenticatedUser(req)
|
||||||
if (!user) return notAuthenticated(res)
|
if (!user) return notAuthenticated(res)
|
||||||
const workspaceId = req.query.workspaceId as string | undefined
|
const workspaceId = req.query.workspaceId as string | undefined
|
||||||
|
if (!workspaceId) return badRequest(res, 'workspaceId is required')
|
||||||
|
const workspace = await prisma.workspace.findFirst({
|
||||||
|
where: { id: workspaceId, members: { some: { userId: user.id } } },
|
||||||
|
select: { plan: true },
|
||||||
|
})
|
||||||
|
if (!workspace) return forbidden(res)
|
||||||
if (req.method === 'GET') {
|
if (req.method === 'GET') {
|
||||||
if (!workspaceId) return badRequest(res, 'workspaceId is required')
|
|
||||||
const workspace = await prisma.workspace.findFirst({
|
|
||||||
where: { id: workspaceId, members: { some: { userId: user.id } } },
|
|
||||||
select: { plan: true },
|
|
||||||
})
|
|
||||||
if (!workspace) return forbidden(res)
|
|
||||||
const typebotId = req.query.typebotId.toString()
|
const typebotId = req.query.typebotId.toString()
|
||||||
const lastResultId = req.query.lastResultId?.toString()
|
const lastResultId = req.query.lastResultId?.toString()
|
||||||
const take = parseInt(req.query.limit?.toString())
|
const take = parseInt(req.query.limit?.toString())
|
||||||
@ -46,7 +46,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
return res.status(200).send({ results })
|
return res.status(200).send({ results })
|
||||||
}
|
}
|
||||||
if (req.method === 'DELETE') {
|
if (req.method === 'DELETE') {
|
||||||
const typebotId = req.query.typebotId.toString()
|
const typebotId = req.query.typebotId as string
|
||||||
const ids = req.query.ids as string[]
|
const ids = req.query.ids as string[]
|
||||||
const results = await prisma.result.deleteMany({
|
const results = await prisma.result.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
|
@ -57,10 +57,12 @@ test.describe('Results page', () => {
|
|||||||
await deleteButtonInConfirmDialog(page).click()
|
await deleteButtonInConfirmDialog(page).click()
|
||||||
await expect(page.locator('text=content199')).toBeHidden()
|
await expect(page.locator('text=content199')).toBeHidden()
|
||||||
await expect(page.locator('text=content198')).toBeHidden()
|
await expect(page.locator('text=content198')).toBeHidden()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await page.click('[data-testid="checkbox"] >> nth=0')
|
await page.click('[data-testid="checkbox"] >> nth=0')
|
||||||
await page.click('button:has-text("Delete198")')
|
await page.click('button:has-text("Delete198")')
|
||||||
await deleteButtonInConfirmDialog(page).click()
|
await deleteButtonInConfirmDialog(page).click()
|
||||||
await expect(page.locator(':nth-match(tr, 2)')).toBeHidden()
|
await page.waitForTimeout(1000)
|
||||||
|
expect(await page.locator('tr').count()).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('submissions table should have infinite scroll', async ({ page }) => {
|
test('submissions table should have infinite scroll', async ({ page }) => {
|
||||||
|
@ -76,10 +76,15 @@ export const useResults = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteResults = async (typebotId: string, ids: string[]) => {
|
export const deleteResults = async (
|
||||||
|
workspaceId: string,
|
||||||
|
typebotId: string,
|
||||||
|
ids: string[]
|
||||||
|
) => {
|
||||||
const params = stringify(
|
const params = stringify(
|
||||||
{
|
{
|
||||||
ids,
|
ids,
|
||||||
|
workspaceId,
|
||||||
},
|
},
|
||||||
{ indices: false }
|
{ indices: false }
|
||||||
)
|
)
|
||||||
@ -89,12 +94,6 @@ export const deleteResults = async (typebotId: string, ids: string[]) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteAllResults = async (typebotId: string) =>
|
|
||||||
sendRequest({
|
|
||||||
url: `/api/typebots/${typebotId}/results`,
|
|
||||||
method: 'DELETE',
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getAllResults = async (workspaceId: string, typebotId: string) => {
|
export const getAllResults = async (workspaceId: string, typebotId: string) => {
|
||||||
const results = []
|
const results = []
|
||||||
let hasMore = true
|
let hasMore = true
|
||||||
|
@ -3604,9 +3604,9 @@
|
|||||||
set-cookie-parser "^2.4.6"
|
set-cookie-parser "^2.4.6"
|
||||||
|
|
||||||
"@mswjs/interceptors@^0.15.1":
|
"@mswjs/interceptors@^0.15.1":
|
||||||
version "0.15.1"
|
version "0.15.3"
|
||||||
resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.15.1.tgz#4a0009f56e51bc2cd3176f1507065c7d2f6c0d5e"
|
resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.15.3.tgz#bcd17b5d7558d4f598007a4bb383b42dc9264f8d"
|
||||||
integrity sha512-D5B+ZJNlfvBm6ZctAfRBdNJdCHYAe2Ix4My5qfbHV5WH+3lkt3mmsjiWJzEh5ZwGDauzY487TldI275If7DJVw==
|
integrity sha512-GJ1qzBq82EQ3bwhsvw5nScbrLzOSI5H/TyB2CGd1K7dDqX58DJDLJHexiN+S5Ucvl6/84FjRdIysz0RxE/L8MA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@open-draft/until" "^1.0.3"
|
"@open-draft/until" "^1.0.3"
|
||||||
"@xmldom/xmldom" "^0.7.5"
|
"@xmldom/xmldom" "^0.7.5"
|
||||||
|
Reference in New Issue
Block a user