From 84420cc7aa4d514d4d1282f21ddaf083096298d1 Mon Sep 17 00:00:00 2001 From: dholms Date: Wed, 10 May 2023 23:20:44 -0500 Subject: [PATCH] add auth --- .gitignore | 1 - package.json | 3 ++- src/auth.ts | 18 +++++++++++++ src/config.ts | 15 +++++++++++ src/feed-generation.ts | 19 ++++++++++---- src/server.ts | 22 ++++++++++------ test.sqlite | 0 yarn.lock | 57 +++++++++++++++++++++++++++++++++++++----- 8 files changed, 115 insertions(+), 20 deletions(-) create mode 100644 src/auth.ts create mode 100644 src/config.ts delete mode 100644 test.sqlite diff --git a/.gitignore b/.gitignore index 5999717..37d7e73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules .env -test.sqlite diff --git a/package.json b/package.json index 72a4e97..516cf93 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,10 @@ "start": "ts-node src/index.ts" }, "dependencies": { + "@atproto/did-resolver": "^0.1.0", "@atproto/repo": "^0.1.0", "@atproto/uri": "^0.0.2", - "@atproto/xrpc-server": "^0.1.0", + "@atproto/xrpc-server": "^0.2.0", "better-sqlite3": "^8.3.0", "dotenv": "^16.0.3", "express": "^4.18.2", diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000..9ccaa99 --- /dev/null +++ b/src/auth.ts @@ -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 => { + 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) + }) +} diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..e3576eb --- /dev/null +++ b/src/config.ts @@ -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 +} diff --git a/src/feed-generation.ts b/src/feed-generation.ts index 4a7aa64..c6d064a 100644 --- a/src/feed-generation.ts +++ b/src/feed-generation.ts @@ -1,13 +1,22 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { Database } from './db' import { Server } from './lexicon' +import { AppContext } from './config' +import { validateAuth } from './auth' -export default function (server: Server, db: Database) { - server.app.bsky.feed.getFeedSkeleton(async ({ params, auth }) => { - if (params.feed !== 'alf.bsky.social') { +export default function (server: Server, ctx: AppContext) { + server.app.bsky.feed.getFeedSkeleton(async ({ params, req }) => { + if (params.feed !== 'did:example:alice/app.bsky.feed.generator/whats-alf') { 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') .selectAll() .orderBy('indexedAt', 'desc') diff --git a/src/server.ts b/src/server.ts index 1a5c36b..059b805 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,16 +1,12 @@ import http from 'http' import events from 'events' import express from 'express' +import { DidResolver, MemoryCache } from '@atproto/did-resolver' import { createServer } from './lexicon' import feedGeneration from './feed-generation' import { createDb, Database, migrateToLatest } from './db' import { FirehoseSubscription } from './subscription' - -export type Config = { - port: number - sqliteLocation: string - subscriptionEndpoint: string -} +import { AppContext, Config } from './config' export class FeedGenerator { public app: express.Application @@ -36,11 +32,18 @@ export class FeedGenerator { port: config?.port ?? 3000, sqliteLocation: config?.sqliteLocation ?? 'test.sqlite', subscriptionEndpoint: config?.subscriptionEndpoint ?? 'wss://bsky.social', + serviceDid: config?.serviceDid ?? 'did:example:test', } const app = express() const db = createDb(cfg.sqliteLocation) const firehose = new FirehoseSubscription(db, cfg.subscriptionEndpoint) + const didCache = new MemoryCache() + const didResolver = new DidResolver( + { plcUrl: 'https://plc.directory' }, + didCache, + ) + const server = createServer({ validateResponse: true, payload: { @@ -49,7 +52,12 @@ export class FeedGenerator { blobLimit: 5 * 1024 * 1024, // 5mb }, }) - feedGeneration(server, db) + const ctx: AppContext = { + db, + didResolver, + cfg, + } + feedGeneration(server, ctx) app.use(server.xrpc.router) return new FeedGenerator(app, db, firehose, cfg) diff --git a/test.sqlite b/test.sqlite deleted file mode 100644 index e69de29..0000000 diff --git a/yarn.lock b/yarn.lock index 781bace..4cab0fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -43,6 +43,16 @@ axios "^0.24.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@*": version "0.1.0" resolved "https://registry.yarnpkg.com/@atproto/identifier/-/identifier-0.1.0.tgz#6b600c8a3da08d9a7d5eab076f8b7064457dde75" @@ -92,12 +102,13 @@ "@atproto/identifier" "*" "@atproto/nsid" "*" -"@atproto/xrpc-server@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.1.0.tgz#2dd3172bb35fbfefb98c3d727d29be8eca5c3d9b" - integrity sha512-I7EjhnLUrlqQKTe2jDEnyAaOTvj26pg9NRjTXflbIOqCOkh+K9+5ztGSI0djF7TSQ7pegXroj3qRnmpVVCBr7Q== +"@atproto/xrpc-server@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@atproto/xrpc-server/-/xrpc-server-0.2.0.tgz#a36616c2ac70339cd79cda83ede0a0b305c74f9b" + integrity sha512-sCJuVUIb1tDIlKCFwHPRHbAgEy0HYGlQ7XhpNqMRKXECh8Z+DRICEne3gLDVaXhyNaC/N7OjHcsyuofDDbuGFQ== dependencies: "@atproto/common" "*" + "@atproto/crypto" "*" "@atproto/lexicon" "*" cbor-x "^1.5.1" 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" 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: version "1.0.0" resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" @@ -334,6 +350,14 @@ axios@^0.24.0: dependencies: 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: version "1.5.1" 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" 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: version "0.5.4" 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" 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: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -624,11 +660,20 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -follow-redirects@^1.14.4: +follow-redirects@^1.14.4, follow-redirects@^1.14.9: version "1.15.2" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" 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: version "0.2.0" 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" 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" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==