diff --git a/astro.config.mjs b/astro.config.mjs index d9eb0a9..2e4c43a 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -3,7 +3,7 @@ import cloudflare from "@astrojs/cloudflare" import { cacheCloudflare } from "@astrojs/cloudflare/cache" import tailwindcss from "@tailwindcss/vite" import { MAX_IMAGE_BYTES } from "./src/config.ts" - +import atproto from "at-astro" import react from "@astrojs/react" export default defineConfig({ @@ -20,5 +20,12 @@ export default defineConfig({ plugins: [tailwindcss()], server: { allowedHosts: ["mbp.puma-rainbow.ts.net"] }, }, - integrations: [react()], + + integrations: [ + react(), + atproto({ + name: "pixl", + scopes: ["repo:pics.pixl.image", "blob:image/*"], + }), + ], }) diff --git a/bun.lock b/bun.lock index e8ea253..abce4c7 100644 --- a/bun.lock +++ b/bun.lock @@ -8,11 +8,12 @@ "@astrojs/cloudflare": "^14.1.3", "@astrojs/react": "^6.0.2", "@atproto/jwk-webcrypto": "^0.3.4", - "@atproto/lex": "^0.3.0", + "@atproto/lex": "^0.3.6", "@atproto/oauth-client": "^0.7.11", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "astro": "^7.1.1", + "at-astro": "^1.0.1", "lucide-react": "^1.28.0", "react": "^19.2.8", "react-dom": "^19.2.8", @@ -91,7 +92,7 @@ "@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.2.4", "", { "dependencies": { "@atproto-labs/simple-store": "^0.4.4", "lru-cache": "^10.2.0" } }, "sha512-xAAUlOP9etqP9GGmJPq9gY1bRuJlZFdsC6wtAJSwUwMTwLRdenR7s8Qg5nxhbth5XL1XOtGABoD59NW3p+sl4A=="], - "@atproto/common": ["@atproto/common@0.7.2", "", { "dependencies": { "@atproto/common-web": "^0.5.6", "@atproto/lex-cbor": "^0.1.4", "@atproto/lex-data": "^0.1.5", "multiformats": "^13.0.0", "pino": "^10.3.1" } }, "sha512-D43QVEnSgWhzEoelJTWouPrwdjfHPAe5xVEc5RtsA6OTKEPaEJN0LSFd495ZtanMrvvC+KrgW8Tk02C8kvp45A=="], + "@atproto/common": ["@atproto/common@0.8.0", "", { "dependencies": { "@atproto/common-web": "^0.5.9", "@atproto/lex-cbor": "^0.1.6", "@atproto/lex-data": "^0.1.7", "multiformats": "^13.0.0", "pino": "^10.3.1" } }, "sha512-fBtd08LdouLqNaL8SaGLEIa0l3Ue27yR810jhUu+iBv/xdoXp+1G0pxvPe6JUStH7YGtf/v/v0w/cRGh1xUOaA=="], "@atproto/common-web": ["@atproto/common-web@0.5.6", "", { "dependencies": { "@atproto/lex-data": "^0.1.5", "@atproto/lex-json": "^0.1.4", "@atproto/syntax": "^0.7.2", "zod": "^3.23.8" } }, "sha512-5Y4MIK9dpkJPiKiE6u7iEHitxj+g3aAU2GfGL686JlKE2zDKD4y18BJb+uVek6nXQKb5XOdNVvw+7BHarpn8Fw=="], @@ -105,25 +106,25 @@ "@atproto/jwk-webcrypto": ["@atproto/jwk-webcrypto@0.3.4", "", { "dependencies": { "@atproto/jwk": "^0.7.4", "@atproto/jwk-jose": "^0.2.4", "zod": "^3.23.8" } }, "sha512-UsFIUozqnRecXPo6HgKV4PW4FqYHxX1V3iAe0rRV6Q2RSfYD8V2mZ89pv8NpvJynnaqJArBQ7HZlcg0F4tRYhA=="], - "@atproto/lex": ["@atproto/lex@0.3.0", "", { "dependencies": { "@atproto/lex-builder": "^0.1.8", "@atproto/lex-client": "^0.3.0", "@atproto/lex-data": "^0.1.5", "@atproto/lex-installer": "^0.1.9", "@atproto/lex-json": "^0.1.4", "@atproto/lex-schema": "^0.2.2", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "bin": { "ts-lex": "./bin/lex", "lex": "./bin/lex" } }, "sha512-Ir5f60NrCScDMZ2JGoxUHOQm+h6XlsDSykk0HzET8/qEeHTLwnI+MJIlBwB64eBoqGQ72qAEoMc8L+bJil+huA=="], + "@atproto/lex": ["@atproto/lex@0.3.6", "", { "dependencies": { "@atproto/lex-builder": "^0.1.12", "@atproto/lex-client": "^0.3.3", "@atproto/lex-data": "^0.1.7", "@atproto/lex-installer": "^0.1.15", "@atproto/lex-json": "^0.1.6", "@atproto/lex-schema": "^0.2.5", "tslib": "^2.8.1", "yargs": "^18.0.0" }, "bin": { "lex": "./bin/lex", "ts-lex": "./bin/lex" } }, "sha512-8Hpc6MbfkPJKaO5CvvIerEylbihI5GfrY5DzapZD4rV2TWfm3RU+DzeRpsxQJ0W9WOZRYvp1JnBcq/gZ48Qb0g=="], - "@atproto/lex-builder": ["@atproto/lex-builder@0.1.8", "", { "dependencies": { "@atproto/lex-document": "^0.1.6", "@atproto/lex-schema": "^0.2.2", "prettier": "^3.2.5", "ts-morph": "^27.0.0", "tslib": "^2.8.1" } }, "sha512-GYWisNom/J1MmFeSgMIFUgEj8yVKnvuJfyX1FwNKCczwfAunEdMy2Iiolq3JZNjWmH8uCtCWj7dg2GwQuEos7Q=="], + "@atproto/lex-builder": ["@atproto/lex-builder@0.1.12", "", { "dependencies": { "@atproto/lex-document": "^0.1.9", "@atproto/lex-schema": "^0.2.5", "prettier": "^3.9.6", "ts-morph": "^27.0.0", "tslib": "^2.8.1" } }, "sha512-uo3mcdoOgqBfQnf+PhYYLNEBdUTgoNe2ARYb3cUaiec8O0oritOeZndUDgmths06ACrlqFkZNTV1YJX6CsH71g=="], - "@atproto/lex-cbor": ["@atproto/lex-cbor@0.1.4", "", { "dependencies": { "@atproto/lex-data": "^0.1.5", "cborg": "^4.5.8", "tslib": "^2.8.1" } }, "sha512-1reaooZfNFunJn2Fz21Up7iFQBPZlifQKlBsRvQoLG67mv43nmH4dZWgnGLD54ShCu8dyZ8Y6Wgh7BCg+2gRSw=="], + "@atproto/lex-cbor": ["@atproto/lex-cbor@0.1.6", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "cborg": "^4.5.8", "tslib": "^2.8.1" } }, "sha512-PxLQy7jzV8u9zTGURNF8ZczLPk+ZH+WNx/2z/z7RacJbu7sWNpx26U2pC7cIkWtt4jyDaruaLhssw3w3atLotA=="], - "@atproto/lex-client": ["@atproto/lex-client@0.3.0", "", { "dependencies": { "@atproto/lex-data": "^0.1.5", "@atproto/lex-json": "^0.1.4", "@atproto/lex-schema": "^0.2.2", "tslib": "^2.8.1" } }, "sha512-P+S3hWdIIOgNTun7J42dvxSyCkQog3Q2ShTjvcwP761TqiRex7zDWaRAaT9gvCy6I4pRuNdBCStysjSMlJeSdg=="], + "@atproto/lex-client": ["@atproto/lex-client@0.3.3", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "@atproto/lex-json": "^0.1.6", "@atproto/lex-schema": "^0.2.5", "tslib": "^2.8.1" } }, "sha512-sWChe7Jukm9uxDfxT6ElICp6vMTqAFmwcHdSLdMDtsKBZSrwO7vWL9F6rpoMPyxS29Ob9nNAhMg30kKfZrb+uw=="], - "@atproto/lex-data": ["@atproto/lex-data@0.1.5", "", { "dependencies": { "multiformats": "^13.0.0", "tslib": "^2.8.1", "unicode-segmenter": "^0.14.0" } }, "sha512-TEM6GHuYpNm4O90LjNgbYq1Gmcr875S+BHrDxkg4PB5w/nlsz4HlbkGQG/WP/xbIV5O8TF5nNHDpCZ5ezTRrFA=="], + "@atproto/lex-data": ["@atproto/lex-data@0.1.7", "", { "dependencies": { "multiformats": "^13.0.0", "tslib": "^2.8.1", "unicode-segmenter": "^0.14.0" } }, "sha512-kW/dPLqo/WgCLV+XESR4JKwV6c1rZWJGOfuPupZGTjEDAKoBbKXdaEzX9/1vKQYbZ9U3j0DS/n7OFFK7wBugyQ=="], - "@atproto/lex-document": ["@atproto/lex-document@0.1.6", "", { "dependencies": { "@atproto/lex-schema": "^0.2.2", "core-js": "^3", "tslib": "^2.8.1" } }, "sha512-Yxc6LJ8tE0cntQ35ewagGbl7HOMX4jRkuEQ3ct+I43LKJBQPSk02rlwTY9A7SR/ULptN4Dn7XVqskEntWmCjWA=="], + "@atproto/lex-document": ["@atproto/lex-document@0.1.9", "", { "dependencies": { "@atproto/lex-schema": "^0.2.5", "core-js": "^3", "tslib": "^2.8.1" } }, "sha512-TmpwttQ9pKCXlvwUlJ/5HMZPtJMFBvgZ1bunEeQD5JfU6n/sgK/fob8tfjhTdL2QrpZMtM6opMEk3w5STm1RDQ=="], - "@atproto/lex-installer": ["@atproto/lex-installer@0.1.9", "", { "dependencies": { "@atproto/lex-builder": "^0.1.8", "@atproto/lex-cbor": "^0.1.4", "@atproto/lex-data": "^0.1.5", "@atproto/lex-document": "^0.1.6", "@atproto/lex-resolver": "^0.2.2", "@atproto/lex-schema": "^0.2.2", "@atproto/syntax": "^0.7.2", "tslib": "^2.8.1" } }, "sha512-9PdApXnwtBRGty0muK/t5CUHv18nBtkut5Q5ibDcBhYKrsVmBAVNCXLqMreC2kbZf3q5L4+IfqtqMejmAZw2Mg=="], + "@atproto/lex-installer": ["@atproto/lex-installer@0.1.15", "", { "dependencies": { "@atproto/lex-builder": "^0.1.12", "@atproto/lex-cbor": "^0.1.6", "@atproto/lex-data": "^0.1.7", "@atproto/lex-document": "^0.1.9", "@atproto/lex-resolver": "^0.2.8", "@atproto/lex-schema": "^0.2.5", "@atproto/syntax": "^0.7.4", "tslib": "^2.8.1" } }, "sha512-4/xDyY7yxxs66vF0WZB+7fcuEy4I5IRDyDoQFzLQx7C6PlQ76/e/PxvYTff7x0/N6hknmOlANODnnxo3rtdnCA=="], - "@atproto/lex-json": ["@atproto/lex-json@0.1.4", "", { "dependencies": { "@atproto/lex-data": "^0.1.5", "tslib": "^2.8.1" } }, "sha512-ENR2cWkVrES+UL6TovbCRdX9BJOyHHJUS8jYx3Lxp3j4vEphjn/u+DW7bWloST2O8ID2OuVlt6+28ftNEEjmQQ=="], + "@atproto/lex-json": ["@atproto/lex-json@0.1.6", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "tslib": "^2.8.1" } }, "sha512-mvrAd0lbyuecIHjyld8QN6MN6CBf4j0GCxLzegsvLh0SvDf+GbYWklkcQqmITL44yFQOwmA/QNIQj0Uvh7+R/g=="], - "@atproto/lex-resolver": ["@atproto/lex-resolver@0.2.2", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.3.5", "@atproto/crypto": "^0.5.4", "@atproto/lex-client": "^0.3.0", "@atproto/lex-data": "^0.1.5", "@atproto/lex-document": "^0.1.6", "@atproto/lex-schema": "^0.2.2", "@atproto/repo": "^0.10.6", "@atproto/syntax": "^0.7.2", "tslib": "^2.8.1" } }, "sha512-/7rm1bq2me4ptNoPWOrWmlmzRSPhxbcm3BaeD0V8+OzJ5VKBrYHxqp8BBZTzqgVSmwF+TqANctUG4c/y0hkigw=="], + "@atproto/lex-resolver": ["@atproto/lex-resolver@0.2.8", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.3.7", "@atproto/crypto": "^0.5.4", "@atproto/lex-client": "^0.3.3", "@atproto/lex-data": "^0.1.7", "@atproto/lex-document": "^0.1.9", "@atproto/lex-schema": "^0.2.5", "@atproto/repo": "^0.10.11", "@atproto/syntax": "^0.7.4", "tslib": "^2.8.1" } }, "sha512-j3dIxeEAStp/RzJS4MECRDPuOFX41mX9fnteGgrQhe9053eRuTnl2oTyJbV7rKgzy+hEI4jH6X6Y4wOStjUb/Q=="], - "@atproto/lex-schema": ["@atproto/lex-schema@0.2.2", "", { "dependencies": { "@atproto/lex-data": "^0.1.5", "@atproto/syntax": "^0.7.2", "@standard-schema/spec": "^1.1.0", "tslib": "^2.8.1" } }, "sha512-UFK0EH8pw31dvL27aoZ5K78z+UMiD3ybIgkCyd7Idm267KM1IpycerTDeABqo4CYP3/IAnzjGpQaUaVc12bELA=="], + "@atproto/lex-schema": ["@atproto/lex-schema@0.2.5", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "@atproto/syntax": "^0.7.4", "@standard-schema/spec": "^1.1.0", "tslib": "^2.8.1" } }, "sha512-/tP3dRqaZu0EIoq0BSVzhvFGL6u0uu2KFDV21i73DpFsg6TXOTddKJ9bvgK83XdAJYL7PWi703XsaR31v9u5Qg=="], "@atproto/lexicon": ["@atproto/lexicon@0.7.7", "", { "dependencies": { "@atproto/common-web": "^0.5.6", "@atproto/syntax": "^0.7.2", "multiformats": "^13.0.0", "zod": "^3.23.8" } }, "sha512-92VH2oEsJdrIVNy7WY8rGn99ANNVglyUffoN7GJc0mxKi+fXN5iVlJ2cOyTfYABhr2ldSiptZWXEPEdBaIR9/A=="], @@ -131,9 +132,9 @@ "@atproto/oauth-types": ["@atproto/oauth-types@0.7.5", "", { "dependencies": { "@atproto/did": "^0.5.4", "@atproto/jwk": "^0.7.4", "zod": "^3.23.8" } }, "sha512-x75O0HsKB1IGfBikAQrrTX6EL8Rt4Q0+wMcwhZQRGPk/N/WqbYbsW3Powj4R8ZJKSfWCpVfaw32Piu1pTi891Q=="], - "@atproto/repo": ["@atproto/repo@0.10.6", "", { "dependencies": { "@atproto/common": "^0.7.2", "@atproto/common-web": "^0.5.6", "@atproto/crypto": "^0.5.4", "@atproto/lex-cbor": "^0.1.4", "@atproto/lex-data": "^0.1.5", "@atproto/syntax": "^0.7.2", "varint": "^6.0.0", "zod": "^3.23.8" } }, "sha512-ayX/ieAni4ZMbpdu6VMOCYIjzT8HctCQtcSBWWR8eoynsGZ6FzK/Lln6bH8i0wAj8vPV2pcecd57d33YsahRcQ=="], + "@atproto/repo": ["@atproto/repo@0.10.11", "", { "dependencies": { "@atproto/common": "^0.8.0", "@atproto/common-web": "^0.5.9", "@atproto/crypto": "^0.5.4", "@atproto/lex-cbor": "^0.1.6", "@atproto/lex-data": "^0.1.7", "@atproto/syntax": "^0.7.4", "varint": "^6.0.0", "zod": "^3.23.8" } }, "sha512-QqDzEv8d6NUuqZ0xvWG+n8DfWd4Vuka04nyuD9oglKsj5avB1zbEsWx3Oit1oYtZFUf9JSVWMDqMXhu3UbB+lA=="], - "@atproto/syntax": ["@atproto/syntax@0.7.2", "", { "dependencies": { "iso-datestring-validator": "^2.2.2", "tslib": "^2.8.1" } }, "sha512-tZ1Tr0R9pK4bI4Zs69t29cjMlCFQvRNBeNkqJC5pGeNuCt64D0eoF7s/AlqeVmYppClmQ0xJquggGgsMDP6j7w=="], + "@atproto/syntax": ["@atproto/syntax@0.7.4", "", { "dependencies": { "iso-datestring-validator": "^2.2.2", "tslib": "^2.8.1" } }, "sha512-EHsEHtasH/DGPljBYecVDjweGMQ5eTu6Ns0GZ5z0qdqpOI8ipBvldWnMIxWkA+5HfZ9Dsu4V1MX0WP7wgL8cuA=="], "@atproto/xrpc": ["@atproto/xrpc@0.8.6", "", { "dependencies": { "@atproto/lexicon": "^0.7.7", "zod": "^3.23.8" } }, "sha512-yVfKrlwZBBm44Ft9jDvHcTCwQ1ElqSlzXHWhT/KcqE+u24EAhWVFosfABfGbUByB/PmN8eLJ06FsWo5pUQcMNQ=="], @@ -617,6 +618,8 @@ "astro": ["astro@7.1.1", "", { "dependencies": { "@astrojs/compiler-rs": "^0.3.1", "@astrojs/internal-helpers": "0.10.1", "@astrojs/markdown-satteri": "0.3.4", "@astrojs/telemetry": "3.3.3", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "am-i-vibing": "^0.4.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.8.1", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.28.0", "flattie": "^1.1.1", "fontace": "~0.4.1", "get-tsconfig": "5.0.0-beta.4", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unstorage": "^1.17.5", "vite": "^8.0.13", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0 || ^0.35.0" }, "peerDependencies": { "@astrojs/markdown-remark": "7.2.1" }, "optionalPeers": ["@astrojs/markdown-remark"], "bin": { "astro": "./bin/astro.mjs" } }, "sha512-yfKhuvbz+DORHihJRL1DHcxyLcX9uTrxvz2nCSTzHH54k2DdACBfuOu2OAAAhdUC9xlu2IRXAiagqcPL3DftCg=="], + "at-astro": ["at-astro@1.0.1", "", { "dependencies": { "@atproto/jwk-webcrypto": "^0.3.4", "@atproto/oauth-client": "^0.8.3" }, "peerDependencies": { "@atproto/lex": "^0.3.6", "astro": "^7" } }, "sha512-0MzyIKkwJcCH2GRJsHt+lq9CrktI0YU+euOCnJ2xb6XxbRvu2s5+HZAQ5u56R0ai1h0nKg0VIEp9DCLX8QFLPw=="], + "atomic-sleep": ["atomic-sleep@1.0.0", "", {}, "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], @@ -1159,6 +1162,20 @@ "@astrojs/react/@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.10.2", "", { "dependencies": { "@types/hast": "^3.0.4", "@types/mdast": "^4.0.4", "js-yaml": "^4.3.0", "picomatch": "^4.0.4", "retext-smartypants": "^6.2.0", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "unified": "^11.0.5" } }, "sha512-yt7fMgPYqSM4Tmr+taTW6Per+hjJ8Pk6lA1PAcDyqzOt8HzJ6Kje5WzCxA2Sd+9wsUW7uhkLeoTMK0cXPwH9rQ=="], + "@atproto/common/@atproto/common-web": ["@atproto/common-web@0.5.9", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "@atproto/lex-json": "^0.1.6", "@atproto/syntax": "^0.7.4", "zod": "^3.23.8" } }, "sha512-2c5C6YV8352JJz9Z5fNfcsstllaKGAb3cQW9aO7unMAGh/FzTGFq+xOwNMW2lTeagO6z530uxYm8AiSRM6O+DQ=="], + + "@atproto/common-web/@atproto/lex-data": ["@atproto/lex-data@0.1.5", "", { "dependencies": { "multiformats": "^13.0.0", "tslib": "^2.8.1", "unicode-segmenter": "^0.14.0" } }, "sha512-TEM6GHuYpNm4O90LjNgbYq1Gmcr875S+BHrDxkg4PB5w/nlsz4HlbkGQG/WP/xbIV5O8TF5nNHDpCZ5ezTRrFA=="], + + "@atproto/common-web/@atproto/lex-json": ["@atproto/lex-json@0.1.4", "", { "dependencies": { "@atproto/lex-data": "^0.1.5", "tslib": "^2.8.1" } }, "sha512-ENR2cWkVrES+UL6TovbCRdX9BJOyHHJUS8jYx3Lxp3j4vEphjn/u+DW7bWloST2O8ID2OuVlt6+28ftNEEjmQQ=="], + + "@atproto/common-web/@atproto/syntax": ["@atproto/syntax@0.7.2", "", { "dependencies": { "iso-datestring-validator": "^2.2.2", "tslib": "^2.8.1" } }, "sha512-tZ1Tr0R9pK4bI4Zs69t29cjMlCFQvRNBeNkqJC5pGeNuCt64D0eoF7s/AlqeVmYppClmQ0xJquggGgsMDP6j7w=="], + + "@atproto/lex-resolver/@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.3.7", "", { "dependencies": { "@atproto-labs/fetch": "^0.3.5", "@atproto-labs/pipe": "^0.2.4", "@atproto-labs/simple-store": "^0.5.1", "@atproto-labs/simple-store-memory": "^0.2.6", "@atproto/did": "^0.5.4", "zod": "^3.23.8" } }, "sha512-F3M3U5cU1QSAXX3J5auGEc5N2pgDgpirAjvyDFsytXuyWfrjWfTPd/H+DZrrED7gK+noW0cAWtxxjdX7/cTSVQ=="], + + "@atproto/lexicon/@atproto/syntax": ["@atproto/syntax@0.7.2", "", { "dependencies": { "iso-datestring-validator": "^2.2.2", "tslib": "^2.8.1" } }, "sha512-tZ1Tr0R9pK4bI4Zs69t29cjMlCFQvRNBeNkqJC5pGeNuCt64D0eoF7s/AlqeVmYppClmQ0xJquggGgsMDP6j7w=="], + + "@atproto/repo/@atproto/common-web": ["@atproto/common-web@0.5.9", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "@atproto/lex-json": "^0.1.6", "@atproto/syntax": "^0.7.4", "zod": "^3.23.8" } }, "sha512-2c5C6YV8352JJz9Z5fNfcsstllaKGAb3cQW9aO7unMAGh/FzTGFq+xOwNMW2lTeagO6z530uxYm8AiSRM6O+DQ=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/generator/@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], @@ -1197,6 +1214,8 @@ "astro/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + "at-astro/@atproto/oauth-client": ["@atproto/oauth-client@0.8.3", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.3.7", "@atproto-labs/fetch": "^0.3.5", "@atproto-labs/handle-resolver": "^0.4.8", "@atproto-labs/identity-resolver": "^0.4.7", "@atproto-labs/simple-store": "^0.5.1", "@atproto-labs/simple-store-memory": "^0.2.6", "@atproto/did": "^0.5.4", "@atproto/jwk": "^0.7.4", "@atproto/oauth-types": "^0.7.5", "@atproto/xrpc": "^0.8.10", "core-js": "^3", "multiformats": "^13.0.0", "zod": "^3.23.8" } }, "sha512-26ise6t6jvsCURx6ykR5UiQZjwSDChm1pRFZ0EZ2Cr1aqFXTK0v3pUtzqTDUxSR/i9gSCtBygHrgIV2DfW9roA=="], + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], @@ -1221,6 +1240,26 @@ "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], + "@atproto/lex-resolver/@atproto-labs/did-resolver/@atproto-labs/fetch": ["@atproto-labs/fetch@0.3.5", "", { "dependencies": { "@atproto-labs/pipe": "^0.2.4" } }, "sha512-iDFZEoNqfL17EVKE+uNzdUD7YF8LWhEhxeBtP6x+PNuAxoXv3ip9vLmB8nNzNxFwVn++FipfDtSiPqmziv1bdA=="], + + "@atproto/lex-resolver/@atproto-labs/did-resolver/@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.5.1", "", {}, "sha512-vfvoDhu6ds6BT3Pqe+d2/LBU1WpDRtt69y6zltlfMCoucPm82m1j50/Wb6xTvv+oZ+2j0JyZVXsmXQ12SWYQJg=="], + + "@atproto/lex-resolver/@atproto-labs/did-resolver/@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.2.6", "", { "dependencies": { "@atproto-labs/simple-store": "^0.5.1", "lru-cache": "^10.2.0" } }, "sha512-DD1v7MEfYF3BAcEpMTTpzfBLWLoI2HuyBhku2YpjHoqPrRrhCtE1alkxfPHjtjJVIjuU/XNU0cF/wB3+5FiKsA=="], + + "at-astro/@atproto/oauth-client/@atproto-labs/did-resolver": ["@atproto-labs/did-resolver@0.3.7", "", { "dependencies": { "@atproto-labs/fetch": "^0.3.5", "@atproto-labs/pipe": "^0.2.4", "@atproto-labs/simple-store": "^0.5.1", "@atproto-labs/simple-store-memory": "^0.2.6", "@atproto/did": "^0.5.4", "zod": "^3.23.8" } }, "sha512-F3M3U5cU1QSAXX3J5auGEc5N2pgDgpirAjvyDFsytXuyWfrjWfTPd/H+DZrrED7gK+noW0cAWtxxjdX7/cTSVQ=="], + + "at-astro/@atproto/oauth-client/@atproto-labs/fetch": ["@atproto-labs/fetch@0.3.5", "", { "dependencies": { "@atproto-labs/pipe": "^0.2.4" } }, "sha512-iDFZEoNqfL17EVKE+uNzdUD7YF8LWhEhxeBtP6x+PNuAxoXv3ip9vLmB8nNzNxFwVn++FipfDtSiPqmziv1bdA=="], + + "at-astro/@atproto/oauth-client/@atproto-labs/handle-resolver": ["@atproto-labs/handle-resolver@0.4.8", "", { "dependencies": { "@atproto-labs/simple-store": "^0.5.1", "@atproto-labs/simple-store-memory": "^0.2.6", "@atproto/did": "^0.5.4", "zod": "^3.23.8" } }, "sha512-38I+j8Efs+cCgW/NX9RFKKu7qv/AF+FqxlKS8sWjXlNZSHQZqPj5cnKVQhSsJCs4ypPB84iKch8w4/STd33bLg=="], + + "at-astro/@atproto/oauth-client/@atproto-labs/identity-resolver": ["@atproto-labs/identity-resolver@0.4.7", "", { "dependencies": { "@atproto-labs/did-resolver": "^0.3.7", "@atproto-labs/handle-resolver": "^0.4.8" } }, "sha512-lKDaiBHoxrqTOFdSKAAPpplEtbbFrH4djGjnXJtOSMnZ46k7+G7AkCr1xycGGPOBYfyF54zRnTp0Tt8iu86qWg=="], + + "at-astro/@atproto/oauth-client/@atproto-labs/simple-store": ["@atproto-labs/simple-store@0.5.1", "", {}, "sha512-vfvoDhu6ds6BT3Pqe+d2/LBU1WpDRtt69y6zltlfMCoucPm82m1j50/Wb6xTvv+oZ+2j0JyZVXsmXQ12SWYQJg=="], + + "at-astro/@atproto/oauth-client/@atproto-labs/simple-store-memory": ["@atproto-labs/simple-store-memory@0.2.6", "", { "dependencies": { "@atproto-labs/simple-store": "^0.5.1", "lru-cache": "^10.2.0" } }, "sha512-DD1v7MEfYF3BAcEpMTTpzfBLWLoI2HuyBhku2YpjHoqPrRrhCtE1alkxfPHjtjJVIjuU/XNU0cF/wB3+5FiKsA=="], + + "at-astro/@atproto/oauth-client/@atproto/xrpc": ["@atproto/xrpc@0.8.10", "", { "dependencies": { "@atproto/lexicon": "^0.7.11", "zod": "^3.23.8" } }, "sha512-++FNpTVF61fF8+3Kk1RYtdTO7xyUTdIc/Cmfk2CMn29Qw9tZD8bmW+M5db1Z0okYWFWK8Wqu7bc9VmF8tUB8Jg=="], + "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "miniflare/sharp/@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], @@ -1277,6 +1316,10 @@ "vscode-languageserver/vscode-languageserver-protocol/vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + "at-astro/@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon": ["@atproto/lexicon@0.7.11", "", { "dependencies": { "@atproto/common-web": "^0.5.9", "@atproto/syntax": "^0.7.4", "multiformats": "^13.0.0", "zod": "^3.23.8" } }, "sha512-ukCZMCWmiu7fAlssBbLs+IMaf2K2R9mADaTKz2jEeC/xeZ3jT77Ka0S2G+ELBB+Dc9ijBACU0iPMQLhquOkuzQ=="], + "miniflare/sharp/@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "at-astro/@atproto/oauth-client/@atproto/xrpc/@atproto/lexicon/@atproto/common-web": ["@atproto/common-web@0.5.9", "", { "dependencies": { "@atproto/lex-data": "^0.1.7", "@atproto/lex-json": "^0.1.6", "@atproto/syntax": "^0.7.4", "zod": "^3.23.8" } }, "sha512-2c5C6YV8352JJz9Z5fNfcsstllaKGAb3cQW9aO7unMAGh/FzTGFq+xOwNMW2lTeagO6z530uxYm8AiSRM6O+DQ=="], } } diff --git a/package.json b/package.json index 96163ab..e390b73 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,12 @@ "@astrojs/cloudflare": "^14.1.3", "@astrojs/react": "^6.0.2", "@atproto/jwk-webcrypto": "^0.3.4", - "@atproto/lex": "^0.3.0", + "@atproto/lex": "^0.3.6", "@atproto/oauth-client": "^0.7.11", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", "astro": "^7.1.1", + "at-astro": "^1.0.1", "lucide-react": "^1.28.0", "react": "^19.2.8", "react-dom": "^19.2.8", diff --git a/src/actions/images.ts b/src/actions/images.ts index 9895602..9fba693 100644 --- a/src/actions/images.ts +++ b/src/actions/images.ts @@ -2,21 +2,12 @@ import { getBlobCidString, l } from "@atproto/lex" import { env } from "cloudflare:workers" import { ActionError, defineAction, type ActionAPIContext } from "astro:actions" import imageRecord from "../lexicons/pics/pixl/image" -import { getAtprotoClient } from "../lib/atproto-client" -import { getOAuthOrigin } from "../lib/atproto-oauth" import { imageDeletion, imageGetParams, ImageQualitySchema, imageUpload } from "./image-contract" import { z } from "astro/zod" -async function requireClient({ request, session, site }: ActionAPIContext) { - if (!session) { - throw new ActionError({ - code: "INTERNAL_SERVER_ERROR", - message: "Astro sessions are not configured", - }) - } - - const client = await getAtprotoClient(getOAuthOrigin(request, site), session) - if (!client) { +async function requireClient({ locals }: ActionAPIContext) { + const { client, did } = await locals.getATProtoClient() + if (did == null) { throw new ActionError({ code: "UNAUTHORIZED", message: "Sign in to manage images", diff --git a/src/env.d.ts b/src/env.d.ts deleted file mode 100644 index 216f772..0000000 --- a/src/env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare namespace App { - interface SessionData { - "pixl:did": string - } -} diff --git a/src/lib/atproto-client.ts b/src/lib/atproto-client.ts deleted file mode 100644 index 9d9dce6..0000000 --- a/src/lib/atproto-client.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Client } from "@atproto/lex" -import type { APIContext, AstroSession } from "astro" -import { getOAuthClient, getOAuthOrigin } from "./atproto-oauth" - -export async function getAtprotoClient(origin: string, session: AstroSession) { - const did = await session.get("pixl:did") - if (!did) return null - - const oauthSession = await getOAuthClient(origin, session).restore(did) - return new Client(oauthSession, { validateRequest: true }) -} - -export async function isSignedIn({ - request, - session, - site, -}: Pick): Promise< - { signedIn: true; did: string } | { signedIn: false } -> { - const isSignedIn = - session != null && (await getAtprotoClient(getOAuthOrigin(request, site), session)) != null - if (isSignedIn) { - const did = await session.get("pixl:did") - return { signedIn: true, did: did! } - } - return { signedIn: false } -} diff --git a/src/lib/atproto-oauth.ts b/src/lib/atproto-oauth.ts deleted file mode 100644 index 53c5702..0000000 --- a/src/lib/atproto-oauth.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { OAuthClient, type Key, type OAuthClientMetadataInput } from "@atproto/oauth-client" -import { WebcryptoKey } from "@atproto/jwk-webcrypto" -import type { SimpleStore } from "@atproto-labs/simple-store" -import type { AstroSession } from "astro" -import * as _ from "./patched-fetch.ts" - -async function importDpopKey(jwk: JsonWebKey) { - if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.d) { - throw new Error("Invalid saved DPoP key") - } - const algorithm = { name: "ECDSA", namedCurve: "P-256" } - const privateKey = await crypto.subtle.importKey("jwk", jwk, algorithm, true, ["sign"]) - const { d: _, key_ops: __, ...publicJwk } = jwk - const publicKey = await crypto.subtle.importKey( - "jwk", - { ...publicJwk, key_ops: ["verify"] }, - algorithm, - true, - ["verify"], - ) - return WebcryptoKey.fromKeypair({ privateKey, publicKey }) -} - -function oauthStore( - prefix: string, - session: AstroSession, - ttl?: number, -): SimpleStore { - return { - async get(key: string) { - const value = await session.get(`${prefix}${key}`) - if (!value) return undefined - const { dpopJwk, ...data } = value - return { ...data, dpopKey: await importDpopKey(dpopJwk) } as T - }, - async set(key: string, value: T) { - const dpopJwk = value.dpopKey.privateJwk - if (!dpopJwk) throw new Error("DPoP key is not private") - const { dpopKey: _, ...data } = value - session.set(`${prefix}${key}`, { ...data, dpopJwk }, ttl ? { ttl } : undefined) - }, - del(key: string) { - return session.delete(`${prefix}${key}`) - }, - } -} - -export function getOAuthMetadata(origin: string) { - const isDev = import.meta.env.DEV - const scope = "atproto repo:pics.pixl.image blob:image/*" - const callback = new URL("/oauth/callback", origin) - if (isDev) callback.hostname = "127.0.0.1" - const clientId = isDev - ? `http://localhost?redirect_uri=${encodeURIComponent(callback.href)}&scope=${encodeURIComponent(scope)}` - : `${origin}/oauth-client-metadata.json` - - return { - client_id: clientId, - client_name: "pixl", - client_uri: origin, - redirect_uris: [callback.href], - grant_types: ["authorization_code", "refresh_token"], - response_types: ["code"], - application_type: "web" as const, - token_endpoint_auth_method: "none" as const, - dpop_bound_access_tokens: true, - scope, - } satisfies OAuthClientMetadataInput -} - -export function getOAuthOrigin(request: Request, site?: URL) { - if (import.meta.env.DEV) return new URL(request.url).origin - if (!site) throw new Error("Astro site is not configured") - return site.origin -} - -export function getOAuthClient(origin: string, session: AstroSession) { - return new OAuthClient({ - responseMode: "query", - clientMetadata: getOAuthMetadata(origin), - handleResolver: "https://bsky.chrisvanderloo.com", - stateStore: oauthStore("pixl:oauth-state:", session, 3600), - sessionStore: oauthStore("pixl:oauth-session:", session), - runtimeImplementation: { - createKey: (algorithms) => - WebcryptoKey.generate(algorithms, undefined, { extractable: true }), - getRandomValues: (length) => crypto.getRandomValues(new Uint8Array(length)), - digest: async (data, { name }) => { - const algorithm = { sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512" }[name] - return new Uint8Array(await crypto.subtle.digest(algorithm, data)) - }, - }, - }) -} diff --git a/src/lib/patched-fetch.ts b/src/lib/patched-fetch.ts deleted file mode 100644 index 8f79c8b..0000000 --- a/src/lib/patched-fetch.ts +++ /dev/null @@ -1,19 +0,0 @@ -const _fetch = globalThis.fetch -// @ts-ignore -globalThis.fetch = (input, init) => { - if (init?.redirect === "error") init = { ...init, redirect: "manual" } - if (input instanceof Request && input.redirect === "error") { - input = new Request(input, { redirect: "manual" }) - } - return _fetch(input, init) -} - -const _Request = globalThis.Request -globalThis.Request = class extends _Request { - constructor(input: RequestInfo | URL, init?: RequestInit) { - if (init?.redirect === "error") init = { ...init, redirect: "manual" } - super(input, init) - } -} - -export {} diff --git a/src/pages/index.astro b/src/pages/index.astro index fe8c496..93eb302 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -2,10 +2,9 @@ import BaseLayout from "../layouts/BaseLayout.astro"; import ImageGallery from "../components/ImageGallery.astro"; import UploadForm from "../components/UploadForm"; -import { isSignedIn } from "../lib/atproto-client"; -const { signedIn }= await isSignedIn(Astro) -if (!signedIn) return Astro.redirect("/sign-in") +const { did } = await Astro.locals.getATProtoClient() +if (did == null) return Astro.redirect("/sign-in") --- @@ -21,7 +20,7 @@ if (!signedIn) return Astro.redirect("/sign-in")
diff --git a/src/pages/oauth-client-metadata.json.ts b/src/pages/oauth-client-metadata.json.ts deleted file mode 100644 index 6aa8159..0000000 --- a/src/pages/oauth-client-metadata.json.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { APIRoute } from "astro" -import { getOAuthMetadata, getOAuthOrigin } from "../lib/atproto-oauth" - -export const GET: APIRoute = ({ request, site }) => { - return Response.json(getOAuthMetadata(getOAuthOrigin(request, site))) -} diff --git a/src/pages/oauth/callback.ts b/src/pages/oauth/callback.ts deleted file mode 100644 index 5de0f96..0000000 --- a/src/pages/oauth/callback.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { APIRoute } from "astro" -import { getOAuthClient, getOAuthOrigin } from "../../lib/atproto-oauth" - -export const GET: APIRoute = async ({ request, session, site }) => { - if (!session) return new Response("Astro sessions are not configured", { status: 500 }) - - const origin = getOAuthOrigin(request, site) - const client = getOAuthClient(origin, session) - const { session: oauthSession } = await client.callback(new URL(request.url).searchParams) - session.set("pixl:did", oauthSession.did, { ttl: 60 * 60 * 24 * 30 }) - - return new Response(null, { status: 302, headers: { location: origin } }) -} diff --git a/src/pages/oauth/login.ts b/src/pages/oauth/login.ts deleted file mode 100644 index 39d6a28..0000000 --- a/src/pages/oauth/login.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { APIRoute } from "astro" -import { getOAuthClient, getOAuthOrigin } from "../../lib/atproto-oauth" - -export const POST: APIRoute = async ({ request, session, site }) => { - if (!session) return new Response("Astro sessions are not configured", { status: 500 }) - - const handle = (await request.formData()).get("handle") - if (typeof handle !== "string" || !handle.trim()) { - return new Response("An AT Protocol handle is required", { status: 400 }) - } - - const origin = getOAuthOrigin(request, site) - const client = getOAuthClient(origin, session) - const redirect = await client.authorize(handle.trim()) - return new Response(null, { status: 302, headers: { location: redirect.toString() } }) -} diff --git a/src/pages/oauth/logout.ts b/src/pages/oauth/logout.ts deleted file mode 100644 index 2f0b0d8..0000000 --- a/src/pages/oauth/logout.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { APIRoute } from "astro" -import { getOAuthClient, getOAuthOrigin } from "../../lib/atproto-oauth" - -export const POST: APIRoute = async ({ request, session, site }) => { - if (!session) return new Response("Astro sessions are not configured", { status: 500 }) - - const did = await session.get("pixl:did") - const origin = getOAuthOrigin(request, site) - - try { - if (did) await getOAuthClient(origin, session).revoke(did) - } finally { - session.destroy() - } - - return new Response(null, { status: 302, headers: { location: origin } }) -} diff --git a/src/pages/sign-in.astro b/src/pages/sign-in.astro index 8874abf..b5da727 100644 --- a/src/pages/sign-in.astro +++ b/src/pages/sign-in.astro @@ -1,9 +1,8 @@ --- import BaseLayout from "../layouts/BaseLayout.astro"; -import { isSignedIn } from "../lib/atproto-client"; -const { signedIn }= await isSignedIn(Astro) -if (signedIn) return Astro.redirect("/") +const { did } = await Astro.locals.getATProtoClient() +if (did == null) return Astro.redirect("/") ---