Skip to content

Commit

Permalink
move from idresolver to dataplane for identity lookups on appview
Browse files Browse the repository at this point in the history
  • Loading branch information
devinivy committed Jan 25, 2024
1 parent cc1027e commit b9aa60d
Show file tree
Hide file tree
Showing 17 changed files with 201 additions and 314 deletions.
29 changes: 16 additions & 13 deletions packages/bsky/src/api/app/bsky/feed/getFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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(
Expand Down Expand Up @@ -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}`,
Expand Down
33 changes: 18 additions & 15 deletions packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -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}`,
Expand Down
25 changes: 23 additions & 2 deletions packages/bsky/src/api/blob-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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')
}
Expand Down
29 changes: 25 additions & 4 deletions packages/bsky/src/auth-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -191,12 +198,26 @@ export class AuthVerifier {
) {
const getSigningKey = async (
did: string,
forceRefresh: boolean,
_forceRefresh: boolean, // @TODO consider propagating to dataplane
): Promise<string> => {
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)
Expand Down
5 changes: 0 additions & 5 deletions packages/bsky/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export class AppContext {
views: Views
signingKey: Keypair
idResolver: IdResolver
didCache?: DidCache
bsyncClient: BsyncClient
courierClient: CourierClient
algos: MountedAlgos
Expand Down Expand Up @@ -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
}
Expand Down
57 changes: 56 additions & 1 deletion packages/bsky/src/data-plane/client.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<typeof Service>
type HttpVersion = '1.1' | '2'
Expand Down Expand Up @@ -69,3 +71,56 @@ const randomElement = <T>(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<string, { Type: string; URL: string }>

type UnpackedKeys = Record<string, { Type: string; PublicKeyMultibase: string }>

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
}
}
Loading

0 comments on commit b9aa60d

Please sign in to comment.