From f0136f360e6fa92ab4f7ddd3f44bb1101ee0efa3 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 14:44:37 +0200 Subject: [PATCH 01/10] fix: Drop global unique name constrain, scope to user --- CHANGELOG.md | 2 ++ app/prisma/schema.prisma | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6822109b69..ba5827c02d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ You can also check the - Invalid URL error message is now translated - Improved UX of interacting with conversion units multiplier input - Fixes + - Custom color palette names are not required to be globally unique anymore, + but unique per user - Fixed an infinite render loop when adding a custom map layer - Styles - Fixed some smaller UI inconsistencies, mostly in table charts diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index be1f7764e3..77e4c5a1c3 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -69,7 +69,7 @@ enum PALETTE_TYPE { model Palette { paletteId String @id @default(uuid()) - name String @unique @db.VarChar(100) + name String @db.VarChar(100) type PALETTE_TYPE colors String[] created_at DateTime @default(now()) @db.Timestamp(6) @@ -79,4 +79,5 @@ model Palette { user_id Int? @@map("palettes") + @@unique([name, user_id]) } \ No newline at end of file From 549fac70500e8e358b8fbd73c6622202fe96046b Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 15:11:07 +0200 Subject: [PATCH 02/10] fix: Types --- app/utils/chart-config/api.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/utils/chart-config/api.ts b/app/utils/chart-config/api.ts index 986380482e..655510d76a 100644 --- a/app/utils/chart-config/api.ts +++ b/app/utils/chart-config/api.ts @@ -13,6 +13,7 @@ import { createId } from "../create-id"; import type apiConfig from "../../pages/api/config/[key]"; import type apiConfigCreate from "../../pages/api/config-create"; import type apiConfigUpdate from "../../pages/api/config-update"; +import type apiUserColorPalette from "../../pages/api/user/color-palette"; export type CreateCustomColorPalette = Omit; export type UpdateCustomColorPalette = Partial< @@ -112,7 +113,7 @@ export const fetchChartConfigs = async () => { export const createCustomColorPalette = async ( options: CreateCustomColorPalette ) => { - return (await apiFetch>( + return (await apiFetch>( "/api/user/color-palette", { method: "POST", @@ -124,7 +125,7 @@ export const createCustomColorPalette = async ( export const getCustomColorPalettes = async (): Promise< CustomPaletteType[] > => { - return (await apiFetch>( + return (await apiFetch>( "/api/user/color-palette", { method: "GET", @@ -135,7 +136,7 @@ export const getCustomColorPalettes = async (): Promise< export const deleteCustomColorPalette = async ( options: DeleteCustomColorPalette ) => { - await apiFetch>( + await apiFetch>( "/api/user/color-palette", { method: "DELETE", @@ -147,7 +148,7 @@ export const deleteCustomColorPalette = async ( export const updateCustomColorPalette = async ( options: UpdateCustomColorPalette ) => { - await apiFetch>( + await apiFetch>( "/api/user/color-palette", { method: "PUT", From 75b84f79b4aebdc0300ba8e2d703abefdc51b237 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 15:12:49 +0200 Subject: [PATCH 03/10] fix: User must be logged in to create a palette --- app/db/palettes.ts | 10 +++++---- app/prisma/schema.prisma | 4 ++-- app/server/user-controller.ts | 38 +++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/app/db/palettes.ts b/app/db/palettes.ts index 57e5c22519..6f30f17c38 100644 --- a/app/db/palettes.ts +++ b/app/db/palettes.ts @@ -10,7 +10,7 @@ import { } from "@/utils/chart-config/api"; export const createPaletteForUser = async ( - data: CreateCustomColorPalette & { user_id?: number } + data: CreateCustomColorPalette & { user_id: number } ): Promise => { const palette = await prisma.palette.create({ data: { @@ -28,9 +28,11 @@ export const createPaletteForUser = async ( }; }; -export const getPalettesForUser = async ( - user_id?: number -): Promise => { +export const getPalettesForUser = async ({ + user_id, +}: { + user_id: number; +}): Promise => { const palettes = await prisma.palette.findMany({ where: { user_id, diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index 77e4c5a1c3..089e85f17b 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -75,8 +75,8 @@ model Palette { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - user User? @relation(fields: [user_id], references: [id]) - user_id Int? + user User @relation(fields: [user_id], references: [id]) + user_id Int @@map("palettes") @@unique([name, user_id]) diff --git a/app/server/user-controller.ts b/app/server/user-controller.ts index 62962cddbb..d3a33757f6 100644 --- a/app/server/user-controller.ts +++ b/app/server/user-controller.ts @@ -18,6 +18,13 @@ export const UserController = controller({ createPalette: async ({ req, res }) => { const session = await getServerSession(req, res, nextAuthOptions); const userId = session?.user?.id; + + if (!userId) { + throw new Error( + "You must be logged in to create a custom color palette!" + ); + } + const data: CreateCustomColorPalette = req.body; return await createPaletteForUser({ @@ -29,16 +36,39 @@ export const UserController = controller({ const session = await getServerSession(req, res, nextAuthOptions); const userId = session?.user?.id; - return await getPalettesForUser(userId); + if (!userId) { + throw new Error( + "You must be logged in to view your custom color palettes!" + ); + } + + return await getPalettesForUser({ user_id: userId }); }, - deletePalette: async ({ req }) => { + deletePalette: async ({ req, res }) => { + const session = await getServerSession(req, res, nextAuthOptions); + const userId = session?.user?.id; + + if (!userId) { + throw new Error( + "You must be logged in to delete a custom color palette!" + ); + } + const data: DeleteCustomColorPalette = req.body; - const paletteId = data.paletteId; await deletePaletteForUser(paletteId); }, - updatePalette: async ({ req }) => { + updatePalette: async ({ req, res }) => { + const session = await getServerSession(req, res, nextAuthOptions); + const userId = session?.user?.id; + + if (!userId) { + throw new Error( + "You must be logged in to update a custom color palette!" + ); + } + const data: UpdateCustomColorPalette = req.body; await updatePaletteForUser(data); From 761ad49ff653cfa06f069f029cfca657cec5d8e7 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 15:13:15 +0200 Subject: [PATCH 04/10] feat: Add uniqueness on paletteId + userId combo --- app/prisma/schema.prisma | 1 + 1 file changed, 1 insertion(+) diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index 089e85f17b..70a806ff7a 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -80,4 +80,5 @@ model Palette { @@map("palettes") @@unique([name, user_id]) + @@unique([paletteId, user_id]) } \ No newline at end of file From 704358481b479d844e67d1eb8a106fdcb560a4c8 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 15:13:37 +0200 Subject: [PATCH 05/10] refactor: Pass user_id to update / delete logic --- app/db/palettes.ts | 34 ++++++++++++++++++++++++++-------- app/server/user-controller.ts | 4 ++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/app/db/palettes.ts b/app/db/palettes.ts index 6f30f17c38..589afed3ab 100644 --- a/app/db/palettes.ts +++ b/app/db/palettes.ts @@ -49,25 +49,43 @@ export const getPalettesForUser = async ({ }); }; -export const deletePaletteForUser = async (paletteId: string) => { +export const deletePaletteForUser = async ({ + paletteId, + user_id, +}: { + paletteId: string; + user_id: number; +}) => { await prisma.palette.delete({ where: { - paletteId, + paletteId_user_id: { + paletteId, + user_id, + }, }, }); }; -export const updatePaletteForUser = async (data: UpdateCustomColorPalette) => { - const type = data.type && convertPaletteTypeToDBType(data.type); +export const updatePaletteForUser = async ({ + type, + paletteId, + name, + colors, + user_id, +}: UpdateCustomColorPalette & { user_id: number }) => { + const dbType = type && convertPaletteTypeToDBType(type); await prisma.palette.update({ where: { - paletteId: data.paletteId, + paletteId_user_id: { + paletteId, + user_id, + }, }, data: { - name: data.name, - colors: data.colors, - type, + name, + colors, + type: dbType, updated_at: new Date(), }, }); diff --git a/app/server/user-controller.ts b/app/server/user-controller.ts index d3a33757f6..9062afb52d 100644 --- a/app/server/user-controller.ts +++ b/app/server/user-controller.ts @@ -56,7 +56,7 @@ export const UserController = controller({ const data: DeleteCustomColorPalette = req.body; - await deletePaletteForUser(paletteId); + await deletePaletteForUser({ ...data, user_id: userId }); }, updatePalette: async ({ req, res }) => { @@ -71,6 +71,6 @@ export const UserController = controller({ const data: UpdateCustomColorPalette = req.body; - await updatePaletteForUser(data); + await updatePaletteForUser({ ...data, user_id: userId }); }, }); From 8e86a176b764c85d35c54e0487e1b26b92a52aa0 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 15:14:57 +0200 Subject: [PATCH 06/10] fix: Remove user palettes on account removal --- app/prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index 70a806ff7a..9e6d695731 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -75,7 +75,7 @@ model Palette { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - user User @relation(fields: [user_id], references: [id]) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int @@map("palettes") From f7311452d527d207aebc58b9595509ebcbfe04d9 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Wed, 28 May 2025 15:26:45 +0200 Subject: [PATCH 07/10] fix: Remove user palettes on account removal --- app/prisma/schema.prisma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index 70a806ff7a..9e6d695731 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -75,7 +75,7 @@ model Palette { created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - user User @relation(fields: [user_id], references: [id]) + user User @relation(fields: [user_id], references: [id], onDelete: Cascade) user_id Int @@map("palettes") From 937a907b9569a8df95ac5817d7907da73610a7ff Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Mon, 13 Oct 2025 15:19:03 +0200 Subject: [PATCH 08/10] feat: Revert palette db changes --- app/prisma/schema.prisma | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index 9e6d695731..853f8e544c 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -69,16 +69,14 @@ enum PALETTE_TYPE { model Palette { paletteId String @id @default(uuid()) - name String @db.VarChar(100) + name String @unique @db.VarChar(100) type PALETTE_TYPE colors String[] created_at DateTime @default(now()) @db.Timestamp(6) updated_at DateTime @default(now()) @db.Timestamp(6) - user User @relation(fields: [user_id], references: [id], onDelete: Cascade) - user_id Int + user User? @relation(fields: [user_id], references: [id], onDelete: Cascade) + user_id Int? @@map("palettes") - @@unique([name, user_id]) - @@unique([paletteId, user_id]) } \ No newline at end of file From 95b8dfb72637a58b03e86cd04d0e008a50b6b317 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Mon, 13 Oct 2025 15:20:51 +0200 Subject: [PATCH 09/10] fix: Prevent updating or removing palettes for unauthorized users --- app/db/palettes.ts | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/db/palettes.ts b/app/db/palettes.ts index 589afed3ab..780dab5058 100644 --- a/app/db/palettes.ts +++ b/app/db/palettes.ts @@ -56,12 +56,19 @@ export const deletePaletteForUser = async ({ paletteId: string; user_id: number; }) => { + const palette = await prisma.palette.findUnique({ + where: { + paletteId, + }, + }); + + if (!palette || palette.user_id !== user_id) { + throw new Error("Palette not found"); + } + await prisma.palette.delete({ where: { - paletteId_user_id: { - paletteId, - user_id, - }, + paletteId, }, }); }; @@ -73,19 +80,24 @@ export const updatePaletteForUser = async ({ colors, user_id, }: UpdateCustomColorPalette & { user_id: number }) => { - const dbType = type && convertPaletteTypeToDBType(type); + const palette = await prisma.palette.findUnique({ + where: { + paletteId, + }, + }); + + if (!palette || palette.user_id !== user_id) { + throw new Error("Palette not found"); + } await prisma.palette.update({ where: { - paletteId_user_id: { - paletteId, - user_id, - }, + paletteId, }, data: { name, colors, - type: dbType, + type: type && convertPaletteTypeToDBType(type), updated_at: new Date(), }, }); From 728817989a6aa94a347087876f072f01a69fb072 Mon Sep 17 00:00:00 2001 From: Bartosz Prusinowski Date: Mon, 13 Oct 2025 15:43:53 +0200 Subject: [PATCH 10/10] docs: Update CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2569314e1c..3f3c7839dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,8 @@ You can also check the component for smaller breakpoints - Maintenance - Improved code organization around Browse page +- Security + - Improved security of color palette API routes ### 6.0.0 - 2025-09-05 @@ -101,8 +103,6 @@ You can also check the - Improved UX of interacting with conversion units multiplier input - Added additional WMS & WMTS providers - Fixes - - Custom color palette names are not required to be globally unique anymore, - but unique per user - Fixed an infinite render loop when adding a custom map layer - Styles - Fixed some smaller UI inconsistencies, mostly in table charts