Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 51 additions & 12 deletions app/api/boards/[id]/notes/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
id: true,
isPublic: true,
organizationId: true,
createdBy: true,
Copy link
Author

@tsahil01 tsahil01 Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required for auth check board.createdBy !== session.user.id, without this field the check fails and creators get 403 errors

notes: {
where: {
deletedAt: null, // Only include non-deleted notes
Expand Down Expand Up @@ -63,18 +64,25 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

const user = await db.user.findUnique({
where: { id: session.user.id },
select: {
organizationId: true,
const userInOrg = await db.user.findFirst({
where: {
id: session.user.id,
organizationId: board.organizationId,
},
});

if (!user?.organizationId) {
return NextResponse.json({ error: "No organization found" }, { status: 403 });
if (!userInOrg) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
Comment on lines +67 to 76
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replaced single user lookup with two-tier authorization.
first check user is in org, then check if they have sharing permissions or are creator.


if (board.organizationId !== user.organizationId) {
const boardShare = await db.boardShare.findFirst({
where: {
boardId: boardId,
userId: session.user.id,
},
});

Comment on lines +78 to +84
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creator access check,
if user created the board, they get access even without explicit sharing

if (!boardShare && board.createdBy !== session.user.id) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}

Expand Down Expand Up @@ -115,9 +123,21 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
}
const { color, checklistItems } = validatedBody;

// Verify user has access to this board (same organization)
// Verify user has access to this board (either through organization membership or explicit board share)
const user = await db.user.findUnique({
where: { id: session.user.id },
where: {
id: session.user.id,
OR: [
{ organizationId: { not: null } },
{
boardShares: {
some: {
boardId: boardId,
},
},
},
],
},
select: {
organizationId: true,
organization: {
Expand All @@ -130,8 +150,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
},
});
Comment on lines 127 to 151
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update user lookup to use OR logic.
checks if user is in any org or has board sharing permissions.
ensures creators can access their boards even if they're not currently in an org context


if (!user?.organizationId) {
return NextResponse.json({ error: "No organization found" }, { status: 403 });
if (!user) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}

const board = await db.board.findUnique({
Expand All @@ -140,6 +160,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
id: true,
name: true,
organizationId: true,
createdBy: true,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is absolutely necessary for authorization check board.createdBy !== session.user.id

sendSlackUpdates: true,
},
});
Expand All @@ -148,7 +169,25 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: "Board not found" }, { status: 404 });
}

if (board.organizationId !== user.organizationId) {
const userInOrg = await db.user.findFirst({
where: {
id: session.user.id,
organizationId: board.organizationId,
},
});

if (!userInOrg) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
Comment on lines +172 to +181
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifies user is in organization


const boardShare = await db.boardShare.findFirst({
where: {
boardId: boardId,
userId: session.user.id,
},
});

if (!boardShare && board.createdBy !== session.user.id) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}
Comment on lines +183 to 192
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creator access check for note creation


Expand Down
41 changes: 34 additions & 7 deletions app/api/boards/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Check if user is member of the organization
// Check if user is in the organization (required for board sharing)
const userInOrg = await db.user.findFirst({
where: {
id: session.user.id,
Expand All @@ -47,6 +47,17 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}

const boardShare = await db.boardShare.findFirst({
where: {
boardId: boardId,
userId: session.user.id,
},
});

if (!boardShare && board.createdBy !== session.user.id) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}

Comment on lines +50 to +60
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creator access check.
if user made the board, they get access even without explicit sharing, creator have access to their content always.

// Return board data without sensitive organization member details
const { organization, ...boardData } = board;

Expand Down Expand Up @@ -105,27 +116,35 @@ export async function PUT(request: NextRequest, { params }: { params: Promise<{
return NextResponse.json({ error: "Board not found" }, { status: 404 });
}

// Check if user is member of the organization and get admin status
// Check if user is in the organization and has admin status (for editing/deleting boards)
// OR if user is the board creator
const currentUser = await db.user.findFirst({
where: {
id: session.user.id,
organizationId: board.organizationId,
OR: [
{ organizationId: board.organizationId },
{ id: board.createdBy }, // Allow board creator to edit their own board
],
},
select: {
id: true,
isAdmin: true,
organizationId: true,
},
});
Comment on lines 121 to 134
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let board creators edit/ delete their own boards without being org admin, so people can manage their own work as well


if (!currentUser) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}

// For admin checks, user must be organization admin OR board creator
const isOrgAdmin = currentUser.isAdmin || board.createdBy === session.user.id;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give edit rights to either the creator OR org admins.


// For name/description/isPublic updates, check if user can edit this board (board creator or admin)
if (
(name !== undefined || description !== undefined || isPublic !== undefined) &&
board.createdBy !== session.user.id &&
!currentUser.isAdmin
!isOrgAdmin
) {
return NextResponse.json(
{ error: "Only the board creator or admin can edit this board" },
Expand Down Expand Up @@ -196,24 +215,32 @@ export async function DELETE(
return NextResponse.json({ error: "Board not found" }, { status: 404 });
}

// Check if user is member of the organization and get admin status
// Check if user is in the organization and has admin status (for editing/deleting boards)
// OR if user is the board creator
const currentUser = await db.user.findFirst({
where: {
id: session.user.id,
organizationId: board.organizationId,
OR: [
{ organizationId: board.organizationId },
{ id: board.createdBy }, // Allow board creator to edit their own board
],
},
select: {
id: true,
isAdmin: true,
organizationId: true,
},
});

if (!currentUser) {
return NextResponse.json({ error: "Access denied" }, { status: 403 });
}

// For admin checks, user must be organization admin OR board creator
const isOrgAdmin = currentUser.isAdmin || board.createdBy === session.user.id;

// Check if user can delete this board (board creator or admin)
if (board.createdBy !== session.user.id && !currentUser.isAdmin) {
if (board.createdBy !== session.user.id && !isOrgAdmin) {
return NextResponse.json(
{ error: "Only the board creator or admin can delete this board" },
{ status: 403 }
Expand Down
Loading