a standard.site publication renderer for SvelteKit.
at main 412 lines 9.3 kB view raw view rendered
1# Content Verification 2 3Verification proves that you own the content you've published to ATProto. This is done through `.well-known` endpoints and `<link>` tags. 4 5## Why Verify? 6 7Verification allows platforms like Leaflet and WhiteWind to: 8 91. Confirm you control the content you claim to have published 102. Prevent impersonation 113. Enable features that require ownership proof 124. Build trust in the federated ecosystem 13 14## Quick Start 15 16### 1. Create .well-known Endpoint 17 18Create a SvelteKit endpoint at `.well-known/site.standard.publication`: 19 20```typescript 21// src/routes/.well-known/site.standard.publication/+server.ts 22import { text } from '@sveltejs/kit'; 23import { generatePublicationWellKnown } from 'svelte-standard-site/verification'; 24 25export function GET() { 26 return text( 27 generatePublicationWellKnown({ 28 did: 'did:plc:your-did-here', 29 publicationRkey: '3abc123xyz789' // From publication creation 30 }) 31 ); 32} 33``` 34 35### 2. Verify It Works 36 37```bash 38curl https://yourblog.com/.well-known/site.standard.publication 39# Should output: at://did:plc:xxx/site.standard.publication/3abc123xyz789 40``` 41 42### 3. Add Link Tag (Optional) 43 44Add verification to individual documents: 45 46```svelte 47<!-- src/routes/blog/[slug]/+page.svelte --> 48<script lang="ts"> 49 import { generateDocumentLinkTag } from 'svelte-standard-site/verification'; 50 51 const { data } = $props(); 52</script> 53 54<svelte:head> 55 {@html generateDocumentLinkTag({ 56 did: 'did:plc:xxx', 57 documentRkey: data.rkey 58 })} 59</svelte:head> 60``` 61 62## Functions 63 64### generatePublicationWellKnown 65 66Generate content for the `.well-known` endpoint: 67 68```typescript 69import { generatePublicationWellKnown } from 'svelte-standard-site/verification'; 70 71const content = generatePublicationWellKnown({ 72 did: 'did:plc:xxx', 73 publicationRkey: '3abc123xyz' 74}); 75// Returns: "at://did:plc:xxx/site.standard.publication/3abc123xyz" 76``` 77 78### generateDocumentLinkTag 79 80Generate a `<link>` tag for a specific document: 81 82```typescript 83import { generateDocumentLinkTag } from 'svelte-standard-site/verification'; 84 85const tag = generateDocumentLinkTag({ 86 did: 'did:plc:xxx', 87 documentRkey: '3xyz789abc' 88}); 89// Returns: '<link rel="site.standard.document" href="at://...">' 90``` 91 92### generatePublicationLinkTag 93 94Generate a `<link>` tag for your publication: 95 96```typescript 97import { generatePublicationLinkTag } from 'svelte-standard-site/verification'; 98 99const tag = generatePublicationLinkTag({ 100 did: 'did:plc:xxx', 101 publicationRkey: '3abc123xyz' 102}); 103``` 104 105### verifyPublicationWellKnown 106 107Programmatically verify a site's `.well-known` endpoint: 108 109```typescript 110import { verifyPublicationWellKnown } from 'svelte-standard-site/verification'; 111 112const isValid = await verifyPublicationWellKnown( 113 'https://example.com', 114 'did:plc:xxx', 115 '3abc123xyz' 116); 117 118if (isValid) { 119 console.log('Site is verified!'); 120} 121``` 122 123## Complete Setup 124 125### Get Your Publication Rkey 126 127When you create a publication, save the rkey: 128 129```typescript 130import { StandardSitePublisher } from 'svelte-standard-site/publisher'; 131 132const publisher = new StandardSitePublisher({ 133 identifier: 'you.bsky.social', 134 password: process.env.ATPROTO_APP_PASSWORD! 135}); 136 137await publisher.login(); 138 139const result = await publisher.publishPublication({ 140 name: 'My Blog', 141 url: 'https://yourblog.com' 142}); 143 144// Save these! 145console.log('DID:', publisher.getDid()); 146console.log('Rkey:', result.uri.split('/').pop()); 147``` 148 149### Store in Environment Variables 150 151```env 152# .env 153PUBLIC_ATPROTO_DID=did:plc:xxx 154PUBLIC_PUBLICATION_RKEY=3abc123xyz 155``` 156 157### Create Endpoint 158 159```typescript 160// src/routes/.well-known/site.standard.publication/+server.ts 161import { text } from '@sveltejs/kit'; 162import { generatePublicationWellKnown } from 'svelte-standard-site/verification'; 163import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public'; 164 165export function GET() { 166 return text( 167 generatePublicationWellKnown({ 168 did: PUBLIC_ATPROTO_DID, 169 publicationRkey: PUBLIC_PUBLICATION_RKEY 170 }), 171 { 172 headers: { 173 'Content-Type': 'text/plain', 174 'Cache-Control': 'public, max-age=3600' 175 } 176 } 177 ); 178} 179``` 180 181### Add to Site Header 182 183```svelte 184<!-- src/routes/+layout.svelte --> 185<script lang="ts"> 186 import { generatePublicationLinkTag } from 'svelte-standard-site/verification'; 187 import { PUBLIC_ATPROTO_DID, PUBLIC_PUBLICATION_RKEY } from '$env/static/public'; 188</script> 189 190<svelte:head> 191 {@html generatePublicationLinkTag({ 192 did: PUBLIC_ATPROTO_DID, 193 publicationRkey: PUBLIC_PUBLICATION_RKEY 194 })} 195</svelte:head> 196``` 197 198## Document Verification 199 200For individual blog posts: 201 202### Store Document Rkeys 203 204When publishing, save the mapping: 205 206```typescript 207// In your publish script 208const result = await publisher.publishDocument({ 209 // ... document data 210}); 211 212// Save mapping: slug -> rkey 213const mapping = { 214 'my-first-post': result.uri.split('/').pop(), 215 'another-post': '3xyz789abc' 216 // etc. 217}; 218 219fs.writeFileSync('document-rkeys.json', JSON.stringify(mapping)); 220``` 221 222### Add to Document Pages 223 224```svelte 225<!-- src/routes/blog/[slug]/+page.svelte --> 226<script lang="ts"> 227 import { generateDocumentLinkTag } from 'svelte-standard-site/verification'; 228 import documentRkeys from '$lib/document-rkeys.json'; 229 230 const { data } = $props(); 231 const rkey = documentRkeys[data.slug]; 232</script> 233 234<svelte:head> 235 {#if rkey} 236 {@html generateDocumentLinkTag({ 237 did: 'did:plc:xxx', 238 documentRkey: rkey 239 })} 240 {/if} 241</svelte:head> 242``` 243 244## AT-URI Utilities 245 246### Build AT-URIs 247 248```typescript 249import { getDocumentAtUri, getPublicationAtUri } from 'svelte-standard-site/verification'; 250 251const docUri = getDocumentAtUri('did:plc:xxx', '3xyz789abc'); 252// "at://did:plc:xxx/site.standard.document/3xyz789abc" 253 254const pubUri = getPublicationAtUri('did:plc:xxx', '3abc123xyz'); 255// "at://did:plc:xxx/site.standard.publication/3abc123xyz" 256``` 257 258### Parse AT-URIs 259 260```typescript 261import { parseAtUri } from 'svelte-standard-site/verification'; 262 263const parsed = parseAtUri('at://did:plc:xxx/site.standard.document/3xyz'); 264 265if (parsed) { 266 console.log(parsed.did); // "did:plc:xxx" 267 console.log(parsed.collection); // "site.standard.document" 268 console.log(parsed.rkey); // "3xyz" 269} 270``` 271 272### Extract from HTML 273 274```typescript 275import { extractDocumentLinkFromHtml, extractPublicationLinkFromHtml } from 'svelte-standard-site/verification'; 276 277const html = await fetch('https://example.com/post').then((r) => r.text()); 278 279const docUri = extractDocumentLinkFromHtml(html); 280// "at://did:plc:xxx/site.standard.document/3xyz" 281 282const pubUri = extractPublicationLinkFromHtml(html); 283// "at://did:plc:xxx/site.standard.publication/3abc" 284``` 285 286## Verification Flow 287 2881. **Publish** a publication to ATProto 2892. **Save** the DID and rkey 2903. **Create** `.well-known` endpoint returning the AT-URI 2914. **Optionally** add `<link>` tags to your HTML 2925. **Platforms** fetch your `.well-known` endpoint to verify ownership 293 294```mermaid 295sequenceDiagram 296 participant You 297 participant ATProto 298 participant Platform 299 300 You->>ATProto: Publish publication 301 ATProto->>You: Return AT-URI 302 You->>Your Site: Add .well-known endpoint 303 Platform->>Your Site: Fetch .well-known 304 Your Site->>Platform: Return AT-URI 305 Platform->>ATProto: Verify AT-URI exists 306 ATProto->>Platform: Confirmed 307 Platform->>Platform: Mark as verified ✓ 308``` 309 310## Troubleshooting 311 312### .well-known Returning 404 313 314SvelteKit requires special handling for `.well-known`: 315 316```typescript 317// Option 1: Create the exact path 318// src/routes/.well-known/site.standard.publication/+server.ts 319 320// Option 2: Use static files 321// static/.well-known/site.standard.publication 322``` 323 324If using static files, make sure your hosting platform allows `.well-known`. 325 326### Wrong MIME Type 327 328Ensure you're returning `text/plain`: 329 330```typescript 331export function GET() { 332 return text(content, { 333 headers: { 334 'Content-Type': 'text/plain' 335 } 336 }); 337} 338``` 339 340### CORS Issues 341 342If platforms can't access your endpoint: 343 344```typescript 345export function GET() { 346 return text(content, { 347 headers: { 348 'Content-Type': 'text/plain', 349 'Access-Control-Allow-Origin': '*' 350 } 351 }); 352} 353``` 354 355### Verification Failing 356 357Use the verification utility to test: 358 359```typescript 360const isValid = await verifyPublicationWellKnown( 361 'https://yourblog.com', 362 'did:plc:xxx', 363 '3abc123xyz' 364); 365 366if (!isValid) { 367 // Check: 368 // 1. .well-known endpoint is accessible 369 // 2. Returns exact AT-URI 370 // 3. No extra whitespace 371 // 4. Correct MIME type 372} 373``` 374 375## Best Practices 376 3771. **Use environment variables** - Don't hardcode DIDs/rkeys 3782. **Add caching headers** - `.well-known` content doesn't change often 3793. **Test before deploying** - Verify the endpoint works 3804. **Keep rkeys secure** - Don't expose in client code unnecessarily 3815. **Monitor** - Check that verification keeps working after deploys 382 383## Hosting Platform Notes 384 385### Vercel 386 387Works out of the box. No special configuration needed. 388 389### Netlify 390 391Add to `netlify.toml`: 392 393```toml 394[[redirects]] 395 from = "/.well-known/site.standard.publication" 396 to = "/.well-known/site.standard.publication/index.html" 397 status = 200 398``` 399 400### Cloudflare Pages 401 402Works by default. Consider adding a cache rule for `.well-known`. 403 404### GitHub Pages 405 406Static files work, but SvelteKit endpoints don't. Use the static file approach. 407 408## Next Steps 409 410- [Publishing](./publishing.md) 411- [Content Transformation](./content-transformation.md) 412- [Comments](./comments.md)