diff --git a/package.json b/package.json index 2423c8c8..e41ecb77 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "@atproto/lexicon": "^0.2.2", "@atproto/repo": "^0.3.2", "@atproto/syntax": "^0.1.2", - "@atproto/xrpc-server": "^0.3.2", + "@atproto/xrpc-server": "^0.6.0", "better-sqlite3": "^8.3.0", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/scripts/publishFeedGen.ts b/scripts/publishFeedGen.ts new file mode 100644 index 00000000..587db4da --- /dev/null +++ b/scripts/publishFeedGen.ts @@ -0,0 +1,81 @@ +import dotenv from 'dotenv' +import { AtpAgent, BlobRef } from '@atproto/api' +import fs from 'fs/promises' +import { ids } from '../src/lexicon/lexicons' + +const run = async () => { + dotenv.config() + + // YOUR bluesky handle + // Ex: user.bsky.social + const handle = '' + + // YOUR bluesky password, or preferably an App Password (found in your client settings) + // Ex: abcd-1234-efgh-5678 + const password = '' + + // A short name for the record that will show in urls + // Lowercase with no spaces. + // Ex: whats-hot + const recordName = '' + + // A display name for your feed + // Ex: What's Hot + const displayName = '' + + // (Optional) A description of your feed + // Ex: Top trending content from the whole network + const description = '' + + // (Optional) The path to an image to be used as your feed's avatar + // Ex: ~/path/to/avatar.jpeg + const avatar: string = '' + + // ------------------------------------- + // NO NEED TO TOUCH ANYTHING BELOW HERE + // ------------------------------------- + + if (!process.env.FEEDGEN_SERVICE_DID && !process.env.FEEDGEN_HOSTNAME) { + throw new Error('Please provide a hostname in the .env file') + } + const feedGenDid = + process.env.FEEDGEN_SERVICE_DID ?? `did:web:${process.env.FEEDGEN_HOSTNAME}` + + // only update this if in a test environment + const agent = new AtpAgent({ service: 'https://bsky.social' }) + await agent.login({ identifier: handle, password }) + + let avatarRef: BlobRef | undefined + if (avatar) { + let encoding: string + if (avatar.endsWith('png')) { + encoding = 'image/png' + } else if (avatar.endsWith('jpg') || avatar.endsWith('jpeg')) { + encoding = 'image/jpeg' + } else { + throw new Error('expected png or jpeg') + } + const img = await fs.readFile(avatar) + const blobRes = await agent.api.com.atproto.repo.uploadBlob(img, { + encoding, + }) + avatarRef = blobRes.data.blob + } + + await agent.api.com.atproto.repo.putRecord({ + repo: agent.session?.did ?? '', + collection: ids.AppBskyFeedGenerator, + rkey: recordName, + record: { + did: feedGenDid, + displayName: displayName, + description: description, + avatar: avatarRef, + createdAt: new Date().toISOString(), + }, + }) + + console.log('All done 🎉') +} + +run() diff --git a/src/auth.ts b/src/auth.ts index eef583e5..a93ca340 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,5 +1,5 @@ import express from 'express' -import { verifyJwt, AuthRequiredError } from '@atproto/xrpc-server' +import { verifyJwt, AuthRequiredError, parseReqNsid } from '@atproto/xrpc-server' import { DidResolver } from '@atproto/identity' export const validateAuth = async ( @@ -12,7 +12,9 @@ export const validateAuth = async ( throw new AuthRequiredError() } const jwt = authorization.replace('Bearer ', '').trim() - return verifyJwt(jwt, serviceDid, async (did: string) => { + const nsid = parseReqNsid(req) + const parsed = await verifyJwt(jwt, serviceDid, nsid, async (did: string) => { return didResolver.resolveAtprotoKey(did) }) + return parsed.iss } diff --git a/yarn.lock b/yarn.lock index bb48a321..2d9fdfef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -25,6 +25,16 @@ uint8arrays "3.0.0" zod "^3.21.4" +"@atproto/common-web@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.0.tgz#36da8c2c31d8cf8a140c3c8f03223319bf4430bb" + integrity sha512-67VnV6JJyX+ZWyjV7xFQMypAgDmjVaR9ZCuU/QW+mqlqI7fex2uL4Fv+7/jHadgzhuJHVd6OHOvNn0wR5WZYtA== + dependencies: + graphemer "^1.4.0" + multiformats "^9.9.0" + uint8arrays "3.0.0" + zod "^3.21.4" + "@atproto/common@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.3.1.tgz#ee131c170bdb564ed4f9692db0a80ada825220c7" @@ -38,6 +48,18 @@ pino "^8.15.0" zod "3.21.4" +"@atproto/common@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.4.1.tgz#ca6fce47001ce8d031acd3fb4942fbfd81f72c43" + integrity sha512-uL7kQIcBTbvkBDNfxMXL6lBH4fO2DQpHd2BryJxMtbw/4iEPKe9xBYApwECHhEIk9+zhhpTRZ15FJ3gxTXN82Q== + dependencies: + "@atproto/common-web" "^0.3.0" + "@ipld/dag-cbor" "^7.0.3" + cbor-x "^1.5.1" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + pino "^8.21.0" + "@atproto/crypto@^0.2.2": version "0.2.2" resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.2.2.tgz#9832dda885512a36401d24f95990489f521593ef" @@ -47,6 +69,15 @@ "@noble/hashes" "^1.3.1" uint8arrays "3.0.0" +"@atproto/crypto@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@atproto/crypto/-/crypto-0.4.0.tgz#dcdd6bf5ba98261ae0ff3b96d7b8695c1ef788e6" + integrity sha512-Kj/4VgJ7hzzXvE42L0rjzP6lM0tai+OfPnP1rxJ+UZg/YUDtuewL4uapnVoWXvlNceKgaLZH98g5n9gXBVTe5Q== + dependencies: + "@noble/curves" "^1.1.0" + "@noble/hashes" "^1.3.1" + uint8arrays "3.0.0" + "@atproto/identity@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@atproto/identity/-/identity-0.2.1.tgz#8203ba53a25c3300d1aec0c28eb10a106919b410" @@ -68,6 +99,17 @@ multiformats "^9.9.0" zod "^3.21.4" +"@atproto/lexicon@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.0.tgz#63e8829945d80c25524882caa8ed27b1151cc576" + integrity sha512-RvCBKdSI4M8qWm5uTNz1z3R2yIvIhmOsMuleOj8YR6BwRD+QbtUBy3l+xQ7iXf4M5fdfJFxaUNa6Ty0iRwdKqQ== + dependencies: + "@atproto/common-web" "^0.3.0" + "@atproto/syntax" "^0.3.0" + iso-datestring-validator "^2.2.2" + multiformats "^9.9.0" + zod "^3.21.4" + "@atproto/repo@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@atproto/repo/-/repo-0.3.2.tgz#46cd9f8a16b82de7fda6760e611999a9d42f5545" @@ -92,14 +134,20 @@ dependencies: "@atproto/common-web" "^0.2.1" -"@atproto/xrpc-server@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.3.2.tgz#85bc901991c3de896d463c0cd1d396ab1c772387" - integrity sha512-aracV1+1t88AU+zN/RTmZGvvjlMiNIooLotz23FvD4qoiUSx6KxrTwttHZdw5ZJE14XTDR1D9brpyvdCJHsKNA== +"@atproto/syntax@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.0.tgz#fafa2dbea9add37253005cb663e7373e05e618b3" + integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA== + +"@atproto/xrpc-server@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.6.0.tgz#00ce91af703287d8a93ea46b20aa18debee0aa24" + integrity sha512-c0UhLQIjkVGxcRIbWLEjJsW0NzKs9uIIUYQWJ27zUUAet5tzgYOyTDuZ5v8FvAJ4wkfJUIZH2GazqxrQDW4G3g== dependencies: - "@atproto/common" "^0.3.1" - "@atproto/crypto" "^0.2.2" - "@atproto/lexicon" "^0.2.2" + "@atproto/common" "^0.4.1" + "@atproto/crypto" "^0.4.0" + "@atproto/lexicon" "^0.4.0" + "@atproto/xrpc" "^0.5.0" cbor-x "^1.5.1" express "^4.17.2" http-errors "^2.0.0" @@ -107,7 +155,7 @@ rate-limiter-flexible "^2.4.1" uint8arrays "3.0.0" ws "^8.12.0" - zod "^3.21.4" + zod "^3.23.8" "@atproto/xrpc@^0.3.2": version "0.3.2" @@ -117,6 +165,14 @@ "@atproto/lexicon" "^0.2.2" zod "^3.21.4" +"@atproto/xrpc@^0.5.0": + version "0.5.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.5.0.tgz#dacbfd8f7b13f0ab5bd56f8fdd4b460e132a6032" + integrity sha512-swu+wyOLvYW4l3n+VAuJbHcPcES+tin2Lsrp8Bw5aIXIICiuFn1YMFlwK9JwVUzTH21Py1s1nHEjr4CJeElJog== + dependencies: + "@atproto/lexicon" "^0.4.0" + zod "^3.21.4" + "@cbor-extract/cbor-extract-darwin-arm64@2.1.1": version "2.1.1" resolved "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.1.1.tgz" @@ -887,6 +943,14 @@ path-to-regexp@0.1.7: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +pino-abstract-transport@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz#97f9f2631931e242da531b5c66d3079c12c9d1b5" + integrity sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q== + dependencies: + readable-stream "^4.0.0" + split2 "^4.0.0" + pino-abstract-transport@v1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" @@ -917,6 +981,23 @@ pino@^8.15.0: sonic-boom "^3.1.0" thread-stream "^2.0.0" +pino@^8.21.0: + version "8.21.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-8.21.0.tgz#e1207f3675a2722940d62da79a7a55a98409f00d" + integrity sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^1.2.0" + pino-std-serializers "^6.0.0" + process-warning "^3.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^3.7.0" + thread-stream "^2.6.0" + prebuild-install@^7.1.0: version "7.1.1" resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" @@ -940,6 +1021,11 @@ process-warning@^2.0.0: resolved "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz" integrity sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg== +process-warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" + integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== + process@^0.11.10: version "0.11.10" resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" @@ -1120,6 +1206,13 @@ sonic-boom@^3.1.0: dependencies: atomic-sleep "^1.0.0" +sonic-boom@^3.7.0: + version "3.8.1" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.1.tgz#d5ba8c4e26d6176c9a1d14d549d9ff579a163422" + integrity sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg== + dependencies: + atomic-sleep "^1.0.0" + split2@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz" @@ -1170,6 +1263,13 @@ thread-stream@^2.0.0: dependencies: real-require "^0.2.0" +thread-stream@^2.6.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.7.0.tgz#d8a8e1b3fd538a6cca8ce69dbe5d3d097b601e11" + integrity sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw== + dependencies: + real-require "^0.2.0" + tlds@^1.234.0: version "1.238.0" resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.238.0.tgz#ffe7c19c8940c35b497cda187a6927f9450325a4" @@ -1297,3 +1397,8 @@ zod@^3.21.4: version "3.22.2" resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b" integrity sha512-wvWkphh5WQsJbVk1tbx1l1Ly4yg+XecD+Mq280uBGt9wa5BKSWf4Mhp6GmrkPixhMxmabYY7RbzlwVP32pbGCg== + +zod@^3.23.8: + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==