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

fix cookie routing

Changed files
+122 -50
public
editor
tabs
src
+78 -14
public/editor/tabs/CLITab.tsx
···
<CardHeader>
<div className="flex items-center gap-2 mb-2">
<CardTitle>Wisp CLI Tool</CardTitle>
-
<Badge variant="secondary" className="text-xs">v0.1.0</Badge>
+
<Badge variant="secondary" className="text-xs">v0.2.0</Badge>
<Badge variant="outline" className="text-xs">Alpha</Badge>
</div>
<CardDescription>
···
</div>
<div className="space-y-3">
-
<h3 className="text-sm font-semibold">Download CLI</h3>
+
<h3 className="text-sm font-semibold">Features</h3>
+
<ul className="text-sm text-muted-foreground space-y-2 list-disc list-inside">
+
<li><strong>Deploy:</strong> Push static sites directly from your terminal</li>
+
<li><strong>Pull:</strong> Download sites from the PDS for development or backup</li>
+
<li><strong>Serve:</strong> Run a local server with real-time firehose updates</li>
+
</ul>
+
</div>
+
+
<div className="space-y-3">
+
<h3 className="text-sm font-semibold">Download v0.2.0</h3>
<div className="grid gap-2">
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
<a
-
href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64"
+
href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin"
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between mb-2"
···
<ExternalLink className="w-4 h-4 text-muted-foreground" />
</a>
<div className="text-xs text-muted-foreground">
-
<span className="font-mono">SHA256: 637e325d9668ca745e01493d80dfc72447ef0a889b313e28913ca65c94c7aaae</span>
+
<span className="font-mono">SHA-1: a8c27ea41c5e2672bfecb3476ece1c801741d759</span>
</div>
</div>
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
···
<ExternalLink className="w-4 h-4 text-muted-foreground" />
</a>
<div className="text-xs text-muted-foreground">
-
<span className="font-mono">SHA256: 01561656b64826f95b39f13c65c97da8bcc63ecd9f4d7e4e369c8ba8c903c22a</span>
+
<span className="font-mono">SHA-1: fd7ee689c7600fc953179ea755b0357c8481a622</span>
</div>
</div>
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
···
<ExternalLink className="w-4 h-4 text-muted-foreground" />
</a>
<div className="text-xs text-muted-foreground">
-
<span className="font-mono">SHA256: 1ff485b9bcf89bc5721a862863c4843cf4530cbcd2489cf200cb24a44f7865a2</span>
+
<span className="font-mono">SHA-1: 8bca6992559e19e1d29ab3d2fcc6d09b28e5a485</span>
+
</div>
+
</div>
+
<div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border">
+
<a
+
href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-windows.exe"
+
target="_blank"
+
rel="noopener noreferrer"
+
className="flex items-center justify-between mb-2"
+
>
+
<span className="font-mono text-sm">Windows (x86_64)</span>
+
<ExternalLink className="w-4 h-4 text-muted-foreground" />
+
</a>
+
<div className="text-xs text-muted-foreground">
+
<span className="font-mono">SHA-1: 90ea3987a06597fa6c42e1df9009e9758e92dd54</span>
</div>
</div>
</div>
</div>
<div className="space-y-3">
-
<h3 className="text-sm font-semibold">Basic Usage</h3>
+
<h3 className="text-sm font-semibold">Deploy a Site</h3>
<CodeBlock
code={`# Download and make executable
-
curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64
-
chmod +x wisp-cli-macos-arm64
+
curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin
+
chmod +x wisp-cli-aarch64-darwin
-
# Deploy your site (will use OAuth)
-
./wisp-cli-macos-arm64 your-handle.bsky.social \\
+
# Deploy your site
+
./wisp-cli-aarch64-darwin deploy your-handle.bsky.social \\
--path ./dist \\
-
--site my-site
+
--site my-site \\
+
--password your-app-password
# Your site will be available at:
# https://sites.wisp.place/your-handle/my-site`}
···
</div>
<div className="space-y-3">
+
<h3 className="text-sm font-semibold">Pull a Site from PDS</h3>
+
<p className="text-xs text-muted-foreground">
+
Download a site from the PDS to your local machine (uses OAuth authentication):
+
</p>
+
<CodeBlock
+
code={`# Pull a site to a specific directory
+
wisp-cli pull your-handle.bsky.social \\
+
--site my-site \\
+
--output ./my-site
+
+
# Pull to current directory
+
wisp-cli pull your-handle.bsky.social \\
+
--site my-site
+
+
# Opens browser for OAuth authentication on first run`}
+
language="bash"
+
/>
+
</div>
+
+
<div className="space-y-3">
+
<h3 className="text-sm font-semibold">Serve a Site Locally with Real-Time Updates</h3>
+
<p className="text-xs text-muted-foreground">
+
Run a local server that monitors the firehose for real-time updates (uses OAuth authentication):
+
</p>
+
<CodeBlock
+
code={`# Serve on http://localhost:8080 (default)
+
wisp-cli serve your-handle.bsky.social \\
+
--site my-site
+
+
# Serve on a custom port
+
wisp-cli serve your-handle.bsky.social \\
+
--site my-site \\
+
--port 3000
+
+
# Downloads site, serves it, and watches firehose for live updates!`}
+
language="bash"
+
/>
+
</div>
+
+
<div className="space-y-3">
<h3 className="text-sm font-semibold">CI/CD with Tangled Spindle</h3>
<p className="text-xs text-muted-foreground">
Deploy automatically on every push using{' '}
···
chmod +x wisp-cli
# Deploy to Wisp
-
./wisp-cli \\
+
./wisp-cli deploy \\
"$WISP_HANDLE" \\
--path "$SITE_PATH" \\
--site "$SITE_NAME" \\
···
chmod +x wisp-cli
# Deploy to Wisp
-
./wisp-cli \\
+
./wisp-cli deploy \\
"$WISP_HANDLE" \\
--path "$SITE_PATH" \\
--site "$SITE_NAME" \\
+5 -5
src/index.ts
···
},
cookie: {
secrets: cookieSecret,
-
sign: true
+
sign: ['did']
}
})
// 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(wispRoutes(client, cookieSecret))
+
.use(domainRoutes(client, cookieSecret))
+
.use(userRoutes(client, cookieSecret))
+
.use(siteRoutes(client, cookieSecret))
.use(adminRoutes(cookieSecret))
.use(
await staticPlugin({
+6 -22
src/routes/auth.ts
···
import { authenticateRequest } from '../lib/wisp-auth'
import { logger } from '../lib/observability'
-
export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia()
+
export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia({
+
cookie: {
+
secrets: cookieSecret,
+
sign: ['did']
+
}
+
})
.post('/api/auth/signin', async (c) => {
let handle = 'unknown'
try {
···
c.cookie.did.remove()
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 {
···
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 {
···
c.cookie.did.remove()
return { authenticated: false }
}
-
}, {
-
cookie: t.Cookie({
-
did: t.Optional(t.String())
-
}, {
-
secrets: cookieSecret,
-
sign: ['did']
-
})
})
+8 -2
src/routes/domain.ts
···
import { verifyCustomDomain } from '../lib/dns-verify'
import { logger } from '../lib/logger'
-
export const domainRoutes = (client: NodeOAuthClient) =>
-
new Elysia({ prefix: '/api/domain' })
+
export const domainRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
+
new Elysia({
+
prefix: '/api/domain',
+
cookie: {
+
secrets: cookieSecret,
+
sign: ['did']
+
}
+
})
// Public endpoints (no auth required)
.get('/check', async ({ query }) => {
try {
+8 -2
src/routes/site.ts
···
import { deleteSite } from '../lib/db'
import { logger } from '../lib/logger'
-
export const siteRoutes = (client: NodeOAuthClient) =>
-
new Elysia({ prefix: '/api/site' })
+
export const siteRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
+
new Elysia({
+
prefix: '/api/site',
+
cookie: {
+
secrets: cookieSecret,
+
sign: ['did']
+
}
+
})
.derive(async ({ cookie }) => {
const auth = await requireAuth(client, cookie)
return { auth }
+9 -3
src/routes/user.ts
···
-
import { Elysia } from 'elysia'
+
import { Elysia, t } from 'elysia'
import { requireAuth } from '../lib/wisp-auth'
import { NodeOAuthClient } from '@atproto/oauth-client-node'
import { Agent } from '@atproto/api'
···
import { syncSitesFromPDS } from '../lib/sync-sites'
import { logger } from '../lib/logger'
-
export const userRoutes = (client: NodeOAuthClient) =>
-
new Elysia({ prefix: '/api/user' })
+
export const userRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
+
new Elysia({
+
prefix: '/api/user',
+
cookie: {
+
secrets: cookieSecret,
+
sign: ['did']
+
}
+
})
.derive(async ({ cookie }) => {
const auth = await requireAuth(client, cookie)
return { auth }
+8 -2
src/routes/wisp.ts
···
return true;
}
-
export const wispRoutes = (client: NodeOAuthClient) =>
-
new Elysia({ prefix: '/wisp' })
+
export const wispRoutes = (client: NodeOAuthClient, cookieSecret: string) =>
+
new Elysia({
+
prefix: '/wisp',
+
cookie: {
+
secrets: cookieSecret,
+
sign: ['did']
+
}
+
})
.derive(async ({ cookie }) => {
const auth = await requireAuth(client, cookie)
return { auth }