rafmacalaba commited on
Commit
8be778a
·
1 Parent(s): 0151f86

update: reverted local changes

Browse files
app/api/auth/callback/route.js CHANGED
@@ -14,8 +14,11 @@ export async function GET(request) {
14
  return NextResponse.json({ error: 'Missing code parameter' }, { status: 400 });
15
  }
16
 
17
- // Note: state verification skipped — cookies don't survive HF iframe redirects.
18
- // Security is handled by the ALLOWED_USERS allowlist instead.
 
 
 
19
 
20
  const clientId = process.env.OAUTH_CLIENT_ID;
21
  const clientSecret = process.env.OAUTH_CLIENT_SECRET;
@@ -90,7 +93,8 @@ export async function GET(request) {
90
  path: '/',
91
  });
92
 
93
-
 
94
 
95
  return response;
96
  } catch (error) {
 
14
  return NextResponse.json({ error: 'Missing code parameter' }, { status: 400 });
15
  }
16
 
17
+ // Verify state
18
+ const savedState = request.cookies.get('oauth_state')?.value;
19
+ if (!savedState || savedState !== state) {
20
+ return NextResponse.json({ error: 'Invalid state parameter' }, { status: 400 });
21
+ }
22
 
23
  const clientId = process.env.OAUTH_CLIENT_ID;
24
  const clientSecret = process.env.OAUTH_CLIENT_SECRET;
 
93
  path: '/',
94
  });
95
 
96
+ // Clear the state cookie
97
+ response.cookies.delete('oauth_state');
98
 
99
  return response;
100
  } catch (error) {
app/api/auth/login/route.js CHANGED
@@ -1,4 +1,5 @@
1
  import { NextResponse } from 'next/server';
 
2
 
3
  /**
4
  * GET /api/auth/login
@@ -19,14 +20,28 @@ export async function GET(request) {
19
  : 'http://localhost:3000';
20
  const redirectUri = `${host}/api/auth/callback`;
21
 
 
 
 
22
  const params = new URLSearchParams({
23
  client_id: clientId,
24
  redirect_uri: redirectUri,
25
  scope: 'openid profile',
26
  response_type: 'code',
 
27
  });
28
 
29
  const authorizeUrl = `https://huggingface.co/oauth/authorize?${params.toString()}`;
30
 
31
- return NextResponse.redirect(authorizeUrl);
 
 
 
 
 
 
 
 
 
 
32
  }
 
1
  import { NextResponse } from 'next/server';
2
+ import crypto from 'crypto';
3
 
4
  /**
5
  * GET /api/auth/login
 
20
  : 'http://localhost:3000';
21
  const redirectUri = `${host}/api/auth/callback`;
22
 
23
+ // Generate state for CSRF protection
24
+ const state = crypto.randomBytes(16).toString('hex');
25
+
26
  const params = new URLSearchParams({
27
  client_id: clientId,
28
  redirect_uri: redirectUri,
29
  scope: 'openid profile',
30
  response_type: 'code',
31
+ state: state,
32
  });
33
 
34
  const authorizeUrl = `https://huggingface.co/oauth/authorize?${params.toString()}`;
35
 
36
+ // Set state in a cookie for verification on callback
37
+ const response = NextResponse.redirect(authorizeUrl);
38
+ response.cookies.set('oauth_state', state, {
39
+ httpOnly: true,
40
+ secure: true,
41
+ sameSite: 'lax',
42
+ maxAge: 300, // 5 minutes
43
+ path: '/',
44
+ });
45
+
46
+ return response;
47
  }
app/api/documents/route.js CHANGED
@@ -1,71 +1,7 @@
1
  import { HF_DATASET_BASE_URL, MAX_DOCS_TO_SCAN } from '../../../utils/config.js';
2
 
3
- const isHFSpace = () => process.env.HF_TOKEN && process.env.NODE_ENV !== 'development';
4
-
5
- /**
6
- * Load annotator assignments config.
7
- * Supports both local file and HF fetch.
8
- */
9
- async function loadAssignments() {
10
  try {
11
- if (isHFSpace()) {
12
- const url = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/annotator_assignments.json`;
13
- const res = await fetch(url, {
14
- headers: { 'Authorization': `Bearer ${process.env.HF_TOKEN}` },
15
- });
16
- if (res.ok) return await res.json();
17
- } else {
18
- const fs = await import('fs');
19
- const path = await import('path');
20
- const filePath = path.default.join(process.cwd(), 'annotator_assignments.json');
21
- if (fs.default.existsSync(filePath)) {
22
- return JSON.parse(fs.default.readFileSync(filePath, 'utf-8'));
23
- }
24
- }
25
- } catch (e) {
26
- console.warn('Could not load annotator assignments:', e.message);
27
- }
28
- return null;
29
- }
30
-
31
- /**
32
- * Get the set of allowed doc indices for a user.
33
- * Returns null if no assignments (= show all).
34
- */
35
- function getAllowedDocs(assignments, username) {
36
- if (!assignments || !username) return null;
37
-
38
- const userConfig = assignments[username] || assignments[username.toLowerCase()];
39
- if (!userConfig) return null;
40
-
41
- const allowed = new Set();
42
-
43
- // Explicit list: "docs": [1, 2, 3]
44
- if (userConfig.docs) {
45
- userConfig.docs.forEach(d => allowed.add(d));
46
- }
47
-
48
- // Range: "docs_range": [10, 100] (inclusive)
49
- if (userConfig.docs_range) {
50
- const [start, end] = userConfig.docs_range;
51
- for (let i = start; i <= end; i++) {
52
- allowed.add(i);
53
- }
54
- }
55
-
56
- return allowed.size > 0 ? allowed : null;
57
- }
58
-
59
- export async function GET(request) {
60
- try {
61
- // Get username from query param
62
- const { searchParams } = new URL(request.url);
63
- const username = searchParams.get('user');
64
-
65
- // Load assignments
66
- const assignments = await loadAssignments();
67
- const allowedDocs = getAllowedDocs(assignments, username);
68
-
69
  // Fetch the index file from HF Datasets
70
  const linksUrl = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/wbg_data/wbg_pdf_links.json`;
71
  const linksRes = await fetch(linksUrl, {
@@ -85,18 +21,10 @@ export async function GET(request) {
85
 
86
  const links = await linksRes.json();
87
 
88
- // Filter to successful links
89
- let successLinks = links.filter(l => l.status === 'success');
90
-
91
- // If user has assignments, filter to allowed docs only
92
- if (allowedDocs) {
93
- successLinks = successLinks.filter(l => allowedDocs.has(l.index));
94
- } else {
95
- // No assignments — take first N
96
- successLinks = successLinks.slice(0, MAX_DOCS_TO_SCAN);
97
- }
98
 
99
- // Parallel fetch
100
  const results = await Promise.allSettled(
101
  successLinks.map(async (link) => {
102
  const docUrl = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/wbg_extractions/doc_${link.index}/raw/doc_${link.index}_direct_judged.jsonl`;
@@ -130,7 +58,7 @@ export async function GET(request) {
130
  status: 200,
131
  headers: {
132
  'Content-Type': 'application/json',
133
- 'Cache-Control': 'no-store'
134
  }
135
  });
136
  } catch (error) {
 
1
  import { HF_DATASET_BASE_URL, MAX_DOCS_TO_SCAN } from '../../../utils/config.js';
2
 
3
+ export async function GET() {
 
 
 
 
 
 
4
  try {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  // Fetch the index file from HF Datasets
6
  const linksUrl = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/wbg_data/wbg_pdf_links.json`;
7
  const linksRes = await fetch(linksUrl, {
 
21
 
22
  const links = await linksRes.json();
23
 
24
+ // Filter to successful links and take the first N
25
+ const successLinks = links.filter(l => l.status === 'success').slice(0, MAX_DOCS_TO_SCAN);
 
 
 
 
 
 
 
 
26
 
27
+ // Parallel fetch — much faster than sequential scanning
28
  const results = await Promise.allSettled(
29
  successLinks.map(async (link) => {
30
  const docUrl = `${HF_DATASET_BASE_URL}/raw/main/annotation_data/wbg_extractions/doc_${link.index}/raw/doc_${link.index}_direct_judged.jsonl`;
 
58
  status: 200,
59
  headers: {
60
  'Content-Type': 'application/json',
61
+ 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=59'
62
  }
63
  });
64
  } catch (error) {
app/globals.css CHANGED
@@ -94,55 +94,6 @@ h4 {
94
  background: var(--accent-hover);
95
  }
96
 
97
- /* ── Login Gate ───────────────────────────────── */
98
-
99
- .login-gate {
100
- display: flex;
101
- align-items: center;
102
- justify-content: center;
103
- width: 100%;
104
- height: 100vh;
105
- background: var(--bg-color);
106
- }
107
-
108
- .login-card {
109
- text-align: center;
110
- background: var(--pane-bg);
111
- border: 1px solid var(--border-color);
112
- border-radius: 16px;
113
- padding: 48px 40px;
114
- max-width: 420px;
115
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
116
- }
117
-
118
- .login-card h1 {
119
- font-size: 1.6rem;
120
- margin-bottom: 12px;
121
- }
122
-
123
- .login-card p {
124
- color: #94a3b8;
125
- margin-bottom: 28px;
126
- line-height: 1.5;
127
- }
128
-
129
- .btn-login-large {
130
- display: inline-block;
131
- font-size: 1rem;
132
- font-weight: 700;
133
- color: #fff;
134
- background: var(--accent);
135
- padding: 12px 28px;
136
- border-radius: 10px;
137
- text-decoration: none;
138
- transition: background 0.2s, transform 0.1s;
139
- }
140
-
141
- .btn-login-large:hover {
142
- background: var(--accent-hover);
143
- transform: translateY(-1px);
144
- }
145
-
146
  .container {
147
  display: flex;
148
  width: 100%;
 
94
  background: var(--accent-hover);
95
  }
96
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  .container {
98
  display: flex;
99
  width: 100%;
app/page.js CHANGED
@@ -43,33 +43,9 @@ export default function Home() {
43
  const annotatablePages = currentDoc?.annotatable_pages ?? [];
44
  const currentPageNumber = annotatablePages[pageIdx] ?? null;
45
 
46
- // Read HF OAuth cookie and load assigned documents
47
  useEffect(() => {
48
- // 1. Read username from cookie
49
- let username = '';
50
- try {
51
- const cookie = document.cookie
52
- .split('; ')
53
- .find(c => c.startsWith('hf_user='));
54
- if (cookie) {
55
- const user = JSON.parse(decodeURIComponent(cookie.split('=').slice(1).join('=')));
56
- if (user.username) {
57
- username = user.username;
58
- setAnnotatorName(user.username);
59
- }
60
- }
61
- } catch (e) {
62
- console.warn('Could not read hf_user cookie', e);
63
- }
64
-
65
- // Not signed in — don't fetch anything, show login screen
66
- if (!username) {
67
- setLoading(false);
68
- return;
69
- }
70
-
71
- // 2. Fetch documents (filtered by user)
72
- fetch(`/api/documents?user=${encodeURIComponent(username)}`)
73
  .then(res => res.json())
74
  .then(data => {
75
  setDocuments(data);
@@ -85,6 +61,23 @@ export default function Home() {
85
  });
86
  }, []);
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  // Update currentDoc when selection changes
89
  useEffect(() => {
90
  if (selectedDocIndex !== null) {
@@ -360,21 +353,6 @@ export default function Home() {
360
  );
361
  }
362
 
363
- // Gate: require login
364
- if (!annotatorName) {
365
- return (
366
- <div className="login-gate">
367
- <div className="login-card">
368
- <h1>🏷️ Annotation Tool</h1>
369
- <p>Sign in with your Hugging Face account to start annotating.</p>
370
- <a href="/api/auth/login" className="btn btn-login-large">
371
- 🤗 Sign in with Hugging Face
372
- </a>
373
- </div>
374
- </div>
375
- );
376
- }
377
-
378
  return (
379
  <div className="app-wrapper">
380
  {/* Top bar with user identity */}
 
43
  const annotatablePages = currentDoc?.annotatable_pages ?? [];
44
  const currentPageNumber = annotatablePages[pageIdx] ?? null;
45
 
46
+ // Load documents on mount
47
  useEffect(() => {
48
+ fetch('/api/documents')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  .then(res => res.json())
50
  .then(data => {
51
  setDocuments(data);
 
61
  });
62
  }, []);
63
 
64
+ // Read HF OAuth cookie for annotator identity
65
+ useEffect(() => {
66
+ try {
67
+ const cookie = document.cookie
68
+ .split('; ')
69
+ .find(c => c.startsWith('hf_user='));
70
+ if (cookie) {
71
+ const user = JSON.parse(decodeURIComponent(cookie.split('=').slice(1).join('=')));
72
+ if (user.username) {
73
+ setAnnotatorName(user.username);
74
+ }
75
+ }
76
+ } catch (e) {
77
+ console.warn('Could not read hf_user cookie', e);
78
+ }
79
+ }, []);
80
+
81
  // Update currentDoc when selection changes
82
  useEffect(() => {
83
  if (selectedDocIndex !== null) {
 
353
  );
354
  }
355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  return (
357
  <div className="app-wrapper">
358
  {/* Top bar with user identity */}