diff --git a/packages/desktop-client/src/components/manager/ConfigServer.tsx b/packages/desktop-client/src/components/manager/ConfigServer.tsx index 7ac54bc875c160599c44931eefaa36aa97e017a3..66e4543304237d860042df52f5152966ca79c8f7 100644 --- a/packages/desktop-client/src/components/manager/ConfigServer.tsx +++ b/packages/desktop-client/src/components/manager/ConfigServer.tsx @@ -8,11 +8,13 @@ import { } from 'loot-core/src/shared/environment'; import { useActions } from '../../hooks/useActions'; +import { useGlobalPref } from '../../hooks/useGlobalPref'; import { useNavigate } from '../../hooks/useNavigate'; import { useSetThemeColor } from '../../hooks/useSetThemeColor'; import { theme } from '../../style'; import { Button, ButtonWithLoading } from '../common/Button2'; import { BigInput } from '../common/Input'; +import { Link } from '../common/Link'; import { Text } from '../common/Text'; import { View } from '../common/View'; import { useServerURL, useSetServerURL } from '../ServerContext'; @@ -32,6 +34,9 @@ export function ConfigServer() { }, [currentUrl]); const [loading, setLoading] = useState(false); const [error, setError] = useState<string | null>(null); + const [_serverSelfSignedCert, setServerSelfSignedCert] = useGlobalPref( + 'serverSelfSignedCert', + ); function getErrorMessage(error: string) { switch (error) { @@ -83,6 +88,23 @@ export function ConfigServer() { setUrl(window.location.origin); } + async function onSelectSelfSignedCertificate() { + const selfSignedCertificateLocation = await window.Actual?.openFileDialog({ + properties: ['openFile'], + filters: [ + { + name: 'Self Signed Certificate', + extensions: ['crt', 'pem'], + }, + ], + }); + + if (selfSignedCertificateLocation) { + setServerSelfSignedCert(selfSignedCertificateLocation[0]); + globalThis.window.Actual.relaunch(); // relaunch to use the certificate + } + } + async function onSkip() { await setServerUrl(null); await loggedIn(); @@ -121,16 +143,43 @@ export function ConfigServer() { </Text> {error && ( - <Text - style={{ - marginTop: 20, - color: theme.errorText, - borderRadius: 4, - fontSize: 15, - }} - > - {getErrorMessage(error)} - </Text> + <> + <Text + style={{ + marginTop: 20, + color: theme.errorText, + borderRadius: 4, + fontSize: 15, + }} + > + {getErrorMessage(error)} + </Text> + {isElectron() && ( + <View + style={{ display: 'flex', flexDirection: 'row', marginTop: 20 }} + > + <Text + style={{ + color: theme.errorText, + borderRadius: 4, + fontSize: 15, + }} + > + <Trans> + If the server is using a self-signed certificate{' '} + <Link + variant="text" + style={{ fontSize: 15 }} + onClick={onSelectSelfSignedCertificate} + > + select it here + </Link> + . + </Trans> + </Text> + </View> + )} + </> )} <View style={{ display: 'flex', flexDirection: 'row', marginTop: 30 }}> diff --git a/packages/loot-core/src/platform/server/fetch/index.electron.ts b/packages/loot-core/src/platform/server/fetch/index.electron.ts index 5aabd6dd82be574c9890d8f049005e21cfe8894d..63ba01462afd7428d4d1a814e72496a264d1e82d 100644 --- a/packages/loot-core/src/platform/server/fetch/index.electron.ts +++ b/packages/loot-core/src/platform/server/fetch/index.electron.ts @@ -1,4 +1,4 @@ -// // @ts-strict-ignore +// @ts-strict-ignore import nodeFetch from 'node-fetch'; export const fetch = (input: RequestInfo | URL, options?: RequestInit) => { diff --git a/packages/loot-core/src/server/main.ts b/packages/loot-core/src/server/main.ts index eee8d7b7665f0ebbd82d1a8b86d832da3a2deba5..0a84bdbaf96b4fcd1604b0ad0848c8eefb401591 100644 --- a/packages/loot-core/src/server/main.ts +++ b/packages/loot-core/src/server/main.ts @@ -1,5 +1,8 @@ // @ts-strict-ignore import './polyfills'; +import https from 'https'; +import tls from 'tls'; + import * as injectAPI from '@actual-app/api/injected'; import * as CRDT from '@actual-app/crdt'; import { v4 as uuidv4 } from 'uuid'; @@ -1253,6 +1256,12 @@ handlers['save-global-prefs'] = async function (prefs) { if ('theme' in prefs) { await asyncStorage.setItem('theme', prefs.theme); } + if ('serverSelfSignedCert' in prefs) { + await asyncStorage.setItem( + 'server-self-signed-cert', + prefs.serverSelfSignedCert, + ); + } return 'ok'; }; @@ -2126,6 +2135,23 @@ export async function initApp(isDev, socketName) { } } + const selfSignedCertPath = await asyncStorage.getItem( + 'server-self-signed-cert', + ); + + if (selfSignedCertPath) { + try { + const selfSignedCert = await fs.readFile(selfSignedCertPath); + https.globalAgent.options.ca = [...tls.rootCertificates, selfSignedCert]; + } catch (error) { + console.error( + 'Unable to add the self signed certificate, removing its reference', + error, + ); + await asyncStorage.removeItem('server-self-signed-cert'); + } + } + const url = await asyncStorage.getItem('server-url'); if (!url) { diff --git a/packages/loot-core/src/types/prefs.d.ts b/packages/loot-core/src/types/prefs.d.ts index c1d7054ea481f70665ee1edaa01bb2aa3c15801b..29f3486313e91102b856a97fd672495613bbb60d 100644 --- a/packages/loot-core/src/types/prefs.d.ts +++ b/packages/loot-core/src/types/prefs.d.ts @@ -88,4 +88,5 @@ export type GlobalPrefs = Partial<{ keyId?: string; theme: Theme; documentDir: string; // Electron only + serverSelfSignedCert: string; // Electron only }>; diff --git a/packages/loot-core/webpack/webpack.browser.config.js b/packages/loot-core/webpack/webpack.browser.config.js index 9498880591dd747dcffa2231c1f2c5e0c4c6fd26..096633ea0099f479b644c89b961635c5a4545cba 100644 --- a/packages/loot-core/webpack/webpack.browser.config.js +++ b/packages/loot-core/webpack/webpack.browser.config.js @@ -41,6 +41,7 @@ module.exports = { process: require.resolve('process/browser'), stream: require.resolve('stream-browserify'), tls: false, + https: false, // used by memfs in a check which we can ignore I think url: false, zlib: require.resolve('browserify-zlib'), diff --git a/upcoming-release-notes/3308.md b/upcoming-release-notes/3308.md new file mode 100644 index 0000000000000000000000000000000000000000..e2b62d70c4218d281c034cef4c226bb94dda1fac --- /dev/null +++ b/upcoming-release-notes/3308.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [MikesGlitch] +--- + +Support servers with self signed certificates in the Desktop app