-
Notifications
You must be signed in to change notification settings - Fork 195
Board level sharing #830
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Board level sharing #830
Changes from all commits
676e234
e74fdda
ca73ebd
8a52e1f
106de04
232f8f5
2e37696
bafe109
6b2742b
fa13bf9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{ | |
| id: true, | ||
| isPublic: true, | ||
| organizationId: true, | ||
| createdBy: true, | ||
| notes: { | ||
| where: { | ||
| deletedAt: null, // Only include non-deleted notes | ||
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replaced single user lookup with two-tier authorization. |
||
|
|
||
| if (board.organizationId !== user.organizationId) { | ||
| const boardShare = await db.boardShare.findFirst({ | ||
| where: { | ||
| boardId: boardId, | ||
| userId: session.user.id, | ||
| }, | ||
| }); | ||
|
|
||
|
Comment on lines
+78
to
+84
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. creator access check, |
||
| if (!boardShare && board.createdBy !== session.user.id) { | ||
| return NextResponse.json({ error: "Access denied" }, { status: 403 }); | ||
| } | ||
|
|
||
|
|
@@ -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: { | ||
|
|
@@ -130,8 +150,8 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ | |
| }, | ||
| }); | ||
|
Comment on lines
127
to
151
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. update user lookup to use OR logic. |
||
|
|
||
| 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({ | ||
|
|
@@ -140,6 +160,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{ | |
| id: true, | ||
| name: true, | ||
| organizationId: true, | ||
| createdBy: true, | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is absolutely necessary for authorization check |
||
| sendSlackUpdates: true, | ||
| }, | ||
| }); | ||
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Creator access check for note creation |
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. creator access check. |
||
| // Return board data without sensitive organization member details | ||
| const { organization, ...boardData } = board; | ||
|
|
||
|
|
@@ -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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" }, | ||
|
|
@@ -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 } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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