fix: URLs to internal resources should not be sent to Iframely

This commit is contained in:
Tom Moor
2023-09-04 14:46:26 -04:00
parent 6079b71d3c
commit 1c99e8519a
2 changed files with 27 additions and 21 deletions

View File

@@ -1,7 +1,8 @@
import Router from "koa-router"; import Router from "koa-router";
import parseDocumentSlug from "@shared/utils/parseDocumentSlug"; import parseDocumentSlug from "@shared/utils/parseDocumentSlug";
import parseMentionUrl from "@shared/utils/parseMentionUrl"; import parseMentionUrl from "@shared/utils/parseMentionUrl";
import { NotFoundError } from "@server/errors"; import { isInternalUrl } from "@shared/utils/urls";
import { NotFoundError, ValidationError } from "@server/errors";
import auth from "@server/middlewares/authentication"; import auth from "@server/middlewares/authentication";
import { rateLimiter } from "@server/middlewares/rateLimiter"; import { rateLimiter } from "@server/middlewares/rateLimiter";
import validate from "@server/middlewares/validate"; import validate from "@server/middlewares/validate";
@@ -26,12 +27,16 @@ router.post(
const { user: actor } = ctx.state.auth; const { user: actor } = ctx.state.auth;
const urlObj = new URL(url); const urlObj = new URL(url);
// Mentions
if (urlObj.protocol === "mention:") { if (urlObj.protocol === "mention:") {
if (!documentId) {
throw ValidationError("Document ID is required to unfurl a mention");
}
const { modelId: userId } = parseMentionUrl(url); const { modelId: userId } = parseMentionUrl(url);
const [user, document] = await Promise.all([ const [user, document] = await Promise.all([
User.findByPk(userId), User.findByPk(userId),
Document.findByPk(documentId!, { Document.findByPk(documentId, {
userId: actor.id, userId: actor.id,
}), }),
]); ]);
@@ -48,20 +53,25 @@ router.post(
return; return;
} }
const previewDocumentId = parseDocumentSlug(url); // Internal resources
if (previewDocumentId) { if (isInternalUrl(url)) {
const document = previewDocumentId const previewDocumentId = parseDocumentSlug(url);
? await Document.findByPk(previewDocumentId, { userId: actor.id }) if (previewDocumentId) {
: undefined; const document = previewDocumentId
if (!document) { ? await Document.findByPk(previewDocumentId, { userId: actor.id })
throw NotFoundError("Document does not exist"); : undefined;
} if (!document) {
authorize(actor, "read", document); throw NotFoundError("Document does not exist");
}
authorize(actor, "read", document);
ctx.body = presentDocument(document, actor); ctx.body = presentDocument(document, actor);
return; return;
}
return (ctx.response.status = 204);
} }
// External resources
if (resolvers.Iframely) { if (resolvers.Iframely) {
const data = await resolvers.Iframely.unfurl(url); const data = await resolvers.Iframely.unfurl(url);
return data.error return data.error

View File

@@ -1,6 +1,6 @@
import escapeRegExp from "lodash/escapeRegExp"; import escapeRegExp from "lodash/escapeRegExp";
import env from "../env"; import env from "../env";
import { parseDomain } from "./domains"; import { getBaseDomain, parseDomain } from "./domains";
/** /**
* Prepends the CDN url to the given path (If a CDN is configured). * Prepends the CDN url to the given path (If a CDN is configured).
@@ -15,10 +15,6 @@ export function cdnPath(path: string): string {
/** /**
* Returns true if the given string is a link to inside the application. * Returns true if the given string is a link to inside the application.
* *
* Important Note: If this is called server-side, it will always return false.
* The reason this is in a shared util is because it's used in an editor plugin
* which is also in the shared code
*
* @param url The url to check. * @param url The url to check.
* @returns True if the url is internal, false otherwise. * @returns True if the url is internal, false otherwise.
*/ */
@@ -36,10 +32,10 @@ export function isInternalUrl(href: string) {
const outline = const outline =
typeof window !== "undefined" typeof window !== "undefined"
? parseDomain(window.location.href) ? parseDomain(window.location.href)
: undefined; : parseDomain(env.URL);
const domain = parseDomain(href); const domain = parseDomain(href);
return outline?.host === domain.host;
return outline.host === domain.host || domain.host.endsWith(getBaseDomain());
} }
/** /**