Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation to warn users of potential dangers from third party repositories and plugins #2

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,15 @@
"updating": "Updating"
}
},
"WarnThirdParty":{
"title_zip": "Third party plugin install",
"title_repo": "Third party store selection",
"button_processing_one": "Please wait {{timer}} second",
"button_processing_many": "Please wait {{timer}} seconds",
"button_idle": "I accept the risks",
"desc_zip": "This plugin that has been requested to be installed is taken outside our official store; we haven't audited it, so it will contain unvetted code; it might be doing exactly what it would be supposed to do or it might have additional features built in that will try to steal your data and take over your Steam account. The Decky team is not responsible for any problem arising from installing this plugin.",
"desc_repo": "This third party store you are installing is not our official store; The plugins it contains aren't being reviewed for security and stability by the Decky team; you should check with whoever is maintaining this store and decide for yourself if you trust them or not. The Decky team is not responsible for any problem arising from installing plugins from this third party store."
},
"Testing": {
"download": "Download"
}
Expand Down
82 changes: 82 additions & 0 deletions frontend/src/components/modals/WarnThirdParty.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { ConfirmModal } from 'decky-frontend-lib';
import { FC, useEffect, useState } from 'react';
import { FaExclamationTriangle } from 'react-icons/fa';

import TranslationHelper, { TranslationClass } from '../../utils/TranslationHelper';

interface WarnThirdPartyProps {
seconds?: number;
type: WarnThirdPartyType;
onOK(): void;
onCancel(): void;
closeModal?(): void;
}

export enum WarnThirdPartyType {
REPO = 0,
ZIP = 1,
}

const WarnThirdParty: FC<WarnThirdPartyProps> = ({ seconds = 5, type, onOK, onCancel, closeModal }) => {
const [waitTimer, setWaitTimer] = useState(seconds);

useEffect(() => {
// exit early when we reach 0
if (waitTimer <= 0) return;

// save intervalId to clear the interval when the
// component re-renders
const intervalId = setInterval(() => {
setWaitTimer(waitTimer - 1);
}, 1000);

// clear interval on re-render to avoid memory leaks
return () => clearInterval(intervalId);
// add waitTimer as a dependency to re-rerun the effect
// when we update it
}, [waitTimer]);

return (
<ConfirmModal
bOKDisabled={waitTimer > 0}
closeModal={closeModal}
onOK={async () => {
await onOK();
}}
onCancel={async () => {
await onCancel();
}}
strTitle={
<div>
<FaExclamationTriangle />
<TranslationHelper trans_class={TranslationClass.WARN_THIRD_PARTY} trans_text="title" warn_type={type} />
</div>
}
strOKButtonText={
waitTimer > 0 ? (
<div>
<TranslationHelper
trans_class={TranslationClass.WARN_THIRD_PARTY}
trans_text="button_processing"
i18n_args={{
timer: waitTimer,
}}
/>
</div>
) : (
<div>
<TranslationHelper trans_class={TranslationClass.WARN_THIRD_PARTY} trans_text="button_idle" />
</div>
)
}
>
<span style={{ color: 'red' }}>
<div>
<TranslationHelper trans_class={TranslationClass.WARN_THIRD_PARTY} trans_text="desc" warn_type={type} />
</div>
</span>
</ConfirmModal>
);
};

export default WarnThirdParty;
17 changes: 16 additions & 1 deletion frontend/src/components/settings/pages/developer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
Navigation,
TextField,
Toggle,
showModal,
} from 'decky-frontend-lib';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
Expand All @@ -18,6 +19,7 @@ import { installFromURL } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
import { getSetting } from '../../../../utils/settings';
import { FileSelectionType } from '../../../modals/filepicker';
import WarnThirdParty, { WarnThirdPartyType } from '../../../modals/WarnThirdParty';
import RemoteDebuggingSettings from '../general/RemoteDebugging';

const logger = new Logger('DeveloperIndex');
Expand Down Expand Up @@ -77,7 +79,20 @@ export default function DeveloperSettings() {
}
icon={<FaLink style={{ display: 'block' }} />}
>
<DialogButton disabled={pluginURL.length == 0} onClick={() => installFromURL(pluginURL)}>
<DialogButton
disabled={pluginURL.length == 0}
onClick={() =>
showModal(
<WarnThirdParty
type={WarnThirdPartyType.ZIP}
onOK={() => {
installFromURL(pluginURL);
}}
onCancel={() => {}}
/>,
)
}
>
{t('SettingsDeveloperIndex.third_party_plugins.button_install')}
</DialogButton>
</Field>
Expand Down
38 changes: 23 additions & 15 deletions frontend/src/components/settings/pages/general/StoreSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Dropdown, Field, TextField } from 'decky-frontend-lib';
import { Dropdown, Field, TextField, showModal } from 'decky-frontend-lib';
import { FunctionComponent } from 'react';
import { useTranslation } from 'react-i18next';
import { FaShapes } from 'react-icons/fa';

import Logger from '../../../../logger';
import { Store } from '../../../../store';
import { useSetting } from '../../../../utils/hooks/useSetting';
import WarnThirdParty, { WarnThirdPartyType } from '../../../modals/WarnThirdParty';

const logger = new Logger('StoreSelect');

Expand Down Expand Up @@ -38,20 +39,27 @@ const StoreSelect: FunctionComponent<{}> = () => {
}}
/>
</Field>
{selectedStore == Store.Custom && (
<Field
label={t('StoreSelect.custom_store.label')}
indentLevel={1}
description={
<TextField
label={t('StoreSelect.custom_store.url_label')}
value={selectedStoreURL || undefined}
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
/>
}
icon={<FaShapes style={{ display: 'block' }} />}
></Field>
)}
{selectedStore == Store.Custom &&
showModal(
<WarnThirdParty
type={WarnThirdPartyType.REPO}
onOK={() => {}}
onCancel={() => setSelectedStore(Store.Default)}
/>,
) && (
<Field
label={t('StoreSelect.custom_store.label')}
indentLevel={1}
description={
<TextField
label={t('StoreSelect.custom_store.url_label')}
value={selectedStoreURL || undefined}
onChange={(e) => setSelectedStoreURL(e?.target.value || null)}
/>
}
icon={<FaShapes style={{ display: 'block' }} />}
></Field>
)}
</>
);
};
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/utils/TranslationHelper.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { FC } from 'react';
import { Translation } from 'react-i18next';

import { WarnThirdPartyType } from '../components/modals/WarnThirdParty';
import Logger from '../logger';
import { InstallType } from '../plugin';

export enum TranslationClass {
PLUGIN_LOADER = 'PluginLoader',
PLUGIN_INSTALL_MODAL = 'PluginInstallModal',
DEVELOPER = 'Developer',
WARN_THIRD_PARTY = 'WarnThirdParty',
}

interface TranslationHelperProps {
trans_class: TranslationClass;
trans_text: string;
i18n_args?: {};
install_type?: number;
warn_type?: WarnThirdPartyType;
}

const logger = new Logger('TranslationHelper');
Expand All @@ -24,6 +27,7 @@ const TranslationHelper: FC<TranslationHelperProps> = ({
trans_text,
i18n_args = null,
install_type = 0,
warn_type = WarnThirdPartyType.REPO,
}) => {
return (
<Translation>
Expand Down Expand Up @@ -52,6 +56,25 @@ const TranslationHelper: FC<TranslationHelperProps> = ({
return i18n_args
? t(TranslationClass.DEVELOPER + '.' + trans_text, i18n_args)
: t(TranslationClass.DEVELOPER + '.' + trans_text);
//Handle different messages in different class cases
case TranslationClass.WARN_THIRD_PARTY:
//Needed only for title and description
if (!trans_text.startsWith('button')) {
switch (warn_type) {
case WarnThirdPartyType.REPO:
return i18n_args
? t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_repo', i18n_args)
: t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_repo');
case WarnThirdPartyType.ZIP:
return i18n_args
? t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_zip', i18n_args)
: t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text + '_zip');
}
} else {
return i18n_args
? t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text, i18n_args)
: t(TranslationClass.WARN_THIRD_PARTY + '.' + trans_text);
}
default:
logger.error('We should never fall in the default case!');
return '';
Expand Down
Loading