mirror of
https://example.com
synced 2024-11-22 10:36:38 +09:00
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
519288317c
commit
e8aeaf7d53
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(
|
export async function apGet(
|
||||||
url: string,
|
url: string,
|
||||||
user?: ILocalUser,
|
user?: ILocalUser,
|
||||||
redirects: boolean = true
|
redirects: boolean = true,
|
||||||
): Promise<IObject> {
|
): Promise<{ finalUrl: string; content: IObject }> {
|
||||||
if (!isValidUrl(url)) {
|
if (!isValidUrl(url)) {
|
||||||
throw new StatusError("Invalid URL", 400);
|
throw new StatusError("Invalid URL", 400);
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,8 @@ export async function apGet(
|
||||||
|
|
||||||
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
||||||
const newUrl = res.headers.get("location");
|
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}`);
|
apLogger.debug(`apGet is redirecting to ${newUrl}`);
|
||||||
return apGet(newUrl, user, false);
|
return apGet(newUrl, user, false);
|
||||||
}
|
}
|
||||||
|
@ -90,7 +91,8 @@ export async function apGet(
|
||||||
|
|
||||||
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
if (redirects && [301, 302, 307, 308].includes(res.status)) {
|
||||||
const newUrl = res.headers.get("location");
|
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}`);
|
apLogger.debug(`apGet is redirecting to ${newUrl}`);
|
||||||
return apGet(newUrl, undefined, false);
|
return apGet(newUrl, undefined, false);
|
||||||
}
|
}
|
||||||
|
@ -98,7 +100,9 @@ export async function apGet(
|
||||||
|
|
||||||
const contentType = res.headers.get("content-type");
|
const contentType = res.headers.get("content-type");
|
||||||
if (contentType == null || !validateContentType(contentType)) {
|
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");
|
if (res.body == null) throw new Error("body is null");
|
||||||
|
@ -106,7 +110,10 @@ export async function apGet(
|
||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
if (text.length > 65536) throw new Error("too big result");
|
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 {
|
function validateContentType(contentType: string): boolean {
|
||||||
|
|
|
@ -6,7 +6,13 @@ import { extractHost, isSelfHost } from "backend-rs";
|
||||||
import { apGet } from "./request.js";
|
import { apGet } from "./request.js";
|
||||||
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
|
import type { IObject, ICollection, IOrderedCollection } from "./type.js";
|
||||||
import { isCollectionOrOrderedCollection, getApId } 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 { parseUri } from "./db-resolver.js";
|
||||||
import renderNote from "@/remote/activitypub/renderer/note.js";
|
import renderNote from "@/remote/activitypub/renderer/note.js";
|
||||||
import { renderLike } from "@/remote/activitypub/renderer/like.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("Getting object from remote, authenticated as user:");
|
||||||
apLogger.debug(JSON.stringify(this.user, null, 2));
|
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 (
|
if (
|
||||||
object == null ||
|
object == null ||
|
||||||
|
@ -127,6 +133,13 @@ export default class Resolver {
|
||||||
throw new Error("invalid response");
|
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;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +173,8 @@ export default class Resolver {
|
||||||
// if rest is a <followee id>
|
// if rest is a <followee id>
|
||||||
if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
|
if (parsed.rest != null && /^\w+$/.test(parsed.rest)) {
|
||||||
const [follower, followee] = await Promise.all(
|
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));
|
return renderActivity(renderFollow(follower, followee, url));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue