From 011aa22b84b6227918624b03e866cc01c7b37438 Mon Sep 17 00:00:00 2001 From: devin ivy Date: Fri, 15 Mar 2024 10:59:13 -0400 Subject: [PATCH] Labeler takedowns (alternate) (#2322) * build hydration ctx in each handler * test hydration w/ takendown labeler * tidy * handle and test labeler takedowns by label * appview/ozone: fix admin repo search --- .../bsky/src/api/app/bsky/actor/getProfile.ts | 16 +++--- .../src/api/app/bsky/actor/getProfiles.ts | 4 +- .../src/api/app/bsky/actor/getSuggestions.ts | 4 +- .../src/api/app/bsky/actor/searchActors.ts | 10 ++-- .../app/bsky/actor/searchActorsTypeahead.ts | 4 +- .../src/api/app/bsky/feed/getActorFeeds.ts | 4 +- .../src/api/app/bsky/feed/getActorLikes.ts | 4 +- .../src/api/app/bsky/feed/getAuthorFeed.ts | 8 ++- .../bsky/src/api/app/bsky/feed/getFeed.ts | 4 +- .../src/api/app/bsky/feed/getFeedGenerator.ts | 9 ++-- .../api/app/bsky/feed/getFeedGenerators.ts | 4 +- .../bsky/src/api/app/bsky/feed/getLikes.ts | 4 +- .../bsky/src/api/app/bsky/feed/getListFeed.ts | 4 +- .../src/api/app/bsky/feed/getPostThread.ts | 4 +- .../bsky/src/api/app/bsky/feed/getPosts.ts | 4 +- .../src/api/app/bsky/feed/getRepostedBy.ts | 4 +- .../api/app/bsky/feed/getSuggestedFeeds.ts | 8 ++- .../bsky/src/api/app/bsky/feed/getTimeline.ts | 9 ++-- .../bsky/src/api/app/bsky/feed/searchPosts.ts | 4 +- .../bsky/src/api/app/bsky/graph/getBlocks.ts | 9 ++-- .../src/api/app/bsky/graph/getFollowers.ts | 8 ++- .../bsky/src/api/app/bsky/graph/getFollows.ts | 8 ++- .../bsky/src/api/app/bsky/graph/getList.ts | 4 +- .../src/api/app/bsky/graph/getListBlocks.ts | 9 ++-- .../src/api/app/bsky/graph/getListMutes.ts | 9 ++-- .../bsky/src/api/app/bsky/graph/getLists.ts | 4 +- .../bsky/src/api/app/bsky/graph/getMutes.ts | 9 ++-- .../bsky/graph/getSuggestedFollowsByActor.ts | 6 +-- .../src/api/app/bsky/labeler/getServices.ts | 6 +-- .../bsky/notification/listNotifications.ts | 9 ++-- .../unspecced/getPopularFeedGenerators.ts | 4 +- packages/bsky/src/hydration/hydrator.ts | 48 +++++++++++++++-- packages/bsky/src/index.ts | 2 +- packages/bsky/tests/label-hydration.test.ts | 18 +++++++ .../bsky/tests/views/labeler-service.test.ts | 9 +--- .../bsky/tests/views/takedown-labels.test.ts | 52 +++++++++++++++++++ .../ozone/src/api/moderation/searchRepos.ts | 5 +- packages/ozone/tests/repo-search.test.ts | 1 + 38 files changed, 237 insertions(+), 95 deletions(-) diff --git a/packages/bsky/src/api/app/bsky/actor/getProfile.ts b/packages/bsky/src/api/app/bsky/actor/getProfile.ts index fa2226fb0ba..0803d2c2d31 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfile.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfile.ts @@ -18,7 +18,11 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, params, req }) => { const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer, includeTakedowns } + const hydrateCtx = await ctx.hydrator.createContext({ + labelers, + viewer, + includeTakedowns, + }) const result = await getProfile({ ...params, hydrateCtx }, ctx) @@ -29,7 +33,7 @@ export default function (server: Server, ctx: AppContext) { body: result, headers: resHeaders({ repoRev, - labelers, + labelers: hydrateCtx.labelers, }), } }, @@ -54,10 +58,10 @@ const hydration = async (input: { skeleton: SkeletonState }) => { const { ctx, params, skeleton } = input - return ctx.hydrator.hydrateProfilesDetailed([skeleton.did], { - ...params.hydrateCtx, - includeTakedowns: true, - }) + return ctx.hydrator.hydrateProfilesDetailed( + [skeleton.did], + params.hydrateCtx.copy({ includeTakedowns: true }), + ) } const presentation = (input: { diff --git a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts index 014f9339fe8..81fb34e5ea5 100644 --- a/packages/bsky/src/api/app/bsky/actor/getProfiles.ts +++ b/packages/bsky/src/api/app/bsky/actor/getProfiles.ts @@ -18,7 +18,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, params, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { viewer, labelers } + const hydrateCtx = await ctx.hydrator.createContext({ viewer, labelers }) const result = await getProfile({ ...params, hydrateCtx }, ctx) @@ -29,7 +29,7 @@ export default function (server: Server, ctx: AppContext) { body: result, headers: resHeaders({ repoRev, - labelers, + labelers: hydrateCtx.labelers, }), } }, diff --git a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts index ba216eb6bbc..da86489a69d 100644 --- a/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts +++ b/packages/bsky/src/api/app/bsky/actor/getSuggestions.ts @@ -25,13 +25,13 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { viewer, labelers } + const hydrateCtx = await ctx.hydrator.createContext({ viewer, labelers }) const result = await getSuggestions({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/actor/searchActors.ts b/packages/bsky/src/api/app/bsky/actor/searchActors.ts index adb3b6704ca..c54a72f3f1d 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActors.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActors.ts @@ -26,14 +26,18 @@ export default function (server: Server, ctx: AppContext) { server.app.bsky.actor.searchActors({ auth: ctx.authVerifier.standardOptional, handler: async ({ auth, params, req }) => { - const viewer = auth.credentials.iss + const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { viewer, labelers } + const hydrateCtx = await ctx.hydrator.createContext({ + viewer, + labelers, + includeTakedowns, + }) const results = await searchActors({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: results, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts index 7517c58ab9d..ce64ab49648 100644 --- a/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts +++ b/packages/bsky/src/api/app/bsky/actor/searchActorsTypeahead.ts @@ -28,7 +28,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const results = await searchActorsTypeahead( { ...params, hydrateCtx }, ctx, @@ -36,7 +36,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: results, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts index 0ba5c15409a..55624324aad 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorFeeds.ts @@ -26,12 +26,12 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, params, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getActorFeeds({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts index 4411f7295ea..d3903a57343 100644 --- a/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getActorLikes.ts @@ -28,7 +28,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getActorLikes({ ...params, hydrateCtx }, ctx) @@ -39,7 +39,7 @@ export default function (server: Server, ctx: AppContext) { body: result, headers: resHeaders({ repoRev, - labelers, + labelers: hydrateCtx.labelers, }), } }, diff --git a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts index 87661bc1ee4..6d515b11f6c 100644 --- a/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getAuthorFeed.ts @@ -30,7 +30,11 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer, includeTakedowns } + const hydrateCtx = await ctx.hydrator.createContext({ + labelers, + viewer, + includeTakedowns, + }) const result = await getAuthorFeed({ ...params, hydrateCtx }, ctx) @@ -41,7 +45,7 @@ export default function (server: Server, ctx: AppContext) { body: result, headers: resHeaders({ repoRev, - labelers, + labelers: hydrateCtx.labelers, }), } }, diff --git a/packages/bsky/src/api/app/bsky/feed/getFeed.ts b/packages/bsky/src/api/app/bsky/feed/getFeed.ts index 26e17ea3e2d..b4c85ad7f2c 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeed.ts @@ -42,7 +42,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const headers = noUndefinedVals({ authorization: req.headers['authorization'], 'accept-language': req.headers['accept-language'], @@ -60,7 +60,7 @@ export default function (server: Server, ctx: AppContext) { body: result, headers: { ...(feedResHeaders ?? {}), - ...resHeaders({ labelers }), + ...resHeaders({ labelers: hydrateCtx.labelers }), 'server-timing': serverTimingHeader([timerSkele, timerHydr]), }, } diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts index 9c94fb2b213..647bfdd090b 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerator.ts @@ -17,11 +17,8 @@ export default function (server: Server, ctx: AppContext) { const { feed } = params const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - - const hydration = await ctx.hydrator.hydrateFeedGens([feed], { - viewer, - labelers, - }) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const hydration = await ctx.hydrator.hydrateFeedGens([feed], hydrateCtx) const feedInfo = hydration.feedgens?.get(feed) if (!feedInfo) { throw new InvalidRequestError('could not find feed') @@ -64,7 +61,7 @@ export default function (server: Server, ctx: AppContext) { isOnline: true, isValid: true, }, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts index 65d5a5dd316..9bf3eaf267d 100644 --- a/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/feed/getFeedGenerators.ts @@ -23,12 +23,12 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const view = await getFeedGenerators({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: view, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getLikes.ts b/packages/bsky/src/api/app/bsky/feed/getLikes.ts index 402a6c44a8a..7e179158dfc 100644 --- a/packages/bsky/src/api/app/bsky/feed/getLikes.ts +++ b/packages/bsky/src/api/app/bsky/feed/getLikes.ts @@ -21,13 +21,13 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getLikes({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getListFeed.ts b/packages/bsky/src/api/app/bsky/feed/getListFeed.ts index 75ae9edf815..5250865cfc2 100644 --- a/packages/bsky/src/api/app/bsky/feed/getListFeed.ts +++ b/packages/bsky/src/api/app/bsky/feed/getListFeed.ts @@ -26,7 +26,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getListFeed({ ...params, hydrateCtx }, ctx) @@ -35,7 +35,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers, repoRev }), + headers: resHeaders({ labelers: hydrateCtx.labelers, repoRev }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts index 3cfd69f47f8..d8219902b92 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPostThread.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPostThread.ts @@ -30,7 +30,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req, res }) => { const { viewer } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) let result: OutputSchema try { @@ -50,7 +50,7 @@ export default function (server: Server, ctx: AppContext) { body: result, headers: resHeaders({ repoRev, - labelers, + labelers: hydrateCtx.labelers, }), } }, diff --git a/packages/bsky/src/api/app/bsky/feed/getPosts.ts b/packages/bsky/src/api/app/bsky/feed/getPosts.ts index 322197c7e3f..283639a5606 100644 --- a/packages/bsky/src/api/app/bsky/feed/getPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/getPosts.ts @@ -19,14 +19,14 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const results = await getPosts({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: results, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts index eee518961b2..2e5c8c02b21 100644 --- a/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts +++ b/packages/bsky/src/api/app/bsky/feed/getRepostedBy.ts @@ -25,13 +25,13 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getRepostedBy({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts index e848eddd634..ad13b3a0a31 100644 --- a/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts +++ b/packages/bsky/src/api/app/bsky/feed/getSuggestedFeeds.ts @@ -18,10 +18,8 @@ export default function (server: Server, ctx: AppContext) { cursor: params.cursor, }) const uris = suggestedRes.uris - const hydration = await ctx.hydrator.hydrateFeedGens(uris, { - labelers, - viewer, - }) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const hydration = await ctx.hydrator.hydrateFeedGens(uris, hydrateCtx) const feedViews = mapDefined(uris, (uri) => ctx.views.feedGenerator(uri, hydration), ) @@ -32,7 +30,7 @@ export default function (server: Server, ctx: AppContext) { feeds: feedViews, cursor: parseString(suggestedRes.cursor), }, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts index c58199d195b..8e5dc488c33 100644 --- a/packages/bsky/src/api/app/bsky/feed/getTimeline.ts +++ b/packages/bsky/src/api/app/bsky/feed/getTimeline.ts @@ -26,16 +26,19 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) - const result = await getTimeline({ ...params, hydrateCtx }, ctx) + const result = await getTimeline( + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, + ctx, + ) const repoRev = await ctx.hydrator.actor.getRepoRevSafe(viewer) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers, repoRev }), + headers: resHeaders({ labelers: hydrateCtx.labelers, repoRev }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts index 0ab4fb8ba3a..ac7268dbba7 100644 --- a/packages/bsky/src/api/app/bsky/feed/searchPosts.ts +++ b/packages/bsky/src/api/app/bsky/feed/searchPosts.ts @@ -29,12 +29,12 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, params, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const results = await searchPosts({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: results, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts index b6723cb3c45..3042b07ed13 100644 --- a/packages/bsky/src/api/app/bsky/graph/getBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getBlocks.ts @@ -20,12 +20,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } - const result = await getBlocks({ ...params, hydrateCtx }, ctx) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const result = await getBlocks( + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, + ctx, + ) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts index a7fe741e18d..0e9a008df78 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollowers.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollowers.ts @@ -31,14 +31,18 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer, includeTakedowns } + const hydrateCtx = await ctx.hydrator.createContext({ + labelers, + viewer, + includeTakedowns, + }) const result = await getFollowers({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getFollows.ts b/packages/bsky/src/api/app/bsky/graph/getFollows.ts index d5a6ba233f9..94bba1c2d2b 100644 --- a/packages/bsky/src/api/app/bsky/graph/getFollows.ts +++ b/packages/bsky/src/api/app/bsky/graph/getFollows.ts @@ -25,7 +25,11 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const { viewer, includeTakedowns } = ctx.authVerifier.parseCreds(auth) const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer, includeTakedowns } + const hydrateCtx = await ctx.hydrator.createContext({ + labelers, + viewer, + includeTakedowns, + }) // @TODO ensure canViewTakedowns gets threaded through and applied properly const result = await getFollows({ ...params, hydrateCtx }, ctx) @@ -33,7 +37,7 @@ export default function (server: Server, ctx: AppContext) { return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getList.ts b/packages/bsky/src/api/app/bsky/graph/getList.ts index 92ea332d0ed..b0e51642332 100644 --- a/packages/bsky/src/api/app/bsky/graph/getList.ts +++ b/packages/bsky/src/api/app/bsky/graph/getList.ts @@ -26,12 +26,12 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getList({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts index 2360781a6ef..89f79edbb8f 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListBlocks.ts @@ -25,12 +25,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } - const result = await getListBlocks({ ...params, hydrateCtx }, ctx) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const result = await getListBlocks( + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, + ctx, + ) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts index cc4f5a64942..fbd62c215c7 100644 --- a/packages/bsky/src/api/app/bsky/graph/getListMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getListMutes.ts @@ -25,12 +25,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } - const result = await getListMutes({ ...params, hydrateCtx }, ctx) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const result = await getListMutes( + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, + ctx, + ) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getLists.ts b/packages/bsky/src/api/app/bsky/graph/getLists.ts index 3ebc7fbb628..0f239bccf4b 100644 --- a/packages/bsky/src/api/app/bsky/graph/getLists.ts +++ b/packages/bsky/src/api/app/bsky/graph/getLists.ts @@ -20,13 +20,13 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getLists({ ...params, hydrateCtx }, ctx) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getMutes.ts b/packages/bsky/src/api/app/bsky/graph/getMutes.ts index 6890087afa9..1f417c32b39 100644 --- a/packages/bsky/src/api/app/bsky/graph/getMutes.ts +++ b/packages/bsky/src/api/app/bsky/graph/getMutes.ts @@ -20,12 +20,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } - const result = await getMutes({ ...params, hydrateCtx }, ctx) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const result = await getMutes( + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, + ctx, + ) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts index 356b33aa7fe..22e0d588356 100644 --- a/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +++ b/packages/bsky/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts @@ -26,15 +26,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, params, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) const result = await getSuggestedFollowsByActor( - { ...params, hydrateCtx }, + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, ctx, ) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/labeler/getServices.ts b/packages/bsky/src/api/app/bsky/labeler/getServices.ts index aeca1a10834..f5df1cbf5ca 100644 --- a/packages/bsky/src/api/app/bsky/labeler/getServices.ts +++ b/packages/bsky/src/api/app/bsky/labeler/getServices.ts @@ -10,11 +10,11 @@ export default function (server: Server, ctx: AppContext) { const { dids, detailed } = params const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - - const hydration = await ctx.hydrator.hydrateLabelers(dids, { + const hydrateCtx = await ctx.hydrator.createContext({ viewer, labelers, }) + const hydration = await ctx.hydrator.hydrateLabelers(dids, hydrateCtx) const views = mapDefined(dids, (did) => { if (detailed) { @@ -39,7 +39,7 @@ export default function (server: Server, ctx: AppContext) { body: { views, }, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts index 8eb3996b2a2..722dfe27d27 100644 --- a/packages/bsky/src/api/app/bsky/notification/listNotifications.ts +++ b/packages/bsky/src/api/app/bsky/notification/listNotifications.ts @@ -28,12 +28,15 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ params, auth, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { labelers, viewer } - const result = await listNotifications({ ...params, hydrateCtx }, ctx) + const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer }) + const result = await listNotifications( + { ...params, hydrateCtx: hydrateCtx.copy({ viewer }) }, + ctx, + ) return { encoding: 'application/json', body: result, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts index 883a451c8ac..d9694382709 100644 --- a/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +++ b/packages/bsky/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts @@ -13,7 +13,7 @@ export default function (server: Server, ctx: AppContext) { handler: async ({ auth, params, req }) => { const viewer = auth.credentials.iss const labelers = ctx.reqLabelers(req) - const hydrateCtx = { viewer, labelers } + const hydrateCtx = await ctx.hydrator.createContext({ viewer, labelers }) if (clearlyBadCursor(params.cursor)) { return { @@ -53,7 +53,7 @@ export default function (server: Server, ctx: AppContext) { feeds: feedViews, cursor, }, - headers: resHeaders({ labelers }), + headers: resHeaders({ labelers: hydrateCtx.labelers }), } }, }) diff --git a/packages/bsky/src/hydration/hydrator.ts b/packages/bsky/src/hydration/hydrator.ts index c935017362c..cdd077e7872 100644 --- a/packages/bsky/src/hydration/hydrator.ts +++ b/packages/bsky/src/hydration/hydrator.ts @@ -53,7 +53,17 @@ import { } from './feed' import { ParsedLabelers } from '../util' -export type HydrateCtx = { +export class HydrateCtx { + labelers = this.vals.labelers + viewer = this.vals.viewer + includeTakedowns = this.vals.includeTakedowns + constructor(private vals: HydrateCtxVals) {} + copy>(vals?: V): HydrateCtx & V { + return new HydrateCtx({ ...this.vals, ...vals }) as HydrateCtx & V + } +} + +export type HydrateCtxVals = { labelers: ParsedLabelers viewer: string | null includeTakedowns?: boolean @@ -97,12 +107,17 @@ export class Hydrator { feed: FeedHydrator graph: GraphHydrator label: LabelHydrator + serviceLabelers: Set - constructor(public dataplane: DataPlaneClient) { + constructor( + public dataplane: DataPlaneClient, + serviceLabelers: string[] = [], + ) { this.actor = new ActorHydrator(dataplane) this.feed = new FeedHydrator(dataplane) this.graph = new GraphHydrator(dataplane) this.label = new LabelHydrator(dataplane) + this.serviceLabelers = new Set(serviceLabelers) } // app.bsky.actor.defs#profileView @@ -555,13 +570,14 @@ export class Hydrator { ): Promise { const [labelers, labelerAggs, labelerViewers, profileState] = await Promise.all([ - this.label.getLabelers(dids), + this.label.getLabelers(dids, ctx.includeTakedowns), this.label.getLabelerAggregates(dids), ctx.viewer ? this.label.getLabelerViewerStates(dids, ctx.viewer) : undefined, - this.hydrateProfiles(dids.map(didFromUri), ctx), + this.hydrateProfiles(dids, ctx), ]) + actionTakedownLabels(dids, labelers, profileState.labels ?? new Labels()) return mergeStates(profileState, { labelers, labelerAggs, @@ -639,6 +655,30 @@ export class Hydrator { } } } + + async createContext(vals: HydrateCtxVals) { + // ensures we're only apply labelers that exist and are not taken down + const labelers = vals.labelers.dids + const nonServiceLabelers = labelers.filter( + (did) => !this.serviceLabelers.has(did), + ) + const labelerActors = await this.actor.getActors( + nonServiceLabelers, + vals.includeTakedowns, + ) + const availableDids = labelers.filter( + (did) => this.serviceLabelers.has(did) || !!labelerActors.get(did), + ) + const availableLabelers = { + dids: availableDids, + redact: vals.labelers.redact, + } + return new HydrateCtx({ + labelers: availableLabelers, + viewer: vals.viewer, + includeTakedowns: vals.includeTakedowns, + }) + } } const listUrisFromProfileViewer = (item: ProfileViewerState | null) => { diff --git a/packages/bsky/src/index.ts b/packages/bsky/src/index.ts index db8d30cb7d2..4c78b1e973c 100644 --- a/packages/bsky/src/index.ts +++ b/packages/bsky/src/index.ts @@ -77,7 +77,7 @@ export class BskyAppView { httpVersion: config.dataplaneHttpVersion, rejectUnauthorized: !config.dataplaneIgnoreBadTls, }) - const hydrator = new Hydrator(dataplane) + const hydrator = new Hydrator(dataplane, config.labelsFromIssuerDids) const views = new Views(imgUriBuilder) const bsyncClient = createBsyncClient({ diff --git a/packages/bsky/tests/label-hydration.test.ts b/packages/bsky/tests/label-hydration.test.ts index efe77512bdc..dc68e44b331 100644 --- a/packages/bsky/tests/label-hydration.test.ts +++ b/packages/bsky/tests/label-hydration.test.ts @@ -4,6 +4,7 @@ import axios from 'axios' describe('label hydration', () => { let network: TestNetwork + let agent: AtpAgent let pdsAgent: AtpAgent let sc: SeedClient @@ -16,6 +17,7 @@ describe('label hydration', () => { network = await TestNetwork.create({ dbPostgresSchema: 'bsky_label_hydration', }) + agent = network.bsky.getClient() pdsAgent = network.pds.getClient() sc = network.getSeedClient() await basicSeed(sc) @@ -106,6 +108,22 @@ describe('label hydration', () => { ]) }) + it('does not hydrate labels from takendown labeler', async () => { + AtpAgent.configure({ appLabelers: [alice, sc.dids.dan] }) + pdsAgent.configureLabelersHeader([]) + await network.bsky.ctx.dataplane.takedownActor({ did: alice }) + const res = await pdsAgent.api.app.bsky.actor.getProfile( + { actor: carol }, + { headers: sc.getHeaders(bob) }, + ) + const { labels = [] } = res.data + expect(labels).toEqual([]) + expect(res.headers['atproto-content-labelers']).toEqual( + `${sc.dids.dan};redact`, // does not include alice + ) + await network.bsky.ctx.dataplane.untakedownActor({ did: alice }) + }) + it('hydrates labels onto list views.', async () => { AtpAgent.configure({ appLabelers: [labelerDid] }) pdsAgent.configureLabelersHeader([]) diff --git a/packages/bsky/tests/views/labeler-service.test.ts b/packages/bsky/tests/views/labeler-service.test.ts index 3ef8d92a144..f8ddd07cf09 100644 --- a/packages/bsky/tests/views/labeler-service.test.ts +++ b/packages/bsky/tests/views/labeler-service.test.ts @@ -136,10 +136,7 @@ describe('labeler service views', () => { }) it('blocked by labeler takedown', async () => { - await network.bsky.ctx.dataplane.takedownRecord({ - recordUri: aliceService.uriStr, - }) - + await network.bsky.ctx.dataplane.takedownActor({ did: alice }) const res = await agent.api.app.bsky.labeler.getServices( { dids: [alice, bob] }, { headers: await network.serviceHeaders(bob) }, @@ -149,8 +146,6 @@ describe('labeler service views', () => { expect(res.data.views[0].creator.did).toEqual(bob) // Cleanup - await network.bsky.ctx.dataplane.untakedownRecord({ - recordUri: aliceService.uriStr, - }) + await network.bsky.ctx.dataplane.untakedownActor({ did: alice }) }) }) diff --git a/packages/bsky/tests/views/takedown-labels.test.ts b/packages/bsky/tests/views/takedown-labels.test.ts index 399afb35e82..d58505fc453 100644 --- a/packages/bsky/tests/views/takedown-labels.test.ts +++ b/packages/bsky/tests/views/takedown-labels.test.ts @@ -1,5 +1,6 @@ import AtpAgent from '@atproto/api' import { TestNetwork, SeedClient, basicSeed, RecordRef } from '@atproto/dev-env' +import { ids } from '../../src/lexicon/lexicons' describe('bsky takedown labels', () => { let network: TestNetwork @@ -40,6 +41,48 @@ describe('bsky takedown labels', () => { 'carol generator', ) + // labelers + await sc.createAccount('labeler1', { + email: 'lab1@test.com', + handle: 'lab1.test', + password: 'lab1', + }) + await sc.agent.api.com.atproto.repo.createRecord( + { + repo: sc.dids.labeler1, + collection: ids.AppBskyLabelerService, + rkey: 'self', + record: { + policies: { labelValues: ['spam'] }, + createdAt: new Date().toISOString(), + }, + }, + { + headers: sc.getHeaders(sc.dids.labeler1), + encoding: 'application/json', + }, + ) + await sc.createAccount('labeler2', { + email: 'lab2@test.com', + handle: 'lab2.test', + password: 'lab2', + }) + await sc.agent.api.com.atproto.repo.createRecord( + { + repo: sc.dids.labeler2, + collection: ids.AppBskyLabelerService, + rkey: 'self', + record: { + policies: { labelValues: ['spam'] }, + createdAt: new Date().toISOString(), + }, + }, + { + headers: sc.getHeaders(sc.dids.labeler2), + encoding: 'application/json', + }, + ) + await network.processAll() takendownSubjects = [ @@ -47,6 +90,7 @@ describe('bsky takedown labels', () => { sc.dids.carol, aliceListRef.uriStr, aliceGenRef.uriStr, + sc.dids.labeler1, ] const src = network.ozone.ctx.cfg.service.did const cts = new Date().toISOString() @@ -123,6 +167,14 @@ describe('bsky takedown labels', () => { expect(res.data.feeds.at(0)?.uri).toEqual(bobGenRef.uriStr) }) + it('takesdown labelers', async () => { + const res = await agent.api.app.bsky.labeler.getServices({ + dids: [sc.dids.labeler1, sc.dids.labeler2], + }) + expect(res.data.views.length).toBe(1) + expect(res.data.views[0].creator?.['did']).toBe(sc.dids.labeler2) + }) + it('only applies if the relevant labeler is configured', async () => { AtpAgent.configure({ appLabelers: ['did:web:example.com'] }) const res = await agent.api.app.bsky.actor.getProfile({ diff --git a/packages/ozone/src/api/moderation/searchRepos.ts b/packages/ozone/src/api/moderation/searchRepos.ts index 4f3dac9cc0b..f29e1abc49c 100644 --- a/packages/ozone/src/api/moderation/searchRepos.ts +++ b/packages/ozone/src/api/moderation/searchRepos.ts @@ -23,7 +23,10 @@ export default function (server: Server, ctx: AppContext) { } } - const res = await ctx.appviewAgent.api.app.bsky.actor.searchActors(params) + const res = await ctx.appviewAgent.api.app.bsky.actor.searchActors( + params, + await ctx.appviewAuth(), + ) const repoMap = await modService.views.repos( res.data.actors.map((a) => a.did), ) diff --git a/packages/ozone/tests/repo-search.test.ts b/packages/ozone/tests/repo-search.test.ts index 96886c0e2b2..717dc2a389e 100644 --- a/packages/ozone/tests/repo-search.test.ts +++ b/packages/ozone/tests/repo-search.test.ts @@ -38,6 +38,7 @@ describe('admin repo search view', () => { did: sc.dids['cara-wiegand69.test'], }, }) + await network.ozone.processAll() }) it('gives relevant results', async () => {