diff --git a/packages/bsky/src/api/app/bsky/feed/getFeed.ts b/packages/bsky/src/api/app/bsky/feed/getFeed.ts index d997397e9a1..27da13439ab 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeed.ts @@ -6,11 +6,6 @@ import { serverTimingHeader, } from '@atproto/xrpc-server' import { ResponseType, XRPCError } from '@atproto/xrpc' -import { - DidDocument, - PoorlyFormattedDidDocumentError, - getFeedGen, -} from '@atproto/identity' import { AtpAgent, AppBskyFeedGetFeedSkeleton } from '@atproto/api' import { noUndefinedVals } from '@atproto/common' import { QueryParams as GetFeedParams } from '../../../../lexicon/types/app/bsky/feed/getFeed' @@ -26,6 +21,13 @@ import { createPipeline, } from '../../../../pipeline' import { FeedItem } from '../../../../hydration/feed' +import { GetIdentityByDidResponse } from '../../../../proto/bsky_pb' +import { + Code, + getServiceEndpoint, + isDataplaneError, + unpackIdentityServices, +} from '../../../../data-plane' export default function (server: Server, ctx: AppContext) { const getFeed = createPipeline( @@ -157,20 +159,21 @@ const skeletonFromFeedGen = async ( throw new InvalidRequestError('could not find feed') } - let resolved: DidDocument | null + let identity: GetIdentityByDidResponse try { - resolved = await ctx.idResolver.did.resolve(feedDid) + identity = await ctx.dataplane.getIdentityByDid({ did: feedDid }) } catch (err) { - if (err instanceof PoorlyFormattedDidDocumentError) { - throw new InvalidRequestError(`invalid did document: ${feedDid}`) + if (isDataplaneError(err, Code.NotFound)) { + throw new InvalidRequestError(`could not resolve identity: ${feedDid}`) } throw err } - if (!resolved) { - throw new InvalidRequestError(`could not resolve did document: ${feedDid}`) - } - const fgEndpoint = getFeedGen(resolved) + const services = unpackIdentityServices(identity.services) + const fgEndpoint = getServiceEndpoint(services, { + id: 'bsky_fg', + type: 'BskyFeedGenerator', + }) if (!fgEndpoint) { throw new InvalidRequestError( `invalid feed generator service details in did document: ${feedDid}`, diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts index 7c0ff2a7e13..57f86af9a28 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts @@ -1,11 +1,13 @@ import { InvalidRequestError } from '@atproto/xrpc-server' -import { - DidDocument, - PoorlyFormattedDidDocumentError, - getFeedGen, -} from '@atproto/identity' import { Server } from '../../../../lexicon' import AppContext from '../../../../context' +import { GetIdentityByDidResponse } from '../../../../proto/bsky_pb' +import { + Code, + getServiceEndpoint, + isDataplaneError, + unpackIdentityServices, +} from '../../../../data-plane' export default function (server: Server, ctx: AppContext) { server.app.bsky.feed.getFeedGenerator({ @@ -21,22 +23,23 @@ export default function (server: Server, ctx: AppContext) { } const feedDid = feedInfo.record.did - let resolved: DidDocument | null + let identity: GetIdentityByDidResponse try { - resolved = await ctx.idResolver.did.resolve(feedDid) + identity = await ctx.dataplane.getIdentityByDid({ did: feedDid }) } catch (err) { - if (err instanceof PoorlyFormattedDidDocumentError) { - throw new InvalidRequestError(`invalid did document: ${feedDid}`) + if (isDataplaneError(err, Code.NotFound)) { + throw new InvalidRequestError( + `could not resolve identity: ${feedDid}`, + ) } throw err } - if (!resolved) { - throw new InvalidRequestError( - `could not resolve did document: ${feedDid}`, - ) - } - const fgEndpoint = getFeedGen(resolved) + const services = unpackIdentityServices(identity.services) + const fgEndpoint = getServiceEndpoint(services, { + id: 'bsky_fg', + type: 'BskyFeedGenerator', + }) if (!fgEndpoint) { throw new InvalidRequestError( `invalid feed generator service details in did document: ${feedDid}`, diff --git a/packages/bsky/src/api/blob-resolver.ts b/packages/bsky/src/api/blob-resolver.ts index 8256025962d..1a2d1ee560d 100644 --- a/packages/bsky/src/api/blob-resolver.ts +++ b/packages/bsky/src/api/blob-resolver.ts @@ -9,6 +9,12 @@ import { DidNotFoundError } from '@atproto/identity' import AppContext from '../context' import { httpLogger as log } from '../logger' import { retryHttp } from '../util/retry' +import { + Code, + getServiceEndpoint, + isDataplaneError, + unpackIdentityServices, +} from '../data-plane' // Resolve and verify blob from its origin host @@ -77,10 +83,25 @@ export const createRouter = (ctx: AppContext): express.Router => { export async function resolveBlob(ctx: AppContext, did: string, cid: CID) { const cidStr = cid.toString() - const [{ pds }, { takenDown }] = await Promise.all([ - ctx.idResolver.did.resolveAtprotoData(did), + const [identity, { takenDown }] = await Promise.all([ + ctx.dataplane.getIdentityByDid({ did }).catch((err) => { + if (isDataplaneError(err, Code.NotFound)) { + return undefined + } + throw err + }), ctx.dataplane.getBlobTakedown({ did, cid: cid.toString() }), ]) + const services = identity && unpackIdentityServices(identity.services) + const pds = + services && + getServiceEndpoint(services, { + id: 'atproto_pds', + type: 'AtprotoPersonalDataServer', + }) + if (!pds) { + throw createError(404, 'Origin not found') + } if (takenDown) { throw createError(404, 'Blob not found') } diff --git a/packages/bsky/src/auth-verifier.ts b/packages/bsky/src/auth-verifier.ts index 5a2bf753072..5de16432c88 100644 --- a/packages/bsky/src/auth-verifier.ts +++ b/packages/bsky/src/auth-verifier.ts @@ -2,9 +2,16 @@ import { AuthRequiredError, verifyJwt as verifyServiceJwt, } from '@atproto/xrpc-server' -import { IdResolver } from '@atproto/identity' import * as ui8 from 'uint8arrays' import express from 'express' +import { + Code, + DataPlaneClient, + getKeyAsDidKey, + isDataplaneError, + unpackIdentityKeys, +} from './data-plane' +import { GetIdentityByDidResponse } from './proto/bsky_pb' type ReqCtx = { req: express.Request @@ -63,7 +70,7 @@ export class AuthVerifier { public ownDid: string public adminDid: string - constructor(public idResolver: IdResolver, opts: AuthVerifierOpts) { + constructor(public dataplane: DataPlaneClient, opts: AuthVerifierOpts) { this._adminPass = opts.adminPass this._moderatorPass = opts.moderatorPass this._triagePass = opts.triagePass @@ -191,12 +198,26 @@ export class AuthVerifier { ) { const getSigningKey = async ( did: string, - forceRefresh: boolean, + _forceRefresh: boolean, // @TODO consider propagating to dataplane ): Promise => { if (opts.iss !== null && !opts.iss.includes(did)) { throw new AuthRequiredError('Untrusted issuer', 'UntrustedIss') } - return this.idResolver.did.resolveAtprotoKey(did, forceRefresh) + let identity: GetIdentityByDidResponse + try { + identity = await this.dataplane.getIdentityByDid({ did }) + } catch (err) { + if (isDataplaneError(err, Code.NotFound)) { + throw new AuthRequiredError('identity unknown') + } + throw err + } + const keys = unpackIdentityKeys(identity.keys) + const didKey = getKeyAsDidKey(keys, { id: 'atproto' }) + if (!didKey) { + throw new AuthRequiredError('missing or bad key') + } + return didKey } const jwtStr = bearerTokenFromReq(reqCtx.req) diff --git a/packages/bsky/src/context.ts b/packages/bsky/src/context.ts index 8c36862715e..cedd9f0a5de 100644 --- a/packages/bsky/src/context.ts +++ b/packages/bsky/src/context.ts @@ -22,7 +22,6 @@ export class AppContext { views: Views signingKey: Keypair idResolver: IdResolver - didCache?: DidCache bsyncClient: BsyncClient courierClient: CourierClient algos: MountedAlgos @@ -62,10 +61,6 @@ export class AppContext { return this.opts.idResolver } - get didCache(): DidCache | undefined { - return this.opts.didCache - } - get bsyncClient(): BsyncClient { return this.opts.bsyncClient } diff --git a/packages/bsky/src/data-plane/client.ts b/packages/bsky/src/data-plane/client.ts index cc03d90851e..4d48bf25a60 100644 --- a/packages/bsky/src/data-plane/client.ts +++ b/packages/bsky/src/data-plane/client.ts @@ -1,6 +1,6 @@ import assert from 'node:assert' import { randomInt } from 'node:crypto' -import { Service } from '../proto/bsky_connect' +import * as ui8 from 'uint8arrays' import { Code, ConnectError, @@ -9,6 +9,8 @@ import { makeAnyClient, } from '@connectrpc/connect' import { createConnectTransport } from '@connectrpc/connect-node' +import { getDidKeyFromMultibase } from '@atproto/identity' +import { Service } from '../proto/bsky_connect' export type DataPlaneClient = PromiseClient type HttpVersion = '1.1' | '2' @@ -69,3 +71,56 @@ const randomElement = (arr: T[]): T | undefined => { if (arr.length === 0) return return arr[randomInt(arr.length)] } + +export const unpackIdentityServices = (servicesBytes: Uint8Array) => { + const servicesStr = ui8.toString(servicesBytes, 'utf8') + if (!servicesStr) return {} + return JSON.parse(servicesStr) as UnpackedServices +} + +export const unpackIdentityKeys = (keysBytes: Uint8Array) => { + const keysStr = ui8.toString(keysBytes, 'utf8') + if (!keysStr) return {} + return JSON.parse(keysStr) as UnpackedKeys +} + +export const getServiceEndpoint = ( + services: UnpackedServices, + opts: { id: string; type: string }, +) => { + const endpoint = + services[opts.id] && + services[opts.id].Type === opts.type && + validateUrl(services[opts.id].URL) + return endpoint || undefined +} + +export const getKeyAsDidKey = (keys: UnpackedKeys, opts: { id: string }) => { + const key = + keys[opts.id] && + getDidKeyFromMultibase({ + type: keys[opts.id].Type, + publicKeyMultibase: keys[opts.id].PublicKeyMultibase, + }) + return key || undefined +} + +type UnpackedServices = Record + +type UnpackedKeys = Record + +const validateUrl = (urlStr: string): string | undefined => { + let url + try { + url = new URL(urlStr) + } catch { + return undefined + } + if (!['http:', 'https:'].includes(url.protocol)) { + return undefined + } else if (!url.hostname) { + return undefined + } else { + return urlStr + } +} diff --git a/packages/bsky/src/data-plane/server/did-cache.ts b/packages/bsky/src/data-plane/server/did-cache.ts deleted file mode 100644 index 2ffe3b5aa69..00000000000 --- a/packages/bsky/src/data-plane/server/did-cache.ts +++ /dev/null @@ -1,110 +0,0 @@ -import PQueue from 'p-queue' -import { CacheResult, DidCache, DidDocument } from '@atproto/identity' -import { Database } from './db' -import { excluded } from './db/util' -import { dbLogger } from '../../logger' - -export class DidSqlCache implements DidCache { - public pQueue: PQueue | null //null during teardown - - constructor( - // @TODO perhaps could use both primary and non-primary. not high enough - // throughput to matter right now. also may just move this over to redis before long! - public db: Database, - public staleTTL: number, - public maxTTL: number, - ) { - this.pQueue = new PQueue() - } - - async cacheDid( - did: string, - doc: DidDocument, - prevResult?: CacheResult, - ): Promise { - if (prevResult) { - await this.db.db - .updateTable('did_cache') - .set({ doc, updatedAt: Date.now() }) - .where('did', '=', did) - .where('updatedAt', '=', prevResult.updatedAt) - .execute() - } else { - await this.db.db - .insertInto('did_cache') - .values({ did, doc, updatedAt: Date.now() }) - .onConflict((oc) => - oc.column('did').doUpdateSet({ - doc: excluded(this.db.db, 'doc'), - updatedAt: excluded(this.db.db, 'updatedAt'), - }), - ) - .executeTakeFirst() - } - } - - async refreshCache( - did: string, - getDoc: () => Promise, - prevResult?: CacheResult, - ): Promise { - this.pQueue?.add(async () => { - try { - const doc = await getDoc() - if (doc) { - await this.cacheDid(did, doc, prevResult) - } else { - await this.clearEntry(did) - } - } catch (err) { - dbLogger.error({ did, err }, 'refreshing did cache failed') - } - }) - } - - async checkCache(did: string): Promise { - const res = await this.db.db - .selectFrom('did_cache') - .where('did', '=', did) - .selectAll() - .executeTakeFirst() - if (!res) return null - - const now = Date.now() - const updatedAt = new Date(res.updatedAt).getTime() - const expired = now > updatedAt + this.maxTTL - const stale = now > updatedAt + this.staleTTL - return { - doc: res.doc, - updatedAt, - did, - stale, - expired, - } - } - - async clearEntry(did: string): Promise { - await this.db.db - .deleteFrom('did_cache') - .where('did', '=', did) - .executeTakeFirst() - } - - async clear(): Promise { - await this.db.db.deleteFrom('did_cache').execute() - } - - async processAll() { - await this.pQueue?.onIdle() - } - - async destroy() { - const pQueue = this.pQueue - this.pQueue = null - pQueue?.pause() - pQueue?.clear() - await pQueue?.onIdle() - } -} - -export default DidSqlCache diff --git a/packages/bsky/src/data-plane/server/index.ts b/packages/bsky/src/data-plane/server/index.ts index fb730a177ba..f925de83c48 100644 --- a/packages/bsky/src/data-plane/server/index.ts +++ b/packages/bsky/src/data-plane/server/index.ts @@ -6,23 +6,20 @@ import createRoutes from './routes' import { Database } from './db' import { IdResolver, MemoryCache } from '@atproto/identity' -export { DidSqlCache } from './did-cache' export { RepoSubscription } from './subscription' export class DataPlaneServer { - constructor(public server: http.Server) {} + constructor(public server: http.Server, public idResolver: IdResolver) {} static async create(db: Database, port: number, plcUrl?: string) { const app = express() - const idResolver = new IdResolver({ - plcUrl, - didCache: new MemoryCache(), - }) + const didCache = new MemoryCache() + const idResolver = new IdResolver({ plcUrl, didCache }) const routes = createRoutes(db, idResolver) app.use(expressConnectMiddleware({ routes })) const server = app.listen(port) await events.once(server, 'listening') - return new DataPlaneServer(server) + return new DataPlaneServer(server, idResolver) } async destroy() { diff --git a/packages/bsky/src/index.ts b/packages/bsky/src/index.ts index 53e52c7bdb8..8bac7d2b7f5 100644 --- a/packages/bsky/src/index.ts +++ b/packages/bsky/src/index.ts @@ -47,29 +47,20 @@ export class BskyAppView { static create(opts: { config: ServerConfig signingKey: Keypair - didCache?: DidCache algos?: MountedAlgos }): BskyAppView { - const { config, signingKey, didCache, algos = {} } = opts + const { config, signingKey, algos = {} } = opts const app = express() app.use(cors()) app.use(loggerMiddleware) app.use(compression()) + // used solely for handle resolution: identity lookups occur on dataplane const idResolver = new IdResolver({ plcUrl: config.didPlcUrl, - didCache, backupNameservers: config.handleResolveNameservers, }) - const authVerifier = new AuthVerifier(idResolver, { - ownDid: config.serverDid, - adminDid: config.modServiceDid, - adminPass: config.adminPassword, - moderatorPass: config.moderatorPassword, - triagePass: config.triagePassword, - }) - const imgUriBuilder = new ImageUriBuilder( config.imgUriEndpoint || `${config.publicUrl}/img`, ) @@ -111,6 +102,14 @@ export class BskyAppView { : [], }) + const authVerifier = new AuthVerifier(dataplane, { + ownDid: config.serverDid, + adminDid: config.modServiceDid, + adminPass: config.adminPassword, + moderatorPass: config.moderatorPassword, + triagePass: config.triagePassword, + }) + const ctx = new AppContext({ cfg: config, dataplane, @@ -119,7 +118,6 @@ export class BskyAppView { views, signingKey, idResolver, - didCache, bsyncClient, courierClient, authVerifier, diff --git a/packages/bsky/tests/admin/admin-auth.test.ts b/packages/bsky/tests/admin/admin-auth.test.ts index ff00d0906b0..cb13b58897a 100644 --- a/packages/bsky/tests/admin/admin-auth.test.ts +++ b/packages/bsky/tests/admin/admin-auth.test.ts @@ -27,15 +27,30 @@ describe('admin auth', () => { bskyDid = network.bsky.ctx.cfg.serverDid modServiceKey = await Secp256k1Keypair.create() - const origResolve = network.bsky.ctx.idResolver.did.resolveAtprotoKey - network.bsky.ctx.idResolver.did.resolveAtprotoKey = async ( + const origResolve = network.bsky.dataplane.idResolver.did.resolve + network.bsky.dataplane.idResolver.did.resolve = async function ( did: string, forceRefresh?: boolean, - ) => { + ) { if (did === modServiceDid || did === altModDid) { - return modServiceKey.did() + return { + '@context': [ + 'https://www.w3.org/ns/did/v1', + 'https://w3id.org/security/multikey/v1', + 'https://w3id.org/security/suites/secp256k1-2019/v1', + ], + id: did, + verificationMethod: [ + { + id: `${did}#atproto`, + type: 'Multikey', + controller: did, + publicKeyMultibase: modServiceKey.did().replace('did:key:', ''), + }, + ], + } } - return origResolve(did, forceRefresh) + return origResolve.call(this, did, forceRefresh) } agent = network.bsky.getClient() @@ -70,9 +85,7 @@ describe('admin auth', () => { ) const res = await agent.api.com.atproto.admin.getSubjectStatus( - { - did: repoSubject.did, - }, + { did: repoSubject.did }, headers, ) expect(res.data.subject.did).toBe(repoSubject.did) diff --git a/packages/bsky/tests/auth.test.ts b/packages/bsky/tests/auth.test.ts index e08049fa84c..d0903174a2b 100644 --- a/packages/bsky/tests/auth.test.ts +++ b/packages/bsky/tests/auth.test.ts @@ -22,7 +22,8 @@ describe('auth', () => { await network.close() }) - it('handles signing key change for service auth.', async () => { + // @TODO invalidations do not originate from appview frontends: requires identity event on the repo stream. + it.skip('handles signing key change for service auth.', async () => { const issuer = sc.dids.alice const attemptWithKey = async (keypair: Keypair) => { const jwt = await createServiceJwt({ diff --git a/packages/bsky/tests/blob-resolver.test.ts b/packages/bsky/tests/blob-resolver.test.ts index 585c9638f60..985f347f7c2 100644 --- a/packages/bsky/tests/blob-resolver.test.ts +++ b/packages/bsky/tests/blob-resolver.test.ts @@ -1,6 +1,6 @@ import axios, { AxiosInstance } from 'axios' import { CID } from 'multiformats/cid' -import { verifyCidForBytes } from '@atproto/common' +import { cidForCbor, verifyCidForBytes } from '@atproto/common' import { TestNetwork, basicSeed } from '@atproto/dev-env' import { randomBytes } from '@atproto/crypto' @@ -44,8 +44,9 @@ describe('blob resolver', () => { }) it('404s on missing blob.', async () => { + const badCid = await cidForCbor({ unknown: true }) const { data, status } = await client.get( - `/blob/did:plc:unknown/${fileCid.toString()}`, + `/blob/${fileDid}/${badCid.toString()}`, ) expect(status).toEqual(404) expect(data).toEqual({ @@ -54,6 +55,17 @@ describe('blob resolver', () => { }) }) + it('404s on missing identity.', async () => { + const { data, status } = await client.get( + `/blob/did:plc:unknown/${fileCid.toString()}`, + ) + expect(status).toEqual(404) + expect(data).toEqual({ + error: 'NotFoundError', + message: 'Origin not found', + }) + }) + it('400s on invalid did.', async () => { const { data, status } = await client.get( `/blob/did::/${fileCid.toString()}`, diff --git a/packages/bsky/tests/data-plane/did-cache.test.ts b/packages/bsky/tests/data-plane/did-cache.test.ts deleted file mode 100644 index e9364761419..00000000000 --- a/packages/bsky/tests/data-plane/did-cache.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { wait } from '@atproto/common' -import { IdResolver } from '@atproto/identity' -import { TestNetwork, SeedClient, usersSeed } from '@atproto/dev-env' -import { DidSqlCache } from '../../src' - -describe('did cache', () => { - let network: TestNetwork - let sc: SeedClient - let idResolver: IdResolver - let didCache: DidSqlCache - - let alice: string - let bob: string - let carol: string - let dan: string - - beforeAll(async () => { - network = await TestNetwork.create({ - dbPostgresSchema: 'bsky_did_cache', - }) - idResolver = network.bsky.ctx.idResolver - didCache = network.bsky.ctx.didCache as DidSqlCache - sc = network.getSeedClient() - await usersSeed(sc) - await network.processAll() - alice = sc.dids.alice - bob = sc.dids.bob - carol = sc.dids.carol - dan = sc.dids.dan - }) - - afterAll(async () => { - await network.close() - }) - - it('caches dids on lookup', async () => { - await didCache.processAll() - const docs = await Promise.all([ - idResolver.did.cache?.checkCache(alice), - idResolver.did.cache?.checkCache(bob), - idResolver.did.cache?.checkCache(carol), - idResolver.did.cache?.checkCache(dan), - ]) - expect(docs.length).toBe(4) - expect(docs[0]?.doc.id).toEqual(alice) - expect(docs[1]?.doc.id).toEqual(bob) - expect(docs[2]?.doc.id).toEqual(carol) - expect(docs[3]?.doc.id).toEqual(dan) - }) - - it('clears cache and repopulates', async () => { - await idResolver.did.cache?.clear() - const docsCleared = await Promise.all([ - idResolver.did.cache?.checkCache(alice), - idResolver.did.cache?.checkCache(bob), - idResolver.did.cache?.checkCache(carol), - idResolver.did.cache?.checkCache(dan), - ]) - expect(docsCleared).toEqual([null, null, null, null]) - - await Promise.all([ - idResolver.did.resolve(alice), - idResolver.did.resolve(bob), - idResolver.did.resolve(carol), - idResolver.did.resolve(dan), - ]) - await didCache.processAll() - - const docs = await Promise.all([ - idResolver.did.cache?.checkCache(alice), - idResolver.did.cache?.checkCache(bob), - idResolver.did.cache?.checkCache(carol), - idResolver.did.cache?.checkCache(dan), - ]) - expect(docs.length).toBe(4) - expect(docs[0]?.doc.id).toEqual(alice) - expect(docs[1]?.doc.id).toEqual(bob) - expect(docs[2]?.doc.id).toEqual(carol) - expect(docs[3]?.doc.id).toEqual(dan) - }) - - it('accurately reports expired dids & refreshes the cache', async () => { - const didCache = new DidSqlCache(network.bsky.db, 1, 60000) - const shortCacheResolver = new IdResolver({ - plcUrl: network.bsky.ctx.cfg.didPlcUrl, - didCache, - }) - const doc = await shortCacheResolver.did.resolve(alice) - await didCache.processAll() - // let's mess with alice's doc so we know what we're getting - await didCache.cacheDid(alice, { ...doc, id: 'did:example:alice' }) - await wait(5) - - // first check the cache & see that we have the stale value - const cached = await shortCacheResolver.did.cache?.checkCache(alice) - expect(cached?.stale).toBe(true) - expect(cached?.doc.id).toEqual('did:example:alice') - // see that the resolver gives us the stale value while it revalidates - const staleGet = await shortCacheResolver.did.resolve(alice) - expect(staleGet?.id).toEqual('did:example:alice') - await didCache.processAll() - - // since it revalidated, ensure we have the new value - const updatedCache = await shortCacheResolver.did.cache?.checkCache(alice) - expect(updatedCache?.doc.id).toEqual(alice) - const updatedGet = await shortCacheResolver.did.resolve(alice) - expect(updatedGet?.id).toEqual(alice) - await didCache.destroy() - }) - - it('does not return expired dids & refreshes the cache', async () => { - const didCache = new DidSqlCache(network.bsky.db, 0, 1) - const shortExpireResolver = new IdResolver({ - plcUrl: network.bsky.ctx.cfg.didPlcUrl, - didCache, - }) - const doc = await shortExpireResolver.did.resolve(alice) - await didCache.processAll() - - // again, we mess with the cached doc so we get something different - await didCache.cacheDid(alice, { ...doc, id: 'did:example:alice' }) - await wait(5) - - // see that the resolver does not return expired value & instead force refreshes - const staleGet = await shortExpireResolver.did.resolve(alice) - expect(staleGet?.id).toEqual(alice) - }) -}) diff --git a/packages/bsky/tests/data-plane/handle-invalidation.test.ts b/packages/bsky/tests/data-plane/handle-invalidation.test.ts index cd281976e24..8469a8507ef 100644 --- a/packages/bsky/tests/data-plane/handle-invalidation.test.ts +++ b/packages/bsky/tests/data-plane/handle-invalidation.test.ts @@ -25,8 +25,10 @@ describe('handle invalidation', () => { alice = sc.dids.alice bob = sc.dids.bob - const origResolve = network.bsky.ctx.idResolver.handle.resolve - network.bsky.ctx.idResolver.handle.resolve = async (handle: string) => { + const origResolve = network.bsky.dataplane.idResolver.handle.resolve + network.bsky.dataplane.idResolver.handle.resolve = async ( + handle: string, + ) => { if (mockHandles[handle] === null) { return undefined } else if (mockHandles[handle]) { diff --git a/packages/dev-env/src/bsky.ts b/packages/dev-env/src/bsky.ts index 614dbf60899..db4ef098a63 100644 --- a/packages/dev-env/src/bsky.ts +++ b/packages/dev-env/src/bsky.ts @@ -83,12 +83,9 @@ export class TestBsky { } await migrationDb.close() - const didCache = new bsky.DidSqlCache(db, HOUR, DAY) - // api server const server = bsky.BskyAppView.create({ config, - didCache, signingKey: serviceKeypair, algos: cfg.algos, }) @@ -96,7 +93,7 @@ export class TestBsky { const sub = new bsky.RepoSubscription({ service: cfg.repoProvider, db, - idResolver: server.ctx.idResolver, + idResolver: dataplane.idResolver, background: new BackgroundQueue(db), }) diff --git a/packages/dev-env/src/util.ts b/packages/dev-env/src/util.ts index 2cfbd6dcbd7..679ca89c7a8 100644 --- a/packages/dev-env/src/util.ts +++ b/packages/dev-env/src/util.ts @@ -7,6 +7,7 @@ export const mockNetworkUtilities = (pds: TestPds, bsky?: TestBsky) => { mockResolvers(pds.ctx.idResolver, pds) if (bsky) { mockResolvers(bsky.ctx.idResolver, pds) + mockResolvers(bsky.dataplane.idResolver, pds) } } diff --git a/packages/identity/src/did/atproto-data.ts b/packages/identity/src/did/atproto-data.ts index c03f76ef598..c0cd9829739 100644 --- a/packages/identity/src/did/atproto-data.ts +++ b/packages/identity/src/did/atproto-data.ts @@ -20,7 +20,13 @@ export { export const getKey = (doc: DidDocument): string | undefined => { const key = getSigningKey(doc) if (!key) return undefined + return getDidKeyFromMultibase(key) +} +export const getDidKeyFromMultibase = (key: { + type: string + publicKeyMultibase: string +}): string | undefined => { const keyBytes = crypto.multibaseToBytes(key.publicKeyMultibase) let didKey: string | undefined = undefined if (key.type === 'EcdsaSecp256r1VerificationKey2019') {