[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!

server: use atproto-labs safe fetch to avoid SSRF

vielle.dev a96f4b02 4ac8eaad

verified
Changed files
+51 -10
server
+1
server/deno.json
··· 11 11 "@atcute/client": "npm:@atcute/client@^4.0.3", 12 12 "@atcute/lex-cli": "npm:@atcute/lex-cli@^2.2.0", 13 13 "@atcute/lexicons": "npm:@atcute/lexicons@^1.1.1", 14 + "@atproto-labs/fetch-node": "npm:@atproto-labs/fetch-node@^0.1.10", 14 15 "@libsql/client": "npm:@libsql/client@^0.15.15", 15 16 "drizzle-kit": "npm:drizzle-kit@^0.31.5", 16 17 "drizzle-orm": "npm:drizzle-orm@^0.44.6"
+26
server/deno.lock
··· 7 7 "npm:@atcute/identity-resolver@^1.1.3": "1.1.3_@atcute+identity@1.1.0", 8 8 "npm:@atcute/lex-cli@^2.2.0": "2.2.0_@badrap+valita@0.4.6", 9 9 "npm:@atcute/lexicons@^1.1.1": "1.1.1", 10 + "npm:@atproto-labs/fetch-node@~0.1.10": "0.1.10", 10 11 "npm:@libsql/client@~0.15.15": "0.15.15", 11 12 "npm:drizzle-kit@*": "0.31.5_esbuild@0.25.10", 12 13 "npm:drizzle-kit@~0.31.5": "0.31.5_esbuild@0.25.10", ··· 94 95 "dependencies": [ 95 96 "@badrap/valita" 96 97 ] 98 + }, 99 + "@atproto-labs/fetch-node@0.1.10": { 100 + "integrity": "sha512-o7hGaonA71A6p7O107VhM6UBUN/g9tTyYohMp1q0Kf6xQ4npnuZYRSHSf2g6reSfGQJ1GoFNjBObETTT1ge/jQ==", 101 + "dependencies": [ 102 + "@atproto-labs/fetch", 103 + "@atproto-labs/pipe", 104 + "ipaddr.js", 105 + "undici" 106 + ] 107 + }, 108 + "@atproto-labs/fetch@0.2.3": { 109 + "integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==", 110 + "dependencies": [ 111 + "@atproto-labs/pipe" 112 + ] 113 + }, 114 + "@atproto-labs/pipe@0.1.1": { 115 + "integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==" 97 116 }, 98 117 "@badrap/valita@0.4.6": { 99 118 "integrity": "sha512-4kdqcjyxo/8RQ8ayjms47HCWZIF5981oE5nIenbfThKDxWXtEHKipAOWlflpPJzZx9y/JWYQkp18Awr7VuepFg==" ··· 583 602 "resolve-pkg-maps" 584 603 ] 585 604 }, 605 + "ipaddr.js@2.2.0": { 606 + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" 607 + }, 586 608 "js-base64@3.7.8": { 587 609 "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==" 588 610 }, ··· 647 669 "undici-types@7.10.0": { 648 670 "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==" 649 671 }, 672 + "undici@6.22.0": { 673 + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==" 674 + }, 650 675 "web-streams-polyfill@3.3.3": { 651 676 "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" 652 677 }, ··· 662 687 "npm:@atcute/identity-resolver@^1.1.3", 663 688 "npm:@atcute/lex-cli@^2.2.0", 664 689 "npm:@atcute/lexicons@^1.1.1", 690 + "npm:@atproto-labs/fetch-node@~0.1.10", 665 691 "npm:@libsql/client@~0.15.15", 666 692 "npm:drizzle-kit@~0.31.5", 667 693 "npm:drizzle-orm@~0.44.6"
+3 -3
server/src/backfill/blob.ts
··· 1 - import { Client, simpleFetchHandler } from "@atcute/client"; 2 - import { getPds, MAX_SITE_SIZE } from "../utils.ts"; 1 + import { Client } from "@atcute/client"; 2 + import { fetchHandler, getPds, MAX_SITE_SIZE } from "../utils.ts"; 3 3 4 4 export default async function ( 5 5 did: `did:${"plc" | "web"}:${string}`, ··· 14 14 if (!pds) throw "PDS not found"; 15 15 16 16 const { data, ok } = await new Client({ 17 - handler: simpleFetchHandler({ service: pds }), 17 + handler: fetchHandler({ service: pds }), 18 18 }).get("com.atproto.sync.getBlob", { 19 19 params: { 20 20 did,
+3 -3
server/src/backfill/new-records.ts
··· 1 - import { db, getPds, isDid, rkeyToUrl } from "../utils.ts"; 1 + import { db, getPds, isDid, rkeyToUrl, fetchHandler } from "../utils.ts"; 2 2 import { decodeFirst } from "@atcute/cbor"; 3 3 import { DevAtcitiesRoute } from "../lexicons/index.ts"; 4 4 import { is } from "@atcute/lexicons"; 5 5 import { ComAtprotoSyncSubscribeRepos } from "@atcute/atproto"; 6 - import { Client, simpleFetchHandler } from "@atcute/client"; 6 + import { Client } from "@atcute/client"; 7 7 import { routes } from "../db/schema.ts"; 8 8 import { and, eq } from "drizzle-orm"; 9 9 ··· 57 57 } 58 58 59 59 const client = new Client({ 60 - handler: simpleFetchHandler({ 60 + handler: fetchHandler({ 61 61 service: pds, 62 62 }), 63 63 });
+4 -4
server/src/backfill/old-records.ts
··· 1 - import { Client, simpleFetchHandler } from "@atcute/client"; 2 - import { db, getPds, isDid, rkeyToUrl } from "../utils.ts"; 1 + import { Client } from "@atcute/client"; 2 + import { db, fetchHandler, getPds, isDid, rkeyToUrl } from "../utils.ts"; 3 3 import { is } from "@atcute/lexicons"; 4 4 import { DevAtcitiesRoute } from "../lexicons/index.ts"; 5 5 import { routes } from "../db/schema.ts"; ··· 8 8 const pds = await getPds(did); 9 9 if (!pds) return console.error(did, "could not be resolved to a pds."); 10 10 const pdsClient = new Client({ 11 - handler: simpleFetchHandler({ service: pds }), 11 + handler: fetchHandler({ service: pds }), 12 12 }); 13 13 14 14 let cursor: string | undefined; ··· 54 54 55 55 export default async function (db: db) { 56 56 const relay = new Client({ 57 - handler: simpleFetchHandler({ 57 + handler: fetchHandler({ 58 58 service: 59 59 Deno.env.get("ATPROTO_RELAY") || 60 60 (() => {
+14
server/src/utils.ts
··· 6 6 PlcDidDocumentResolver, 7 7 WebDidDocumentResolver, 8 8 } from "@atcute/identity-resolver"; 9 + import { FetchHandler, SimpleFetchHandlerOptions } from "@atcute/client"; 10 + import { safeFetchWrap } from "@atproto-labs/fetch-node"; 9 11 10 12 export type db = LibSQLDatabase<typeof schema> & { 11 13 $client: Client; ··· 37 39 web: new WebDidDocumentResolver(), 38 40 }, 39 41 }); 42 + 43 + export const fetchHandler: ({ 44 + service, 45 + }: SimpleFetchHandlerOptions) => FetchHandler = ({ 46 + service, 47 + }: SimpleFetchHandlerOptions): FetchHandler => { 48 + const safeFetch = safeFetchWrap(); 49 + return async (pathname, init) => { 50 + const url = new URL(pathname, service); 51 + return await safeFetch(url, init); 52 + }; 53 + }; 40 54 41 55 export async function getPds( 42 56 did: `did:${"plc" | "web"}:${string}`