Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol. wisp.place
96
fork

Configure Feed

Select the types of activity you want to include in your feed.

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