-
Notifications
You must be signed in to change notification settings - Fork 23
Fix/bughunt reem19 15 #16
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
base: main
Are you sure you want to change the base?
Changes from all commits
a3419c5
4c0bf6b
462a950
ce41266
ebd0b74
504d997
e467cbb
2b6d239
f893ae4
f61793d
ee6f9c1
a006096
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 | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,7 +13,6 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string | |||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const { id } = await ctx.params | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Validate ObjectId | ||||||||||||||||||||||||
| if (!mongoose.Types.ObjectId.isValid(id)) { | ||||||||||||||||||||||||
| return NextResponse.json({ error: 'Not found' }, { status: 404 }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
@@ -25,7 +24,6 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string | |||||||||||||||||||||||
| return NextResponse.json({ error: 'Invalid JSON in request body' }, { status: 400 }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Sanitize: only allow whitelisted fields | ||||||||||||||||||||||||
| const sanitizedBody: Record<string, unknown> = {} | ||||||||||||||||||||||||
| for (const key of ALLOWED_UPDATE_FIELDS) { | ||||||||||||||||||||||||
| if (key in body) { | ||||||||||||||||||||||||
|
|
@@ -34,13 +32,27 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string | |||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| await connectDB() | ||||||||||||||||||||||||
| const grade = await Grade.findOneAndUpdate( | ||||||||||||||||||||||||
| { _id: id }, | ||||||||||||||||||||||||
| sanitizedBody, | ||||||||||||||||||||||||
| { new: true } | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| if (!grade) return NextResponse.json({ error: 'Not found' }, { status: 404 }) | ||||||||||||||||||||||||
| return NextResponse.json(grade) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // 1. Find first to ensure ownership | ||||||||||||||||||||||||
| const existingGrade = await Grade.findOne({ _id: id, teacherId: userId }) | ||||||||||||||||||||||||
| if (!existingGrade) return NextResponse.json({ error: 'Not found' }, { status: 404 }) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // 2. Apply whitelisted updates | ||||||||||||||||||||||||
| Object.assign(existingGrade, sanitizedBody) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // 3. Recalculate grade string if marks changed | ||||||||||||||||||||||||
| if (existingGrade.marks != null && existingGrade.maxMarks != null) { | ||||||||||||||||||||||||
| const percentage = (existingGrade.marks / existingGrade.maxMarks) * 100 | ||||||||||||||||||||||||
| if (percentage >= 90) existingGrade.grade = 'A+' | ||||||||||||||||||||||||
| else if (percentage >= 80) existingGrade.grade = 'A' | ||||||||||||||||||||||||
| else if (percentage >= 70) existingGrade.grade = 'B' | ||||||||||||||||||||||||
| else existingGrade.grade = 'F' | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+43
to
+50
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. Keep PUT grading consistent with POST creation.
🐛 Proposed fix if (existingGrade.marks != null && existingGrade.maxMarks != null) {
const percentage = (existingGrade.marks / existingGrade.maxMarks) * 100
if (percentage >= 90) existingGrade.grade = 'A+'
else if (percentage >= 80) existingGrade.grade = 'A'
- else if (percentage >= 70) existingGrade.grade = 'B'
+ else if (percentage >= 70) existingGrade.grade = 'B+'
+ else if (percentage >= 60) existingGrade.grade = 'B'
+ else if (percentage >= 50) existingGrade.grade = 'C'
+ else if (percentage >= 40) existingGrade.grade = 'D'
else existingGrade.grade = 'F'
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // 4. Save the document | ||||||||||||||||||||||||
| await existingGrade.save() | ||||||||||||||||||||||||
| return NextResponse.json(existingGrade) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||
| if (error instanceof Error) { | ||||||||||||||||||||||||
| console.error('PUT /api/grades/[id] error:', error.message) | ||||||||||||||||||||||||
|
|
@@ -49,14 +61,15 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string | |||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| // Only teachers can delete their own grades | ||||||||||||||||||||||||
| export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: string }> }) { | ||||||||||||||||||||||||
| const { userId } = await auth() | ||||||||||||||||||||||||
| if (!userId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| const { id } = await ctx.params | ||||||||||||||||||||||||
| await connectDB() | ||||||||||||||||||||||||
| const deleted = await Grade.findOneAndDelete({ _id: id }) | ||||||||||||||||||||||||
| const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId }) | ||||||||||||||||||||||||
|
Comment on lines
70
to
+72
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
python - <<'PY'
from pathlib import Path
path = Path("app/api/grades/[id]/route.ts")
text = path.read_text()
delete_handler = text[text.index("export async function DELETE"):]
print("ObjectId.isValid in DELETE handler:", "ObjectId.isValid" in delete_handler)
print(delete_handler[:700])
PYRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 826 🏁 Script executed: #!/bin/bash
python - <<'PY'
from pathlib import Path
path = Path("app/api/grades/[id]/route.ts")
text = path.read_text()
# Extract PUT handler
if "export async function PUT" in text:
put_start = text.index("export async function PUT")
# Find the next export to determine where PUT ends
next_export_idx = text.find("export async function", put_start + 1)
if next_export_idx == -1:
put_handler = text[put_start:]
else:
put_handler = text[put_start:next_export_idx]
print("=== PUT HANDLER ===")
print("ObjectId.isValid in PUT handler:", "ObjectId.isValid" in put_handler)
print("\nFirst 1200 characters:")
print(put_handler[:1200])
else:
print("PUT handler not found")
PYRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 1366 🏁 Script executed: #!/bin/bash
python - <<'PY'
from pathlib import Path
path = Path("app/api/grades/[id]/route.ts")
text = path.read_text()
# Extract full DELETE handler including catch block
if "export async function DELETE" in text:
delete_start = text.index("export async function DELETE")
# Find end of function
delete_section = text[delete_start:]
# Get more of the delete handler to see the catch block
lines = delete_section.split('\n')
handler_lines = []
brace_count = 0
found_start = False
for line in lines[:50]: # First 50 lines should be enough
if '{' in line:
brace_count += line.count('{')
found_start = True
if '}' in line:
brace_count -= line.count('}')
handler_lines.append(line)
if found_start and brace_count == 0:
break
full_handler = '\n'.join(handler_lines)
print(full_handler)
else:
print("DELETE handler not found")
PYRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 820 🏁 Script executed: #!/bin/bash
# Verify Mongoose behavior with malformed ObjectIds
python - <<'PY'
import subprocess
import sys
# Check if there's a package.json to see mongoose version
try:
with open("package.json", "r") as f:
content = f.read()
if "mongoose" in content:
print("✓ Mongoose is in dependencies")
# Extract mongoose version
import re
match = re.search(r'"mongoose":\s*"([^"]+)"', content)
if match:
print(f" Version: {match.group(1)}")
except:
pass
# Also check if there are any test files that might show how errors are handled
print("\n=== Checking for test files or usage patterns ===")
result = subprocess.run(["find", ".", "-name", "*.test.ts", "-o", "-name", "*.test.js", "-o", "-name", "*.spec.ts"],
capture_output=True, text=True)
if result.stdout:
print("Test files found:")
print(result.stdout)
else:
print("No obvious test files found")
# Search for any error handling patterns with malformed ObjectIds
print("\n=== Searching for CastError handling ===")
result = subprocess.run(["rg", "CastError|isValid.*ObjectId", "-n"],
capture_output=True, text=True)
if result.stdout:
print(result.stdout)
else:
print("No CastError patterns found in codebase")
PYRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 292 Add ObjectId validation to DELETE handler for consistent error handling. The PUT handler validates the grade id with Proposed fix const { id } = await ctx.params
+ if (!mongoose.Types.ObjectId.isValid(id)) {
+ return NextResponse.json({ error: 'Grade not found' }, { status: 404 })
+ }
+
await connectDB()
const deleted = await Grade.findOneAndDelete({ _id: id, teacherId: userId })📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if (!deleted) { | ||||||||||||||||||||||||
| return NextResponse.json({ error: 'Grade not found' }, { status: 404 }) | ||||||||||||||||||||||||
|
|
@@ -69,4 +82,4 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str | |||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -33,12 +33,12 @@ export async function PUT(req: NextRequest, ctx: { params: Promise<{ id: string | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await connectDB() | ||||||||||||||||||||||||||
| const student = await Student.findOneAndUpdate( | ||||||||||||||||||||||||||
| { _id: id }, | ||||||||||||||||||||||||||
| sanitizedBody, | ||||||||||||||||||||||||||
| { new: true } | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| // FIXED | ||||||||||||||||||||||||||
| const student = await Student.findOneAndUpdate( | ||||||||||||||||||||||||||
| { _id: id, teacherId: userId }, // ← add this | ||||||||||||||||||||||||||
| sanitizedBody, | ||||||||||||||||||||||||||
| { new: true } | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
|
Comment on lines
+36
to
+41
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. Connect to MongoDB before updating. This 🐛 Proposed fix- // FIXED
-const student = await Student.findOneAndUpdate(
- { _id: id, teacherId: userId }, // ← add this
- sanitizedBody,
- { new: true }
-)
+ await connectDB()
+ const student = await Student.findOneAndUpdate(
+ { _id: id, teacherId: userId },
+ sanitizedBody,
+ { new: true }
+ )📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| if (!student) return NextResponse.json({ error: 'Not found' }, { status: 404 }) | ||||||||||||||||||||||||||
| return NextResponse.json(student) | ||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||
|
|
@@ -65,7 +65,8 @@ export async function DELETE(_req: NextRequest, ctx: { params: Promise<{ id: str | |||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| await connectDB() | ||||||||||||||||||||||||||
| const deleted = await Student.findOneAndDelete({ _id: id }) | ||||||||||||||||||||||||||
| // FIXED | ||||||||||||||||||||||||||
| const deleted = await Student.findOneAndDelete({ _id: id, teacherId: userId }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| if (!deleted) { | ||||||||||||||||||||||||||
| return NextResponse.json({ error: 'Student not found' }, { status: 404 }) | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,7 +29,6 @@ export async function GET(req: NextRequest) { | |
| const search = (searchParams.get("search") ?? "").replace(/\s+/g, ' '); | ||
| const classFilter = searchParams.get("class") ?? ""; | ||
|
|
||
| // Parse and validate pagination | ||
| const pageStr = searchParams.get("page") ?? "1"; | ||
| const limitStr = searchParams.get("limit") ?? "20"; | ||
|
|
||
|
|
@@ -38,19 +37,17 @@ export async function GET(req: NextRequest) { | |
|
|
||
| if (Number.isNaN(page) || page < 1) page = 1; | ||
| if (Number.isNaN(limit) || limit < 1) limit = 20; | ||
| limit = Math.min(limit, 100); // Cap at 100 | ||
| limit = Math.min(limit, 100); | ||
|
|
||
| const query: Record<string, unknown> = { teacherId: userId }; | ||
| if (search) { | ||
| // Escape regex special characters to prevent ReDoS | ||
| const escapedSearch = escapeRegex(search); | ||
| query.$or = [ | ||
| { name: { $regex: escapedSearch, $options: "i" } }, | ||
| { rollNo: { $regex: escapedSearch, $options: "i" } }, | ||
| { class: { $regex: escapedSearch, $options: "i" } }, | ||
| ]; | ||
| } | ||
| // Add class filter if provided and not 'all' | ||
| if (classFilter && classFilter !== "all") { | ||
| query.class = classFilter; | ||
| } | ||
|
|
@@ -84,17 +81,25 @@ export async function POST(req: NextRequest) { | |
|
|
||
| try { | ||
| await connectDB() | ||
|
|
||
| let body | ||
| try { | ||
| body = await req.json() | ||
| } catch { | ||
| return NextResponse.json({ error: 'Malformed JSON' }, { status: 400 }) | ||
| } | ||
|
|
||
| StudentSchema.safeParse(body) | ||
|
|
||
| const student = await Student.create({ ...(body as Record<string, unknown>), teacherId: userId }) | ||
| // 1. Perform actual validation and catch errors | ||
| const validation = StudentSchema.safeParse(body) | ||
| if (!validation.success) { | ||
| return NextResponse.json({ | ||
| error: 'Validation failed', | ||
| details: validation.error.format() | ||
| }, { status: 400 }) | ||
| } | ||
|
|
||
| // 2. Only use validated data (prevents field injection) | ||
| const student = await Student.create({ ...validation.data, teacherId: userId }) | ||
|
Comment on lines
+92
to
+102
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -n -C2 'name:\s*z\.string\(\)\.min\(1\)|rollNo:\s*z\.string\(\)\.min\(1\)|class:\s*z\.string\(\)\.min\(1\)' --glob 'route.ts'Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 1147 🏁 Script executed: #!/bin/bash
# Check Zod version in package files
fd -e json 'package' | head -5 | xargs -I {} grep -l 'zod' {} 2>/dev/null || echo "Checking package.json directly"
cat package.json 2>/dev/null | grep -A2 -B2 '"zod"' || echo "package.json not found"Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 222 🏁 Script executed: #!/bin/bash
# Get the full StudentSchema definition to see all validation rules
cat -n app/api/students/route.ts | head -20Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 851 🏁 Script executed: #!/bin/bash
# Check if trim is used anywhere in the students route file
rg -n 'trim|\.trim\(\)' app/api/students/route.tsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 66 🏁 Script executed: #!/bin/bash
# Check the validation call and how validation.data is used (around lines 92-102)
cat -n app/api/students/route.ts | sed -n '85,110p'Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 1192 🏁 Script executed: #!/bin/bash
# Check if body is preprocessed before validation
cat -n app/api/students/route.ts | sed -n '1,95p' | tail -30Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 1095 Add The validation accepts whitespace-only strings for Suggested fixconst StudentSchema = z.object({
- name: z.string().min(1),
- rollNo: z.string().min(1),
- class: z.string().min(1),
+ name: z.string().trim().min(1),
+ rollNo: z.string().trim().min(1),
+ class: z.string().trim().min(1),
email: z.string().email().optional().or(z.literal('')),🤖 Prompt for AI Agents |
||
| return NextResponse.json(student, { status: 201 }) | ||
| } catch (error) { | ||
| if (error instanceof Error) { | ||
|
|
@@ -105,4 +110,4 @@ export async function POST(req: NextRequest) { | |
| } | ||
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ const AttendanceSchema = new Schema<IAttendance>( | |
| }, | ||
| { timestamps: true } | ||
| ) | ||
|
|
||
| AttendanceSchema.index({ studentId: 1, date: 1 }, { unique: true }) | ||
| //FIXED | ||
| AttendanceSchema.index({ teacherId: 1, studentId: 1, date: 1 }, { unique: true }) | ||
|
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. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Look for a migration or startup index-sync path that removes/replaces
# the old Attendance unique index.
rg -n -C3 'studentId_1_date_1|dropIndex|dropIndexes|syncIndexes|diffIndexes|AttendanceSchema\.index' --glob '!node_modules/**'Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 805 🏁 Script executed: #!/bin/bash
# Search for migration files and db initialization logic
fd -e 'mjs|js|ts' -t f | xargs grep -l 'migrate\|migration\|dropIndex\|syncIndexes\|Attendance.*index' 2>/dev/null | head -20Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 66 🏁 Script executed: #!/bin/bash
# Check if there's database initialization or connection setup that syncs indexes
rg -A 10 'connect.*mongoose|connection.*open|db.*init|syncIndexes' --type ts --type js -iRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 576 🏁 Script executed: #!/bin/bash
# Check the seed.mjs file context - is it auto-generated or manually maintained?
head -20 scripts/seed.mjsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 664 🏁 Script executed: #!/bin/bash
# Look for any index migration or database update scripts
fd -e 'js' -e 'mjs' -e 'ts' scripts/ migrations/ db/ 2>/dev/null | xargs cat 2>/dev/null | head -100Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 66 🏁 Script executed: #!/bin/bash
# Get full connectDB function to check for index sync
sed -n '1,50p' lib/mongodb.tsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 1051 🏁 Script executed: #!/bin/bash
# Check git history for Attendance.ts changes
git log --oneline models/Attendance.ts 2>/dev/null | head -10Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 149 🏁 Script executed: #!/bin/bash
# Check if seed.mjs is actively used or just example code
grep -r "seed" package.json README.md 2>/dev/nullRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 132 🏁 Script executed: #!/bin/bash
# Look for any TODO or FIXME comments related to indexes
rg 'TODO|FIXME|XXX' models/Attendance.ts scripts/seed.mjs lib/mongodb.tsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 66 🏁 Script executed: #!/bin/bash
# Check full seed.mjs context around the Attendance schema - does it create/sync indexes?
sed -n '95,115p' scripts/seed.mjsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 963 🏁 Script executed: #!/bin/bash
# Look for .syncIndexes() calls anywhere in the codebase
rg 'syncIndexes|ensureIndexes|dropIndex' -t ts -t js -t mjsRepository: JavaScript-Mastery-Pro/discord-challenge Length of output: 111 🏁 Script executed: #!/bin/bash
# Check if there's a production vs dev distinction for seed.mjs
grep -B5 -A5 "seed" README.md 2>/dev/null | head -30Repository: JavaScript-Mastery-Pro/discord-challenge Length of output: 66 Update seed.mjs and add index migration. The schema fix on line 27 is correct, but the seed script still creates the old 🤖 Prompt for AI Agents |
||
|
|
||
| export const Attendance = models.Attendance ?? model<IAttendance>('Attendance', AttendanceSchema) | ||
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.
🧩 Analysis chain
🌐 Web query:
Does Mongoose run schema validators by default for findOneAndUpdate, or is runValidators: true required?💡 Result:
No, Mongoose does not run schema validators by default for findOneAndUpdate. The runValidators option must be set to true to enable update validators.
Citations:
Add
runValidators: trueto thefindOneAndUpdatecall.Mongoose does not run schema validators by default for
findOneAndUpdate. Without therunValidatorsoption, a teacher can submit allowed fields with invalid values (such as an invalidstatusenum or negativemaxMarks) and persist them to the database. The ownership filter is correct, but data validation is still needed.🛡️ Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents