From 72d364a5c18e7236805612cd5cae4438c34af8e8 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 8 Aug 2023 13:17:38 -0700 Subject: [PATCH 1/4] Desktop: Show warning when password is empty --- .eslintignore | 1 + .gitignore | 1 + .../gui/ConfigScreen/ConfigScreen.tsx | 19 ++++++++++++++++++- .../app-desktop/gui/MainScreen/MainScreen.tsx | 9 +++++++++ .../screens/ConfigScreen/ConfigScreen.tsx | 2 +- .../shared/{ => config}/config-shared.js | 8 ++++---- .../shouldShowMissingPasswordWarning.ts | 13 +++++++++++++ .../defaultPlugins/defaultPluginsUtils.ts | 2 +- readme/faq.md | 5 +++++ 9 files changed, 53 insertions(+), 7 deletions(-) rename packages/lib/components/shared/{ => config}/config-shared.js (96%) create mode 100644 packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts diff --git a/.eslintignore b/.eslintignore index a5e7e5d9fbb..b436a7e3a58 100644 --- a/.eslintignore +++ b/.eslintignore @@ -516,6 +516,7 @@ packages/lib/commands/index.js packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/synchronize.js packages/lib/components/EncryptionConfigScreen/utils.js +packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js packages/lib/components/shared/note-screen-shared.js packages/lib/components/shared/reduxSharedMiddleware.js packages/lib/database-driver-better-sqlite.js diff --git a/.gitignore b/.gitignore index d4aa75c28e0..60ea327b09e 100644 --- a/.gitignore +++ b/.gitignore @@ -502,6 +502,7 @@ packages/lib/commands/index.js packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/synchronize.js packages/lib/components/EncryptionConfigScreen/utils.js +packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js packages/lib/components/shared/note-screen-shared.js packages/lib/components/shared/reduxSharedMiddleware.js packages/lib/database-driver-better-sqlite.js diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index 8d94a989081..a352f0c3e78 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -12,7 +12,7 @@ const { connect } = require('react-redux'); const { themeStyle } = require('@joplin/lib/theme'); const pathUtils = require('@joplin/lib/path-utils'); import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; -const shared = require('@joplin/lib/components/shared/config-shared.js'); +const shared = require('@joplin/lib/components/shared/config/config-shared.js'); import ClipperConfigScreen from '../ClipperConfigScreen'; import restart from '../../services/restart'; import PluginService from '@joplin/lib/services/plugins/PluginService'; @@ -20,6 +20,8 @@ import { getDefaultPluginsInstallState, updateDefaultPluginsInstallState } from import getDefaultPluginsInfo from '@joplin/lib/services/plugins/defaultPlugins/desktopDefaultPluginsInfo'; import JoplinCloudConfigScreen from '../JoplinCloudConfigScreen'; import ToggleAdvancedSettingsButton from './controls/ToggleAdvancedSettingsButton'; +import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning'; +import shim from '@joplin/lib/shim'; const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen'); const settingKeyToControl: any = { @@ -181,6 +183,21 @@ class ConfigScreenComponent extends React.Component { if (section.name === 'sync') { const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']); const statusStyle = { ...theme.textStyle, marginTop: 10 }; + const warningStyle = { ...theme.textStyle, color: theme.colorWarn }; + + if (shouldShowMissingPasswordWarning(settings['sync.target'], settings)) { + const macInfoLink = ( + + {_('Why is my password missing?')} + + ); + settingComps.push( +

+ {_('Warning: Missing password.')} + {shim.isMac() ? macInfoLink : null} +

+ ); + } if (syncTargetMd.supportsConfigCheck) { const messages = shared.checkSyncConfigMessages(this); diff --git a/packages/app-desktop/gui/MainScreen/MainScreen.tsx b/packages/app-desktop/gui/MainScreen/MainScreen.tsx index 4ed7444d74e..2c649e1fcc5 100644 --- a/packages/app-desktop/gui/MainScreen/MainScreen.tsx +++ b/packages/app-desktop/gui/MainScreen/MainScreen.tsx @@ -20,6 +20,7 @@ import NoteListWrapper from '../NoteListWrapper/NoteListWrapper'; import { AppState } from '../../app.reducer'; import { saveLayout, loadLayout } from '../ResizableLayout/utils/persist'; import Setting from '@joplin/lib/models/Setting'; +import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning'; import produce from 'immer'; import shim from '@joplin/lib/shim'; import bridge from '../../services/bridge'; @@ -67,6 +68,7 @@ interface Props { shouldUpgradeSyncTarget: boolean; hasDisabledSyncItems: boolean; hasDisabledEncryptionItems: boolean; + hasMissingSyncCredentials: boolean; showMissingMasterKeyMessage: boolean; showNeedUpgradingMasterKeyMessage: boolean; showShouldReencryptMessage: boolean; @@ -645,6 +647,12 @@ class MainScreenComponent extends React.Component { _('Set the password'), onViewEncryptionConfigScreen ); + } else if (this.props.hasMissingSyncCredentials) { + msg = this.renderNotificationMessage( + _('The synchronisation password is missing.'), + _('Set the password'), + onViewEncryptionConfigScreen + ); } else if (this.props.showInstallTemplatesPlugin) { msg = this.renderNotificationMessage( 'The template feature has been moved to a plugin called "Templates".', @@ -875,6 +883,7 @@ const mapStateToProps = (state: AppState) => { showNeedUpgradingMasterKeyMessage: showNeedUpgradingEnabledMasterKeyMessage, showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES, shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO, + hasMissingSyncCredentials: shouldShowMissingPasswordWarning(state.settings['sync.target'], state.settings), pluginsLegacy: state.pluginsLegacy, plugins: state.pluginService.plugins, pluginHtmlContents: state.pluginService.pluginHtmlContents, diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index aabff19f353..dd9b53b74a8 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -20,7 +20,7 @@ const { _ } = require('@joplin/lib/locale'); const { BaseScreenComponent } = require('../../base-screen.js'); const { Dropdown } = require('../../Dropdown'); const { themeStyle } = require('../../global-style.js'); -const shared = require('@joplin/lib/components/shared/config-shared.js'); +const shared = require('@joplin/lib/components/shared/config/config-shared.js'); import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; import { openDocumentTree } from '@joplin/react-native-saf-x'; import biometricAuthenticate from '../../biometrics/biometricAuthenticate'; diff --git a/packages/lib/components/shared/config-shared.js b/packages/lib/components/shared/config/config-shared.js similarity index 96% rename from packages/lib/components/shared/config-shared.js rename to packages/lib/components/shared/config/config-shared.js index c0dbd72f10e..a4776ebe49a 100644 --- a/packages/lib/components/shared/config-shared.js +++ b/packages/lib/components/shared/config/config-shared.js @@ -1,7 +1,7 @@ -const Setting = require('../../models/Setting').default; -const SyncTargetRegistry = require('../../SyncTargetRegistry').default; -const ObjectUtils = require('../../ObjectUtils'); -const { _ } = require('../../locale'); +const Setting = require('../../../models/Setting').default; +const SyncTargetRegistry = require('../../../SyncTargetRegistry').default; +const ObjectUtils = require('../../../ObjectUtils'); +const { _ } = require('../../../locale'); const { createSelector } = require('reselect'); const Logger = require('@joplin/utils/Logger').default; diff --git a/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts b/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts new file mode 100644 index 00000000000..399aa69c128 --- /dev/null +++ b/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts @@ -0,0 +1,13 @@ +import SyncTargetRegistry from '../../../SyncTargetRegistry'; + +const shouldShowMissingPasswordWarning = (syncTargetId: number, settings: any) => { + // List of sync targets that expect a non-empty password setting + const targetsExpectingPassword = [ + 'webdav', 'nextcloud', 'amazon_s3', 'joplinServer', 'joplinCloud', + ].map(name => SyncTargetRegistry.nameToId(name)); + + const expectsPassword = targetsExpectingPassword.includes(syncTargetId); + return expectsPassword && settings[`sync.${syncTargetId}.password`] === ''; +}; + +export default shouldShowMissingPasswordWarning; diff --git a/packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.ts b/packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.ts index a08249fd276..9376dc4b510 100644 --- a/packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.ts +++ b/packages/lib/services/plugins/defaultPlugins/defaultPluginsUtils.ts @@ -5,7 +5,7 @@ import shim from '../../../shim'; import PluginService, { defaultPluginSetting, DefaultPluginsInfo, PluginSettings } from '../PluginService'; import Logger from '@joplin/utils/Logger'; import * as React from 'react'; -const shared = require('../../../components/shared/config-shared.js'); +const shared = require('../../../components/shared/config/config-shared.js'); const logger = Logger.create('defaultPluginsUtils'); diff --git a/readme/faq.md b/readme/faq.md index b7af78f890c..782dfd0f32e 100644 --- a/readme/faq.md +++ b/readme/faq.md @@ -141,6 +141,11 @@ In this case, [make sure you enter the correct WebDAV URL](https://github.com/la - Check the WebDAV URL - to get the correct URL, go to Nextcloud and, in the left sidebar, click on "Settings" and copy the WebDAV URL from there. **Do not forget to add the folder you've created to that URL**. For example, if the base the WebDAV URL is "https://example.com/nextcloud/remote.php/webdav/" and you want the notes to be synced in the "Joplin" directory, you need to give the URL "https://example.com/nextcloud/remote.php/webdav/Joplin" **and you need to create the "Joplin" directory yourself**. - Did you enable **2FA** (Multi-factor authentication) on Nextcloud? In that case, you need to [create an app password for Joplin in the Nextcloud admin interface](https://github.com/laurent22/joplin/issues/1453#issuecomment-486640902). +## Why did my sync and encryption passwords disappear after updating Joplin? + +- With version 2.12, Joplin supports M1 Macs natively! As a result, upgrading Joplin on one of these systems causes Joplin to lose access to information stored by older versions of the app in the system keychain. This includes sync and encryption passwords. +- Re-entering the passwords should fix related sync and encryption issues. + ## How can I use self-signed SSL certificates on Android? If you want to serve using https but can't or don't want to use SSL certificates signed by trusted certificate authorities (like "Let's Encrypt"), it's possible to generate a custom CA and sign your certificates with it. You can generate the CA and certificates using [openssl](https://gist.github.com/fntlnz/cf14feb5a46b2eda428e000157447309), but I like to use a tool called [mkcert](https://github.com/FiloSottile/mkcert) for it's simplicity. Finally, you have to add your CA certificate to Android settings so that Android can recognize the certificates you signed with your CA as valid ([link](https://support.google.com/nexus/answer/2844832?hl=en-GB)). From b5605a9afef7b45c20cbb5b622138577508e6a79 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 8 Aug 2023 13:53:38 -0700 Subject: [PATCH 2/4] Fix FAQ and settings links --- .../gui/ConfigScreen/ConfigScreen.tsx | 25 +++++++++++++++---- .../app-desktop/gui/MainScreen/MainScreen.tsx | 24 ++++++++++++------ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index a352f0c3e78..f12b1542c18 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -22,6 +22,7 @@ import JoplinCloudConfigScreen from '../JoplinCloudConfigScreen'; import ToggleAdvancedSettingsButton from './controls/ToggleAdvancedSettingsButton'; import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning'; import shim from '@joplin/lib/shim'; +import StyledLink from '../style/StyledLink'; const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen'); const settingKeyToControl: any = { @@ -185,16 +186,30 @@ class ConfigScreenComponent extends React.Component { const statusStyle = { ...theme.textStyle, marginTop: 10 }; const warningStyle = { ...theme.textStyle, color: theme.colorWarn }; - if (shouldShowMissingPasswordWarning(settings['sync.target'], settings)) { + // Don't show the missing password warning if the user just changed the sync target (but hasn't + // saved yet). + const matchesSavedTarget = settings['sync.target'] === this.props.settings['sync.target']; + if (matchesSavedTarget && shouldShowMissingPasswordWarning(settings['sync.target'], settings)) { + const openMissingPasswordFAQ = () => + bridge().openExternal('https://joplinapp.org/faq#why-did-my-sync-and-encryption-passwords-disappear-after-updating-joplin'); + const macInfoLink = ( - + {_('Why is my password missing?')} - + ); + + // The FAQ section related to missing passwords is specific to MacOS/ARM -- only show it + // in that case. + const showMacInfoLink = shim.isMac(); + settingComps.push( -

+

{_('Warning: Missing password.')} - {shim.isMac() ? macInfoLink : null} + {showMacInfoLink ? macInfoLink : null}

); } diff --git a/packages/app-desktop/gui/MainScreen/MainScreen.tsx b/packages/app-desktop/gui/MainScreen/MainScreen.tsx index 2c649e1fcc5..be895e56cb7 100644 --- a/packages/app-desktop/gui/MainScreen/MainScreen.tsx +++ b/packages/app-desktop/gui/MainScreen/MainScreen.tsx @@ -563,6 +563,16 @@ class MainScreenComponent extends React.Component { }); }; + const onViewSyncSettingsScreen = () => { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'Config', + props: { + defaultSection: 'sync', + }, + }); + }; + const onViewPluginScreen = () => { this.props.dispatch({ type: 'NAV_GO', @@ -600,6 +610,12 @@ class MainScreenComponent extends React.Component { _('Disable safe mode and restart'), onDisableSafeModeAndRestart ); + } else if (this.props.hasMissingSyncCredentials) { + msg = this.renderNotificationMessage( + _('The synchronisation password is missing.'), + _('Set the password'), + onViewSyncSettingsScreen + ); } else if (this.props.shouldUpgradeSyncTarget) { msg = this.renderNotificationMessage( _('The sync target needs to be upgraded before Joplin can sync. The operation may take a few minutes to complete and the app needs to be restarted. To proceed please click on the link.'), @@ -647,12 +663,6 @@ class MainScreenComponent extends React.Component { _('Set the password'), onViewEncryptionConfigScreen ); - } else if (this.props.hasMissingSyncCredentials) { - msg = this.renderNotificationMessage( - _('The synchronisation password is missing.'), - _('Set the password'), - onViewEncryptionConfigScreen - ); } else if (this.props.showInstallTemplatesPlugin) { msg = this.renderNotificationMessage( 'The template feature has been moved to a plugin called "Templates".', @@ -670,7 +680,7 @@ class MainScreenComponent extends React.Component { public messageBoxVisible(props: Props = null) { if (!props) props = this.props; - return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin; + return props.hasDisabledSyncItems || props.showMissingMasterKeyMessage || props.hasMissingSyncCredentials || props.showNeedUpgradingMasterKeyMessage || props.showShouldReencryptMessage || props.hasDisabledEncryptionItems || this.props.shouldUpgradeSyncTarget || props.isSafeMode || this.showShareInvitationNotification(props) || this.props.needApiAuth || this.props.showInstallTemplatesPlugin; } public registerCommands() { From 480dbfb0a5242e792a387ce6ba902b1970faed9d Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 8 Aug 2023 15:32:39 -0700 Subject: [PATCH 3/4] Fix spacing between password warning and link --- packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index f12b1542c18..d889774353c 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -208,8 +208,7 @@ class ConfigScreenComponent extends React.Component { settingComps.push(

- {_('Warning: Missing password.')} - {showMacInfoLink ? macInfoLink : null} + {_('Warning: Missing password.')} {showMacInfoLink ? macInfoLink : null}

); } From 4aba7b840c7540e2c02120b4eaa9c9414faba2a2 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Thu, 10 Aug 2023 11:48:39 -0700 Subject: [PATCH 4/4] Apply suggestions from code review and add tests --- .eslintignore | 1 + .gitignore | 1 + .../gui/ConfigScreen/ConfigScreen.tsx | 6 +-- packages/lib/BaseSyncTarget.ts | 6 +++ packages/lib/SyncTargetAmazonS3.js | 4 ++ packages/lib/SyncTargetJoplinCloud.ts | 4 ++ packages/lib/SyncTargetJoplinServer.ts | 4 ++ packages/lib/SyncTargetNextcloud.js | 4 ++ packages/lib/SyncTargetWebDAV.js | 4 ++ .../shouldShowMissingPasswordWarning.test.ts | 42 +++++++++++++++++++ .../shouldShowMissingPasswordWarning.ts | 8 +--- packages/lib/testing/test-utils.ts | 2 + 12 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.ts diff --git a/.eslintignore b/.eslintignore index b436a7e3a58..0d0a1776b68 100644 --- a/.eslintignore +++ b/.eslintignore @@ -516,6 +516,7 @@ packages/lib/commands/index.js packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/synchronize.js packages/lib/components/EncryptionConfigScreen/utils.js +packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js packages/lib/components/shared/note-screen-shared.js packages/lib/components/shared/reduxSharedMiddleware.js diff --git a/.gitignore b/.gitignore index 60ea327b09e..08b45a60b77 100644 --- a/.gitignore +++ b/.gitignore @@ -502,6 +502,7 @@ packages/lib/commands/index.js packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/synchronize.js packages/lib/components/EncryptionConfigScreen/utils.js +packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.js packages/lib/components/shared/config/shouldShowMissingPasswordWarning.js packages/lib/components/shared/note-screen-shared.js packages/lib/components/shared/reduxSharedMiddleware.js diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index d889774353c..53ba0cc9168 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -198,17 +198,17 @@ class ConfigScreenComponent extends React.Component { onClick={openMissingPasswordFAQ} style={theme.linkStyle} > - {_('Why is my password missing?')} + {_('Help')} ); // The FAQ section related to missing passwords is specific to MacOS/ARM -- only show it // in that case. - const showMacInfoLink = shim.isMac(); + const showMacInfoLink = shim.isMac() && process.arch === 'arm64'; settingComps.push(

- {_('Warning: Missing password.')} {showMacInfoLink ? macInfoLink : null} + {_('Warning: Missing password.')}{' '}{showMacInfoLink ? macInfoLink : null}

); } diff --git a/packages/lib/BaseSyncTarget.ts b/packages/lib/BaseSyncTarget.ts index d91535bb062..95a42a09376 100644 --- a/packages/lib/BaseSyncTarget.ts +++ b/packages/lib/BaseSyncTarget.ts @@ -26,6 +26,12 @@ export default class BaseSyncTarget { return false; } + // Returns true if the sync target expects a non-empty sync.{id}.password + // setting. + public static requiresPassword() { + return false; + } + public static description(): string { return ''; } diff --git a/packages/lib/SyncTargetAmazonS3.js b/packages/lib/SyncTargetAmazonS3.js index 7b5563b9f4a..729e40e4f1e 100644 --- a/packages/lib/SyncTargetAmazonS3.js +++ b/packages/lib/SyncTargetAmazonS3.js @@ -36,6 +36,10 @@ class SyncTargetAmazonS3 extends BaseSyncTarget { return true; } + static requiresPassword() { + return true; + } + static s3BucketName() { return Setting.value('sync.8.path'); } diff --git a/packages/lib/SyncTargetJoplinCloud.ts b/packages/lib/SyncTargetJoplinCloud.ts index 8ebc3a54c2d..3734d1cca4c 100644 --- a/packages/lib/SyncTargetJoplinCloud.ts +++ b/packages/lib/SyncTargetJoplinCloud.ts @@ -49,6 +49,10 @@ export default class SyncTargetJoplinCloud extends BaseSyncTarget { return true; } + public static requiresPassword() { + return true; + } + public async fileApi(): Promise { return super.fileApi(); } diff --git a/packages/lib/SyncTargetJoplinServer.ts b/packages/lib/SyncTargetJoplinServer.ts index 97b83bdfb58..b5174a37ab4 100644 --- a/packages/lib/SyncTargetJoplinServer.ts +++ b/packages/lib/SyncTargetJoplinServer.ts @@ -65,6 +65,10 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget { return true; } + public static requiresPassword() { + return true; + } + public async fileApi(): Promise { return super.fileApi(); } diff --git a/packages/lib/SyncTargetNextcloud.js b/packages/lib/SyncTargetNextcloud.js index b44585276b1..ad63aface33 100644 --- a/packages/lib/SyncTargetNextcloud.js +++ b/packages/lib/SyncTargetNextcloud.js @@ -33,6 +33,10 @@ class SyncTargetNextcloud extends BaseSyncTarget { return true; } + static requiresPassword() { + return true; + } + static async checkConfig(options) { return SyncTargetWebDAV.checkConfig(options); } diff --git a/packages/lib/SyncTargetWebDAV.js b/packages/lib/SyncTargetWebDAV.js index ac163916868..0bf42859114 100644 --- a/packages/lib/SyncTargetWebDAV.js +++ b/packages/lib/SyncTargetWebDAV.js @@ -32,6 +32,10 @@ class SyncTargetWebDAV extends BaseSyncTarget { return true; } + static requiresPassword() { + return true; + } + static async newFileApi_(syncTargetId, options) { const apiOptions = { baseUrl: () => options.path(), diff --git a/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.ts b/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.ts new file mode 100644 index 00000000000..ff8f1e4b714 --- /dev/null +++ b/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.test.ts @@ -0,0 +1,42 @@ +import SyncTargetRegistry from '../../../SyncTargetRegistry'; +import shouldShowMissingPasswordWarning from './shouldShowMissingPasswordWarning'; + +// Maps targets to whether each target requires a password. +// A subset of all sync targets. +const targetToRequiresPassword: Record = { + 'nextcloud': true, + 'webdav': true, + 'amazon_s3': true, + 'joplinServer': true, + 'joplinCloud': true, + 'onedrive': false, + 'dropbox': false, +}; + +describe('shouldShowMissingPasswordWarning', () => { + it('should return true when sync target requires a password and the password is missing', () => { + for (const targetName in targetToRequiresPassword) { + const targetId = SyncTargetRegistry.nameToId(targetName); + const expected = targetToRequiresPassword[targetName]; + + expect(shouldShowMissingPasswordWarning(targetId, {})).toBe(expected); + + // Should also consider an empty string to be missing + const settings = { + [`sync.${targetId}.password`]: '', + }; + expect(shouldShowMissingPasswordWarning(targetId, settings)).toBe(expected); + } + }); + + it('should return false when a password is present', () => { + for (const targetName in targetToRequiresPassword) { + const targetId = SyncTargetRegistry.nameToId(targetName); + const settings = { + [`sync.${targetId}.password`]: 'some nonempty', + }; + + expect(shouldShowMissingPasswordWarning(targetId, settings)).toBe(false); + } + }); +}); diff --git a/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts b/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts index 399aa69c128..c70dc5c9ed4 100644 --- a/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts +++ b/packages/lib/components/shared/config/shouldShowMissingPasswordWarning.ts @@ -1,13 +1,9 @@ import SyncTargetRegistry from '../../../SyncTargetRegistry'; const shouldShowMissingPasswordWarning = (syncTargetId: number, settings: any) => { - // List of sync targets that expect a non-empty password setting - const targetsExpectingPassword = [ - 'webdav', 'nextcloud', 'amazon_s3', 'joplinServer', 'joplinCloud', - ].map(name => SyncTargetRegistry.nameToId(name)); + const syncTargetClass = SyncTargetRegistry.classById(syncTargetId); - const expectsPassword = targetsExpectingPassword.includes(syncTargetId); - return expectsPassword && settings[`sync.${syncTargetId}.password`] === ''; + return syncTargetClass.requiresPassword() && !settings[`sync.${syncTargetId}.password`]; }; export default shouldShowMissingPasswordWarning; diff --git a/packages/lib/testing/test-utils.ts b/packages/lib/testing/test-utils.ts index e517293c148..00c285dc2d1 100644 --- a/packages/lib/testing/test-utils.ts +++ b/packages/lib/testing/test-utils.ts @@ -41,6 +41,7 @@ const SyncTargetFilesystem = require('../SyncTargetFilesystem.js'); const SyncTargetNextcloud = require('../SyncTargetNextcloud.js'); const SyncTargetDropbox = require('../SyncTargetDropbox.js'); const SyncTargetAmazonS3 = require('../SyncTargetAmazonS3.js'); +const SyncTargetWebDAV = require('../SyncTargetWebDAV.js'); import SyncTargetJoplinServer from '../SyncTargetJoplinServer'; import EncryptionService from '../services/e2ee/EncryptionService'; import DecryptionWorker from '../services/DecryptionWorker'; @@ -122,6 +123,7 @@ SyncTargetRegistry.addClass(SyncTargetOneDrive); SyncTargetRegistry.addClass(SyncTargetNextcloud); SyncTargetRegistry.addClass(SyncTargetDropbox); SyncTargetRegistry.addClass(SyncTargetAmazonS3); +SyncTargetRegistry.addClass(SyncTargetWebDAV); SyncTargetRegistry.addClass(SyncTargetJoplinServer); SyncTargetRegistry.addClass(SyncTargetJoplinCloud);