Skip to content

Commit 47d6c00

Browse files
committed
refactor(worker): use env secret for API key, enhance R2 analytics fetch, and add error diagnostics
1 parent db818fd commit 47d6c00

File tree

3 files changed

+79
-18
lines changed

3 files changed

+79
-18
lines changed

analytics-dashboard/src/App.js

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,50 @@ function App() {
77
const [isAuthenticated, setIsAuthenticated] = useState(false);
88

99
useEffect(() => {
10-
// Simple authentication check
11-
if (apiKey && apiKey.length > 10) {
12-
setIsAuthenticated(true);
10+
// Test stored API key by making a quick API call
11+
if (apiKey) {
12+
fetch('https://analytics.ujjwalvivek.com/api?range=1d', {
13+
headers: {
14+
'Authorization': `Bearer ${apiKey}`,
15+
},
16+
})
17+
.then(response => {
18+
if (response.ok) {
19+
setIsAuthenticated(true);
20+
} else {
21+
// Invalid stored key, clear it
22+
localStorage.removeItem('analytics_api_key');
23+
setApiKey('');
24+
}
25+
})
26+
.catch(() => {
27+
// Network error, assume key is valid for now
28+
setIsAuthenticated(true);
29+
});
1330
}
1431
}, [apiKey]);
1532

16-
const handleLogin = (e) => {
33+
const handleLogin = async (e) => {
1734
e.preventDefault();
1835
const inputKey = e.target.apiKey.value;
1936

20-
// Simple validation - in production, validate against your API
21-
if (inputKey && inputKey.length > 10) {
22-
localStorage.setItem('analytics_api_key', inputKey);
23-
setApiKey(inputKey);
24-
setIsAuthenticated(true);
25-
} else {
26-
alert('Invalid API key');
37+
// Test the API key by making a request to the analytics API
38+
try {
39+
const response = await fetch('https://analytics.ujjwalvivek.com/api?range=1d', {
40+
headers: {
41+
'Authorization': `Bearer ${inputKey}`,
42+
},
43+
});
44+
45+
if (response.ok) {
46+
localStorage.setItem('analytics_api_key', inputKey);
47+
setApiKey(inputKey);
48+
setIsAuthenticated(true);
49+
} else {
50+
alert('Invalid API key. Access denied.');
51+
}
52+
} catch (error) {
53+
alert('Failed to validate API key. Please check your connection.');
2754
}
2855
};
2956

analytics-worker/src/worker.js

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
const corsHeaders = {
66
'Access-Control-Allow-Origin': 'https://analytics.ujjwalvivek.com',
77
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
8-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
8+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-API-Key',
99
'Access-Control-Max-Age': '86400',
1010
};
1111

@@ -39,6 +39,21 @@ function validateEvent(event) {
3939
return event;
4040
}
4141

42+
// Validate API key for dashboard access
43+
function validateApiKey(request, env) {
44+
const authHeader = request.headers.get('Authorization');
45+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
46+
return false;
47+
}
48+
49+
const apiKey = authHeader.substring(7); // Remove "Bearer " prefix
50+
51+
// Use environment variable for API key (set in Cloudflare dashboard)
52+
const validApiKey = env.ANALYTICS_API_KEY;
53+
54+
return apiKey === validApiKey;
55+
}
56+
4257
// Store event in R2 bucket (organized by date)
4358
async function storeEvent(event, env) {
4459
const date = new Date(event.timestamp);
@@ -81,6 +96,14 @@ async function isRateLimited(hashedIP, env) {
8196

8297
async function handleDashboardData(request, env) {
8398
try {
99+
// Validate API key for dashboard access
100+
if (!validateApiKey(request, env)) {
101+
return new Response('Unauthorized', {
102+
status: 401,
103+
headers: corsHeaders
104+
});
105+
}
106+
84107
// Get query parameters
85108
const url = new URL(request.url);
86109
const range = url.searchParams.get('range') || '7d';
@@ -113,7 +136,12 @@ async function handleDashboardData(request, env) {
113136
});
114137
} catch (error) {
115138
console.error('Dashboard data error:', error);
116-
return new Response(JSON.stringify({ error: 'Failed to fetch analytics data' }), {
139+
console.error('Error stack:', error.stack);
140+
return new Response(JSON.stringify({
141+
error: 'Failed to fetch analytics data',
142+
details: error.message,
143+
stack: error.stack
144+
}), {
117145
status: 500,
118146
headers: {
119147
...corsHeaders,
@@ -124,6 +152,7 @@ async function handleDashboardData(request, env) {
124152
}
125153

126154
async function getAnalyticsFromR2(env, startDate, endDate) {
155+
console.log('Getting analytics data from R2...');
127156
// For now, return minimal real data structure
128157
// You can expand this to actually read from R2 later
129158
const events = [];
@@ -132,8 +161,10 @@ async function getAnalyticsFromR2(env, startDate, endDate) {
132161
try {
133162
const today = new Date().toISOString().split('T')[0];
134163
const prefix = `analytics/${today}/`;
164+
console.log('Listing R2 objects with prefix:', prefix);
135165

136166
const list = await env.analytics_data.list({ prefix, limit: 100 });
167+
console.log('Found objects:', list.objects.length);
137168

138169
for (const object of list.objects) {
139170
try {
@@ -144,12 +175,13 @@ async function getAnalyticsFromR2(env, startDate, endDate) {
144175
events.push(event);
145176
}
146177
} catch (e) {
178+
console.error('Error parsing event:', e);
147179
// Skip invalid events
148180
continue;
149181
}
150182
}
151183
} catch (error) {
152-
console.log('No analytics data found yet');
184+
console.error('Error reading from R2:', error);
153185
}
154186

155187
// Process events into dashboard data
@@ -235,14 +267,14 @@ const worker = {
235267
return new Response(null, { headers: corsHeaders });
236268
}
237269

238-
// Route handling
270+
// Route handling for analytics.ujjwalvivek.com/api
239271
if (url.pathname === '/api' && request.method === 'POST') {
240272
// Analytics data collection endpoint
241273
return handleAnalyticsCollection(request, env);
242274
} else if (url.pathname === '/api' && request.method === 'GET') {
243275
// Dashboard data endpoint
244276
return handleDashboardData(request, env);
245-
} else if (url.pathname === '/dashboard' && request.method === 'GET') {
277+
} else if (url.pathname === '/api/dashboard' && request.method === 'GET') {
246278
// Dashboard data endpoint (future feature)
247279
return new Response('Dashboard API endpoint', { headers: corsHeaders });
248280
} else {

analytics-worker/wrangler.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,7 @@ binding = "ANALYTICS_KV"
1111
id = "2c6d5729ab6649d5b8d4da134c761317"
1212
preview_id = "f37f9d7d47244545b51afe1e0cd6069f"
1313

14-
# Single route for both dev and prod
15-
route = "analytics.ujjwalvivek.com/*"
14+
# Route configuration for custom domain - using path instead of subdomain
15+
[[routes]]
16+
pattern = "analytics.ujjwalvivek.com/api*"
17+
zone_name = "ujjwalvivek.com"

0 commit comments

Comments
 (0)