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

updates

Changed files
+143 -18
public
src
+3 -2
bun.lock
···
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "elysia-static",
···
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.2",
"actor-typeahead": "^0.1.1",
-
"atproto-ui": "^0.11.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"elysia": "latest",
···
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
-
"atproto-ui": ["atproto-ui@0.11.1", "", { "dependencies": { "@atcute/atproto": "^3.1.7", "@atcute/bluesky": "^3.2.3", "@atcute/client": "^4.0.3", "@atcute/identity-resolver": "^1.1.3", "@atcute/tangled": "^1.0.10" }, "peerDependencies": { "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["react-dom"] }, "sha512-RpX9OGx3GDw0uL2X0Lw0bgzqEKKhfMeFuTUIgJuAa3W3MlLBH6h4qOWzaHXdrVQpru+6SQ0HznfRlQHK6nYRkQ=="],
"await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="],
···
{
"lockfileVersion": 1,
+
"configVersion": 0,
"workspaces": {
"": {
"name": "elysia-static",
···
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.2",
"actor-typeahead": "^0.1.1",
+
"atproto-ui": "^0.11.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"elysia": "latest",
···
"atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="],
+
"atproto-ui": ["atproto-ui@0.11.3", "", { "dependencies": { "@atcute/atproto": "^3.1.7", "@atcute/bluesky": "^3.2.3", "@atcute/client": "^4.0.3", "@atcute/identity-resolver": "^1.1.3", "@atcute/tangled": "^1.0.10" }, "peerDependencies": { "react": "^18.2.0 || ^19.0.0", "react-dom": "^18.2.0 || ^19.0.0" }, "optionalPeers": ["react-dom"] }, "sha512-NIBsORuo9lpCpr1SNKcKhNvqOVpsEy9IoHqFe1CM9gNTArpQL1hUcoP1Cou9a1O5qzCul9kaiu5xBHnB81I/WQ=="],
"await-lock": ["await-lock@2.2.2", "", {}, "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw=="],
+1 -1
package.json
···
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.2",
"actor-typeahead": "^0.1.1",
-
"atproto-ui": "^0.11.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"elysia": "latest",
···
"@radix-ui/react-tabs": "^1.1.13",
"@tanstack/react-query": "^5.90.2",
"actor-typeahead": "^0.1.1",
+
"atproto-ui": "^0.11.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"elysia": "latest",
+3 -4
public/index.tsx
···
import { Button } from '@public/components/ui/button'
import { Card } from '@public/components/ui/card'
import { BlueskyPostList, BlueskyProfile, BlueskyPost, AtProtoProvider, useLatestRecord, type AtProtoStyles, type FeedPostRecord } from 'atproto-ui'
-
import 'atproto-ui/styles.css'
//Credit to https://tangled.org/@jakelazaroff.com/actor-typeahead
interface Actor {
···
width: '100%',
listStyle: 'none',
overflow: 'hidden',
-
backgroundColor: 'rgba(255, 255, 255, 0.7)',
backgroundClip: 'padding-box',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
-
border: '1px solid hsl(var(--border))',
borderRadius: '8px',
boxShadow: '0 6px 6px -4px rgba(0, 0, 0, 0.2)',
padding: '4px',
···
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
-
color: 'hsl(var(--foreground))'
}}
>
{actor.handle}
···
import { Button } from '@public/components/ui/button'
import { Card } from '@public/components/ui/card'
import { BlueskyPostList, BlueskyProfile, BlueskyPost, AtProtoProvider, useLatestRecord, type AtProtoStyles, type FeedPostRecord } from 'atproto-ui'
//Credit to https://tangled.org/@jakelazaroff.com/actor-typeahead
interface Actor {
···
width: '100%',
listStyle: 'none',
overflow: 'hidden',
+
backgroundColor: 'rgba(255, 255, 255, 0.8)',
backgroundClip: 'padding-box',
backdropFilter: 'blur(12px)',
WebkitBackdropFilter: 'blur(12px)',
+
border: '1px solid rgba(0, 0, 0, 0.1)',
borderRadius: '8px',
boxShadow: '0 6px 6px -4px rgba(0, 0, 0, 0.2)',
padding: '4px',
···
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
+
color: '#000000'
}}
>
{actor.handle}
+10 -2
src/index.ts
···
cleanupExpiredSessions,
rotateKeysIfNeeded
} from './lib/oauth-client'
import { authRoutes } from './routes/auth'
import { wispRoutes } from './routes/wisp'
import { domainRoutes } from './routes/domain'
···
// Initialize admin setup (prompt if no admin exists)
await promptAdminSetup()
const client = await getOAuthClient(config)
···
maxRequestBodySize: 1024 * 1024 * 128 * 3,
development: Bun.env.NODE_ENV !== 'production' ? true : false,
id: Bun.env.NODE_ENV !== 'production' ? undefined : null,
}
})
// Observability middleware
···
})
.onError(observabilityMiddleware('main-app').onError)
.use(csrfProtection())
-
.use(authRoutes(client))
.use(wispRoutes(client))
.use(domainRoutes(client))
.use(userRoutes(client))
.use(siteRoutes(client))
-
.use(adminRoutes())
.use(
await staticPlugin({
prefix: '/'
···
cleanupExpiredSessions,
rotateKeysIfNeeded
} from './lib/oauth-client'
+
import { getCookieSecret } from './lib/db'
import { authRoutes } from './routes/auth'
import { wispRoutes } from './routes/wisp'
import { domainRoutes } from './routes/domain'
···
// Initialize admin setup (prompt if no admin exists)
await promptAdminSetup()
+
+
// Get or generate cookie signing secret
+
const cookieSecret = await getCookieSecret()
const client = await getOAuthClient(config)
···
maxRequestBodySize: 1024 * 1024 * 128 * 3,
development: Bun.env.NODE_ENV !== 'production' ? true : false,
id: Bun.env.NODE_ENV !== 'production' ? undefined : null,
+
},
+
cookie: {
+
secrets: cookieSecret,
+
sign: true
}
})
// Observability middleware
···
})
.onError(observabilityMiddleware('main-app').onError)
.use(csrfProtection())
+
.use(authRoutes(client, cookieSecret))
.use(wispRoutes(client))
.use(domainRoutes(client))
.use(userRoutes(client))
.use(siteRoutes(client))
+
.use(adminRoutes(cookieSecret))
.use(
await staticPlugin({
prefix: '/'
+29
src/lib/db.ts
···
)
`;
// Domains table maps subdomain -> DID (now supports up to 3 domains per user)
await db`
CREATE TABLE IF NOT EXISTS domains (
···
total: Number(wispCount[0]?.count || 0) + Number(customCount[0]?.count || 0),
};
};
···
)
`;
+
// Cookie secrets table for signed cookies
+
await db`
+
CREATE TABLE IF NOT EXISTS cookie_secrets (
+
id TEXT PRIMARY KEY DEFAULT 'default',
+
secret TEXT NOT NULL,
+
created_at BIGINT DEFAULT EXTRACT(EPOCH FROM NOW())
+
)
+
`;
+
// Domains table maps subdomain -> DID (now supports up to 3 domains per user)
await db`
CREATE TABLE IF NOT EXISTS domains (
···
total: Number(wispCount[0]?.count || 0) + Number(customCount[0]?.count || 0),
};
};
+
+
// Cookie secret management - ensure we have a secret for signing cookies
+
export const getCookieSecret = async (): Promise<string> => {
+
// Check if secret already exists
+
const rows = await db`SELECT secret FROM cookie_secrets WHERE id = 'default' LIMIT 1`;
+
+
if (rows.length > 0) {
+
return rows[0].secret as string;
+
}
+
+
// Generate new secret if none exists
+
const secret = crypto.randomUUID() + crypto.randomUUID(); // 72 character random string
+
await db`
+
INSERT INTO cookie_secrets (id, secret, created_at)
+
VALUES ('default', ${secret}, EXTRACT(EPOCH FROM NOW()))
+
`;
+
+
console.log('[CookieSecret] Generated new cookie signing secret');
+
return secret;
+
};
+65 -3
src/routes/admin.ts
···
import { logCollector, errorTracker, metricsCollector } from '../lib/observability'
import { db } from '../lib/db'
-
export const adminRoutes = () =>
new Elysia({ prefix: '/api/admin' })
// Login
.post(
···
body: t.Object({
username: t.String(),
password: t.String()
})
}
)
···
}
cookie.admin_session.remove()
return { success: true }
})
// Check auth status
···
authenticated: true,
username: session.username
}
})
// Get logs (protected)
···
)
return { logs: allLogs.slice(0, filter.limit || 100) }
})
// Get errors (protected)
···
)
return { errors: allErrors.slice(0, filter.limit || 100) }
})
// Get metrics (protected)
···
hostingService: hostingServiceStats,
timeWindow
}
})
// Get database stats (protected)
···
// Get recent sites (including those without domains)
const recentSites = await db`
-
SELECT
s.did,
s.rkey,
s.display_name,
···
message: error instanceof Error ? error.message : String(error)
}
}
})
// Get sites listing (protected)
···
try {
const sites = await db`
-
SELECT
s.did,
s.rkey,
s.display_name,
···
message: error instanceof Error ? error.message : String(error)
}
}
})
// Get system health (protected)
···
},
timestamp: new Date().toISOString()
}
})
···
import { logCollector, errorTracker, metricsCollector } from '../lib/observability'
import { db } from '../lib/db'
+
export const adminRoutes = (cookieSecret: string) =>
new Elysia({ prefix: '/api/admin' })
// Login
.post(
···
body: t.Object({
username: t.String(),
password: t.String()
+
}),
+
cookie: t.Cookie({
+
admin_session: t.String()
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
})
}
)
···
}
cookie.admin_session.remove()
return { success: true }
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Check auth status
···
authenticated: true,
username: session.username
}
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Get logs (protected)
···
)
return { logs: allLogs.slice(0, filter.limit || 100) }
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Get errors (protected)
···
)
return { errors: allErrors.slice(0, filter.limit || 100) }
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Get metrics (protected)
···
hostingService: hostingServiceStats,
timeWindow
}
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Get database stats (protected)
···
// Get recent sites (including those without domains)
const recentSites = await db`
+
SELECT
s.did,
s.rkey,
s.display_name,
···
message: error instanceof Error ? error.message : String(error)
}
}
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Get sites listing (protected)
···
try {
const sites = await db`
+
SELECT
s.did,
s.rkey,
s.display_name,
···
message: error instanceof Error ? error.message : String(error)
}
}
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
// Get system health (protected)
···
},
timestamp: new Date().toISOString()
}
+
}, {
+
cookie: t.Cookie({
+
admin_session: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['admin_session']
+
})
})
+32 -6
src/routes/auth.ts
···
-
import { Elysia } from 'elysia'
import { NodeOAuthClient } from '@atproto/oauth-client-node'
-
import { getSitesByDid, getDomainByDid } from '../lib/db'
import { syncSitesFromPDS } from '../lib/sync-sites'
import { authenticateRequest } from '../lib/wisp-auth'
import { logger } from '../lib/observability'
-
export const authRoutes = (client: NodeOAuthClient) => new Elysia()
.post('/api/auth/signin', async (c) => {
let handle = 'unknown'
try {
···
}
const cookieSession = c.cookie
-
cookieSession.did.value = session.did
// Sync sites from PDS to database cache
logger.debug('[Auth] Syncing sites from PDS for', session.did)
···
logger.error('[Auth] OAuth callback error', err)
return c.redirect('/?error=auth_failed')
}
})
.post('/api/auth/logout', async (c) => {
try {
···
const did = cookieSession.did?.value
// Clear the session cookie
-
cookieSession.did.value = ''
-
cookieSession.did.maxAge = 0
// If we have a DID, try to revoke the OAuth session
if (did && typeof did === 'string') {
···
logger.error('[Auth] Logout error', err)
return { error: 'Logout failed' }
}
})
.get('/api/auth/status', async (c) => {
try {
···
logger.error('[Auth] Status check error', err)
return { authenticated: false }
}
})
···
+
import { Elysia, t } from 'elysia'
import { NodeOAuthClient } from '@atproto/oauth-client-node'
+
import { getSitesByDid, getDomainByDid, getCookieSecret } from '../lib/db'
import { syncSitesFromPDS } from '../lib/sync-sites'
import { authenticateRequest } from '../lib/wisp-auth'
import { logger } from '../lib/observability'
+
export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia()
.post('/api/auth/signin', async (c) => {
let handle = 'unknown'
try {
···
}
const cookieSession = c.cookie
+
cookieSession.did.set({
+
value: session.did,
+
httpOnly: true,
+
secure: process.env.NODE_ENV === 'production',
+
sameSite: 'lax',
+
maxAge: 30 * 24 * 60 * 60 // 30 days
+
})
// Sync sites from PDS to database cache
logger.debug('[Auth] Syncing sites from PDS for', session.did)
···
logger.error('[Auth] OAuth callback error', err)
return c.redirect('/?error=auth_failed')
}
+
}, {
+
cookie: t.Cookie({
+
did: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['did']
+
})
})
.post('/api/auth/logout', async (c) => {
try {
···
const did = cookieSession.did?.value
// Clear the session cookie
+
cookieSession.did.remove()
// If we have a DID, try to revoke the OAuth session
if (did && typeof did === 'string') {
···
logger.error('[Auth] Logout error', err)
return { error: 'Logout failed' }
}
+
}, {
+
cookie: t.Cookie({
+
did: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['did']
+
})
})
.get('/api/auth/status', async (c) => {
try {
···
logger.error('[Auth] Status check error', err)
return { authenticated: false }
}
+
}, {
+
cookie: t.Cookie({
+
did: t.Optional(t.String())
+
}, {
+
secrets: cookieSecret,
+
sign: ['did']
+
})
})