-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path_worker.js
More file actions
125 lines (111 loc) · 5.54 KB
/
_worker.js
File metadata and controls
125 lines (111 loc) · 5.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// _worker.js — Cloudflare Pages: GitHub OAuth only
// Server-side auth on the edge
const GITHUB_CLIENT_ID = 'Ov23liTCel0d8P7EssBd';
const GITHUB_CLIENT_SECRET = '7180edff00516aeb39d6dd179fe75c9ba1e8696a';
const ALLOWED_USERS = ['kszongic']; // GitHub usernames allowed access
const COOKIE_NAME = 'claw_auth';
const SESSION_DURATION = 7 * 24 * 60 * 60;
function getCookie(request, name) {
const cookie = request.headers.get('Cookie') || '';
const match = cookie.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
return match ? match[1] : null;
}
function loginPage(error = '', origin = '') {
const ghUrl = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&redirect_uri=${encodeURIComponent(origin + '/__auth/callback')}&scope=read:user&state=${crypto.randomUUID()}`;
return new Response(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>🦞 Claw Toolbox — Login</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{min-height:100vh;display:flex;align-items:center;justify-content:center;background:#0f172a;font-family:system-ui,-apple-system,sans-serif;color:#e2e8f0}
.card{background:#1e293b;border-radius:16px;padding:2.5rem;width:100%;max-width:400px;box-shadow:0 25px 50px rgba(0,0,0,.4)}
h1{font-size:1.5rem;text-align:center;margin-bottom:.5rem}
.subtitle{text-align:center;color:#94a3b8;font-size:.875rem;margin-bottom:2rem}
.error{background:#7f1d1d;color:#fca5a5;padding:.75rem;border-radius:8px;margin-bottom:1rem;font-size:.875rem;text-align:center}
.gh-btn{display:flex;align-items:center;justify-content:center;gap:.75rem;width:100%;padding:.85rem;background:#238636;color:#fff;border:none;border-radius:8px;font-size:1rem;font-weight:600;cursor:pointer;text-decoration:none;transition:background .2s}
.gh-btn:hover{background:#2ea043}
.gh-btn svg{width:20px;height:20px;fill:#fff}
.lobster{font-size:3rem;text-align:center;margin-bottom:1rem}
.footer{text-align:center;margin-top:2rem;color:#475569;font-size:.75rem}
</style>
</head>
<body>
<div class="card">
<div class="lobster">🦞</div>
<h1>Claw Toolbox</h1>
<p class="subtitle">Sign in with GitHub to continue</p>
${error ? '<div class="error">' + error + '</div>' : ''}
<a class="gh-btn" href="${ghUrl}">
<svg viewBox="0 0 16 16"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>
Sign in with GitHub
</a>
<div class="footer">Only authorized GitHub accounts can access this toolbox.</div>
</div>
</body>
</html>`, {
status: 401,
headers: { 'Content-Type': 'text/html;charset=utf-8' }
});
}
function setAuthCookie(origin) {
const token = crypto.randomUUID();
return new Response(null, {
status: 302,
headers: {
'Location': '/',
'Set-Cookie': `${COOKIE_NAME}=${token}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${SESSION_DURATION}`
}
});
}
export default {
async fetch(request, env) {
const url = new URL(request.url);
// GitHub OAuth: callback
if (url.pathname === '/__auth/callback') {
try {
const code = url.searchParams.get('code');
if (!code) return loginPage('GitHub login failed — no code received.', url.origin);
const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'ClawToolbox'
},
body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, client_secret: GITHUB_CLIENT_SECRET, code })
});
const tokenText = await tokenRes.text();
let tokenData;
try { tokenData = JSON.parse(tokenText); } catch { return loginPage('GitHub token response invalid.', url.origin); }
if (!tokenData.access_token) return loginPage(`GitHub login failed — ${tokenData.error_description || tokenData.error || 'no token'}.`, url.origin);
const userRes = await fetch('https://api.github.com/user', {
headers: { 'Authorization': `Bearer ${tokenData.access_token}`, 'User-Agent': 'ClawToolbox' }
});
const userText = await userRes.text();
let user;
try { user = JSON.parse(userText); } catch { return loginPage('GitHub user response invalid.', url.origin); }
if (!ALLOWED_USERS.includes(user.login)) {
return loginPage(`Access denied for @${user.login}. Not on the allowlist.`, url.origin);
}
return setAuthCookie(url.origin);
} catch (e) {
return loginPage(`Error: ${e.message}`, url.origin);
}
}
// Logout
if (url.pathname === '/__auth/logout') {
return new Response(null, {
status: 302,
headers: { 'Location': '/', 'Set-Cookie': `${COOKIE_NAME}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0` }
});
}
// Check auth cookie
const authCookie = getCookie(request, COOKIE_NAME);
if (!authCookie) return loginPage('', url.origin);
// Authenticated — serve static assets
return env.ASSETS.fetch(request);
}
};