Skip to content

fix: resolve security, logic, and data integrity issues#10

Open
Astr-oIT wants to merge 6 commits intoJavaScript-Mastery-Pro:mainfrom
Astr-oIT:fix/bughunt-luisgabrielle
Open

fix: resolve security, logic, and data integrity issues#10
Astr-oIT wants to merge 6 commits intoJavaScript-Mastery-Pro:mainfrom
Astr-oIT:fix/bughunt-luisgabrielle

Conversation

@Astr-oIT
Copy link
Copy Markdown

@Astr-oIT Astr-oIT commented Apr 18, 2026

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Adjusted grade threshold for A+ rating (≥90%)
  • New Features

    • Added validation to reject past assignment deadlines
    • Added validation to prevent future attendance entries
  • Improvements

    • Strengthened authorization checks for data modifications
    • Enhanced error responses for unauthorized access
  • Removals

    • Removed attendance data retrieval endpoint

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

Multiple API route handlers and database connection utility were refactored to improve error handling consistency, add validation for past deadlines and future dates, couple authorization checks to database queries, simplify request parsing with .catch() fallbacks, and update schema defaults for optional fields. The MongoDB cache implementation was also simplified by removing explicit TypeScript interfaces.

Changes

Cohort / File(s) Summary
Assignment API Updates
app/api/assignments/[id]/route.ts, app/api/assignments/route.ts
Renamed ALLOWED_UPDATE_FIELDS to ALLOWED_FIELDS; coupled authorization to findOneAndUpdate/findOneAndDelete by adding teacherId filter; added deadline validation rejecting dates in the past; simplified error handling by removing per-error logging; changed error messages from Not found to Not found or unauthorized; updated schema defaults for maxMarks, status, and kanbanStatus.
Attendance API Refactor
app/api/attendance/route.ts
Removed exported GET handler entirely; refactored POST to use single validation path (z.array(AttendanceSchema) vs AttendanceSchema) replacing dedicated BulkSchema; added validation rejecting attendance records with future dates; simplified JSON parsing and error handling; removed error logging.
Grades API Refinement
app/api/grades/route.ts
Updated GradeSchema.maxMarks to use default value 100 instead of optional; modified calcGrade to award 'A+' for percentages >= 90 (was > 90); simplified GET query construction using inline object composition; unified error handling to return generic 500 response without logging.
Students API Authorization
app/api/students/[id]/route.ts
Added teacherId filter to PUT and DELETE queries coupling authorization to document lookup; added validation rejecting empty sanitized payloads with HTTP 400; simplified JSON parsing with .catch() fallback; unified error handling removing duplicate-key error special handling.
Database Connection Utility
lib/mongodb.ts
Removed explicit MongooseCache interface and declare global typing; replaced with untyped cache via (global as any).mongoose; simplified promise creation by removing .catch() pattern; retained cached.promise reset on error.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 Authorization guards our fields with pride,
Deadlines checked, no future dates inside,
Error handling simplified, clean and bright,
MongoDB cache unburdened, dancing light,
Schema defaults set, validation's song—
A polish pass to keep our API strong!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary changes across all modified files: authorization fixes (teacher ID checks), error handling improvements, validation enhancements (deadline/date checks), and data integrity improvements (default values, sanitization). It is specific and clearly conveys the main focus without being overly detailed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
lib/mongodb.ts (1)

5-6: Replace the untyped global cache with a properly typed version.

Lines 5-6 use (global as any).mongoose, which removes type guarantees for conn and promise. This allows future edits to accidentally store the wrong cache shape. Define a MongooseCache type and narrow globalThis instead to preserve both type safety and current runtime behavior.

♻️ Proposed typed cache refactor
+type MongooseCache = {
+  conn: typeof mongoose | null
+  promise: Promise<typeof mongoose> | null
+}
+
+const globalForMongoose = globalThis as typeof globalThis & {
+  mongoose?: MongooseCache
+}
+
-let cached = (global as any).mongoose
-if (!cached) cached = (global as any).mongoose = { conn: null, promise: null }
+const cached =
+  globalForMongoose.mongoose ??
+  (globalForMongoose.mongoose = { conn: null, promise: null })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/mongodb.ts` around lines 5 - 6, Replace the untyped global cache usage by
declaring a MongooseCache type (with conn and promise fields) and narrow
globalThis to that shape before using it: create a MongooseCache interface/type,
assert or augment globalThis as { mongoose?: MongooseCache }, then read/write
the cache via globalThis.mongoose instead of (global as any).mongoose so cached,
conn, and promise are properly typed; update the existing variables (cached,
conn, promise) to use the new MongooseCache type and preserve the current
runtime initialization logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/assignments/`[id]/route.ts:
- Around line 14-20: The update handler currently assumes req.json() returns an
object and uses the `in` operator on `body`, which throws for non-object values;
fix by ensuring `body` is an object (e.g., check typeof body === 'object' &&
body !== null) before iterating `ALLOWED_FIELDS` to build `sanitized`; also
enforce the same `deadline` validation used in the POST flow (reject `deadline`
if it's a past date) when `sanitized.deadline` is present; finally, call
`Assignment.findOneAndUpdate` with the option `runValidators: true` (alongside
`{ new: true }`) so Mongoose schema validators are applied during updates.

In `@app/api/assignments/route.ts`:
- Around line 69-71: The deadline validation rejects same-day ISO date strings
and lets invalid dates through; update the schema to use z.coerce.date() for
parsed.data.deadline so invalid/malformed dates fail validation, and change the
comparison in the route (the block checking parsed.data.deadline) to compare
date-only ISO strings (e.g., deadline.toISOString().slice(0,10) >= new
Date().toISOString().slice(0,10)) instead of comparing Date objects directly so
a "YYYY-MM-DD" deadline for today is accepted; ensure you return the existing
400 error for invalid/parsed failures.

In `@app/api/attendance/route.ts`:
- Line 15: The POST-only route removed the exported GET handler that four
consumers (StudentsClient.tsx, AttendanceClient.tsx twice for history and
heatmap, and OverviewClient.tsx for dashboard stats) still call; restore an
exported async function GET(req: NextRequest) that performs the attendance read
logic (parsing query params like studentId/date/range as the clients expect),
invokes the same attendance retrieval service/DB used elsewhere, and returns a
proper NextResponse/JSON payload with status codes and CORS/headers consistent
with POST so those components continue to work. Ensure the exported symbol name
is GET and that its response shape matches what StudentsClient.tsx:165,
AttendanceClient.tsx:103/131, and OverviewClient.tsx:198 expect.
- Around line 28-34: The current date validation accepts any non-empty string
and then uses lexicographic comparison in isFuture (used with parsed and
isBulk), which misclassifies datetimes or malformed strings; update the Zod
schema's date field (replace the existing date: z.string().min(1)) to use an ISO
date validator (e.g., z.iso.date() per the review) so only YYYY-MM-DD strings
pass validation, then keep the existing isFuture check (or, optionally, replace
it by parsing validated strings to Date for clarity) so parsed.data dates are
guaranteed to be valid ISO dates before the comparison in isFuture.

In `@app/api/students/`[id]/route.ts:
- Around line 14-20: The request body may be a JSON primitive which makes the
`in` operator throw and also `findOneAndUpdate` is missing validators; after
parsing `req.json()` ensure the body is an object (typeof body === 'object' &&
body !== null && !Array.isArray(body') ) and return a 400 with "No valid fields"
if it's not an object or has no allowed keys; build `sanitizedBody` from
`ALLOWED_UPDATE_FIELDS` only when body is a valid object. Then call
`connectDB()` and update the student using `Student.findOneAndUpdate({ _id: id,
teacherId: userId }, { $set: sanitizedBody }, { new: true, runValidators: true
})` (include runValidators:true so model validation runs).

---

Nitpick comments:
In `@lib/mongodb.ts`:
- Around line 5-6: Replace the untyped global cache usage by declaring a
MongooseCache type (with conn and promise fields) and narrow globalThis to that
shape before using it: create a MongooseCache interface/type, assert or augment
globalThis as { mongoose?: MongooseCache }, then read/write the cache via
globalThis.mongoose instead of (global as any).mongoose so cached, conn, and
promise are properly typed; update the existing variables (cached, conn,
promise) to use the new MongooseCache type and preserve the current runtime
initialization logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6ae80d3-1b4b-4fcd-a630-5dcd4d02c228

📥 Commits

Reviewing files that changed from the base of the PR and between c71cbbf and d902424.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • app/api/assignments/[id]/route.ts
  • app/api/assignments/route.ts
  • app/api/attendance/route.ts
  • app/api/grades/route.ts
  • app/api/students/[id]/route.ts
  • lib/mongodb.ts

Comment on lines +14 to +20
const body = await req.json().catch(() => ({}))
const sanitized: any = {}
ALLOWED_FIELDS.forEach(k => { if (k in body) sanitized[k] = body[k] })
if (Object.keys(sanitized).length === 0) return NextResponse.json({ error: 'No valid fields' }, { status: 400 })

await connectDB()

let body
try {
body = await req.json()
} catch {
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) {
sanitizedBody[key] = body[key]
}
}

const assignment = await Assignment.findOneAndUpdate(
{ _id: id },
sanitizedBody,
{ new: true }
)
if (!assignment) return NextResponse.json({ error: 'Not found' }, { status: 404 })
return NextResponse.json(assignment)
} catch (error) {
if (error instanceof Error) {
console.error('PUT /api/assignments/[id] error:', error.message)
}
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
}
const updated = await Assignment.findOneAndUpdate({ _id: id, teacherId: userId }, { $set: sanitized }, { new: true })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirms primitive JSON bodies can throw during `in` checks, and checks whether validators are enabled on assignment updates.

node - <<'NODE'
for (const value of [null, 1, true, 'text']) {
  try {
    console.log(typeof value, '=>', 'deadline' in value)
  } catch (error) {
    console.log(typeof value, '=> throws:', error.message)
  }
}
NODE

rg -nUP 'Assignment\.findOneAndUpdate\((?s:.*?)runValidators' 'app/api/assignments/[id]/route.ts' || true

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 385


🏁 Script executed:

rg -n "ALLOWED_FIELDS" app/api/assignments/[id]/route.ts

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 292


🏁 Script executed:

cat -n app/api/assignments/[id]/route.ts | head -50

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 2419


🏁 Script executed:

cat -n app/api/assignments/route.ts | head -80

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 3563


Validate assignment update payloads and enforce deadline consistency.

The in operator on line 16 throws when req.json() successfully parses to a non-object value (e.g., null, a string, or a number). Additionally, deadline is allowed in updates without the past-date validation applied in POST /api/assignments (lines 69–72 of route.ts), and findOneAndUpdate omits runValidators: true, bypassing Mongoose schema validation.

Proposed fix
-    const body = await req.json().catch(() => ({}))
-    const sanitized: any = {}
+    const body = await req.json().catch(() => null)
+    if (!body || typeof body !== 'object' || Array.isArray(body)) {
+      return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
+    }
+
+    const sanitized: Record<string, unknown> = {}
     ALLOWED_FIELDS.forEach(k => { if (k in body) sanitized[k] = body[k] })
     if (Object.keys(sanitized).length === 0) return NextResponse.json({ error: 'No valid fields' }, { status: 400 })
+
+    if (typeof sanitized.deadline === 'string') {
+      if (new Date(sanitized.deadline) < new Date()) {
+        return NextResponse.json({ error: 'Deadline cannot be in the past' }, { status: 400 })
+      }
+    }
 
     await connectDB()
-    const updated = await Assignment.findOneAndUpdate({ _id: id, teacherId: userId }, { $set: sanitized }, { new: true })
+    const updated = await Assignment.findOneAndUpdate(
+      { _id: id, teacherId: userId },
+      { $set: sanitized },
+      { new: true, runValidators: true }
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/assignments/`[id]/route.ts around lines 14 - 20, The update handler
currently assumes req.json() returns an object and uses the `in` operator on
`body`, which throws for non-object values; fix by ensuring `body` is an object
(e.g., check typeof body === 'object' && body !== null) before iterating
`ALLOWED_FIELDS` to build `sanitized`; also enforce the same `deadline`
validation used in the POST flow (reject `deadline` if it's a past date) when
`sanitized.deadline` is present; finally, call `Assignment.findOneAndUpdate`
with the option `runValidators: true` (alongside `{ new: true }`) so Mongoose
schema validators are applied during updates.

Comment on lines +69 to +71
// FIX: Deadline validation (Prevent past dates)
if (new Date(parsed.data.deadline) < new Date()) {
return NextResponse.json({ error: 'Deadline cannot be in the past' }, { status: 400 });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Demonstrates why timestamp comparison rejects same-day date-only strings
# and why invalid strings need schema-level validation.

node - <<'NODE'
const today = new Date().toISOString().slice(0, 10)
console.log({ today, parsedAsDate: new Date(today).toISOString(), rejectedByTimestampCompare: new Date(today) < new Date() })

const invalid = new Date('not-a-date')
console.log({ invalidDateIsNaN: Number.isNaN(invalid.getTime()), invalidComparisonResult: invalid < new Date() })
NODE

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 248


🏁 Script executed:

# Search for AssignmentSchema definition and the deadline validation code
cd /repo && find . -type f -name "*.ts" -o -name "*.js" | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 139


🏁 Script executed:

# Look for AssignmentSchema and deadline validation
rg "AssignmentSchema" --type ts

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 419


🏁 Script executed:

# Check the specific file and lines mentioned
cat -n app/api/assignments/route.ts | head -100

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 3563


🏁 Script executed:

# Check if z.date() or similar Zod date validation exists
rg "z\.(date|iso)" --type ts -A 2

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 66


Fix deadline validation to handle date-only strings and invalid dates correctly.

The current validation has two problems:

  1. Same-day deadlines are rejected: new Date('YYYY-MM-DD') parses at midnight UTC, so deadlines with today's date are treated as past times after midnight.
  2. Invalid dates bypass validation: Invalid Date < new Date() returns false, allowing malformed dates to reach the database and cause casting errors.

Use z.coerce.date() in the schema and compare dates as ISO strings:

Proposed fix
 const AssignmentSchema = z.object({
   title: z.string().min(1),
   description: z.string().optional(),
   subject: z.string().min(1),
   class: z.string().min(1),
-  deadline: z.string().min(1),
+  deadline: z.coerce.date(),
   maxMarks: z.number().min(1).default(100),
   status: z.enum(['active', 'closed']).default('active'),
   kanbanStatus: z.enum(['todo', 'in_progress', 'submitted']).default('todo'),
 })
-    // FIX: Deadline validation (Prevent past dates)
-    if (new Date(parsed.data.deadline) < new Date()) {
+    // Deadline validation (compare date-only values)
+    const today = new Date().toISOString().split('T')[0]
+    const deadlineDate = parsed.data.deadline.toISOString().split('T')[0]
+    if (deadlineDate < today) {
       return NextResponse.json({ error: 'Deadline cannot be in the past' }, { status: 400 });
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/assignments/route.ts` around lines 69 - 71, The deadline validation
rejects same-day ISO date strings and lets invalid dates through; update the
schema to use z.coerce.date() for parsed.data.deadline so invalid/malformed
dates fail validation, and change the comparison in the route (the block
checking parsed.data.deadline) to compare date-only ISO strings (e.g.,
deadline.toISOString().slice(0,10) >= new Date().toISOString().slice(0,10))
instead of comparing Date objects directly so a "YYYY-MM-DD" deadline for today
is accepted; ensure you return the existing 400 error for invalid/parsed
failures.

}
}

export async function POST(req: NextRequest) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirms remaining GET consumers and whether the route still exports GET.

rg -n -C2 'fetch\((`|")/api/attendance' 
rg -nP 'export\s+async\s+function\s+GET\b' 'app/api/attendance/route.ts' || true

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 2428


Restore GET /api/attendance before merging.

The route handler no longer exports GET, but four consumers still depend on it: StudentsClient.tsx:165 (student attendance), AttendanceClient.tsx:103 (history), AttendanceClient.tsx:131 (heatmap), and OverviewClient.tsx:198 (dashboard stats). Removing it without providing an alternative will break all attendance read operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/attendance/route.ts` at line 15, The POST-only route removed the
exported GET handler that four consumers (StudentsClient.tsx,
AttendanceClient.tsx twice for history and heatmap, and OverviewClient.tsx for
dashboard stats) still call; restore an exported async function GET(req:
NextRequest) that performs the attendance read logic (parsing query params like
studentId/date/range as the clients expect), invokes the same attendance
retrieval service/DB used elsewhere, and returns a proper NextResponse/JSON
payload with status codes and CORS/headers consistent with POST so those
components continue to work. Ensure the exported symbol name is GET and that its
response shape matches what StudentsClient.tsx:165,
AttendanceClient.tsx:103/131, and OverviewClient.tsx:198 expect.

Comment on lines +28 to +34
const today = new Date().toISOString().split('T')[0]
const data = parsed.data as any

// BUG FIX: Prevent future dating
const isFuture = (d: string) => d > today
if (isBulk ? data.some((r: any) => isFuture(r.date)) : isFuture(data.date)) {
return NextResponse.json({ error: "Cannot mark attendance for future dates" }, { status: 400 })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Demonstrates that raw string comparison rejects same-day datetime strings.

node - <<'NODE'
const today = '2026-04-18'
for (const value of ['2026-04-18', '2026-04-18T12:00:00Z', 'not-a-date']) {
  console.log({ value, isFutureByStringCompare: value > today })
}
NODE

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 259


🏁 Script executed:

cat -n app/api/attendance/route.ts | head -60

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 2633


🏁 Script executed:

# Check Zod version and available date validation methods
fd 'package.json' | head -5 | xargs cat | grep -i zod

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 103


🏁 Script executed:

# Verify if z.string().date() is available in Zod 4.3.6
# Check Zod documentation or examples in the codebase
rg -i "z\.string.*\.date\|z\.date" | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 66


🏁 Script executed:

# Check Zod 4.3.6 API - look for how to validate date strings
# Try searching for common Zod patterns or check if there are examples elsewhere
rg "z\." app/ | grep -i "date\|parse\|refine" | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 271


🏁 Script executed:

# Let's also verify the exact Zod version and its capabilities
# by checking if z.string().date() or similar exists
node <<'EOF'
try {
  const z = require('zod');
  // Check available methods on z
  console.log('z.string() methods:', Object.getOwnPropertyNames(z.ZodString.prototype).filter(m => m.includes('date')));
  // Try to use z.string().date() 
  const schema = z.string().date();
  console.log('z.string().date() exists:', !!schema);
} catch(e) {
  console.log('Error:', e.message);
}
EOF

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 159


🏁 Script executed:

# Search for any date validation patterns already used in the codebase
rg "regex|refine|transform" app/ | grep -i date | head -10

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 66


🏁 Script executed:

# Check if there are any tests or other examples of date validation
fd -e ts -e tsx -e js | xargs grep -l "date" | head -10

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 376


🏁 Script executed:

# Let's check Zod's actual API in version 4.3.6 by looking at its usage patterns
# Search for any validation patterns that might give us clues about date handling
rg "z\.string" app/ -A 2 | head -30

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 1728


🏁 Script executed:

# Check if there's a package-lock.json that might give us exact Zod version info
cat package-lock.json | grep -A 10 '"zod"' | head -20

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 664


🌐 Web query:

Zod 4.3.6 date validation string.date() API

💡 Result:

In Zod 4.3.6, there is no string.date method on z.string. Date validation for strings uses top-level factory functions under the z.iso namespace, such as z.iso.date for ISO date strings in YYYY-MM-DD format. These are regex-based validations for common date formats: - z.iso.date: Validates YYYY-MM-DD format. Example: const date = z.iso.date; date.parse("2020-01-01"); // ✅ date.parse("2020-1-1"); // ❌ date.parse("2020-01-32"); // ❌ - z.iso.datetime: Validates ISO 8601 datetime strings (with options for precision, offset). Example: const datetime = z.iso.datetime; datetime.parse("2020-01-01T06:15:00Z"); // ✅ - z.iso.time: Validates HH:MM[:SS[.s+]] format. To coerce strings to Date objects, use z.coerce.date or z.string.pipe(z.coerce.date). For bidirectional string-to-Date conversion, use z.codec(z.iso.datetime, z.date, { decode: (iso) => new Date(iso), encode: (date) => date.toISOString }). Zod 4.3.6 was released January 4, 2026, and these APIs are unchanged from earlier v4 releases. No z.string.date API exists; date validations are via z.iso.* factory functions, not chainable string methods.

Citations:


Use Zod's ISO date validator to prevent string-comparison vulnerabilities.

The schema accepts any non-empty string for date (line 11), but the future-date check uses lexicographic string comparison (line 32). This causes false positives: '2026-04-18T12:00:00Z' > '2026-04-18' is true (same-day datetime flagged as future), and 'not-a-date' > '2026-04-18' is true (malformed string flagged as future). Attendance records may be incorrectly rejected or accepted.

Replace date: z.string().min(1) with date: z.iso.date() to enforce YYYY-MM-DD format at validation time, ensuring the string comparison logic receives only valid dates.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/attendance/route.ts` around lines 28 - 34, The current date
validation accepts any non-empty string and then uses lexicographic comparison
in isFuture (used with parsed and isBulk), which misclassifies datetimes or
malformed strings; update the Zod schema's date field (replace the existing
date: z.string().min(1)) to use an ISO date validator (e.g., z.iso.date() per
the review) so only YYYY-MM-DD strings pass validation, then keep the existing
isFuture check (or, optionally, replace it by parsing validated strings to Date
for clarity) so parsed.data dates are guaranteed to be valid ISO dates before
the comparison in isFuture.

Comment on lines +14 to +20
const body = await req.json().catch(() => ({}))
const sanitizedBody: any = {}
ALLOWED_UPDATE_FIELDS.forEach(k => { if (k in body) sanitizedBody[k] = body[k] })
if (Object.keys(sanitizedBody).length === 0) return NextResponse.json({ error: 'No valid fields' }, { status: 400 })
await connectDB()
const student = await Student.findOneAndUpdate(
{ _id: id },
sanitizedBody,
{ new: true }
)
if (!student) return NextResponse.json({ error: 'Not found' }, { status: 404 })
const student = await Student.findOneAndUpdate({ _id: id, teacherId: userId }, { $set: sanitizedBody }, { new: true })
if (!student) return NextResponse.json({ error: 'Not found or unauthorized' }, { status: 404 })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirms valid JSON primitives can throw with `in`, and checks whether the route enables Mongoose update validators.

node - <<'NODE'
for (const value of [null, 1, true, 'text']) {
  try {
    // Mirrors: if (k in body)
    console.log(typeof value, '=>', 'name' in value)
  } catch (error) {
    console.log(typeof value, '=> throws:', error.message)
  }
}
NODE

rg -nUP 'Student\.findOneAndUpdate\((?s:.*?)runValidators' 'app/api/students/[id]/route.ts' || true

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 369


🏁 Script executed:

# Check if the file exists and read lines 14-20
if [ -f "app/api/students/[id]/route.ts" ]; then
  echo "=== File exists, reading lines 1-25 for context ==="
  head -25 "app/api/students/[id]/route.ts" | cat -n
else
  echo "File not found at standard path, searching..."
  fd "route.ts" --path "*students*" -x head -25 {} \; -x cat -n {} \;
fi

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 1780


🏁 Script executed:

# Also check the entire file for Student.findOneAndUpdate usage
rg -n "Student\.findOneAndUpdate" app/api/students/

Repository: JavaScript-Mastery-Pro/discord-challenge

Length of output: 240


Reject non-object bodies and run validators on student updates.

The in operator throws when body is a valid JSON primitive (null, number, boolean, or string), causing a 500 error instead of 400 for malformed payloads. Additionally, findOneAndUpdate persists updates without runValidators: true, allowing invalid values in allowed fields to bypass model validation.

🛡️ Proposed fix
-    const body = await req.json().catch(() => ({}))
-    const sanitizedBody: any = {}
+    const body = await req.json().catch(() => null)
+    if (!body || typeof body !== 'object' || Array.isArray(body)) {
+      return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
+    }
+
+    const sanitizedBody: Record<string, unknown> = {}
     ALLOWED_UPDATE_FIELDS.forEach(k => { if (k in body) sanitizedBody[k] = body[k] })
     if (Object.keys(sanitizedBody).length === 0) return NextResponse.json({ error: 'No valid fields' }, { status: 400 })
     await connectDB()
-    const student = await Student.findOneAndUpdate({ _id: id, teacherId: userId }, { $set: sanitizedBody }, { new: true })
+    const student = await Student.findOneAndUpdate(
+      { _id: id, teacherId: userId },
+      { $set: sanitizedBody },
+      { new: true, runValidators: true }
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const body = await req.json().catch(() => ({}))
const sanitizedBody: any = {}
ALLOWED_UPDATE_FIELDS.forEach(k => { if (k in body) sanitizedBody[k] = body[k] })
if (Object.keys(sanitizedBody).length === 0) return NextResponse.json({ error: 'No valid fields' }, { status: 400 })
await connectDB()
const student = await Student.findOneAndUpdate(
{ _id: id },
sanitizedBody,
{ new: true }
)
if (!student) return NextResponse.json({ error: 'Not found' }, { status: 404 })
const student = await Student.findOneAndUpdate({ _id: id, teacherId: userId }, { $set: sanitizedBody }, { new: true })
if (!student) return NextResponse.json({ error: 'Not found or unauthorized' }, { status: 404 })
const body = await req.json().catch(() => null)
if (!body || typeof body !== 'object' || Array.isArray(body)) {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
}
const sanitizedBody: Record<string, unknown> = {}
ALLOWED_UPDATE_FIELDS.forEach(k => { if (k in body) sanitizedBody[k] = body[k] })
if (Object.keys(sanitizedBody).length === 0) return NextResponse.json({ error: 'No valid fields' }, { status: 400 })
await connectDB()
const student = await Student.findOneAndUpdate(
{ _id: id, teacherId: userId },
{ $set: sanitizedBody },
{ new: true, runValidators: true }
)
if (!student) return NextResponse.json({ error: 'Not found or unauthorized' }, { status: 404 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/students/`[id]/route.ts around lines 14 - 20, The request body may be
a JSON primitive which makes the `in` operator throw and also `findOneAndUpdate`
is missing validators; after parsing `req.json()` ensure the body is an object
(typeof body === 'object' && body !== null && !Array.isArray(body') ) and return
a 400 with "No valid fields" if it's not an object or has no allowed keys; build
`sanitizedBody` from `ALLOWED_UPDATE_FIELDS` only when body is a valid object.
Then call `connectDB()` and update the student using `Student.findOneAndUpdate({
_id: id, teacherId: userId }, { $set: sanitizedBody }, { new: true,
runValidators: true })` (include runValidators:true so model validation runs).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant