import { Hono } from 'hono' import { randomBytes, createHash } from 'crypto' import { Buffer } from 'buffer' const app = new Hono() // In-memory PKCE store (should use Redis or similar in production) const pkceStore = new Map() const generateCodeVerifier = () => { return randomBytes(32).toString('hex') } const generateCodeChallenge = (verifier: string) => { const hash = createHash('sha256').update(verifier).digest() return hash.toString('base64url') } // Step 1: Redirect to GitHub with PKCE app.get('/login', (c) => { const codeVerifier = generateCodeVerifier() const codeChallenge = generateCodeChallenge(codeVerifier) const state = randomBytes(8).toString('hex') pkceStore.set(state, codeVerifier) const params = new URLSearchParams({ client_id: process.env.GITHUB_CLIENT_ID!, redirect_uri: 'http://localhost:8787/callback', scope: 'read:user', state, response_type: 'code', code_challenge: codeChallenge, code_challenge_method: 'S256', }) return c.redirect(`https://github.com/login/oauth/authorize?${params}`) }) // Step 2: GitHub redirects back here app.get('/callback', async (c) => { const url = new URL(c.req.url) const code = url.searchParams.get('code') const state = url.searchParams.get('state') if (!code || !state) { return c.text('Missing code or state', 400) } const codeVerifier = pkceStore.get(state) if (!codeVerifier) { return c.text('Invalid or expired state', 400) } // Step 3: Exchange code + verifier for token const tokenRes = await fetch('https://github.com/login/oauth/access_token', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ client_id: process.env.GITHUB_CLIENT_ID, client_secret: process.env.GITHUB_CLIENT_SECRET, code, redirect_uri: 'http://localhost:8787/callback', code_verifier: codeVerifier, }), }) const tokenData = await tokenRes.json() if (!tokenData.access_token) { return c.text('Failed to get access token', 500) } // Step 4: Use token to fetch user profile const userRes = await fetch('https://api.github.com/user', { headers: { Authorization: `Bearer ${tokenData.access_token}`, 'User-Agent': 'hono-app', }, }) const user = await userRes.json() return c.json({ message: 'GitHub login successful!', user, }) }) export default { port: 8787, fetch: app.fetch, }