anky2002 commited on
Commit
ae80634
·
1 Parent(s): b8e7113

feat: add user authentication checks and improve navigation with logout functionality

Browse files
web/app/api/analyze/route.ts CHANGED
@@ -1,11 +1,19 @@
1
  import { NextRequest, NextResponse } from "next/server";
 
2
 
3
  const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
 
 
 
 
 
 
 
7
  const body = await req.json();
8
- const { text } = body;
9
 
10
  if (!text || typeof text !== "string" || text.trim().length < 50) {
11
  return NextResponse.json(
@@ -13,6 +21,30 @@ export async function POST(req: NextRequest) {
13
  { status: 400 }
14
  );
15
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
  // Step 1: Submit to Gradio Space
18
  const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/_analysis_and_index`, {
 
1
  import { NextRequest, NextResponse } from "next/server";
2
+ import { createClient } from "@/lib/supabase/server";
3
 
4
  const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
5
 
6
  export async function POST(req: NextRequest) {
7
  try {
8
+ const supabase = await createClient();
9
+ const { data: { user } } = await supabase.auth.getUser();
10
+
11
+ if (!user) {
12
+ return NextResponse.json({ error: "Unauthorized. Please log in to analyze texts." }, { status: 401 });
13
+ }
14
+
15
  const body = await req.json();
16
+ let { text } = body;
17
 
18
  if (!text || typeof text !== "string" || text.trim().length < 50) {
19
  return NextResponse.json(
 
21
  { status: 400 }
22
  );
23
  }
24
+
25
+ // Check scan limits
26
+ const { data: profile } = await supabase
27
+ .from("profiles")
28
+ .select("plan, role")
29
+ .eq("id", user.id)
30
+ .single();
31
+
32
+ const isAdmin = profile?.role === "admin";
33
+ const plan = profile?.plan || "free";
34
+
35
+ const { count: scanCount } = await supabase
36
+ .from("analysis_history")
37
+ .select("*", { count: "exact", head: true })
38
+ .gte("created_at", new Date(new Date().getFullYear(), new Date().getMonth(), 1).toISOString())
39
+ .eq("user_id", user.id);
40
+
41
+ const limit = isAdmin ? 999999 : plan === "free" ? 10 : 999999;
42
+ if ((scanCount ?? 0) >= limit) {
43
+ return NextResponse.json({ error: "Monthly scan limit reached. Please upgrade to premium." }, { status: 403 });
44
+ }
45
+
46
+ // Sanitize basic HTML tags if any to prevent XSS down the line
47
+ text = text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
48
 
49
  // Step 1: Submit to Gradio Space
50
  const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/_analysis_and_index`, {
web/app/api/chat/route.ts CHANGED
@@ -1,9 +1,17 @@
1
  import { NextRequest, NextResponse } from "next/server";
 
2
 
3
  const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
 
 
 
 
 
 
 
7
  const body = await req.json();
8
  const { message, history } = body;
9
 
 
1
  import { NextRequest, NextResponse } from "next/server";
2
+ import { createClient } from "@/lib/supabase/server";
3
 
4
  const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
5
 
6
  export async function POST(req: NextRequest) {
7
  try {
8
+ const supabase = await createClient();
9
+ const { data: { user } } = await supabase.auth.getUser();
10
+
11
+ if (!user) {
12
+ return NextResponse.json({ error: "Unauthorized. Please log in." }, { status: 401 });
13
+ }
14
+
15
  const body = await req.json();
16
  const { message, history } = body;
17
 
web/app/api/compare/route.ts CHANGED
@@ -1,11 +1,19 @@
1
  import { NextRequest, NextResponse } from "next/server";
 
2
 
3
  const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
4
 
5
  export async function POST(req: NextRequest) {
6
  try {
 
 
 
 
 
 
 
7
  const body = await req.json();
8
- const { text_a, text_b } = body;
9
 
10
  if (!text_a || !text_b || text_a.trim().length < 50 || text_b.trim().length < 50) {
11
  return NextResponse.json(
@@ -13,6 +21,10 @@ export async function POST(req: NextRequest) {
13
  { status: 400 }
14
  );
15
  }
 
 
 
 
16
 
17
  // Call Gradio Space API
18
  const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/run_comparison`, {
 
1
  import { NextRequest, NextResponse } from "next/server";
2
+ import { createClient } from "@/lib/supabase/server";
3
 
4
  const GRADIO_URL = process.env.CLAUSEGUARD_GRADIO_URL || "https://gaurv007-clauseguard.hf.space";
5
 
6
  export async function POST(req: NextRequest) {
7
  try {
8
+ const supabase = await createClient();
9
+ const { data: { user } } = await supabase.auth.getUser();
10
+
11
+ if (!user) {
12
+ return NextResponse.json({ error: "Unauthorized. Please log in." }, { status: 401 });
13
+ }
14
+
15
  const body = await req.json();
16
+ let { text_a, text_b } = body;
17
 
18
  if (!text_a || !text_b || text_a.trim().length < 50 || text_b.trim().length < 50) {
19
  return NextResponse.json(
 
21
  { status: 400 }
22
  );
23
  }
24
+
25
+ // Sanitize basically
26
+ text_a = text_a.replace(/</g, "&lt;").replace(/>/g, "&gt;");
27
+ text_b = text_b.replace(/</g, "&lt;").replace(/>/g, "&gt;");
28
 
29
  // Call Gradio Space API
30
  const submitRes = await fetch(`${GRADIO_URL}/gradio_api/call/run_comparison`, {
web/app/api/parse-upload/route.ts CHANGED
@@ -1,9 +1,20 @@
1
  import { NextRequest, NextResponse } from "next/server";
 
2
 
3
  export const runtime = "nodejs";
4
 
 
 
 
5
  export async function POST(req: NextRequest) {
6
  try {
 
 
 
 
 
 
 
7
  const formData = await req.formData();
8
  const file = formData.get("file") as File | null;
9
 
@@ -11,13 +22,20 @@ export async function POST(req: NextRequest) {
11
  return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
12
  }
13
 
 
 
 
 
14
  const name = file.name.toLowerCase();
15
  const buffer = Buffer.from(await file.arrayBuffer());
16
  let text = "";
17
 
18
- if (name.endsWith(".txt") || name.endsWith(".md")) {
 
 
 
19
  text = new TextDecoder().decode(buffer);
20
- } else if (name.endsWith(".pdf")) {
21
  // pdf-parse v2
22
  await import("pdf-parse/worker");
23
  const { PDFParse } = await import("pdf-parse");
 
1
  import { NextRequest, NextResponse } from "next/server";
2
+ import { createClient } from "@/lib/supabase/server";
3
 
4
  export const runtime = "nodejs";
5
 
6
+ // Add a 5MB size limit
7
+ const MAX_FILE_SIZE = 5 * 1024 * 1024;
8
+
9
  export async function POST(req: NextRequest) {
10
  try {
11
+ const supabase = await createClient();
12
+ const { data: { user } } = await supabase.auth.getUser();
13
+
14
+ if (!user) {
15
+ return NextResponse.json({ error: "Unauthorized. Please log in." }, { status: 401 });
16
+ }
17
+
18
  const formData = await req.formData();
19
  const file = formData.get("file") as File | null;
20
 
 
22
  return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
23
  }
24
 
25
+ if (file.size > MAX_FILE_SIZE) {
26
+ return NextResponse.json({ error: "File exceeds 5MB size limit" }, { status: 400 });
27
+ }
28
+
29
  const name = file.name.toLowerCase();
30
  const buffer = Buffer.from(await file.arrayBuffer());
31
  let text = "";
32
 
33
+ // Validate MIME types alongside extension
34
+ const mimeType = file.type;
35
+
36
+ if ((name.endsWith(".txt") || name.endsWith(".md")) && (mimeType.includes("text/plain") || mimeType.includes("text/markdown"))) {
37
  text = new TextDecoder().decode(buffer);
38
+ } else if (name.endsWith(".pdf") && mimeType === "application/pdf") {
39
  // pdf-parse v2
40
  await import("pdf-parse/worker");
41
  const { PDFParse } = await import("pdf-parse");
web/app/auth/callback/route.ts CHANGED
@@ -4,9 +4,14 @@ import { NextResponse } from "next/server";
4
  export async function GET(request: Request) {
5
  const requestUrl = new URL(request.url);
6
  const code = requestUrl.searchParams.get("code");
7
- const next = requestUrl.searchParams.get("next") || "/dashboard-pages/dashboard";
8
  const origin = requestUrl.origin;
9
 
 
 
 
 
 
10
  if (code) {
11
  const supabase = await createClient();
12
  const { error } = await supabase.auth.exchangeCodeForSession(code);
 
4
  export async function GET(request: Request) {
5
  const requestUrl = new URL(request.url);
6
  const code = requestUrl.searchParams.get("code");
7
+ let next = requestUrl.searchParams.get("next") || "/dashboard-pages/dashboard";
8
  const origin = requestUrl.origin;
9
 
10
+ // Prevent open redirect
11
+ if (next && !next.startsWith("/")) {
12
+ next = "/dashboard-pages/dashboard";
13
+ }
14
+
15
  if (code) {
16
  const supabase = await createClient();
17
  const { error } = await supabase.auth.exchangeCodeForSession(code);
web/app/auth/login/page.tsx CHANGED
@@ -21,16 +21,16 @@ function LoginForm() {
21
  // Check if already logged in — redirect immediately
22
  useEffect(() => {
23
  supabase.auth.getUser().then(({ data: { user } }) => {
24
- if (user) { window.location.href = next; }
25
  else { setChecking(false); }
26
  });
27
- }, [next, supabase.auth]);
28
 
29
  async function handleLogin(e: React.FormEvent) {
30
  e.preventDefault(); setLoading(true); setError("");
31
  const { error } = await supabase.auth.signInWithPassword({ email, password });
32
  if (error) { setError(error.message); setLoading(false); }
33
- else { window.location.href = next; }
34
  }
35
 
36
  async function handleMagicLink() {
 
21
  // Check if already logged in — redirect immediately
22
  useEffect(() => {
23
  supabase.auth.getUser().then(({ data: { user } }) => {
24
+ if (user) { router.push(next); }
25
  else { setChecking(false); }
26
  });
27
+ }, [next, supabase.auth, router]);
28
 
29
  async function handleLogin(e: React.FormEvent) {
30
  e.preventDefault(); setLoading(true); setError("");
31
  const { error } = await supabase.auth.signInWithPassword({ email, password });
32
  if (error) { setError(error.message); setLoading(false); }
33
+ else { router.push(next); }
34
  }
35
 
36
  async function handleMagicLink() {
web/app/auth/signup/page.tsx CHANGED
@@ -18,10 +18,10 @@ export default function SignupPage() {
18
  // Redirect if already logged in
19
  useEffect(() => {
20
  supabase.auth.getUser().then(({ data: { user } }) => {
21
- if (user) { window.location.href = "/dashboard-pages/dashboard"; }
22
  else { setChecking(false); }
23
  });
24
- }, []);
25
 
26
  async function handleSignup(e: React.FormEvent) {
27
  e.preventDefault(); setLoading(true); setError("");
 
18
  // Redirect if already logged in
19
  useEffect(() => {
20
  supabase.auth.getUser().then(({ data: { user } }) => {
21
+ if (user) { router.push("/dashboard-pages/dashboard"); }
22
  else { setChecking(false); }
23
  });
24
+ }, [router, supabase.auth]);
25
 
26
  async function handleSignup(e: React.FormEvent) {
27
  e.preventDefault(); setLoading(true); setError("");
web/components/nav.tsx CHANGED
@@ -119,6 +119,15 @@ export function Nav() {
119
  }`}>
120
  <Settings className="w-3.5 h-3.5" /> Settings
121
  </Link>
 
 
 
 
 
 
 
 
 
122
  <Link href="/dashboard-pages/analyze"
123
  className="ml-1 px-3 py-1.5 text-[13px] font-medium text-white bg-zinc-900 rounded-md hover:bg-zinc-800 transition-colors flex items-center gap-1.5">
124
  <Zap className="w-3.5 h-3.5" /> New scan
@@ -188,10 +197,24 @@ export function Nav() {
188
 
189
  {/* Auth actions */}
190
  {isLoggedIn ? (
191
- <Link href="/dashboard-pages/analyze" onClick={() => setOpen(false)}
192
- className="block px-3 py-2.5 text-sm font-medium text-white bg-zinc-900 rounded-lg text-center hover:bg-zinc-800">
193
- New Scan
194
- </Link>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  ) : (
196
  <>
197
  <Link href="/auth/login" onClick={() => setOpen(false)}
 
119
  }`}>
120
  <Settings className="w-3.5 h-3.5" /> Settings
121
  </Link>
122
+ <button onClick={async () => {
123
+ const supabase = createClient();
124
+ await supabase.auth.signOut();
125
+ setUserEmail(null);
126
+ setUserRole(null);
127
+ window.location.href = "/";
128
+ }} className="flex items-center gap-1.5 px-2.5 py-1.5 text-[13px] text-zinc-500 hover:text-zinc-900 rounded-md hover:bg-zinc-50 transition-colors">
129
+ Log out
130
+ </button>
131
  <Link href="/dashboard-pages/analyze"
132
  className="ml-1 px-3 py-1.5 text-[13px] font-medium text-white bg-zinc-900 rounded-md hover:bg-zinc-800 transition-colors flex items-center gap-1.5">
133
  <Zap className="w-3.5 h-3.5" /> New scan
 
197
 
198
  {/* Auth actions */}
199
  {isLoggedIn ? (
200
+ <>
201
+ <Link href="/dashboard-pages/analyze" onClick={() => setOpen(false)}
202
+ className="block px-3 py-2.5 text-sm font-medium text-white bg-zinc-900 rounded-lg text-center hover:bg-zinc-800 mb-1">
203
+ New Scan
204
+ </Link>
205
+ <button
206
+ onClick={async () => {
207
+ setOpen(false);
208
+ const supabase = createClient();
209
+ await supabase.auth.signOut();
210
+ setUserEmail(null);
211
+ setUserRole(null);
212
+ window.location.href = "/";
213
+ }}
214
+ className="w-full text-left flex items-center gap-2.5 px-3 py-2.5 text-sm text-zinc-600 rounded-md hover:bg-zinc-50">
215
+ Log out
216
+ </button>
217
+ </>
218
  ) : (
219
  <>
220
  <Link href="/auth/login" onClick={() => setOpen(false)}