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
··· 16 <CardHeader> 17 <div className="flex items-center gap-2 mb-2"> 18 <CardTitle>Wisp CLI Tool</CardTitle> 19 - <Badge variant="secondary" className="text-xs">v0.1.0</Badge> 20 <Badge variant="outline" className="text-xs">Alpha</Badge> 21 </div> 22 <CardDescription> ··· 32 </div> 33 34 <div className="space-y-3"> 35 - <h3 className="text-sm font-semibold">Download CLI</h3> 36 <div className="grid gap-2"> 37 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> 38 <a 39 - href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64" 40 target="_blank" 41 rel="noopener noreferrer" 42 className="flex items-center justify-between mb-2" ··· 45 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 46 </a> 47 <div className="text-xs text-muted-foreground"> 48 - <span className="font-mono">SHA256: 637e325d9668ca745e01493d80dfc72447ef0a889b313e28913ca65c94c7aaae</span> 49 </div> 50 </div> 51 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 59 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 60 </a> 61 <div className="text-xs text-muted-foreground"> 62 - <span className="font-mono">SHA256: 01561656b64826f95b39f13c65c97da8bcc63ecd9f4d7e4e369c8ba8c903c22a</span> 63 </div> 64 </div> 65 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 73 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 74 </a> 75 <div className="text-xs text-muted-foreground"> 76 - <span className="font-mono">SHA256: 1ff485b9bcf89bc5721a862863c4843cf4530cbcd2489cf200cb24a44f7865a2</span> 77 </div> 78 </div> 79 </div> 80 </div> 81 82 <div className="space-y-3"> 83 - <h3 className="text-sm font-semibold">Basic Usage</h3> 84 <CodeBlock 85 code={`# Download and make executable 86 - curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-macos-arm64 87 - chmod +x wisp-cli-macos-arm64 88 89 - # Deploy your site (will use OAuth) 90 - ./wisp-cli-macos-arm64 your-handle.bsky.social \\ 91 --path ./dist \\ 92 - --site my-site 93 94 # Your site will be available at: 95 # https://sites.wisp.place/your-handle/my-site`} ··· 98 </div> 99 100 <div className="space-y-3"> 101 <h3 className="text-sm font-semibold">CI/CD with Tangled Spindle</h3> 102 <p className="text-xs text-muted-foreground"> 103 Deploy automatically on every push using{' '} ··· 147 chmod +x wisp-cli 148 149 # Deploy to Wisp 150 - ./wisp-cli \\ 151 "$WISP_HANDLE" \\ 152 --path "$SITE_PATH" \\ 153 --site "$SITE_NAME" \\ ··· 210 chmod +x wisp-cli 211 212 # Deploy to Wisp 213 - ./wisp-cli \\ 214 "$WISP_HANDLE" \\ 215 --path "$SITE_PATH" \\ 216 --site "$SITE_NAME" \\
··· 16 <CardHeader> 17 <div className="flex items-center gap-2 mb-2"> 18 <CardTitle>Wisp CLI Tool</CardTitle> 19 + <Badge variant="secondary" className="text-xs">v0.2.0</Badge> 20 <Badge variant="outline" className="text-xs">Alpha</Badge> 21 </div> 22 <CardDescription> ··· 32 </div> 33 34 <div className="space-y-3"> 35 + <h3 className="text-sm font-semibold">Features</h3> 36 + <ul className="text-sm text-muted-foreground space-y-2 list-disc list-inside"> 37 + <li><strong>Deploy:</strong> Push static sites directly from your terminal</li> 38 + <li><strong>Pull:</strong> Download sites from the PDS for development or backup</li> 39 + <li><strong>Serve:</strong> Run a local server with real-time firehose updates</li> 40 + </ul> 41 + </div> 42 + 43 + <div className="space-y-3"> 44 + <h3 className="text-sm font-semibold">Download v0.2.0</h3> 45 <div className="grid gap-2"> 46 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> 47 <a 48 + href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin" 49 target="_blank" 50 rel="noopener noreferrer" 51 className="flex items-center justify-between mb-2" ··· 54 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 55 </a> 56 <div className="text-xs text-muted-foreground"> 57 + <span className="font-mono">SHA-1: a8c27ea41c5e2672bfecb3476ece1c801741d759</span> 58 </div> 59 </div> 60 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 68 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 69 </a> 70 <div className="text-xs text-muted-foreground"> 71 + <span className="font-mono">SHA-1: fd7ee689c7600fc953179ea755b0357c8481a622</span> 72 </div> 73 </div> 74 <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> ··· 82 <ExternalLink className="w-4 h-4 text-muted-foreground" /> 83 </a> 84 <div className="text-xs text-muted-foreground"> 85 + <span className="font-mono">SHA-1: 8bca6992559e19e1d29ab3d2fcc6d09b28e5a485</span> 86 + </div> 87 + </div> 88 + <div className="p-3 bg-muted/50 hover:bg-muted rounded-lg transition-colors border border-border"> 89 + <a 90 + href="https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-x86_64-windows.exe" 91 + target="_blank" 92 + rel="noopener noreferrer" 93 + className="flex items-center justify-between mb-2" 94 + > 95 + <span className="font-mono text-sm">Windows (x86_64)</span> 96 + <ExternalLink className="w-4 h-4 text-muted-foreground" /> 97 + </a> 98 + <div className="text-xs text-muted-foreground"> 99 + <span className="font-mono">SHA-1: 90ea3987a06597fa6c42e1df9009e9758e92dd54</span> 100 </div> 101 </div> 102 </div> 103 </div> 104 105 <div className="space-y-3"> 106 + <h3 className="text-sm font-semibold">Deploy a Site</h3> 107 <CodeBlock 108 code={`# Download and make executable 109 + curl -O https://sites.wisp.place/nekomimi.pet/wisp-cli-binaries/wisp-cli-aarch64-darwin 110 + chmod +x wisp-cli-aarch64-darwin 111 112 + # Deploy your site 113 + ./wisp-cli-aarch64-darwin deploy your-handle.bsky.social \\ 114 --path ./dist \\ 115 + --site my-site \\ 116 + --password your-app-password 117 118 # Your site will be available at: 119 # https://sites.wisp.place/your-handle/my-site`} ··· 122 </div> 123 124 <div className="space-y-3"> 125 + <h3 className="text-sm font-semibold">Pull a Site from PDS</h3> 126 + <p className="text-xs text-muted-foreground"> 127 + Download a site from the PDS to your local machine (uses OAuth authentication): 128 + </p> 129 + <CodeBlock 130 + code={`# Pull a site to a specific directory 131 + wisp-cli pull your-handle.bsky.social \\ 132 + --site my-site \\ 133 + --output ./my-site 134 + 135 + # Pull to current directory 136 + wisp-cli pull your-handle.bsky.social \\ 137 + --site my-site 138 + 139 + # Opens browser for OAuth authentication on first run`} 140 + language="bash" 141 + /> 142 + </div> 143 + 144 + <div className="space-y-3"> 145 + <h3 className="text-sm font-semibold">Serve a Site Locally with Real-Time Updates</h3> 146 + <p className="text-xs text-muted-foreground"> 147 + Run a local server that monitors the firehose for real-time updates (uses OAuth authentication): 148 + </p> 149 + <CodeBlock 150 + code={`# Serve on http://localhost:8080 (default) 151 + wisp-cli serve your-handle.bsky.social \\ 152 + --site my-site 153 + 154 + # Serve on a custom port 155 + wisp-cli serve your-handle.bsky.social \\ 156 + --site my-site \\ 157 + --port 3000 158 + 159 + # Downloads site, serves it, and watches firehose for live updates!`} 160 + language="bash" 161 + /> 162 + </div> 163 + 164 + <div className="space-y-3"> 165 <h3 className="text-sm font-semibold">CI/CD with Tangled Spindle</h3> 166 <p className="text-xs text-muted-foreground"> 167 Deploy automatically on every push using{' '} ··· 211 chmod +x wisp-cli 212 213 # Deploy to Wisp 214 + ./wisp-cli deploy \\ 215 "$WISP_HANDLE" \\ 216 --path "$SITE_PATH" \\ 217 --site "$SITE_NAME" \\ ··· 274 chmod +x wisp-cli 275 276 # Deploy to Wisp 277 + ./wisp-cli deploy \\ 278 "$WISP_HANDLE" \\ 279 --path "$SITE_PATH" \\ 280 --site "$SITE_NAME" \\
+5 -5
src/index.ts
··· 70 }, 71 cookie: { 72 secrets: cookieSecret, 73 - sign: true 74 } 75 }) 76 // Observability middleware ··· 105 .onError(observabilityMiddleware('main-app').onError) 106 .use(csrfProtection()) 107 .use(authRoutes(client, cookieSecret)) 108 - .use(wispRoutes(client)) 109 - .use(domainRoutes(client)) 110 - .use(userRoutes(client)) 111 - .use(siteRoutes(client)) 112 .use(adminRoutes(cookieSecret)) 113 .use( 114 await staticPlugin({
··· 70 }, 71 cookie: { 72 secrets: cookieSecret, 73 + sign: ['did'] 74 } 75 }) 76 // Observability middleware ··· 105 .onError(observabilityMiddleware('main-app').onError) 106 .use(csrfProtection()) 107 .use(authRoutes(client, cookieSecret)) 108 + .use(wispRoutes(client, cookieSecret)) 109 + .use(domainRoutes(client, cookieSecret)) 110 + .use(userRoutes(client, cookieSecret)) 111 + .use(siteRoutes(client, cookieSecret)) 112 .use(adminRoutes(cookieSecret)) 113 .use( 114 await staticPlugin({
+6 -22
src/routes/auth.ts
··· 5 import { authenticateRequest } from '../lib/wisp-auth' 6 import { logger } from '../lib/observability' 7 8 - export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia() 9 .post('/api/auth/signin', async (c) => { 10 let handle = 'unknown' 11 try { ··· 74 c.cookie.did.remove() 75 return c.redirect('/?error=auth_failed') 76 } 77 - }, { 78 - cookie: t.Cookie({ 79 - did: t.Optional(t.String()) 80 - }, { 81 - secrets: cookieSecret, 82 - sign: ['did'] 83 - }) 84 }) 85 .post('/api/auth/logout', async (c) => { 86 try { ··· 106 logger.error('[Auth] Logout error', err) 107 return { error: 'Logout failed' } 108 } 109 - }, { 110 - cookie: t.Cookie({ 111 - did: t.Optional(t.String()) 112 - }, { 113 - secrets: cookieSecret, 114 - sign: ['did'] 115 - }) 116 }) 117 .get('/api/auth/status', async (c) => { 118 try { ··· 132 c.cookie.did.remove() 133 return { authenticated: false } 134 } 135 - }, { 136 - cookie: t.Cookie({ 137 - did: t.Optional(t.String()) 138 - }, { 139 - secrets: cookieSecret, 140 - sign: ['did'] 141 - }) 142 })
··· 5 import { authenticateRequest } from '../lib/wisp-auth' 6 import { logger } from '../lib/observability' 7 8 + export const authRoutes = (client: NodeOAuthClient, cookieSecret: string) => new Elysia({ 9 + cookie: { 10 + secrets: cookieSecret, 11 + sign: ['did'] 12 + } 13 + }) 14 .post('/api/auth/signin', async (c) => { 15 let handle = 'unknown' 16 try { ··· 79 c.cookie.did.remove() 80 return c.redirect('/?error=auth_failed') 81 } 82 }) 83 .post('/api/auth/logout', async (c) => { 84 try { ··· 104 logger.error('[Auth] Logout error', err) 105 return { error: 'Logout failed' } 106 } 107 }) 108 .get('/api/auth/status', async (c) => { 109 try { ··· 123 c.cookie.did.remove() 124 return { authenticated: false } 125 } 126 })
+8 -2
src/routes/domain.ts
··· 24 import { verifyCustomDomain } from '../lib/dns-verify' 25 import { logger } from '../lib/logger' 26 27 - export const domainRoutes = (client: NodeOAuthClient) => 28 - new Elysia({ prefix: '/api/domain' }) 29 // Public endpoints (no auth required) 30 .get('/check', async ({ query }) => { 31 try {
··· 24 import { verifyCustomDomain } from '../lib/dns-verify' 25 import { logger } from '../lib/logger' 26 27 + export const domainRoutes = (client: NodeOAuthClient, cookieSecret: string) => 28 + new Elysia({ 29 + prefix: '/api/domain', 30 + cookie: { 31 + secrets: cookieSecret, 32 + sign: ['did'] 33 + } 34 + }) 35 // Public endpoints (no auth required) 36 .get('/check', async ({ query }) => { 37 try {
+8 -2
src/routes/site.ts
··· 5 import { deleteSite } from '../lib/db' 6 import { logger } from '../lib/logger' 7 8 - export const siteRoutes = (client: NodeOAuthClient) => 9 - new Elysia({ prefix: '/api/site' }) 10 .derive(async ({ cookie }) => { 11 const auth = await requireAuth(client, cookie) 12 return { auth }
··· 5 import { deleteSite } from '../lib/db' 6 import { logger } from '../lib/logger' 7 8 + export const siteRoutes = (client: NodeOAuthClient, cookieSecret: string) => 9 + new Elysia({ 10 + prefix: '/api/site', 11 + cookie: { 12 + secrets: cookieSecret, 13 + sign: ['did'] 14 + } 15 + }) 16 .derive(async ({ cookie }) => { 17 const auth = await requireAuth(client, cookie) 18 return { auth }
+9 -3
src/routes/user.ts
··· 1 - import { Elysia } from 'elysia' 2 import { requireAuth } from '../lib/wisp-auth' 3 import { NodeOAuthClient } from '@atproto/oauth-client-node' 4 import { Agent } from '@atproto/api' ··· 6 import { syncSitesFromPDS } from '../lib/sync-sites' 7 import { logger } from '../lib/logger' 8 9 - export const userRoutes = (client: NodeOAuthClient) => 10 - new Elysia({ prefix: '/api/user' }) 11 .derive(async ({ cookie }) => { 12 const auth = await requireAuth(client, cookie) 13 return { auth }
··· 1 + import { Elysia, t } from 'elysia' 2 import { requireAuth } from '../lib/wisp-auth' 3 import { NodeOAuthClient } from '@atproto/oauth-client-node' 4 import { Agent } from '@atproto/api' ··· 6 import { syncSitesFromPDS } from '../lib/sync-sites' 7 import { logger } from '../lib/logger' 8 9 + export const userRoutes = (client: NodeOAuthClient, cookieSecret: string) => 10 + new Elysia({ 11 + prefix: '/api/user', 12 + cookie: { 13 + secrets: cookieSecret, 14 + sign: ['did'] 15 + } 16 + }) 17 .derive(async ({ cookie }) => { 18 const auth = await requireAuth(client, cookie) 19 return { auth }
+8 -2
src/routes/wisp.ts
··· 37 return true; 38 } 39 40 - export const wispRoutes = (client: NodeOAuthClient) => 41 - new Elysia({ prefix: '/wisp' }) 42 .derive(async ({ cookie }) => { 43 const auth = await requireAuth(client, cookie) 44 return { auth }
··· 37 return true; 38 } 39 40 + export const wispRoutes = (client: NodeOAuthClient, cookieSecret: string) => 41 + new Elysia({ 42 + prefix: '/wisp', 43 + cookie: { 44 + secrets: cookieSecret, 45 + sign: ['did'] 46 + } 47 + }) 48 .derive(async ({ cookie }) => { 49 const auth = await requireAuth(client, cookie) 50 return { auth }