From b3d7eb99eb9d84b1d452737b2306595da73b8b6b Mon Sep 17 00:00:00 2001 From: misterkirill Date: Wed, 16 Jul 2025 01:00:29 +0500 Subject: [PATCH] fix: improve action security --- src/app/actions/blocks.ts | 41 ++++++++++++++++++++++++++++++++- src/app/actions/notes.ts | 14 +++++++++++ src/components/editor/Block.tsx | 17 +++++++++++--- src/lib/db/index.ts | 2 +- 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/app/actions/blocks.ts b/src/app/actions/blocks.ts index 72dab66..cc37661 100644 --- a/src/app/actions/blocks.ts +++ b/src/app/actions/blocks.ts @@ -4,9 +4,28 @@ import { revalidatePath } from "next/cache"; import { eq } from "drizzle-orm"; import { db } from "@/lib/db"; import { blocksTable, IBlock } from "@/lib/db/schema"; +import { requireAuth } from "./auth"; +import { assertNoteOwner } from "./notes"; + +export async function assertBlockOwner(blockId: string): Promise { + const user = await requireAuth(); + + const block = await db.query.blocksTable.findFirst({ + where: eq(blocksTable.id, blockId), + with: { note: true }, + }); + if (!block) { + return false; + } + + return block.note.authorId === user.id; +} export async function createBlock(formData: FormData) { const noteId = formData.get("noteId") as string; + const isAllowed = await assertNoteOwner(noteId); + if (!isAllowed) return; + const blocks = await getBlocks(noteId); const lastBlock = blocks.pop(); const order = lastBlock === undefined ? 1 : lastBlock.order + 1; @@ -17,6 +36,9 @@ export async function createBlock(formData: FormData) { } async function getBlock(blockId: string): Promise { + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return null; + const blocks = await db .select() .from(blocksTable) @@ -29,6 +51,8 @@ async function getBlock(blockId: string): Promise { } export async function getBlocks(noteId: string): Promise { + const isAllowed = await assertNoteOwner(noteId); + if (!isAllowed) return []; return db .select() .from(blocksTable) @@ -38,6 +62,8 @@ export async function getBlocks(noteId: string): Promise { export async function deleteBlock(formData: FormData) { const blockId = formData.get("blockId") as string; + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; await db .delete(blocksTable) .where(eq(blocksTable.id, blockId)); @@ -47,6 +73,8 @@ export async function deleteBlock(formData: FormData) { export async function changeLock(formData: FormData) { const blockId = formData.get("blockId") as string; const isLocked = formData.get("isLocked") === null ? false : true; + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; await db .update(blocksTable) .set({ isLocked }) @@ -56,6 +84,8 @@ export async function changeLock(formData: FormData) { export async function addLine(formData: FormData) { const blockId = formData.get("blockId") as string; + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; const blocks = await db .select() .from(blocksTable) @@ -74,6 +104,8 @@ export async function addLine(formData: FormData) { export async function deleteLine(formData: FormData) { const blockId = formData.get("blockId") as string; const block = await getBlock(blockId); + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; if (!block) { return; } @@ -86,6 +118,8 @@ export async function deleteLine(formData: FormData) { export async function moveUp(formData: FormData) { const blockId = formData.get("blockId") as string; + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; const block = await getBlock(blockId); if (!block) return; @@ -112,6 +146,8 @@ export async function moveUp(formData: FormData) { export async function moveDown(formData: FormData) { const blockId = formData.get("blockId") as string; + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; const block = await getBlock(blockId); if (!block) return; @@ -137,7 +173,8 @@ export async function moveDown(formData: FormData) { } export async function setLines(blockId: string, lines: string[]) { - console.log(lines) + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; await db .update(blocksTable) .set({ lines }) @@ -145,6 +182,8 @@ export async function setLines(blockId: string, lines: string[]) { } export async function setTag(blockId: string, tag: string) { + const isAllowed = await assertBlockOwner(blockId); + if (!isAllowed) return; await db .update(blocksTable) .set({ tag }) diff --git a/src/app/actions/notes.ts b/src/app/actions/notes.ts index d5d6990..77eed6e 100644 --- a/src/app/actions/notes.ts +++ b/src/app/actions/notes.ts @@ -7,6 +7,19 @@ import { blocksTable, INote, notesTable, usersTable } from "@/lib/db/schema"; import { requireAuth } from "./auth"; import { db } from "@/lib/db"; +export async function assertNoteOwner(noteId: string): Promise { + const user = await requireAuth(); + + const note = await db.query.notesTable.findFirst({ + where: eq(notesTable.id, noteId), + }); + if (!note) { + return false; + } + + return note.authorId === user.id; +} + export async function createNote() { const user = await requireAuth(); const result = await db @@ -47,6 +60,7 @@ export async function deleteNote(formData: FormData) { } export async function setTitle(noteId: string, title: string) { + if (title === "") return; await db .update(notesTable) .set({ title }) diff --git a/src/components/editor/Block.tsx b/src/components/editor/Block.tsx index 8447212..3273008 100644 --- a/src/components/editor/Block.tsx +++ b/src/components/editor/Block.tsx @@ -1,6 +1,6 @@ import { ChangeEvent, useState } from "react"; import { ArrowDown, ArrowUp, LockOpen, Lock, Minus, Plus, X } from "lucide-react"; -import { addLine, changeLock, deleteBlock, deleteLine, moveDown, moveUp, setLines } from "@/app/actions/blocks"; +import { addLine, changeLock, deleteBlock, deleteLine, moveDown, moveUp, setLines, setTag } from "@/app/actions/blocks"; import { useDebounce } from "@/lib/hooks/useDebounce"; import { IBlock } from "@/lib/db/schema"; import IconOnlyButton from "../ui/IconOnlyButton"; @@ -8,11 +8,16 @@ import LineInput from "./LineInput"; export default function Block({ block }: { block: IBlock }) { const [lines, setLinesState] = useState(block.lines); + const [tag, setTagState] = useState(block.tag); useDebounce(() => { setLines(block.id, lines); }, [lines]); + useDebounce(() => { + setTag(block.id, tag); + }, [tag]); + const lineChangeHandler = (i: number, e: ChangeEvent) => { const newLines = [...lines]; newLines[i] = e.target.value; @@ -26,11 +31,17 @@ export default function Block({ block }: { block: IBlock }) { type="text" placeholder="enter tag..." className="w-full focus:outline-none" - defaultValue={block.tag} + onChange={(e) => setTagState(e.target.value)} + value={tag} disabled={block.isLocked} /> {block.lines.map((line, i) => ( - lineChangeHandler(i, e)} /> + lineChangeHandler(i, e)} + /> ))}
diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index e9e240a..754ba95 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -1,4 +1,4 @@ import { drizzle } from 'drizzle-orm/node-postgres'; import * as schema from "./schema"; -export const db = drizzle(process.env.DATABASE_URL!); +export const db = drizzle(process.env.DATABASE_URL!, { schema });