mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(api): unfurl links to a self-hosted instance's own domain (#2891)
This commit is contained in:
@@ -272,8 +272,6 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig {
|
||||
donationProxyKey,
|
||||
},
|
||||
hosts: {
|
||||
invite: extractHostname(master.endpoints.invite),
|
||||
gift: extractHostname(master.endpoints.gift),
|
||||
marketing: extractHostname(master.endpoints.marketing),
|
||||
unfurlIgnored: master.services.api.unfurl_ignored_hosts,
|
||||
},
|
||||
|
||||
@@ -143,8 +143,6 @@ export interface APIConfig {
|
||||
donationProxyKey: string;
|
||||
};
|
||||
hosts: {
|
||||
invite: string;
|
||||
gift: string;
|
||||
marketing: string;
|
||||
unfurlIgnored: Array<string>;
|
||||
};
|
||||
|
||||
@@ -5,13 +5,18 @@ import * as RegexUtils from '@app/api/utils/RegexUtils';
|
||||
|
||||
let _invitePattern: RegExp | null = null;
|
||||
|
||||
function getInviteEndpointBase(): string {
|
||||
const url = new URL(Config.endpoints.invite);
|
||||
return `${url.hostname}${url.pathname.replace(/\/+$/, '')}`;
|
||||
}
|
||||
|
||||
function getInvitePattern(): RegExp {
|
||||
if (!_invitePattern) {
|
||||
_invitePattern = new RegExp(
|
||||
[
|
||||
'(?:https?:\\/\\/)?',
|
||||
'(?:',
|
||||
`${RegexUtils.escapeRegex(Config.hosts.invite)}(?:\\/#)?\\/(?!invite\\/)([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`,
|
||||
`${RegexUtils.escapeRegex(getInviteEndpointBase())}(?:\\/#)?\\/(?!invite\\/)([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`,
|
||||
'|',
|
||||
`${RegexUtils.escapeRegex(new URL(Config.endpoints.webApp).hostname)}(?:\\/#)?\\/invite\\/([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`,
|
||||
')',
|
||||
|
||||
@@ -8,24 +8,17 @@ import * as InviteUtils from '@app/api/utils/InviteUtils';
|
||||
import {URL_REGEX} from '@fluxer/constants/src/Core';
|
||||
import * as idna from 'idna-uts46-hx';
|
||||
|
||||
const MARKETING_PATH_PREFIXES = ['/channels/', '/theme/'];
|
||||
const CLIENT_ROUTE_PATH_PREFIXES = ['/channels/', '/theme/'];
|
||||
|
||||
interface ExcludedLinkBase {
|
||||
hostname: string;
|
||||
pathPrefix: string;
|
||||
}
|
||||
|
||||
function normalizeHostname(hostname: string | undefined) {
|
||||
return hostname?.trim().toLowerCase() || '';
|
||||
}
|
||||
|
||||
let _marketingHostname: string | null = null;
|
||||
|
||||
function getMarketingHostname() {
|
||||
if (!_marketingHostname) {
|
||||
_marketingHostname = normalizeHostname(Config.hosts.marketing);
|
||||
}
|
||||
return _marketingHostname;
|
||||
}
|
||||
|
||||
const isMarketingPath = (hostname: string, pathname: string) =>
|
||||
hostname === getMarketingHostname() && MARKETING_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix));
|
||||
|
||||
function getWebAppHostname() {
|
||||
try {
|
||||
return new URL(Config.endpoints.webApp).hostname;
|
||||
@@ -34,23 +27,32 @@ function getWebAppHostname() {
|
||||
}
|
||||
}
|
||||
|
||||
let _excludedHostnames: Set<string> | null = null;
|
||||
|
||||
function getExcludedHostnames(): Set<string> {
|
||||
if (!_excludedHostnames) {
|
||||
_excludedHostnames = new Set<string>();
|
||||
const addHostname = (hostname: string | undefined) => {
|
||||
const normalized = normalizeHostname(hostname);
|
||||
if (normalized) {
|
||||
_excludedHostnames!.add(normalized);
|
||||
}
|
||||
};
|
||||
addHostname(Config.hosts.invite);
|
||||
addHostname(Config.hosts.gift);
|
||||
Config.hosts.unfurlIgnored.forEach(addHostname);
|
||||
addHostname(getWebAppHostname());
|
||||
function endpointLinkBase(endpoint: string): ExcludedLinkBase | null {
|
||||
try {
|
||||
const url = new URL(endpoint);
|
||||
return {hostname: normalizeHostname(url.hostname), pathPrefix: `${url.pathname.replace(/\/+$/, '')}/`};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return _excludedHostnames;
|
||||
}
|
||||
|
||||
let _excludedLinkBases: Array<ExcludedLinkBase> | null = null;
|
||||
|
||||
function getExcludedLinkBases(): Array<ExcludedLinkBase> {
|
||||
if (!_excludedLinkBases) {
|
||||
const bases: Array<ExcludedLinkBase | null> = [
|
||||
...Config.hosts.unfurlIgnored.map((hostname) => ({hostname: normalizeHostname(hostname), pathPrefix: '/'})),
|
||||
endpointLinkBase(Config.endpoints.invite),
|
||||
endpointLinkBase(Config.endpoints.gift),
|
||||
];
|
||||
for (const hostname of [getWebAppHostname(), Config.hosts.marketing]) {
|
||||
for (const pathPrefix of CLIENT_ROUTE_PATH_PREFIXES) {
|
||||
bases.push({hostname: normalizeHostname(hostname), pathPrefix});
|
||||
}
|
||||
}
|
||||
_excludedLinkBases = bases.filter((base): base is ExcludedLinkBase => base !== null && base.hostname !== '');
|
||||
}
|
||||
return _excludedLinkBases;
|
||||
}
|
||||
|
||||
function idnaEncodeURL(url: string) {
|
||||
@@ -80,8 +82,9 @@ function isFluxerAppExcludedURL(url: string) {
|
||||
try {
|
||||
const parsedUrl = new URL(url);
|
||||
const hostname = normalizeHostname(parsedUrl.hostname);
|
||||
const isMarketingPathMatch = isMarketingPath(hostname, parsedUrl.pathname);
|
||||
return isMarketingPathMatch || getExcludedHostnames().has(hostname);
|
||||
return getExcludedLinkBases().some(
|
||||
(base) => base.hostname === hostname && parsedUrl.pathname.startsWith(base.pathPrefix),
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user