1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-07-16 00:14:34 +02:00

Desktop: Resolves #8625: Show missing sync password warning and link to FAQ (#8644)

This commit is contained in:
Henry Heino
2023-08-14 10:12:49 -07:00
committed by GitHub
parent 9e55d90736
commit c6c2733726
17 changed files with 146 additions and 8 deletions

View File

@ -516,6 +516,8 @@ 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
packages/lib/database-driver-better-sqlite.js

2
.gitignore vendored
View File

@ -502,6 +502,8 @@ 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
packages/lib/database-driver-better-sqlite.js

View File

@ -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,9 @@ 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';
import StyledLink from '../style/StyledLink';
const { KeymapConfigScreen } = require('../KeymapConfig/KeymapConfigScreen');
const settingKeyToControl: any = {
@ -181,6 +184,34 @@ class ConfigScreenComponent extends React.Component<any, any> {
if (section.name === 'sync') {
const syncTargetMd = SyncTargetRegistry.idToMetadata(settings['sync.target']);
const statusStyle = { ...theme.textStyle, marginTop: 10 };
const warningStyle = { ...theme.textStyle, color: theme.colorWarn };
// 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 = (
<StyledLink href="#"
onClick={openMissingPasswordFAQ}
style={theme.linkStyle}
>
{_('Help')}
</StyledLink>
);
// The FAQ section related to missing passwords is specific to MacOS/ARM -- only show it
// in that case.
const showMacInfoLink = shim.isMac() && process.arch === 'arm64';
settingComps.push(
<p key='missing-password-warning' style={warningStyle}>
{_('Warning: Missing password.')}{' '}{showMacInfoLink ? macInfoLink : null}
</p>
);
}
if (syncTargetMd.supportsConfigCheck) {
const messages = shared.checkSyncConfigMessages(this);

View File

@ -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;
@ -561,6 +563,16 @@ class MainScreenComponent extends React.Component<Props, State> {
});
};
const onViewSyncSettingsScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Config',
props: {
defaultSection: 'sync',
},
});
};
const onViewPluginScreen = () => {
this.props.dispatch({
type: 'NAV_GO',
@ -598,6 +610,12 @@ class MainScreenComponent extends React.Component<Props, State> {
_('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.'),
@ -662,7 +680,7 @@ class MainScreenComponent extends React.Component<Props, State> {
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() {
@ -875,6 +893,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,

View File

@ -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';

View File

@ -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 '';
}

View File

@ -36,6 +36,10 @@ class SyncTargetAmazonS3 extends BaseSyncTarget {
return true;
}
static requiresPassword() {
return true;
}
static s3BucketName() {
return Setting.value('sync.8.path');
}

View File

@ -49,6 +49,10 @@ export default class SyncTargetJoplinCloud extends BaseSyncTarget {
return true;
}
public static requiresPassword() {
return true;
}
public async fileApi(): Promise<FileApi> {
return super.fileApi();
}

View File

@ -65,6 +65,10 @@ export default class SyncTargetJoplinServer extends BaseSyncTarget {
return true;
}
public static requiresPassword() {
return true;
}
public async fileApi(): Promise<FileApi> {
return super.fileApi();
}

View File

@ -33,6 +33,10 @@ class SyncTargetNextcloud extends BaseSyncTarget {
return true;
}
static requiresPassword() {
return true;
}
static async checkConfig(options) {
return SyncTargetWebDAV.checkConfig(options);
}

View File

@ -32,6 +32,10 @@ class SyncTargetWebDAV extends BaseSyncTarget {
return true;
}
static requiresPassword() {
return true;
}
static async newFileApi_(syncTargetId, options) {
const apiOptions = {
baseUrl: () => options.path(),

View File

@ -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;

View File

@ -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<string, boolean> = {
'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);
}
});
});

View File

@ -0,0 +1,9 @@
import SyncTargetRegistry from '../../../SyncTargetRegistry';
const shouldShowMissingPasswordWarning = (syncTargetId: number, settings: any) => {
const syncTargetClass = SyncTargetRegistry.classById(syncTargetId);
return syncTargetClass.requiresPassword() && !settings[`sync.${syncTargetId}.password`];
};
export default shouldShowMissingPasswordWarning;

View File

@ -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');

View File

@ -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);

View File

@ -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)).