Monorepo for Wisp.place. A static site hosting service built on top of the AT Protocol.
1import { Elysia } from 'elysia' 2import { cors } from '@elysiajs/cors' 3import { staticPlugin } from '@elysiajs/static' 4import { openapi, fromTypes } from '@elysiajs/openapi' 5 6import type { Config } from './lib/types' 7import { BASE_HOST } from './lib/constants' 8import { 9 createClientMetadata, 10 getOAuthClient, 11 getCurrentKeys, 12 cleanupExpiredSessions, 13 rotateKeysIfNeeded 14} from './lib/oauth-client' 15import { authRoutes } from './routes/auth' 16import { wispRoutes } from './routes/wisp' 17import { domainRoutes } from './routes/domain' 18import { userRoutes } from './routes/user' 19 20const config: Config = { 21 domain: (Bun.env.DOMAIN ?? `https://${BASE_HOST}`) as `https://${string}`, 22 clientName: Bun.env.CLIENT_NAME ?? 'PDS-View' 23} 24 25const client = await getOAuthClient(config) 26 27// Periodic maintenance: cleanup expired sessions and rotate keys 28// Run every hour 29const runMaintenance = async () => { 30 console.log('[Maintenance] Running periodic maintenance...') 31 await cleanupExpiredSessions() 32 await rotateKeysIfNeeded() 33} 34 35// Run maintenance on startup 36runMaintenance() 37 38// Schedule maintenance to run every hour 39setInterval(runMaintenance, 60 * 60 * 1000) 40 41export const app = new Elysia() 42 // Security headers middleware 43 .onAfterHandle(({ set }) => { 44 // Prevent clickjacking attacks 45 set.headers['X-Frame-Options'] = 'DENY' 46 // Prevent MIME type sniffing 47 set.headers['X-Content-Type-Options'] = 'nosniff' 48 // Strict Transport Security (HSTS) - enforce HTTPS 49 set.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' 50 // Referrer policy - limit referrer information 51 set.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' 52 // Content Security Policy 53 set.headers['Content-Security-Policy'] = 54 "default-src 'self'; " + 55 "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + 56 "style-src 'self' 'unsafe-inline'; " + 57 "img-src 'self' data: https:; " + 58 "font-src 'self' data:; " + 59 "connect-src 'self' https:; " + 60 "frame-ancestors 'none'; " + 61 "base-uri 'self'; " + 62 "form-action 'self'" 63 // Additional security headers 64 set.headers['X-XSS-Protection'] = '1; mode=block' 65 set.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()' 66 }) 67 .use( 68 openapi({ 69 references: fromTypes() 70 }) 71 ) 72 .use( 73 await staticPlugin({ 74 prefix: '/' 75 }) 76 ) 77 .use(authRoutes(client)) 78 .use(wispRoutes(client)) 79 .use(domainRoutes(client)) 80 .use(userRoutes(client)) 81 .get('/client-metadata.json', (c) => { 82 return createClientMetadata(config) 83 }) 84 .get('/jwks.json', (c) => { 85 const keys = getCurrentKeys() 86 if (!keys.length) return { keys: [] } 87 88 return { 89 keys: keys.map((k) => { 90 const jwk = k.publicJwk ?? k 91 const { ...pub } = jwk 92 return pub 93 }) 94 } 95 }) 96 .use(cors({ 97 origin: config.domain, 98 credentials: true, 99 methods: ['GET', 'POST', 'DELETE', 'OPTIONS'], 100 allowedHeaders: ['Content-Type', 'Authorization'], 101 maxAge: 86400 // 24 hours 102 })) 103 .listen(8000) 104 105console.log( 106 `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 107)