Merge pull request #158 from Snazzah/trusted-links

This commit is contained in:
Paul Makles 2021-09-03 11:18:16 +01:00 committed by GitHub
commit 966daa6c78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 14 deletions

View file

@ -18,13 +18,15 @@ import { useCallback, useContext } from "preact/hooks";
import { internalEmit } from "../../lib/eventEmitter"; import { internalEmit } from "../../lib/eventEmitter";
import { getState } from "../../redux";
import { useIntermediate } from "../../context/intermediate/Intermediate";
import { AppContext } from "../../context/revoltjs/RevoltClient"; import { AppContext } from "../../context/revoltjs/RevoltClient";
import { generateEmoji } from "../common/Emoji"; import { generateEmoji } from "../common/Emoji";
import { emojiDictionary } from "../../assets/emojis"; import { emojiDictionary } from "../../assets/emojis";
import { MarkdownProps } from "./Markdown"; import { MarkdownProps } from "./Markdown";
import {useIntermediate} from "../../context/intermediate/Intermediate";
// TODO: global.d.ts file for defining globals // TODO: global.d.ts file for defining globals
declare global { declare global {
@ -35,9 +37,9 @@ declare global {
const ALLOWED_ORIGINS = [ const ALLOWED_ORIGINS = [
location.hostname, location.hostname,
'app.revolt.chat', "app.revolt.chat",
'nightly.revolt.chat', "nightly.revolt.chat",
'local.revolt.chat', "local.revolt.chat",
]; ];
// Handler for code block copy. // Handler for code block copy.
@ -176,13 +178,16 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
element.removeAttribute("data-type"); element.removeAttribute("data-type");
element.removeAttribute("target"); element.removeAttribute("target");
let internal; let internal,
url: URL | null = null;
const href = element.href; const href = element.href;
if (href) { if (href) {
try { try {
const url = new URL(href, location.href); url = new URL(href, location.href);
if (ALLOWED_ORIGINS.includes(url.hostname)) { if (
ALLOWED_ORIGINS.includes(url.hostname)
) {
internal = true; internal = true;
element.addEventListener( element.addEventListener(
"click", "click",
@ -202,12 +207,20 @@ export default function Renderer({ content, disallowBigEmoji }: MarkdownProps) {
if (!internal) { if (!internal) {
element.setAttribute("target", "_blank"); element.setAttribute("target", "_blank");
element.onclick = (ev) => { element.onclick = (ev) => {
const { trustedLinks } = getState();
if (
!url ||
!trustedLinks.domains?.includes(
url.hostname,
)
) {
ev.preventDefault(); ev.preventDefault();
openScreen({ openScreen({
id: "external_link_prompt", id: "external_link_prompt",
link: href link: href,
}) });
} }
};
} }
}, },
); );

View file

@ -1,6 +1,7 @@
import { Text } from "preact-i18n"; import { Text } from "preact-i18n";
import Modal from "../../../components/ui/Modal"; import Modal from "../../../components/ui/Modal";
import { dispatch } from "../../../redux";
interface Props { interface Props {
onClose: () => void; onClose: () => void;
@ -29,8 +30,23 @@ export function ExternalLinkModal({ onClose, link }: Props) {
confirmation: false, confirmation: false,
children: "Cancel", children: "Cancel",
}, },
{
onClick: () => {
try {
const url = new URL(link);
dispatch({
type: "TRUSTED_LINKS_ADD_DOMAIN",
domain: url.hostname
});
} catch(e) {}
window.open(link, "_blank");
onClose();
},
plain: true,
children: <Text id="app.special.modals.external_links.trust_domain" />,
}
]}> ]}>
<Text id={"app.special.modals.external_links.short"} /> <br /> <Text id="app.special.modals.external_links.short" /> <br />
<a>{link}</a> <a>{link}</a>
</Modal> </Modal>
); );

View file

@ -14,6 +14,7 @@ import { QueuedMessage } from "./reducers/queue";
import { SectionToggle } from "./reducers/section_toggle"; import { SectionToggle } from "./reducers/section_toggle";
import { Settings } from "./reducers/settings"; import { Settings } from "./reducers/settings";
import { SyncOptions } from "./reducers/sync"; import { SyncOptions } from "./reducers/sync";
import { TrustedLinks } from "./reducers/trusted_links";
import { Unreads } from "./reducers/unreads"; import { Unreads } from "./reducers/unreads";
export type State = { export type State = {
@ -29,6 +30,7 @@ export type State = {
lastOpened: LastOpened; lastOpened: LastOpened;
notifications: Notifications; notifications: Notifications;
sectionToggle: SectionToggle; sectionToggle: SectionToggle;
trustedLinks: TrustedLinks;
}; };
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -59,6 +61,7 @@ store.subscribe(() => {
lastOpened, lastOpened,
notifications, notifications,
sectionToggle, sectionToggle,
trustedLinks,
} = store.getState() as State; } = store.getState() as State;
localForage.setItem("state", { localForage.setItem("state", {
@ -74,6 +77,7 @@ store.subscribe(() => {
lastOpened, lastOpened,
notifications, notifications,
sectionToggle, sectionToggle,
trustedLinks,
}); });
}); });

View file

@ -12,6 +12,7 @@ import { sectionToggle, SectionToggleAction } from "./section_toggle";
import { config, ConfigAction } from "./server_config"; import { config, ConfigAction } from "./server_config";
import { settings, SettingsAction } from "./settings"; import { settings, SettingsAction } from "./settings";
import { sync, SyncAction } from "./sync"; import { sync, SyncAction } from "./sync";
import { trustedLinks, TrustedLinksAction } from "./trusted_links";
import { unreads, UnreadsAction } from "./unreads"; import { unreads, UnreadsAction } from "./unreads";
export default combineReducers({ export default combineReducers({
@ -27,6 +28,7 @@ export default combineReducers({
lastOpened, lastOpened,
notifications, notifications,
sectionToggle, sectionToggle,
trustedLinks,
}); });
export type Action = export type Action =
@ -42,4 +44,5 @@ export type Action =
| LastOpenedAction | LastOpenedAction
| NotificationsAction | NotificationsAction
| SectionToggleAction | SectionToggleAction
| TrustedLinksAction
| { type: "__INIT"; state: State }; | { type: "__INIT"; state: State };

View file

@ -0,0 +1,37 @@
export interface TrustedLinks {
domains?: string[];
}
export type TrustedLinksAction =
| { type: undefined }
| {
type: "TRUSTED_LINKS_ADD_DOMAIN";
domain: string;
}
| {
type: "TRUSTED_LINKS_REMOVE_DOMAIN";
domain: string;
};
export function trustedLinks(
state = {} as TrustedLinks,
action: TrustedLinksAction,
): TrustedLinks {
switch (action.type) {
case "TRUSTED_LINKS_ADD_DOMAIN":
return {
...state,
domains: [
...(state.domains ?? []).filter((v) => v !== action.domain),
action.domain,
],
};
case "TRUSTED_LINKS_REMOVE_DOMAIN":
return {
...state,
domains: state.domains?.filter((v) => v !== action.domain),
};
default:
return state;
}
}