diff --git a/db/federation/actor.go b/db/federation/actor.go index ea9dddc4..6138ce37 100644 --- a/db/federation/actor.go +++ b/db/federation/actor.go @@ -247,8 +247,9 @@ func fetchRemoteActor(actor *core.Record, iri string, includeFollows bool) (*pub req.Header.Add(k, v) } - dbPrivateKey := actor.GetString("private_key") - if dbPrivateKey != "" { + if actor != nil && actor.GetString("private_key") != "" { + dbPrivateKey := actor.GetString("private_key") + algs := []httpsig.Algorithm{httpsig.RSA_SHA256} postHeaders := []string{"(request-target)", "Date", "Digest", "Content-Type", "Host"} expiresIn := 60 @@ -311,7 +312,6 @@ func FetchCollection(actor *core.Record, url string) (*pub.OrderedCollection, er if len(encryptionKey) == 0 { return nil, fmt.Errorf("POCKETBASE_ENCRYPTION_KEY not set") } - req, _ := http.NewRequest("GET", url, nil) headers := map[string]string{ @@ -354,9 +354,15 @@ func FetchCollection(actor *core.Record, url string) (*pub.OrderedCollection, er } resp, err := http.DefaultClient.Do(req) - if err != nil || resp.StatusCode != http.StatusOK { + if err != nil { return nil, fmt.Errorf("collection fetch failed for %s: %v", url, err) } + if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusNotFound { + return nil, fmt.Errorf("profile is private") + } + return nil, fmt.Errorf("collection fetch %s returned: %v", url, resp.StatusCode) + } defer resp.Body.Close() var collection pub.OrderedCollection diff --git a/db/main.go b/db/main.go index 43d0b041..6683a868 100644 --- a/db/main.go +++ b/db/main.go @@ -1069,9 +1069,13 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) { iri := e.Request.URL.Query().Get("iri") follows := e.Request.URL.Query().Get("follows") == "true" - userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id) - if err != nil { - return err + var userActor *core.Record + var err error + if e.Auth != nil { + userActor, err = e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id) + if err != nil { + return err + } } var actor *core.Record @@ -1088,7 +1092,7 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) { } else if err != nil && actor != nil { if err.Error() == "profile is private" { // this is our own profile - if actor.GetString("user") == e.Auth.Id { + if e.Auth != nil && actor.GetString("user") == e.Auth.Id { return e.JSON(http.StatusOK, map[string]any{"actor": actor, "error": nil}) } else { return e.JSON(http.StatusNotFound, map[string]any{"error": "profile is private"}) @@ -1119,9 +1123,12 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) { return err } - userActor, err := e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id) - if err != nil { - return err + var userActor *core.Record + if e.Auth != nil { + userActor, err = e.App.FindFirstRecordByData("activitypub_actors", "user", e.Auth.Id) + if err != nil { + return err + } } url := actor.GetString(followType) @@ -1131,6 +1138,9 @@ func registerRoutes(se *core.ServeEvent, client meilisearch.ServiceManager) { } collection, err := federation.FetchCollection(userActor, fmt.Sprintf("%s?page=%d", url, intPage)) if err != nil { + if err.Error() == "profile is private" { + return e.JSON(http.StatusNotFound, map[string]any{"error": "profile is private"}) + } return err } return e.JSON(http.StatusOK, collection) diff --git a/web/src/lib/components/list/list_panel.svelte b/web/src/lib/components/list/list_panel.svelte index c048876d..e95b3178 100644 --- a/web/src/lib/components/list/list_panel.svelte +++ b/web/src/lib/components/list/list_panel.svelte @@ -205,7 +205,7 @@ onmouseenter={() => handleTrailMouseEnter(trail, i)} onmouseleave={() => handleTrailMouseLeave(trail, i)} > - + {/each} diff --git a/web/src/routes/api/v1/activitypub/user/[handle]/+server.ts b/web/src/routes/api/v1/activitypub/user/[handle]/+server.ts index 256c44f1..6fb8852a 100644 --- a/web/src/routes/api/v1/activitypub/user/[handle]/+server.ts +++ b/web/src/routes/api/v1/activitypub/user/[handle]/+server.ts @@ -20,7 +20,7 @@ export async function GET(event: RequestEvent) { const [username, domain] = splitUsername(fullUsername, env.ORIGIN) - const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`username:lower='${username?.toLowerCase()}'&&isLocal=true`) + const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`preferred_username:lower='${username?.toLowerCase()}'&&isLocal=true`) const user: UserAnonymous = await event.locals.pb.collection("users_anonymous").getOne(actor.user!) diff --git a/web/src/routes/api/v1/activitypub/user/[handle]/followers/+server.ts b/web/src/routes/api/v1/activitypub/user/[handle]/followers/+server.ts index 49172de1..84f6eaec 100644 --- a/web/src/routes/api/v1/activitypub/user/[handle]/followers/+server.ts +++ b/web/src/routes/api/v1/activitypub/user/[handle]/followers/+server.ts @@ -25,7 +25,7 @@ export async function GET(event: RequestEvent) { const [username, domain] = splitUsername(fullUsername, env.ORIGIN) - const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`username:lower='${username?.toLowerCase()}'&&isLocal=true`) + const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`preferred_username:lower='${username?.toLowerCase()}'&&isLocal=true`) const followers: ListResult = await event.locals.pb.collection("follows").getList(intPage, 10, { sort: "-created", filter: `followee='${actor.id}'&&status='accepted'`, expand: "follower" }) @@ -43,7 +43,7 @@ export async function GET(event: RequestEvent) { ...(hasNextPage ? { next: `${id}/outbox?page=${intPage + 1}` } : {}), partOf: id + "/followers", totalItems: followers.totalItems, - orderedItems: followers.items.map(f => f.expand!.follower.iri) + orderedItems: followers.items.filter(f => f.expand?.follower !== undefined).map(f => f.expand!.follower.iri) } const headers = new Headers() diff --git a/web/src/routes/api/v1/activitypub/user/[handle]/following/+server.ts b/web/src/routes/api/v1/activitypub/user/[handle]/following/+server.ts index 4848f6a2..e3aa11e7 100644 --- a/web/src/routes/api/v1/activitypub/user/[handle]/following/+server.ts +++ b/web/src/routes/api/v1/activitypub/user/[handle]/following/+server.ts @@ -26,7 +26,7 @@ export async function GET(event: RequestEvent) { const [username, domain] = splitUsername(fullUsername, env.ORIGIN) - const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`username:lower='${username?.toLowerCase()}'&&isLocal=true`) + const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`preferred_username:lower='${username?.toLowerCase()}'&&isLocal=true`) const followers: ListResult = await event.locals.pb.collection("follows").getList(intPage, 10, { sort: "-created", filter: `follower='${actor.id}'&&status='accepted'`, expand: "followee" }) @@ -44,7 +44,7 @@ export async function GET(event: RequestEvent) { ...(hasNextPage ? { next: `${id}/following?page=${intPage + 1}` } : {}), partOf: id + "/following", totalItems: followers.totalItems, - orderedItems: followers.items.map(f => f.expand!.followee.iri) + orderedItems: followers.items.filter(f => f.expand?.followee !== undefined).map(f => f.expand!.followee.iri) } const headers = new Headers() diff --git a/web/src/routes/api/v1/activitypub/user/[handle]/outbox/+server.ts b/web/src/routes/api/v1/activitypub/user/[handle]/outbox/+server.ts index c0d40700..715f82c5 100644 --- a/web/src/routes/api/v1/activitypub/user/[handle]/outbox/+server.ts +++ b/web/src/routes/api/v1/activitypub/user/[handle]/outbox/+server.ts @@ -28,7 +28,7 @@ export async function GET(event: RequestEvent) { const [username, domain] = splitUsername(fullUsername, env.ORIGIN) - const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`username:lower='${username?.toLowerCase()}'&&isLocal=true`) + const actor: Actor = await event.locals.pb.collection("activitypub_actors").getFirstListItem(`preferred_username:lower='${username?.toLowerCase()}'&&isLocal=true`) const filter = `actor='${actor.iri}'&&type='Create'${safeSearchParams.filter ? '&&' + safeSearchParams.filter : ''}` const activities: ListResult = await event.locals.pb.collection("activitypub_activities").getList(page, perPage, { sort: safeSearchParams.sort ?? "-created", filter }) diff --git a/web/src/routes/api/v1/follow/+server.ts b/web/src/routes/api/v1/follow/+server.ts index 69eaa2ab..5ccc771f 100644 --- a/web/src/routes/api/v1/follow/+server.ts +++ b/web/src/routes/api/v1/follow/+server.ts @@ -22,8 +22,21 @@ export async function GET(event: RequestEvent) { const { actor }: { actor: Actor } = await event.locals.pb.send(`/activitypub/actor?resource=acct:${handle}`, { method: "GET", fetch: event.fetch, }); const page = event.url.searchParams.get("page") ?? "1" - - const followers: APOrderedCollectionPage = await event.locals.pb.send(`/activitypub/actor/${actor.id}/${type}?page=${page}`, { method: "GET", fetch: event.fetch, }); + + let followers: APOrderedCollectionPage; + + // fetch followers locally to not run into auth issues with private profiles + if (actor.id === event.locals.user.actor) { + const r = await event.fetch(actor[type as "followers" | "following"]! + '?' + new URLSearchParams({ page })) + + if (!r.ok) { + const errorResponse = await r.json() + throw new ClientResponseError({ status: r.status, response: errorResponse }); + } + followers = await r.json() + } else { + followers = await event.locals.pb.send(`/activitypub/actor/${actor.id}/${type}?page=${page}`, { method: "GET", fetch: event.fetch, }); + } const followerActors: Actor[] = [] for (const f of followers.orderedItems ?? []) { @@ -37,6 +50,7 @@ export async function GET(event: RequestEvent) { } + const result: ListResult = { items: followerActors, page: parseInt(page), diff --git a/web/src/routes/api/v1/profile/[handle]/trails/+server.ts b/web/src/routes/api/v1/profile/[handle]/trails/+server.ts index 40b1492e..cf6e569d 100644 --- a/web/src/routes/api/v1/profile/[handle]/trails/+server.ts +++ b/web/src/routes/api/v1/profile/[handle]/trails/+server.ts @@ -32,6 +32,9 @@ export async function POST(event: RequestEvent) { r.hits.forEach(h => { h.thumbnail = `${origin}/api/v1/files/trails/${h.id}/${h.thumbnail}`; h.domain = actor.domain + if(h.iri == '') { + h.iri = `${origin}/api/v1/trails/${h.id}` + } }) } diff --git a/web/src/routes/lists/[[handle]]/[[id]]/+page.svelte b/web/src/routes/lists/[[handle]]/[[id]]/+page.svelte index aa69d86b..7b209e7a 100644 --- a/web/src/routes/lists/[[handle]]/[[id]]/+page.svelte +++ b/web/src/routes/lists/[[handle]]/[[id]]/+page.svelte @@ -373,7 +373,7 @@ {/if} -
+
-
- -