Skip to content

Commit

Permalink
Support unauthed usage of feeds (#1884)
Browse files Browse the repository at this point in the history
* update local feedgens to not require a viewer where possible

* update getFeed to use optional auth

* test feeds w/ optional auth
  • Loading branch information
devinivy authored Nov 27, 2023
1 parent f232371 commit 95d33f7
Show file tree
Hide file tree
Showing 12 changed files with 420 additions and 34 deletions.
14 changes: 8 additions & 6 deletions packages/bsky/src/api/app/bsky/feed/getFeed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default function (server: Server, ctx: AppContext) {
presentation,
)
server.app.bsky.feed.getFeed({
auth: ctx.authVerifierAnyAudience,
auth: ctx.authOptionalVerifierAnyAudience,
handler: async ({ params, auth, req }) => {
const db = ctx.db.getReplica()
const feedService = ctx.services.feed(db)
Expand Down Expand Up @@ -98,13 +98,15 @@ const hydration = async (state: SkeletonState, ctx: Context) => {

const noBlocksOrMutes = (state: HydrationState) => {
const { viewer } = state.params
state.feedItems = state.feedItems.filter(
(item) =>
state.feedItems = state.feedItems.filter((item) => {
if (!viewer) return true
return (
!state.bam.block([viewer, item.postAuthorDid]) &&
!state.bam.block([viewer, item.originatorDid]) &&
!state.bam.mute([viewer, item.postAuthorDid]) &&
!state.bam.mute([viewer, item.originatorDid]),
)
!state.bam.mute([viewer, item.originatorDid])
)
})
return state
}

Expand All @@ -130,7 +132,7 @@ type Context = {
authorization?: string
}

type Params = GetFeedParams & { viewer: string }
type Params = GetFeedParams & { viewer: string | null }

type SkeletonState = {
params: Params
Expand Down
12 changes: 8 additions & 4 deletions packages/bsky/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,18 @@ export const authVerifier =
return { credentials: { did: payload.iss }, artifacts: { aud: opts.aud } }
}

export const authOptionalVerifier =
(idResolver: IdResolver, opts: { aud: string | null }) =>
async (reqCtx: { req: express.Request; res: express.Response }) => {
export const authOptionalVerifier = (
idResolver: IdResolver,
opts: { aud: string | null },
) => {
const verify = authVerifier(idResolver, opts)
return async (reqCtx: { req: express.Request; res: express.Response }) => {
if (!reqCtx.req.headers.authorization) {
return { credentials: { did: null } }
}
return authVerifier(idResolver, opts)(reqCtx)
return verify(reqCtx)
}
}

export const authOptionalAccessOrRoleVerifier = (
idResolver: IdResolver,
Expand Down
4 changes: 4 additions & 0 deletions packages/bsky/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ export class AppContext {
return auth.authVerifier(this.idResolver, { aud: null })
}

get authOptionalVerifierAnyAudience() {
return auth.authOptionalVerifier(this.idResolver, { aud: null })
}

get authOptionalVerifier() {
return auth.authOptionalVerifier(this.idResolver, {
aud: this.cfg.serverDid,
Expand Down
9 changes: 6 additions & 3 deletions packages/bsky/src/feed-gen/best-of-follows.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { InvalidRequestError } from '@atproto/xrpc-server'
import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/getFeedSkeleton'
import { AlgoHandler, AlgoResponse } from './types'
import { GenericKeyset, paginate } from '../db/pagination'
Expand All @@ -7,12 +7,15 @@ import AppContext from '../context'
const handler: AlgoHandler = async (
ctx: AppContext,
params: SkeletonParams,
viewer: string,
viewer: string | null,
): Promise<AlgoResponse> => {
if (!viewer) {
throw new AuthRequiredError('This feed requires being logged-in')
}

const { limit, cursor } = params
const db = ctx.db.getReplica('feed')
const feedService = ctx.services.feed(db)

const { ref } = db.db.dynamic

// candidates are ranked within a materialized view by like count, depreciated over time.
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/src/feed-gen/bsky-team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const BSKY_TEAM: NotEmptyArray<string> = [
const handler: AlgoHandler = async (
ctx: AppContext,
params: SkeletonParams,
_viewer: string,
_viewer: string | null,
): Promise<AlgoResponse> => {
const { limit = 50, cursor } = params
const db = ctx.db.getReplica('feed')
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/src/feed-gen/hot-classic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const NO_WHATS_HOT_LABELS: NotEmptyArray<string> = ['!no-promote']
const handler: AlgoHandler = async (
ctx: AppContext,
params: SkeletonParams,
_viewer: string,
_viewer: string | null,
): Promise<AlgoResponse> => {
const { limit = 50, cursor } = params
const db = ctx.db.getReplica('feed')
Expand Down
8 changes: 6 additions & 2 deletions packages/bsky/src/feed-gen/mutuals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import AppContext from '../context'
import { paginate } from '../db/pagination'
import { AlgoHandler, AlgoResponse } from './types'
import { FeedKeyset, getFeedDateThreshold } from '../api/app/bsky/util/feed'
import { AuthRequiredError } from '@atproto/xrpc-server'

const handler: AlgoHandler = async (
ctx: AppContext,
params: SkeletonParams,
viewer: string,
viewer: string | null,
): Promise<AlgoResponse> => {
if (!viewer) {
throw new AuthRequiredError('This feed requires being logged-in')
}

const { limit = 50, cursor } = params
const db = ctx.db.getReplica('feed')
const feedService = ctx.services.feed(db)

const { ref } = db.db.dynamic

const mutualsSubquery = db.db
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/src/feed-gen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type AlgoResponse = {
export type AlgoHandler = (
ctx: AppContext,
params: SkeletonParams,
requester: string,
viewer: string | null,
) => Promise<AlgoResponse>

export type MountedAlgos = Record<string, AlgoHandler>
Expand Down
2 changes: 1 addition & 1 deletion packages/bsky/src/feed-gen/whats-hot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const NO_WHATS_HOT_LABELS: NotEmptyArray<string> = [
const handler: AlgoHandler = async (
ctx: AppContext,
params: SkeletonParams,
_viewer: string,
_viewer: string | null,
): Promise<AlgoResponse> => {
const { limit, cursor } = params
const db = ctx.db.getReplica('feed')
Expand Down
10 changes: 7 additions & 3 deletions packages/bsky/src/feed-gen/with-friends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,20 @@ import { QueryParams as SkeletonParams } from '../lexicon/types/app/bsky/feed/ge
import { paginate } from '../db/pagination'
import { AlgoHandler, AlgoResponse } from './types'
import { FeedKeyset, getFeedDateThreshold } from '../api/app/bsky/util/feed'
import { AuthRequiredError } from '@atproto/xrpc-server'

const handler: AlgoHandler = async (
ctx: AppContext,
params: SkeletonParams,
requester: string,
viewer: string | null,
): Promise<AlgoResponse> => {
if (!viewer) {
throw new AuthRequiredError('This feed requires being logged-in')
}

const { cursor, limit = 50 } = params
const db = ctx.db.getReplica('feed')
const feedService = ctx.services.feed(db)

const { ref } = db.db.dynamic

const keyset = new FeedKeyset(ref('post.sortAt'), ref('post.cid'))
Expand All @@ -23,7 +27,7 @@ const handler: AlgoHandler = async (
.innerJoin('follow', 'follow.subjectDid', 'post.creator')
.innerJoin('post_agg', 'post_agg.uri', 'post.uri')
.where('post_agg.likeCount', '>=', 5)
.where('follow.creator', '=', requester)
.where('follow.creator', '=', viewer)
.where('post.sortAt', '>', getFeedDateThreshold(sortFrom))

postsQb = paginate(postsQb, { limit, cursor, keyset, tryIndex: true })
Expand Down
Loading

0 comments on commit 95d33f7

Please sign in to comment.