-
Notifications
You must be signed in to change notification settings - Fork 49
feat: Report tag #419
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: staging
Are you sure you want to change the base?
feat: Report tag #419
Changes from 1 commit
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 | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,140 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| import { NextResponse } from "next/server"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { connectToDatabase } from "@/lib/database/mongoose"; | ||||||||||||||||||||||||||||||||||||||||||||
| import TagReport from "@/db/tagReport"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const ipCounters = new Map<string, { count: number; resetAt: number }>(); | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| const IP_LIMIT = 3; // tesing purpose ->> 3 reports/per IP/ per paper/ per hour | ||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||
| const IP_LIMIT = 3; // tesing purpose ->> 3 reports/per IP/ per paper/ per hour | |
| const IP_LIMIT = 3; // testing purpose ->> 3 reports/per IP/ per paper/ per hour |
Outdated
Copilot
AI
Nov 27, 2025
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.
The getClientIp function can be easily spoofed since it relies on x-forwarded-for and x-real-ip headers which can be manipulated by clients. This undermines the rate limiting mechanism. Consider implementing additional validation or using a more robust IP detection method, especially if not behind a trusted proxy.
| function getClientIp(req: Request) { | |
| const forwarded = req.headers.get("x-forwarded-for"); | |
| if (forwarded) { | |
| const first = forwarded.split(",")[0] ?? forwarded; | |
| return first.trim(); | |
| } | |
| const real = req.headers.get("x-real-ip"); | |
| if (real) return real; | |
| try { | |
| const url = new URL(req.url); | |
| return url.hostname || "127.0.0.1"; | |
| } catch { | |
| return "127.0.0.1"; | |
| } | |
| // WARNING: Cannot reliably determine client IP in this environment. | |
| // The use of x-forwarded-for and x-real-ip headers is insecure unless behind a trusted proxy. | |
| // For secure rate limiting, ensure your deployment is behind a trusted proxy that sets these headers, | |
| // and use a server adapter that exposes the remote address (e.g., req.socket.remoteAddress). | |
| // Here, we return a static value to avoid spoofing. | |
| function getClientIp(req: Request) { | |
| return "unknown"; |
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.
instead of using "x-forwarded-for" and "x-real-ip"(which can be spoofed) --> req.ip is being used to get the actual ip
Additionally
added a per-paper report limit, so even if someone tries to bypass the IP limit, each paper can only receive a small fixed number of reports.
Outdated
Copilot
AI
Nov 27, 2025
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.
The year validation logic is incomplete. After checking the year range format, the code continues to the next iteration but doesn't validate single year formats (e.g., "2024"). A single year should also be validated to ensure it falls within the 1900-2100 range. Add validation for single year format after the range check.
| } | |
| } | |
| // Validate single year format | |
| const singleYearMatch = val.match(/^(\d{4})$/); | |
| if (singleYearMatch) { | |
| const year = Number(singleYearMatch[1]); | |
| if (year < 1900 || year > 2100) { | |
| return NextResponse.json( | |
| { error: `Invalid year: ${rf.value}` }, | |
| { status: 400 } | |
| ); | |
| } | |
| continue; | |
| } | |
| // If not a valid range or single year, reject | |
| return NextResponse.json( | |
| { error: `Invalid year format: ${rf.value}` }, | |
| { status: 400 } | |
| ); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||
| "use client"; | ||||||
|
|
||||||
| import { useState } from "react"; | ||||||
| import { FaFlag } from "react-icons/fa6"; | ||||||
| import { Button } from "./ui/button"; | ||||||
| import ReportTagModal from "./ReportTagModal"; | ||||||
|
|
||||||
| export default function ReportButton({ | ||||||
| paperId, subject, exam, slot, year | ||||||
| }: { | ||||||
| paperId: string; | ||||||
| subject?: string; | ||||||
| exam?: string; | ||||||
| slot?: string; | ||||||
| year?: string; | ||||||
| }) { | ||||||
| const [open, setOpen] = useState(false); | ||||||
|
|
||||||
| return ( | ||||||
| <> | ||||||
| <Button | ||||||
| onClick={() => setOpen(true)} | ||||||
| className="h-10 w-10 rounded p-0 text-white transition hover:bg-red-600 bg-red-500" | ||||||
|
||||||
| className="h-10 w-10 rounded p-0 text-white transition hover:bg-red-600 bg-red-500" | |
| className="h-10 w-10 p-0 rounded bg-red-500 text-white hover:bg-red-600 transition" |
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.
The
ipCountersMap will grow indefinitely and never clear expired entries, causing a memory leak over time. Implement a cleanup mechanism to periodically remove expired entries or use a time-based cache with automatic expiration (e.g., node-cache or lru-cache).