Skip to content

Fix/reply edit delete auth check 177#228

Merged
knoxiboy merged 3 commits into
knoxiboy:mainfrom
dreynox:fix/reply-edit-delete-auth-check-177
May 25, 2026
Merged

Fix/reply edit delete auth check 177#228
knoxiboy merged 3 commits into
knoxiboy:mainfrom
dreynox:fix/reply-edit-delete-auth-check-177

Conversation

@dreynox
Copy link
Copy Markdown
Contributor

@dreynox dreynox commented May 23, 2026

Description

Adds authentication and ownership checks to app/api/replies/action/[id]/route.ts so PATCH and DELETE requests now require a signed-in user and verify that the caller is either the reply author or the classroom teacher.

Related Issue

Closes #177

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Test addition or update

Screenshots (if UI change)

N/A

How Has This Been Tested?

  • Tested locally with npm run dev
  • Verified with an unauthenticated DELETE request returning 401
  • Ran Jest tests for the replies action endpoint

Checklist

  • I have tested my changes locally (npm run dev)
  • My code follows the existing code style (TypeScript, Tailwind, no any types)
  • I have not introduced unrelated changes (each PR should address one issue)
  • I have added comments where necessary
  • My branch is up to date with main
  • I have linked the related issue above
  • Screenshots are included (if this is a UI change)

Copilot AI review requested due to automatic review settings May 23, 2026 09:07
@vercel
Copy link
Copy Markdown

vercel Bot commented May 23, 2026

@dreynox is attempting to deploy a commit to the Karan Mani Tripathi 's projects Team on Vercel.

A member of the Team first needs to authorize it.

@dreynox dreynox force-pushed the fix/reply-edit-delete-auth-check-177 branch from 2752699 to a9420ff Compare May 23, 2026 09:10
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses issue #177 by adding authentication + authorization (owner/teacher) checks to the replies edit/delete API, and tightens reply upvote identity to use the authenticated Clerk user id instead of a client-provided name.

Changes:

  • Enforced auth + ownership/teacher authorization on PATCH/DELETE for /api/replies/action/[id].
  • Updated reply voting to use Clerk user.id as the stored like identity and adjusted the client vote request accordingly.
  • Added Jest coverage for the vote endpoint identity behavior and the replies action endpoint auth/authorization responses.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
components/DoubtRepliesModal.tsx Builds safer replies fetch URL and updates voting UX to rely on server auth (handles 401 + error toasts).
app/api/replies/vote/route.ts Switches vote identity from client-provided userName to authenticated Clerk user.id.
app/api/replies/route.ts Uses authenticated identity for computing hasUpvoted when returning replies.
app/api/replies/action/[id]/route.ts Adds signed-in + owner/teacher checks for editing/deleting replies.
tests/api/replies-vote.test.ts Adds test ensuring vote storage uses Clerk identity even if a client userName is sent.
tests/api/replies-action.test.ts Adds tests for 401 unauthenticated delete, 403 non-owner patch, and successful owner delete.
Comments suppressed due to low confidence (1)

app/api/replies/action/[id]/route.ts:70

  • Same as PATCH: authorization depends on email, but if the authenticated user lacks primaryEmailAddress, the handler can't correctly evaluate ownership/teacher permissions and will respond with a misleading 403 after performing DB reads. Add an early if (!email) ... response to match the pattern used elsewhere (e.g., app/api/replies/route.ts).
        const user = await currentUser();
        if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
        const email = user.primaryEmailAddress?.emailAddress;

        const [reply] = await db.select().from(repliesTable).where(eq(repliesTable.id, replyId)).limit(1);
        if (!reply) return NextResponse.json({ error: "Reply not found" }, { status: 404 });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 28 to 32
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 49a9061

Comment thread app/api/replies/route.ts
Comment on lines 30 to 31
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 49a9061

Comment thread app/api/replies/action/[id]/route.ts Outdated
// Authorization: must be authenticated and either owner or classroom teacher
const user = await currentUser();
if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const email = user.primaryEmailAddress?.emailAddress;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 49a9061

Comment on lines +22 to +33
const [reply] = await db.select().from(repliesTable).where(eq(repliesTable.id, replyId)).limit(1);
if (!reply) return NextResponse.json({ error: "Reply not found" }, { status: 404 });

// Find parent doubt for teacher check
const [doubt] = await db.select().from(doubtsTable).where(eq(doubtsTable.id, reply.doubtId)).limit(1);
let isTeacher = false;
if (doubt?.classroomId) {
const [room] = await db.select().from(classroomsTable).where(eq(classroomsTable.id, doubt.classroomId)).limit(1);
isTeacher = !!(room && email && room.teacherEmail === email);
}

const isOwner = email && reply.userEmail === email;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in commit 49a9061

@knoxiboy knoxiboy added type:bug Bug fix level:intermediate Intermediate level task gssoc'26 GSSoC program issue labels May 23, 2026
knoxiboy

This comment was marked as outdated.

@dreynox
Copy link
Copy Markdown
Contributor Author

dreynox commented May 23, 2026

Technical Review

Hi @dreynox! Thank you for your contribution to DoubtDesk.

The code changes look good. Before we can complete the technical review, approve, and merge this pull request, we have one final requirement for all contributors: Please star the DoubtDesk repository.

Once you have starred the repository, please drop a comment here saying "done" and we will proceed with approving and merging your PR. Thank you.

Hi @knoxiboy,
Thank you for merging my earlier PRs — I really appreciate it. I have already starred the repository before working on it.

Before the current PR is merged, I just wanted to kindly ask you to add the GSSoC tracking label (gssoc:approved, quality:clean or quality:exceptional). It’s required on the issue/PR side for points to be counted, and it seems the previous PRs were merged without the label.

Thanks again for your time and support!

@dreynox
Copy link
Copy Markdown
Contributor Author

dreynox commented May 23, 2026

@knoxiboy Please add the required labels that are gssoc:approved, quality:clean or quality:exceptional, before merging the commit because i need points for the GSSoC '26 program, if these are not added then my contribution will mean nothing to me. If you add them after merging the commit, then they are of no use.

@knoxiboy knoxiboy added gssoc:approved Approved for GSSoC mentor:knoxiboy Reviewed by mentor knoxiboy labels May 24, 2026
knoxiboy

This comment was marked as spam.

knoxiboy

This comment was marked as spam.

Copy link
Copy Markdown
Owner

@knoxiboy knoxiboy left a comment

Choose a reason for hiding this comment

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

Hi @dreynox! This PR currently has merge conflicts with the main branch. Please pull the latest changes, resolve the conflicts locally, and push again so we can merge it. Thank you!

@knoxiboy knoxiboy self-requested a review May 24, 2026 12:26
@knoxiboy knoxiboy added gssoc and removed gssoc labels May 24, 2026
Copy link
Copy Markdown
Owner

@knoxiboy knoxiboy left a comment

Choose a reason for hiding this comment

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

Automated review: Thank you for starring the repository! The PR is approved.

Note: This PR currently has merge conflicts with the main branch. Please resolve the merge conflicts so we can complete the merge!

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

app/api/replies/action/[id]/route.ts:108

  • The DELETE handler also has duplicated blocks that redeclare reply and isTeacher, which will fail TypeScript compilation and cause extra queries. Keep a single fetch of the reply and a single teacher/owner permission check.
        const [reply] = await db.select().from(repliesTable).where(eq(repliesTable.id, replyId)).limit(1);
        if (!reply) return NextResponse.json({ error: "Reply not found" }, { status: 404 });

        let isTeacher = false;
        if (reply.doubtId) {
            const [doubt] = await db.select().from(doubtsTable).where(eq(doubtsTable.id, reply.doubtId)).limit(1);
            if (doubt?.classroomId) {
                const [room] = await db.select().from(classroomsTable).where(eq(classroomsTable.id, doubt.classroomId)).limit(1);
                isTeacher = !!(room && email && room.teacherEmail === email);
            }
        }
        const [reply] = await db.select().from(repliesTable).where(eq(repliesTable.id, replyId)).limit(1);
        if (!reply) return NextResponse.json({ error: "Reply not found" }, { status: 404 });

        let isTeacher = false;
        if (reply.doubtId) {
            const [doubt] = await db.select().from(doubtsTable).where(eq(doubtsTable.id, reply.doubtId)).limit(1);

app/api/replies/action/[id]/route.ts:114

  • Unresolved git merge-conflict marker (>>>>>>> upstream/main) is present in the DELETE handler. This must be removed by resolving the merge conflict before this can ship.
            if (doubt?.classroomId) {
                const [room] = await db.select().from(classroomsTable).where(eq(classroomsTable.id, doubt.classroomId)).limit(1);
                isTeacher = !!(room && email && room.teacherEmail === email);
            }
>>>>>>> upstream/main
        }

Comment on lines +44 to 61
const [reply] = await db.select().from(repliesTable).where(eq(repliesTable.id, replyId)).limit(1);
if (!reply) return NextResponse.json({ error: "Reply not found" }, { status: 404 });

let isTeacher = false;
if (reply.doubtId) {
const [doubt] = await db.select().from(doubtsTable).where(eq(doubtsTable.id, reply.doubtId)).limit(1);
if (doubt?.classroomId) {
const [room] = await db.select().from(classroomsTable).where(eq(classroomsTable.id, doubt.classroomId)).limit(1);
isTeacher = !!(room && email && room.teacherEmail === email);
}
}

const isOwner = email && reply.userEmail === email;
if (!isOwner && !isTeacher) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
>>>>>>> upstream/main
}

Comment on lines 56 to 60
const isOwner = email && reply.userEmail === email;
if (!isOwner && !isTeacher) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
>>>>>>> upstream/main
}
Comment on lines 31 to 38
let isTeacher = false;
if (reply.doubtId) {
const [doubt] = await db.select().from(doubtsTable).where(eq(doubtsTable.id, reply.doubtId)).limit(1);
if (doubt?.classroomId) {
const [room] = await db.select().from(classroomsTable).where(eq(classroomsTable.id, doubt.classroomId)).limit(1);
isTeacher = !!(room && email && room.teacherEmail === email);
}
}
@knoxiboy knoxiboy added level:advanced Advanced level task and removed level:intermediate Intermediate level task labels May 25, 2026
@knoxiboy knoxiboy added the type:security Security fix label May 25, 2026
@knoxiboy knoxiboy merged commit 30d54b5 into knoxiboy:main May 25, 2026
5 of 8 checks passed
@github-actions github-actions Bot added quality:clean Clean code quality and removed size/m labels May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gssoc:approved Approved for GSSoC gssoc'26 GSSoC program issue level:advanced Advanced level task mentor:knoxiboy Reviewed by mentor knoxiboy quality:clean Clean code quality type:bug Bug fix type:security Security fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security]: Reply edit/delete endpoint has no authentication or ownership check

3 participants