at://nekomimi.pet/pub.leaflet.document/3m5fy2jkurk2a

Back to Collection

Record JSON

{
  "$type": "pub.leaflet.document",
  "author": "did:plc:ttdrpj45ibqunmfhdsb4zdwq",
  "description": "",
  "pages": [
    {
      "$type": "pub.leaflet.pages.linearDocument",
      "blocks": [
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "wisp.place implements a two-tier domain system:"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.unorderedList",
            "children": [
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "Wisp Subdomains ({handle}.wisp.place) - Free, first-come-first-serve"
                }
              },
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "Custom Domains (BYOD) - User-owned domains verified via DNS"
                }
              }
            ]
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.horizontalRule"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.header",
            "facets": [],
            "level": 2,
            "plaintext": "Database Schema"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "The system uses PostgreSQL with three key tables:"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "sql",
            "plaintext": "-- Wisp subdomains\nCREATE TABLE domains (\n    domain TEXT PRIMARY KEY,           -- e.g., \"mysite.wisp.place\"\n    did TEXT NOT NULL,                 -- User's DID (did:plc:xxx)\n    rkey TEXT,                         -- Site rkey (place.wisp.fs record identifier null until mapped)\n);\n\n-- Custom domains\nCREATE TABLE custom_domains (\n    id TEXT PRIMARY KEY,               -- SHA256 hash (first 16 chars)\n    domain TEXT UNIQUE NOT NULL,       -- e.g., \"example.com\"\n    did TEXT NOT NULL,                 -- User's DID\n    rkey TEXT,                         -- Site rkey (null until mapped)\n    verified BOOLEAN DEFAULT false,    -- DNS verification status\n    last_verified_at BIGINT,           -- Last verification timestamp\n);\n\n-- Sites cache\nCREATE TABLE sites (\n    did TEXT NOT NULL,\n    rkey TEXT NOT NULL,\n    display_name TEXT,\n    PRIMARY KEY (did, rkey)\n);",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "Of course with appropriate indexes:"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.unorderedList",
            "children": [
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "domains(did, rkey) - Find sites by user"
                }
              },
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "custom_domains(did) - Find user's custom domains"
                }
              },
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "custom_domains(verified) - Batch verification checks"
                }
              }
            ]
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.horizontalRule"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.header",
            "facets": [],
            "level": 2,
            "plaintext": "Subdomains"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [
              {
                "features": [
                  {
                    "$type": "pub.leaflet.richtext.facet#code"
                  }
                ],
                "index": {
                  "byteEnd": 368,
                  "byteStart": 351
                }
              }
            ],
            "plaintext": "To claim a subdomain, they must be authed into my service. That way I can prove that they control the DID (OAuthed in), and that in real time I can do schema validation and let them know if what they're trying to claim hasn't already been claimed. This is actually one thing I don't bother injesting from the firehose for though I do write a metadata place.wisp.domain into their repo."
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "sql",
            "plaintext": "/* POST /api/domain/claim\nBody: { handle: \"myhandle\" }\n\n-- 1. Validate handle\n   - 3-63 characters\n   - Only a-z, 0-9, hyphen\n   - Not reserved (www, api, admin, static, public, preview)\n   - Doesn't start/end with hyphen or contain */\n\n-- 2. Check user domain limit (max 3 per user)\n   SELECT COUNT(*) FROM domains WHERE did = ${userDid}\n\n-- 3. Insert into database\n   INSERT INTO domains (domain, did, rkey)\n   VALUES ('myhandle.wisp.place', did, null)",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "ts-tags",
            "plaintext": "//Write place.wisp.domain record to PDS\nawait agent.com.atproto.repo.putRecord({\n    repo: auth.did,\n\tcollection: \"place.wisp.domain\",\n\trkey: \"self\",\n\trecord: {\n\t    $type: \"place.wisp.domain\",\n\t\tdomain,\n\t\tcreatedAt: new Date().toISOString(),\n\t} as place.wisp.domain,\n});",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "The metadata is more acknowledgement that they claimed a wisp domain but the source of truth for this matter is with my database. The reason for this is that I really want to make this a conscious choice to delete routing because once its gone, there's now a window of opportunity that someone could hijack a former site and start hosting malicious content. Sure, not \"atproto\", but I'd rather this way."
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.horizontalRule"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.header",
            "facets": [],
            "level": 2,
            "plaintext": "Custom Domains"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "sql",
            "plaintext": "/* POST /api/domain/custom/add\n   Body: { domain: \"example.com\" }\n\n 1. Comprehensive validation:\n   - Length: 3-253 characters (RFC 1035)\n   - Format: ^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z]{2,}$\n   - Each label: 1-63 characters, no leading/trailing hyphen\n   - TLD: minimum 2 chars, not all numeric\n   - No non-ASCII characters (homograph attack prevention)\n   - Blocks: localhost, example.com, IP addresses, private IPs\n*/\n\n-- 2. Check if domain already claimed\n   SELECT * FROM custom_domains WHERE domain = ${domainLower}\n\n-- 3. Generate hash ID\n   id = SHA256(\"${did}:${domain}\").substring(0, 16)\n\n-- 4. Insert unverified\n   INSERT INTO custom_domains (id, domain, did, rkey, verified)\n   VALUES (hash, domain, did, null, false)",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "Database insertion:"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.unorderedList",
            "children": [
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "verified = false initially (pending DNS verification)"
                }
              },
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "rkey = null (set when user maps it to a site)"
                }
              },
              {
                "$type": "pub.leaflet.blocks.unorderedList#listItem",
                "children": [],
                "content": {
                  "$type": "pub.leaflet.blocks.text",
                  "facets": [],
                  "plaintext": "id is deterministic: SHA256 hash of DID + domain"
                }
              }
            ]
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [
              {
                "features": [
                  {
                    "$type": "pub.leaflet.richtext.facet#link",
                    "uri": "https://caddyserver.com/on-demand-tls"
                  }
                ],
                "index": {
                  "byteEnd": 191,
                  "byteStart": 178
                }
              }
            ],
            "plaintext": "This requires a little more work to get working due to TLS certs. The reverse proxy I use to terminate TLS into my services is Caddy, and they have a really nifty feature called on demand TLS. With this, anyone who hits the end point can have Caddy create a matching cert to then secure the connection. The initial request is quick enough to not have the connection even dropped while keeping it in a local cache to then be continually renewed every 90 days."
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [
              {
                "features": [
                  {
                    "$type": "pub.leaflet.richtext.facet#code"
                  }
                ],
                "index": {
                  "byteEnd": 500,
                  "byteStart": 486
                }
              }
            ],
            "plaintext": "To claim ownership of a custom domain, the process is much of the same except now I have to verify that the DID owns control of the domain. I do this the same way everyone else does, a TXT record. I ask them to put a _wisp TXT record at a level below of what they're asking, so if they want blog.nekomimi.pet, they would have to insert it at _wisp.blog.nekomimi.pet containing just their DID. Same way Bluesky and Leaflet do it. The CNAME is then the first 16 letters of sha256 hash of {DID}:{domain} to be deterministic. The TXT is authoritative (sufficient for verification)while CNAME is advisory (account for CNAME flattening services like Cloudflare)."
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "plaintext": "_wisp.example.com  TXT  did:plc:abc123xyz...\nexample.com        CNAME {hash}.dns.wisp.place",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "I check every 10 minutes to see if the TXT matches expected to initially validate and then see if it remains valid. The api route for caddy to then check the validity of the domain so it can issue a cert looks like this."
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "ts-tags",
            "plaintext": "// GET /api/domain/registered?domain=example.com\n\n.get('/registered', async ({ query, set }) =\u003e {\n\ttry {\n\t\tconst domain = (query.domain || \"\").trim().toLowerCase();\n\n\t\tif (!domain) {\n\t\t\tset.status = 400;\n\t\t\treturn { error: 'Domain parameter required' };\n\t\t}\n\n\t\tconst result = await isDomainRegistered(domain);\n\n\t\t// For Caddy on-demand TLS: 200 = allow, 404 = deny\n\t\tif (result.registered) {\n\t\t\tset.status = 200;\n\t\t\treturn result;\n\t\t} else {\n\t\t\tset.status = 404;\n\t\t\treturn { registered: false };\n\t\t}\n\t} catch (err) {\n\t\tlogger.error('[Domain] Registered check error', err);\n\t\tset.status = 500;\n\t\treturn { error: 'Failed to check domain' };\n\t}\n})",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.header",
            "facets": [],
            "level": 2,
            "plaintext": "Domain-to-Site Mapping"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "After verification/claiming, users map domains to sites"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "sql",
            "plaintext": "-- POST /api/domain/wisp/map-site\n-- Body: { domain: \"myhandle.wisp.place\", siteRkey: \"site123\" }\n\nUPDATE domains SET rkey = ${siteRkey} WHERE domain = ${domain}\n\n-- POST /api/domain/custom/{id}/map-site\n-- Body: { siteRkey: \"site123\" }\n\nUPDATE custom_domains SET rkey = ${siteRkey} WHERE id = ${id}",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.header",
            "facets": [
              {
                "features": [
                  {
                    "$type": "pub.leaflet.richtext.facet#bold"
                  }
                ],
                "index": {
                  "byteEnd": 34,
                  "byteStart": 0
                }
              }
            ],
            "level": 2,
            "plaintext": "Serving Sites via Hosting Service"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "The hosting-service (separate Node microservice) routes requests based on domain type:"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "ts-tags",
            "plaintext": "// Route 1: sites.wisp.place/{did}/{site}/*\nif (hostname === 'sites.wisp.place')\n    → Direct path resolution, immediate serve\n\n// Route 2: DNS hash subdomain (for custom domains in-flight)\nif (hostname.match(/^([a-f0-9]{16})\\.dns\\.wisp\\.place$/))\n    → Lookup by hash: getCustomDomainByHash(hash)\n    → Get rkey, fetch from user's DID/rkey\n\n// Route 3: Wisp subdomains (*.wisp.place)\nif (hostname.endsWith('.wisp.place'))\n    → Lookup: getWispDomain(hostname)\n    → Database query: SELECT did, rkey FROM domains WHERE domain = ${hostname}\n\n// Route 4: Custom domains (primary public route)\nconst customDomain = await getCustomDomain(hostname)\n    → Database query: SELECT did, rkey, verified FROM custom_domains \n                      WHERE domain = ${hostname} AND verified = true",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "The result of these is that the hosting service can resolve the domain to fetch the site and multiple domains point to the same site, all while being under the DID's control. "
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": ""
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "Here's the Caddyconf I use for this"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.code",
            "language": "json",
            "plaintext": "{\n    on_demand_tls {\n        ask http://wisp-place:8000/api/domain/registered\n    }\n}\n\n*.dns.wisp.place *.wisp.place {\n    reverse_proxy hosting-service:3001\n}\n\nhttps:// {\n    tls {\n        on_demand\n    }\n    reverse_proxy hosting-service:3001\n}",
            "syntaxHighlightingTheme": "rose-pine"
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [
              {
                "features": [
                  {
                    "$type": "pub.leaflet.richtext.facet#link",
                    "uri": "https://wisp.place"
                  }
                ],
                "index": {
                  "byteEnd": 35,
                  "byteStart": 25
                }
              }
            ],
            "plaintext": "This is the only part of wisp.place that isn't 'ATprotated' if you don't count the simple act of signing into the service as being that. "
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": ""
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": "I hope this serves useful to someone doing something similar. As far as I can tell, Leaflet does it a very similar way but instead of Caddy, they rely on Vercel's DNS and reverse proxy services to terminate the TLS and ensure custom domains get the certs up. "
          }
        },
        {
          "$type": "pub.leaflet.pages.linearDocument#block",
          "block": {
            "$type": "pub.leaflet.blocks.text",
            "facets": [],
            "plaintext": ""
          }
        }
      ]
    }
  ],
  "postRef": {
    "cid": "bafyreid5mn3qfq5d2u66evkh3ctq6mpig4qiuoal56htyknsr4raacmwjm",
    "commit": {
      "cid": "bafyreidkunk7kza6x7rsyhluj3hinkmjmpstplm6gundlejwcwvx2h4rby",
      "rev": "3m5fy2szcj22t"
    },
    "uri": "at://did:plc:ttdrpj45ibqunmfhdsb4zdwq/app.bsky.feed.post/3m5fy2svigs2a",
    "validationStatus": "valid"
  },
  "publication": "at://did:plc:ttdrpj45ibqunmfhdsb4zdwq/pub.leaflet.publication/3m2se2dzync2c",
  "publishedAt": "2025-11-12T06:02:04.982Z",
  "title": "How wisp.place maps domains to DIDs"
}