diff --git a/package-lock.json b/package-lock.json index 5e59585..87f35e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1051,9 +1051,9 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.53.2", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.2.tgz", - "integrity": "sha512-M+MqAvFve12T1HWws/2npP/s3hFtyjw3GB/OXW/8a1jZBk48qnvPJrtgE+VOMc3RnjUMxc4mv/vQ73nvj2uNMg==", + "version": "2.53.4", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.53.4.tgz", + "integrity": "sha512-iAIPEahFgDJJyvz8g0jP08KvqnM6JvdW8YfsygZ+pMeMvyM2zssWMltcsotETvjSZ82G3VlitgDtBIvpQSZrTA==", "license": "MIT", "peer": true, "dependencies": { @@ -2025,10 +2025,13 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", + "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", "license": "(MPL-2.0 OR Apache-2.0)", + "engines": { + "node": ">=20" + }, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } diff --git a/prisma/seed.ts b/prisma/seed.ts index c6a0bcb..d6f72a1 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -22,7 +22,8 @@ async function main() { } // Create admin user - const passwordHash = await bcrypt.hash('admin123', 12); + const seedPassword = process.env.SEED_ADMIN_PASSWORD || 'admin123'; + const passwordHash = await bcrypt.hash(seedPassword, 12); await prisma.user.upsert({ where: { tenantId_email: { tenantId: tenant.id, email: 'admin@example.com' } }, update: {}, @@ -90,7 +91,7 @@ async function main() { } console.log('Seed complete.'); - console.log(' Admin: admin@example.com / admin123'); + console.log(' Admin: admin@example.com (password set via SEED_ADMIN_PASSWORD env var)'); } main() diff --git a/server.js b/server.js index 4717bb4..482cd07 100644 --- a/server.js +++ b/server.js @@ -87,7 +87,29 @@ io.on('connection', (socket) => { // Track which boards this socket has joined (for cleanup on disconnect) const joinedBoards = new Set(); - socket.on('join-board', (boardId) => { + socket.on('join-board', async (boardId) => { + // Validate boardId format (UUID) + if (typeof boardId !== 'string' || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(boardId)) { + return; + } + + // Check board access permission + try { + const board = await prisma.board.findUnique({ + where: { id: boardId }, + select: { visibility: true, members: { select: { userId: true } } } + }); + if (!board) return; + if (board.visibility !== 'PUBLIC') { + if (!user) return; + const isMember = board.members.some((m) => m.userId === user.id); + if (!isMember && user.tenantRole !== 'ADMIN' && user.globalRole !== 'SITE_ADMIN') return; + } + } catch (err) { + logger.error({ err, boardId }, 'Board access check failed'); + return; + } + socket.join(`board:${boardId}`); joinedBoards.add(boardId); diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 6bb081e..76a5bfe 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -67,6 +67,11 @@ export const handle: Handle = async ({ event, resolve }) => { : undefined }); + // Security headers + response.headers.set('X-Frame-Options', 'DENY'); + response.headers.set('X-Content-Type-Options', 'nosniff'); + response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin'); + logger.info({ method: event.request.method, path: event.url.pathname, diff --git a/src/lib/components/AssigneePicker.svelte b/src/lib/components/AssigneePicker.svelte index 3a81ca9..5281b5e 100644 --- a/src/lib/components/AssigneePicker.svelte +++ b/src/lib/components/AssigneePicker.svelte @@ -64,7 +64,7 @@ {assignee.user.name} {#if canEdit} - {#if showDropdown} -
(showDropdown = false)}>
+
{#each boardMembers as member} diff --git a/src/lib/components/AttachmentList.svelte b/src/lib/components/AttachmentList.svelte index 1f6e3c8..e3e8ff6 100644 --- a/src/lib/components/AttachmentList.svelte +++ b/src/lib/components/AttachmentList.svelte @@ -19,11 +19,11 @@ let { cardId, boardId, columnId, attachments = [], canEdit }: Props = $props(); - let localAttachments = $state(attachments); + let localAttachments = $state([]); let uploading = $state(false); let uploadProgress = $state(0); - $effect(() => { + $effect.pre(() => { localAttachments = attachments; }); diff --git a/src/lib/components/AutomationBuilder.svelte b/src/lib/components/AutomationBuilder.svelte index 8d7522b..f7536a8 100644 --- a/src/lib/components/AutomationBuilder.svelte +++ b/src/lib/components/AutomationBuilder.svelte @@ -147,16 +147,16 @@
- - {#each triggers as t} {/each}
- - {#each actions as a} {/each} @@ -244,7 +244,7 @@ class="rounded border-gray-300 dark:border-gray-600 text-[var(--color-primary)] focus:ring-[var(--color-primary)]" /> - {#if open} +