fix (backend): verify object id host matches final URL when fetching remote activities
5f6096c1b7
Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
parent
850c52ef63
commit
ada0137a35
2 changed files with 30 additions and 9 deletions
|
@ -42,8 +42,8 @@ export default async (user: { id: User["id"] }, url: string, object: any) => {
|
|||
export async function apGet(
|
||||
url: string,
|
||||
user?: ILocalUser,
|
||||
redirects: boolean = true
|
||||
): Promise<IObject> {
|
||||
redirects: boolean = true,
|
||||
): Promise<{ finalUrl: string; content: IObject }> {
|
||||
if (!isValidUrl(url)) {
|
||||
throw new StatusError("Invalid URL", 400);
|
||||
}
|
||||
|
@ -72,7 +72,8 @@ export async function apGet(
|
|||
|
||||
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
||||
const newUrl = res.headers.get("location");
|
||||
if (newUrl == null) throw new Error("apGet got redirect but no target location");
|
||||
if (newUrl == null)
|
||||
throw new Error("apGet got redirect but no target location");
|
||||
apLogger.debug(`apGet is redirecting to ${newUrl}`);
|
||||
return apGet(newUrl, user, false);
|
||||
}
|
||||
|
@ -90,7 +91,8 @@ export async function apGet(
|
|||
|
||||
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
||||
const newUrl = res.headers.get("location");
|
||||
if (newUrl == null) throw new Error("apGet got redirect but no target location");
|
||||
if (newUrl == null)
|
||||
throw new Error("apGet got redirect but no target location");
|
||||
apLogger.debug(`apGet is redirecting to ${newUrl}`);
|
||||
return apGet(newUrl, undefined, false);
|
||||
}
|
||||
|
@ -98,7 +100,9 @@ export async function apGet(
|
|||
|
||||
const contentType = res.headers.get("content-type");
|
||||
if (contentType == null || !validateContentType(contentType)) {
|
||||
throw new Error(`apGet response had unexpected content-type: ${contentType}`);
|
||||
throw new Error(
|
||||
`apGet response had unexpected content-type: ${contentType}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (res.body == null) throw new Error("body is null");
|
||||
|
@ -106,7 +110,10 @@ export async function apGet(
|
|||
const text = await res.text();
|
||||
if (text.length > 65536) throw new Error("too big result");
|
||||
|
||||
return JSON.parse(text) as IObject;
|
||||
return {
|
||||
finalUrl: res.url,
|
||||
content: JSON.parse(text) as IObject,
|
||||
};
|
||||
}
|
||||
|
||||
function validateContentType(contentType: string): boolean {
|
||||
|
|
|
@ -6,7 +6,13 @@ import { extractDbHost, isSelfHost } from "@/misc/convert-host.js";
|
|||
import { apGet } from "./request.js";
|
||||
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
|
||||
import { isCollectionOrOrderedCollection, getApId } from "./type.js";
|
||||
import { FollowRequests, Notes, NoteReactions, Polls, Users } from "@/models/index.js";
|
||||
import {
|
||||
FollowRequests,
|
||||
Notes,
|
||||
NoteReactions,
|
||||
Polls,
|
||||
Users,
|
||||
} from "@/models/index.js";
|
||||
import { parseUri } from "./db-resolver.js";
|
||||
import renderNote from "@/remote/activitypub/renderer/note.js";
|
||||
import { renderLike } from "@/remote/activitypub/renderer/like.js";
|
||||
|
@ -114,7 +120,7 @@ export default class Resolver {
|
|||
apLogger.debug("Getting object from remote, authenticated as user:");
|
||||
apLogger.debug(JSON.stringify(this.user, null, 2));
|
||||
|
||||
const object = await apGet(value, this.user);
|
||||
const { finalUrl, content: object } = await apGet(value, this.user);
|
||||
|
||||
if (
|
||||
object == null ||
|
||||
|
@ -127,6 +133,13 @@ export default class Resolver {
|
|||
throw new Error("invalid response");
|
||||
}
|
||||
|
||||
if (
|
||||
object.id != null &&
|
||||
new URL(finalUrl).host != new URL(object.id).host
|
||||
) {
|
||||
throw new Error("Object ID host doesn't match final url host");
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
|
@ -160,7 +173,8 @@ export default class Resolver {
|
|||
// if rest is a <followee id>
|
||||
if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
|
||||
const [follower, followee] = await Promise.all(
|
||||
[parsed.id, parsed.rest].map((id) => Users.findOneByOrFail({ id })));
|
||||
[parsed.id, parsed.rest].map((id) => Users.findOneByOrFail({ id })),
|
||||
);
|
||||
return renderActivity(renderFollow(follower, followee, url));
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue