This commit is contained in:
dholms 2023-05-10 23:20:44 -05:00
parent 2a13e9e97c
commit 84420cc7aa
8 changed files with 115 additions and 20 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
node_modules node_modules
.env .env
test.sqlite

View File

@ -10,9 +10,10 @@
"start": "ts-node src/index.ts" "start": "ts-node src/index.ts"
}, },
"dependencies": { "dependencies": {
"@atproto/did-resolver": "^0.1.0",
"@atproto/repo": "^0.1.0", "@atproto/repo": "^0.1.0",
"@atproto/uri": "^0.0.2", "@atproto/uri": "^0.0.2",
"@atproto/xrpc-server": "^0.1.0", "@atproto/xrpc-server": "^0.2.0",
"better-sqlite3": "^8.3.0", "better-sqlite3": "^8.3.0",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"express": "^4.18.2", "express": "^4.18.2",

18
src/auth.ts Normal file
View File

@ -0,0 +1,18 @@
import express from 'express'
import { verifyJwt, AuthRequiredError } from '@atproto/xrpc-server'
import { DidResolver } from '@atproto/did-resolver'
export const validateAuth = async (
req: express.Request,
serviceDid: string,
didResolver: DidResolver,
): Promise<string> => {
const { authorization = '' } = req.headers
if (!authorization.startsWith('Bearer ')) {
throw new AuthRequiredError()
}
const jwt = authorization.replace('Bearer ', '').trim()
return verifyJwt(jwt, serviceDid, async (did: string) => {
return didResolver.resolveAtprotoKey(did)
})
}

15
src/config.ts Normal file
View File

@ -0,0 +1,15 @@
import { Database } from './db'
import { DidResolver } from '@atproto/did-resolver'
export type AppContext = {
db: Database
didResolver: DidResolver
cfg: Config
}
export type Config = {
port: number
sqliteLocation: string
subscriptionEndpoint: string
serviceDid: string
}

View File

@ -1,13 +1,22 @@
import { InvalidRequestError } from '@atproto/xrpc-server' import { InvalidRequestError } from '@atproto/xrpc-server'
import { Database } from './db'
import { Server } from './lexicon' import { Server } from './lexicon'
import { AppContext } from './config'
import { validateAuth } from './auth'
export default function (server: Server, db: Database) { export default function (server: Server, ctx: AppContext) {
server.app.bsky.feed.getFeedSkeleton(async ({ params, auth }) => { server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => {
if (params.feed !== 'alf.bsky.social') { if (params.feed !== 'did:example:alice/app.bsky.feed.generator/whats-alf') {
throw new InvalidRequestError('algorithm unsupported') throw new InvalidRequestError('algorithm unsupported')
} }
let builder = db // example of how to check auth
// feel free to remove if requesterDid is not used
const requesterDid = await validateAuth(
req,
ctx.cfg.serviceDid,
ctx.didResolver,
)
let builder = ctx.db
.selectFrom('post') .selectFrom('post')
.selectAll() .selectAll()
.orderBy('indexedAt', 'desc') .orderBy('indexedAt', 'desc')

View File

@ -1,16 +1,12 @@
import http from 'http' import http from 'http'
import events from 'events' import events from 'events'
import express from 'express' import express from 'express'
import { DidResolver, MemoryCache } from '@atproto/did-resolver'
import { createServer } from './lexicon' import { createServer } from './lexicon'
import feedGeneration from './feed-generation' import feedGeneration from './feed-generation'
import { createDb, Database, migrateToLatest } from './db' import { createDb, Database, migrateToLatest } from './db'
import { FirehoseSubscription } from './subscription' import { FirehoseSubscription } from './subscription'
import { AppContext, Config } from './config'
export type Config = {
port: number
sqliteLocation: string
subscriptionEndpoint: string
}
export class FeedGenerator { export class FeedGenerator {
public app: express.Application public app: express.Application
@ -36,11 +32,18 @@ export class FeedGenerator {
port: config?.port ?? 3000, port: config?.port ?? 3000,
sqliteLocation: config?.sqliteLocation ?? 'test.sqlite', sqliteLocation: config?.sqliteLocation ?? 'test.sqlite',
subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social',
serviceDid: config?.serviceDid ?? 'did:example:test',
} }
const app = express() const app = express()
const db = createDb(cfg.sqliteLocation) const db = createDb(cfg.sqliteLocation)
const firehose = new FirehoseSubscription(db, cfg.subscriptionEndpoint) const firehose = new FirehoseSubscription(db, cfg.subscriptionEndpoint)
const didCache = new MemoryCache()
const didResolver = new DidResolver(
{ plcUrl: 'https://plc.directory' },
didCache,
)
const server = createServer({ const server = createServer({
validateResponse: true, validateResponse: true,
payload: { payload: {
@ -49,7 +52,12 @@ export class FeedGenerator {
blobLimit: 5 * 1024 * 1024, // 5mb blobLimit: 5 * 1024 * 1024, // 5mb
}, },
}) })
feedGeneration(server, db) const ctx: AppContext = {
db,
didResolver,
cfg,
}
feedGeneration(server, ctx)
app.use(server.xrpc.router) app.use(server.xrpc.router)
return new FeedGenerator(app, db, firehose, cfg) return new FeedGenerator(app, db, firehose, cfg)

View File

View File

@ -43,6 +43,16 @@
axios "^0.24.0" axios "^0.24.0"
did-resolver "^4.0.0" did-resolver "^4.0.0"
"@atproto/did-resolver@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@atproto/did-resolver/-/did-resolver-0.1.0.tgz#58f42447700aaad61bad2f0d70b721966268aa02"
integrity sha512-ztljyMMCqXvJSi/Qqa2zEQFvOm1AUUR7Bybr3cM1BCddbhW46gk6/g8BgdZeDt2sMBdye37qTctR9O/FjhigvQ==
dependencies:
"@atproto/common-web" "*"
"@atproto/crypto" "*"
axios "^0.27.2"
zod "^3.14.2"
"@atproto/identifier@*": "@atproto/identifier@*":
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/@atproto/identifier/-/identifier-0.1.0.tgz#6b600c8a3da08d9a7d5eab076f8b7064457dde75" resolved "https://registry.yarnpkg.com/@atproto/identifier/-/identifier-0.1.0.tgz#6b600c8a3da08d9a7d5eab076f8b7064457dde75"
@ -92,12 +102,13 @@
"@atproto/identifier" "*" "@atproto/identifier" "*"
"@atproto/nsid" "*" "@atproto/nsid" "*"
"@atproto/xrpc-server@^0.1.0": "@atproto/xrpc-server@^0.2.0":
version "0.1.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.1.0.tgz#2dd3172bb35fbfefb98c3d727d29be8eca5c3d9b" resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.2.0.tgz#a36616c2ac70339cd79cda83ede0a0b305c74f9b"
integrity sha512-I7EjhnLUrlqQKTe2jDEnyAaOTvj26pg9NRjTXflbIOqCOkh+K9+5ztGSI0djF7TSQ7pegXroj3qRnmpVVCBr7Q== integrity sha512-sCJuVUIb1tDIlKCFwHPRHbAgEy0HYGlQ7XhpNqMRKXECh8Z+DRICEne3gLDVaXhyNaC/N7OjHcsyuofDDbuGFQ==
dependencies: dependencies:
"@atproto/common" "*" "@atproto/common" "*"
"@atproto/crypto" "*"
"@atproto/lexicon" "*" "@atproto/lexicon" "*"
cbor-x "^1.5.1" cbor-x "^1.5.1"
express "^4.17.2" express "^4.17.2"
@ -322,6 +333,11 @@ array-flatten@1.1.1:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
atomic-sleep@^1.0.0: atomic-sleep@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
@ -334,6 +350,14 @@ axios@^0.24.0:
dependencies: dependencies:
follow-redirects "^1.14.4" follow-redirects "^1.14.4"
axios@^0.27.2:
version "0.27.2"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972"
integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==
dependencies:
follow-redirects "^1.14.9"
form-data "^4.0.0"
base64-js@^1.3.1: base64-js@^1.3.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
@ -446,6 +470,13 @@ chownr@^1.1.1:
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
content-disposition@0.5.4: content-disposition@0.5.4:
version "0.5.4" version "0.5.4"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
@ -492,6 +523,11 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@2.0.0: depd@2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
@ -624,11 +660,20 @@ finalhandler@1.2.0:
statuses "2.0.1" statuses "2.0.1"
unpipe "~1.0.0" unpipe "~1.0.0"
follow-redirects@^1.14.4: follow-redirects@^1.14.4, follow-redirects@^1.14.9:
version "1.15.2" version "1.15.2"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
form-data@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
mime-types "^2.1.12"
forwarded@0.2.0: forwarded@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
@ -755,7 +800,7 @@ mime-db@1.52.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35" version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==