A minimal web editor for managing standard.site records in your atproto PDS
at main 2.7 kB view raw
1import { Hono } from 'hono'; 2import { serveStatic } from 'hono/bun'; 3import { getCookie, setCookie, deleteCookie } from 'hono/cookie'; 4import { authRoutes } from './routes/auth'; 5import { publicationRoutes } from './routes/publication'; 6import { documentRoutes } from './routes/documents'; 7import { layout } from './views/layouts/main'; 8import { homePage } from './views/home'; 9import { getSession } from './lib/session'; 10import { getClientMetadata, getJwks } from './lib/oauth'; 11import { csrfProtection, getCSRFToken } from './lib/csrf'; 12 13export const app = new Hono(); 14 15// Static files 16app.use('/public/*', serveStatic({ root: './' })); 17 18// OAuth metadata endpoints at root level 19// These MUST be publicly accessible (no authentication) 20app.get('/client-metadata.json', async (c) => { 21 try { 22 const metadata = await getClientMetadata(); 23 // Set appropriate cache headers 24 c.header('Cache-Control', 'public, max-age=600'); // Cache for 10 minutes 25 c.header('Access-Control-Allow-Origin', '*'); 26 return c.json(metadata); 27 } catch (error) { 28 console.error('Error getting client metadata:', error); 29 return c.json({ error: 'Failed to get client metadata' }, 500); 30 } 31}); 32 33app.get('/jwks.json', async (c) => { 34 try { 35 const jwks = await getJwks(); 36 // Set appropriate cache headers 37 c.header('Cache-Control', 'public, max-age=600'); // Cache for 10 minutes 38 c.header('Access-Control-Allow-Origin', '*'); 39 return c.json(jwks); 40 } catch (error) { 41 console.error('Error getting JWKS:', error); 42 return c.json({ error: 'Failed to get JWKS' }, 500); 43 } 44}); 45 46// Session middleware - adds session and CSRF token to context 47app.use('*', async (c, next) => { 48 const session = await getSession(c); 49 c.set('session', session); 50 // Generate CSRF token for all requests (sets cookie if not present) 51 const csrfToken = getCSRFToken(c); 52 c.set('csrfToken', csrfToken); 53 await next(); 54}); 55 56// CSRF protection for state-changing requests 57// Applied after session middleware but before routes 58app.use('/auth/*', csrfProtection); 59app.use('/publication/*', csrfProtection); 60app.use('/documents/*', csrfProtection); 61 62// Home page 63app.get('/', async (c) => { 64 const session = c.get('session'); 65 return c.html(layout(homePage(session), { session })); 66}); 67 68// Mount routes 69app.route('/auth', authRoutes); 70app.route('/publication', publicationRoutes); 71app.route('/documents', documentRoutes); 72 73const port = parseInt(process.env.PORT || '8000'); 74console.log(`Starting server on http://localhost:${port}`); 75console.log(`Public URL: ${process.env.PUBLIC_URL || 'http://localhost:' + port}`); 76 77export default { 78 port, 79 fetch: app.fetch, 80};