fix: resolve 7 bugs across API routes (security, logic, edge cases)#9
Conversation
📝 WalkthroughWalkthroughMultiple API endpoints were updated to enhance authorization enforcement and error handling. Ownership checks (teacherId validation) were added to mutation endpoints. Error responses were standardized to exclude stack traces. Validation logic was improved with explicit Zod result checking, and authentication flow was simplified. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~22 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
app/api/grades/[id]/route.ts (1)
56-63:⚠️ Potential issue | 🟡 MinorDELETE is missing ObjectId validation — inconsistent with sibling routes and with the new PUT behavior.
An invalid
idreachesGrade.findOneAndDeleteand triggers a Mongoose CastError caught as 500 instead of 400. Bothapp/api/assignments/[id]/route.ts(lines 60-63) andapp/api/students/[id]/route.ts(lines 62-65) validate here, and the PR already aligned PUT to return 400 on invalid id — DELETE should match.🛠️ Proposed fix
try { const { id } = await ctx.params + + // Validate ObjectId + if (!mongoose.Types.ObjectId.isValid(id)) { + return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) + } + await connectDB() const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/grades/`[id]/route.ts around lines 56 - 63, The DELETE handler is missing ObjectId validation causing invalid ids to bubble into Grade.findOneAndDelete and produce a 500; before calling connectDB/Grade.findOneAndDelete, validate the extracted id (from ctx.params) using Mongoose's ObjectId validation (e.g. mongoose.isValidObjectId or Types.ObjectId.isValid) and if invalid return NextResponse.json({ error: 'Invalid id' }, { status: 400 }) to match the PUT and sibling routes (ensure this check occurs before calling connectDB and before invoking Grade.findOneAndDelete).app/api/grades/route.ts (1)
49-53:⚠️ Potential issue | 🟠 MajorStack-trace leak in GET handler was NOT fixed — PR objective is incomplete.
Line 51 still returns
error.stackin the 500 response body, which the PR description explicitly targets ("grades/route.ts … replace error.stack with a generic message in 500 responses"). Only the POST handler below was updated. Stack traces expose internal file paths, dependency internals, and can aid attackers. Please apply the same fix here.🛡️ Proposed fix
} catch (error) { - console.error('GET /api/grades error:', error instanceof Error ? error.message : error) - return NextResponse.json({ error: error instanceof Error ? error.stack : 'Internal server error' }, { status: 500 }) + if (error instanceof Error) { + console.error('GET /api/grades error:', error.message) + } + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/grades/route.ts` around lines 49 - 53, The GET handler's catch block in app/api/grades/route.ts still returns error.stack in the 500 JSON response; update the catch for the GET (the try/catch surrounding the GET handler function) to return a generic message (e.g., "Internal server error") instead of error.stack, while keeping or enhancing the existing console.error('GET /api/grades error:', ...) to log the actual error/stactrace server-side for debugging; ensure the change mirrors the POST handler fix so no stack traces are exposed in responses.
🧹 Nitpick comments (2)
app/api/students/route.ts (1)
95-99: Correct fix for the unvalidated-input bug; minor inconsistency to consider.Using
parsed.datainstead ofbodyinStudent.createprevents callers from injecting unknown fields (e.g.,teacherId,_id) into the document. This is the right fix.One minor inconsistency:
app/api/assignments/route.tsandapp/api/grades/route.tsreturn validation errors withparsed.error.flatten(), while this endpoint returnsz.treeifyError(parsed.error). The two produce different shapes — front-end error handling becomes branchy. Consider standardizing.♻️ Optional alignment with other routes
- if (!parsed.success) return NextResponse.json({ error: z.treeifyError(parsed.error) }, { status: 400 }) + if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/students/route.ts` around lines 95 - 99, The Student create handler correctly uses StudentSchema.safeParse and must continue to pass parsed.data to Student.create to avoid unvalidated fields (see StudentSchema and Student.create usage), but standardize the validation error shape to match other endpoints by replacing z.treeifyError(parsed.error) with parsed.error.flatten() (used by app/api/assignments/route.ts and app/api/grades/route.ts) in the NextResponse.json call so all routes return a consistent error payload via NextResponse.json.app/api/grades/route.ts (1)
72-80: Targeted fixes look correct.
data.maxMarks ?? 100aligns with the schema default (models/Grade.tsline 28).- Adding
awaittoGrade.findOneAndUpdateensures the resolved document is returned (previously the response serialized a pending promise).- On the upsert-create path,
...dataspreadsmaxMarks: undefinedwhen omitted; Mongoose drops undefined and the schema default (100) applies, so behavior is consistent — but explicitly settingmaxMarks: maxin$setwould make intent clearer.♻️ Optional clarity tweak
- { $set: { ...data, term, teacherId: userId, grade: calcGrade(data.marks, max) } }, + { $set: { ...data, maxMarks: max, term, teacherId: userId, grade: calcGrade(data.marks, max) } },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/api/grades/route.ts` around lines 72 - 80, The upsert currently spreads ...data into the $set which can leave maxMarks undefined and rely on Mongoose defaults; explicitly set maxMarks to the computed max so intent is clear and calcGrade uses the same value — update the Grade.findOneAndUpdate call to include maxMarks: max inside the $set (alongside term, teacherId and grade computed by calcGrade(data.marks, max)), ensuring the code uses the existing max variable and the calcGrade function for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@app/api/grades/`[id]/route.ts:
- Around line 56-63: The DELETE handler is missing ObjectId validation causing
invalid ids to bubble into Grade.findOneAndDelete and produce a 500; before
calling connectDB/Grade.findOneAndDelete, validate the extracted id (from
ctx.params) using Mongoose's ObjectId validation (e.g. mongoose.isValidObjectId
or Types.ObjectId.isValid) and if invalid return NextResponse.json({ error:
'Invalid id' }, { status: 400 }) to match the PUT and sibling routes (ensure
this check occurs before calling connectDB and before invoking
Grade.findOneAndDelete).
In `@app/api/grades/route.ts`:
- Around line 49-53: The GET handler's catch block in app/api/grades/route.ts
still returns error.stack in the 500 JSON response; update the catch for the GET
(the try/catch surrounding the GET handler function) to return a generic message
(e.g., "Internal server error") instead of error.stack, while keeping or
enhancing the existing console.error('GET /api/grades error:', ...) to log the
actual error/stactrace server-side for debugging; ensure the change mirrors the
POST handler fix so no stack traces are exposed in responses.
---
Nitpick comments:
In `@app/api/grades/route.ts`:
- Around line 72-80: The upsert currently spreads ...data into the $set which
can leave maxMarks undefined and rely on Mongoose defaults; explicitly set
maxMarks to the computed max so intent is clear and calcGrade uses the same
value — update the Grade.findOneAndUpdate call to include maxMarks: max inside
the $set (alongside term, teacherId and grade computed by calcGrade(data.marks,
max)), ensuring the code uses the existing max variable and the calcGrade
function for consistency.
In `@app/api/students/route.ts`:
- Around line 95-99: The Student create handler correctly uses
StudentSchema.safeParse and must continue to pass parsed.data to Student.create
to avoid unvalidated fields (see StudentSchema and Student.create usage), but
standardize the validation error shape to match other endpoints by replacing
z.treeifyError(parsed.error) with parsed.error.flatten() (used by
app/api/assignments/route.ts and app/api/grades/route.ts) in the
NextResponse.json call so all routes return a consistent error payload via
NextResponse.json.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: cc27aa22-9979-4bae-a0c2-01948f6f084e
📒 Files selected for processing (7)
app/api/assignments/[id]/route.tsapp/api/assignments/route.tsapp/api/grades/[id]/route.tsapp/api/grades/route.tsapp/api/profile/route.tsapp/api/students/[id]/route.tsapp/api/students/route.ts
Summary
Fixed 7 predefined bugs across API route handlers covering security vulnerabilities, logic errors, and edge cases.
Fixes
fix: add await to Grade.findOneAndUpdate (
app/api/grades/route.ts)fix: default maxMarks to 100 when undefined (
app/api/grades/route.ts)maxMarksis optional in the schema; using!causedcalcGradeto receiveundefined, producing wrong letter gradesfix: validate and use parsed result in POST /api/students (
app/api/students/route.ts)safeParseresult was never checked; raw unvalidated body was passed directly toStudent.create()fix: prevent IDOR in GET /api/profile (
app/api/profile/route.ts)userIdwas accepted as a query param, allowing any authenticated user to fetch another teacher's profile by passing an arbitraryuserIdfix: scope mutations to authenticated teacherId (
grades/[id],students/[id],assignments/[id])_id, allowing any authenticated teacher to modify or delete another teacher's recordsfix: replace error.stack with generic message in 500 responses (
grades/route.ts,assignments/route.ts)fix: return 400 not 404 for invalid ObjectId in PUT /api/grades/[id]
Issues Fixed: 7
Summary by CodeRabbit
Bug Fixes
Security