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' 19import { csrfProtection } from './lib/csrf' 20import { DNSVerificationWorker } from './lib/dns-verification-worker' 21import { logger } from './lib/logger' 22 23const config: Config = { 24 domain: (Bun.env.DOMAIN ?? `https://${BASE_HOST}`) as `https://${string}`, 25 clientName: Bun.env.CLIENT_NAME ?? 'PDS-View' 26} 27 28const client = await getOAuthClient(config) 29 30// Periodic maintenance: cleanup expired sessions and rotate keys 31// Run every hour 32const runMaintenance = async () => { 33 console.log('[Maintenance] Running periodic maintenance...') 34 await cleanupExpiredSessions() 35 await rotateKeysIfNeeded() 36} 37 38// Run maintenance on startup 39runMaintenance() 40 41// Schedule maintenance to run every hour 42setInterval(runMaintenance, 60 * 60 * 1000) 43 44// Start DNS verification worker (runs every hour) 45const dnsVerifier = new DNSVerificationWorker( 46 60 * 60 * 1000, // 1 hour 47 (msg, data) => { 48 logger.info('[DNS Verifier]', msg, data || '') 49 } 50) 51 52dnsVerifier.start() 53logger.info('[DNS Verifier] Started - checking custom domains every hour') 54 55export const app = new Elysia() 56 // Security headers middleware 57 .onAfterHandle(({ set }) => { 58 // Prevent clickjacking attacks 59 set.headers['X-Frame-Options'] = 'DENY' 60 // Prevent MIME type sniffing 61 set.headers['X-Content-Type-Options'] = 'nosniff' 62 // Strict Transport Security (HSTS) - enforce HTTPS 63 set.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' 64 // Referrer policy - limit referrer information 65 set.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' 66 // Content Security Policy 67 set.headers['Content-Security-Policy'] = 68 "default-src 'self'; " + 69 "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " + 70 "style-src 'self' 'unsafe-inline'; " + 71 "img-src 'self' data: https:; " + 72 "font-src 'self' data:; " + 73 "connect-src 'self' https:; " + 74 "frame-ancestors 'none'; " + 75 "base-uri 'self'; " + 76 "form-action 'self'" 77 // Additional security headers 78 set.headers['X-XSS-Protection'] = '1; mode=block' 79 set.headers['Permissions-Policy'] = 'geolocation=(), microphone=(), camera=()' 80 }) 81 .use( 82 await staticPlugin({ 83 prefix: '/' 84 }) 85 ) 86 .use(csrfProtection()) 87 .use(authRoutes(client)) 88 .use(wispRoutes(client)) 89 .use(domainRoutes(client)) 90 .use(userRoutes(client)) 91 .get('/client-metadata.json', (c) => { 92 return createClientMetadata(config) 93 }) 94 .get('/jwks.json', async (c) => { 95 const keys = await getCurrentKeys() 96 if (!keys.length) return { keys: [] } 97 98 return { 99 keys: keys.map((k) => { 100 const jwk = k.publicJwk ?? k 101 const { ...pub } = jwk 102 return pub 103 }) 104 } 105 }) 106 .get('/api/health', () => { 107 const dnsVerifierHealth = dnsVerifier.getHealth() 108 return { 109 status: 'ok', 110 timestamp: new Date().toISOString(), 111 dnsVerifier: dnsVerifierHealth 112 } 113 }) 114 .post('/api/admin/verify-dns', async () => { 115 try { 116 await dnsVerifier.trigger() 117 return { 118 success: true, 119 message: 'DNS verification triggered' 120 } 121 } catch (error) { 122 return { 123 success: false, 124 error: error instanceof Error ? error.message : String(error) 125 } 126 } 127 }) 128 .use(cors({ 129 origin: config.domain, 130 credentials: true, 131 methods: ['GET', 'POST', 'DELETE', 'PUT', 'PATCH', 'OPTIONS'], 132 allowedHeaders: ['Content-Type', 'Authorization', 'Origin', 'X-Forwarded-Host'], 133 exposeHeaders: ['Content-Type'], 134 maxAge: 86400 // 24 hours 135 })) 136 .listen(8000) 137 138console.log( 139 `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 140)