Desktop, Mobile: Resolves #16710: Improve E2EE UI when a key is stored in an unknown format (#16711)

Co-authored-by: Laurent Cozic <laurent22@users.noreply.github.com>
This commit is contained in:
Henry HeinoandLaurent Cozic authored and GitHub committed 2026-10-06 15:42:30 +01:00
1 parent 9e4ca948be
commit ab241bd8d6
9 files changed
+165 -27

No files matched your search

@@ -5,10 +5,10 @@ import { _ } from '@joplin/lib/locale';
import time from '@joplin/lib/time';
import shim, { MessageBoxType } from '@joplin/lib/shim';
import dialogs from '../dialogs';
import { decryptedStatText, determineKeyPassword, dontReencryptData, enableEncryptionConfirmationMessages, onSavePasswordClick, onToggleEnabledClick, reencryptData, upgradeMasterKey, useInputPasswords, useNeedMasterPassword, usePasswordChecker, useStats, useToggleShowDisabledMasterKeys } from '@joplin/lib/components/EncryptionConfigScreen/utils';
import { decryptedStatText, determineKeyPassword, dontReencryptData, enableEncryptionConfirmationMessages, onSavePasswordClick, onToggleEnabledClick, reencryptData, upgradeMasterKey, useHasUnknownEncryptionMethodKeysMessage, useInputPasswords, useNeedMasterPassword, usePasswordChecker, useStats, useToggleShowDisabledMasterKeys } from '@joplin/lib/components/EncryptionConfigScreen/utils';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import { getEncryptionEnabled, localSyncInfoSelector, masterKeyEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { getDefaultMasterKey, getMasterPasswordStatusMessage, masterPasswordIsValid, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import { getDefaultMasterKey, getMasterPasswordStatusMessage, isKnownEncryptionMethod, masterPasswordIsValid, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import Button, { ButtonLevel } from '../Button/Button';
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import { connect } from 'react-redux';
@@ -76,6 +76,7 @@ export const EncryptionConfigScreen = (props: Props) => {
const { passwordChecks, masterPasswordKeys, masterPasswordStatus } = usePasswordChecker(props.masterKeys, props.activeMasterKeyId, props.masterPassword, props.passwords);
const { showDisabledMasterKeys, toggleShowDisabledMasterKeys } = useToggleShowDisabledMasterKeys();
const needMasterPassword = useNeedMasterPassword(passwordChecks, props.masterKeys);
const unknownEncryptionMethodMessage = useHasUnknownEncryptionMethodKeysMessage(props.masterKeys);
useEffect(() => {
const wasOpen = wasMasterPasswordDialogOpen.current;
@@ -173,6 +174,7 @@ export const EncryptionConfigScreen = (props: Props) => {
const password = inputPasswords[mk.id] ? inputPasswords[mk.id] : '';
const isActive = props.activeMasterKeyId === mk.id;
const activeIcon = isActive ? '✔' : '';
const knownKeyType = isKnownEncryptionMethod(mk.encryption_method);
const passwordOk = passwordChecks[mk.id] === true ? '✔' : '❌';
const renderPasswordInput = (masterKeyId: string) => {
@@ -207,7 +209,9 @@ export const EncryptionConfigScreen = (props: Props) => {
<td style={theme.textStyle}>{mk.id}<br/>{_('Source: ')}{mk.source_application}</td>
<td style={theme.textStyle}>{_('Created: ')}{time.formatMsToLocal(mk.created_time)}<br/>{_('Updated: ')}{time.formatMsToLocal(mk.updated_time)}</td>
{renderPasswordInput(mk.id)}
<td style={theme.textStyle}>{passwordOk}</td>
<td
style={theme.textStyle}
>{knownKeyType ? passwordOk : <span title={_('Unknown key format')}>❓</span>}</td>
<td style={theme.textStyle}>
<button style={theme.buttonStyle} onClick={() => onToggleEnabledClick(mk)}>{masterKeyEnabled(mk) ? _('Disable') : _('Enable')}</button>
</td>
@@ -456,7 +460,7 @@ export const EncryptionConfigScreen = (props: Props) => {
const buttonTitle = CommandService.instance().label('openMasterPasswordDialog');
const needPasswordMessage = !needMasterPassword ? null : (
<p className="needpassword">
<p className="warning">
{_('Your password is needed to decrypt some of your data.')}
<br/>
{_('Please click on "%s" to proceed, or set the passwords in the "%s" list below.', buttonTitle, _('Encryption keys'))}
@@ -468,12 +472,19 @@ export const EncryptionConfigScreen = (props: Props) => {
</p>
);
const needUpgradeAppMessage = !!unknownEncryptionMethodMessage && (
<p className='warning'>
{unknownEncryptionMethodMessage}
</p>
);
return (
<div className="section">
<div className="manage-password-section">
<h2>{_('Master password')}</h2>
<p className="status"><span>{_('Master password:')}</span>&nbsp;<span className="bold">{getMasterPasswordStatusMessage(masterPasswordStatus)}</span></p>
{needPasswordMessage}
{needUpgradeAppMessage}
<Button className="managebutton" level={needMasterPassword ? ButtonLevel.Primary : ButtonLevel.Secondary} onClick={onManageMasterPassword} title={buttonTitle} />
</div>
</div>
@@ -8,6 +8,6 @@
align-items: center;
}
.manage-password-section > .needpassword {
.manage-password-section > .warning {
color: var(--joplin-color-warn);
}
+9 -1
View File
@@ -15,7 +15,7 @@ import Logger from '@joplin/utils/Logger';
import { AppState } from '../app.reducer';
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { showMissingMasterKeyMessage, showUnknownKeyFormatBanner } from '@joplin/lib/services/e2ee/utils';
import shouldShowMissingPasswordWarning from '@joplin/lib/components/shared/config/shouldShowMissingPasswordWarning';
import { connect } from 'react-redux';
@@ -32,6 +32,7 @@ interface Props {
processingShareInvitationResponse: boolean;
shareInvitations: ShareInvitation[];
hasDisabledSyncItems: boolean;
showUnknownKeyFormatMessage: boolean;
showMissingMasterKeyMessage: boolean;
mustUpgradeAppMessage: string;
syncTargetAppMinVersion: string;
@@ -225,6 +226,12 @@ const WarningBanner: React.FC<Props> = props => {
} else {
msg = renderNotificationMessage(props.mustUpgradeAppMessage);
}
} else if (props.showUnknownKeyFormatMessage) {
msg = renderNotificationMessage(
_('One or more encryption keys are stored in an unknown format.'),
_('Manage'),
onViewEncryptionConfigScreen,
);
} else if (props.shouldSwitchToAppleSiliconVersion) {
msg = renderNotificationMessage(
_('You are running the Intel version of Joplin on an Apple Silicon processor. Download the Apple Silicon one for better performance.'),
@@ -283,6 +290,7 @@ const mapStateToProps = (state: AppState) => {
hasDisabledSyncItems: state.hasDisabledSyncItems,
hasDisabledEncryptionItems: state.hasDisabledEncryptionItems,
showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
showUnknownKeyFormatMessage: showUnknownKeyFormatBanner(syncInfo),
showNeedUpgradingMasterKeyMessage: showNeedUpgradingEnabledMasterKeyMessage,
showShouldReencryptMessage: state.settings['encryption.shouldReencrypt'] >= Setting.SHOULD_REENCRYPT_YES,
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
@@ -5,7 +5,7 @@ import { Platform } from 'react-native';
import { AppState } from '../../utils/types';
import WarningBox, { WarningBoxTarget } from './WarningBox';
import { _ } from '@joplin/lib/locale';
import { showMissingMasterKeyMessage } from '@joplin/lib/services/e2ee/utils';
import { showMissingMasterKeyMessage, showUnknownKeyFormatBanner } from '@joplin/lib/services/e2ee/utils';
import { localSyncInfoFromState } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import Setting from '@joplin/lib/models/Setting';
import { ShareInvitation, ShareUserStatus } from '@joplin/lib/services/share/reducer';
@@ -20,6 +20,7 @@ const logger = Logger.create('WarningBanner');
interface Props {
themeId: number;
showMissingMasterKeyMessage: boolean;
showUnknownKeyFormatMessage: boolean;
hasDisabledSyncItems: boolean;
shouldUpgradeSyncTarget: boolean;
showShouldUpgradeSyncTargetMessage: boolean|undefined;
@@ -110,6 +111,11 @@ const WarningBannerComponent: React.FC<Props> = props => {
if (props.showMissingMasterKeyMessage) {
warningComps.push(renderWarningBox('missingDecryptionPassword', _('Press to set the decryption password.'), { screen: 'EncryptionConfig' }));
}
if (props.showUnknownKeyFormatMessage) {
warningComps.push(
renderWarningBox('unknownKeyFormat', _('One or more encryption keys are stored in an unknown format. Press for more info.'), { screen: 'EncryptionConfig' }),
);
}
if (props.hasDisabledSyncItems) {
warningComps.push(renderWarningBox('cannotSync', _('Some items cannot be synchronised. Press for more info.'), { screen: 'Status' }));
}
@@ -157,6 +163,10 @@ const isSyncLoginRoute = (state: AppState) => {
return false;
};
const isEncryptionConfigRoute = (state: AppState) => {
return state.route?.routeName === 'EncryptionConfig';
};
export default connect((state: AppState) => {
const syncInfo = localSyncInfoFromState(state);
@@ -166,7 +176,8 @@ export default connect((state: AppState) => {
noteSelectionEnabled: state.noteSelectionEnabled,
selectedFolderId: state.selectedFolderId,
notesParentType: state.notesParentType,
showMissingMasterKeyMessage: showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
showMissingMasterKeyMessage: !isEncryptionConfigRoute(state) && showMissingMasterKeyMessage(syncInfo, state.notLoadedMasterKeys),
showUnknownKeyFormatMessage: !isEncryptionConfigRoute(state) && showUnknownKeyFormatBanner(syncInfo),
hasDisabledSyncItems: state.hasDisabledSyncItems,
shouldUpgradeSyncTarget: state.settings['sync.upgradeState'] === Setting.SYNC_UPGRADE_STATE_SHOULD_DO,
mustUpgradeAppMessage: state.mustUpgradeAppMessage,
@@ -6,15 +6,22 @@ import { themeStyle } from '../global-style';
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { _ } from '@joplin/lib/locale';
import time from '@joplin/lib/time';
import { decryptedStatText, enableEncryptionConfirmationMessages, onSavePasswordClick, useInputMasterPassword, useInputPasswords, usePasswordChecker, useStats } from '@joplin/lib/components/EncryptionConfigScreen/utils';
import { decryptedStatText, enableEncryptionConfirmationMessages, onSavePasswordClick, useHasUnknownEncryptionMethodKeysMessage, useInputMasterPassword, useInputPasswords, usePasswordChecker, useStats } from '@joplin/lib/components/EncryptionConfigScreen/utils';
import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import { State } from '@joplin/lib/reducer';
import { localSyncInfoSelector, masterKeyEnabled } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { getDefaultMasterKey, setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import { getDefaultMasterKey, getMasterPasswordStatusMessage, isKnownEncryptionMethod, setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import { useMemo, useState } from 'react';
import { Divider, List } from 'react-native-paper';
import shim from '@joplin/lib/shim';
const allMasterPasswordKeysStoredInUnknownFormat = (keyIds: string[], allKeys: MasterKeyEntity[]) => {
return keyIds.length > 0 && keyIds.every(id => {
const masterKey = allKeys.find(key => key.id === id);
return !!masterKey && !isKnownEncryptionMethod(masterKey.encryption_method);
});
};
interface Props {
themeId: number;
masterKeys: MasterKeyEntity[];
@@ -30,7 +37,7 @@ const EncryptionConfigScreen = (props: Props) => {
const [passwordPromptAnswer, setPasswordPromptAnswer] = useState('');
const [passwordPromptConfirmAnswer, setPasswordPromptConfirmAnswer] = useState('');
const stats = useStats();
const { passwordChecks, masterPasswordKeys } = usePasswordChecker(props.masterKeys, props.activeMasterKeyId, props.masterPassword, props.passwords);
const { passwordChecks, masterPasswordKeys, masterPasswordStatus } = usePasswordChecker(props.masterKeys, props.activeMasterKeyId, props.masterPassword, props.passwords);
const { inputPasswords, onInputPasswordChange } = useInputPasswords(props.passwords);
const { inputMasterPassword, onMasterPasswordSave, onMasterPasswordChange } = useInputMasterPassword(props.masterKeys, props.activeMasterKeyId);
const [showDisabledKeys, setShowDisabledKeys] = useState(false);
@@ -69,6 +76,27 @@ const EncryptionConfigScreen = (props: Props) => {
fontSize: theme.fontSize,
color: theme.color,
},
warningText: {
...theme.normalText,
color: theme.color,
},
warningTextBold: {
fontWeight: 'bold',
},
warningBox: {
marginBottom: theme.itemMarginBottom,
backgroundColor: theme.warningBackgroundColor,
paddingTop: 5,
paddingBottom: 5,
paddingLeft: 10,
paddingRight: 10,
},
masterPasswordLabel: {
...theme.normalText,
flex: 0,
flexBasis: 'auto',
marginRight: 5,
},
normalTextInput: {
margin: 10,
color: theme.color,
@@ -77,12 +105,16 @@ const EncryptionConfigScreen = (props: Props) => {
},
container: {
flex: 1,
flexBasis: 'auto',
padding: theme.margin,
},
disabledContainer: {
paddingLeft: theme.margin,
paddingRight: theme.margin,
},
unknownKeyFormatMessage: {
paddingTop: theme.margin,
},
});
}, [theme]);
@@ -141,6 +173,9 @@ const EncryptionConfigScreen = (props: Props) => {
accessibilityRole='header'
>{_('Master Key %s', mk.id.substr(0, 6))}</Text>
<Text style={styles.normalText}>{_('Created: %s', time.formatMsToLocal(mk.created_time))}</Text>
{!isKnownEncryptionMethod(mk.encryption_method) && (
<Text style={styles.normalText}>{_('Status: Unsupported key format')}</Text>
)}
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ flex: 0, fontSize: theme.fontSize, marginRight: 10, color: theme.color }}>{_('Password:')}</Text>
{renderPasswordInput(mk.id)}
@@ -255,11 +290,16 @@ const EncryptionConfigScreen = (props: Props) => {
borderBottomColor: theme.dividerColor,
};
if (passwordChecks['master']) {
if (
passwordChecks['master'] ||
allMasterPasswordKeysStoredInUnknownFormat(
Object.keys(masterPasswordKeys), props.masterKeys,
)
) {
return (
<View style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<Text style={{ ...styles.normalText, flex: 0, marginRight: 5 }}>{_('Master password:')}</Text>
<Text style={{ ...styles.normalText, fontWeight: 'bold' }}>{_('Loaded')}</Text>
<Text style={styles.masterPasswordLabel}>{_('Master password:')}</Text>
<Text style={{ ...styles.normalText, fontWeight: 'bold' }}>{getMasterPasswordStatusMessage(masterPasswordStatus)}</Text>
</View>
);
} else {
@@ -360,23 +400,32 @@ const EncryptionConfigScreen = (props: Props) => {
</View>
</List.Accordion> : null;
const unknownKeyFormatText = useHasUnknownEncryptionMethodKeysMessage(props.masterKeys);
const unknownKeyFormatMessage = !!unknownKeyFormatText && <View style={styles.warningBox}>
<Text
style={styles.warningText}
><Text style={styles.warningTextBold}>{_('Warning: ')}</Text>{unknownKeyFormatText}</Text>
</View>;
return (
<View style={rootStyle}>
<ScreenHeader title={_('Encryption Config')} />
<ScrollView>
<View style={styles.container}>
<View style={{ backgroundColor: theme.warningBackgroundColor, paddingTop: 5, paddingBottom: 5, paddingLeft: 10, paddingRight: 10 }}>
<Text>{_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')}</Text>
<View style={styles.warningBox}>
<Text style={styles.warningText}>{_('For more information about End-To-End Encryption (E2EE) and advice on how to enable it please check the documentation:')}</Text>
<TouchableOpacity
onPress={() => {
void Linking.openURL('https://joplinapp.org/help/apps/sync/e2ee');
}}
accessibilityRole='link'
>
<Text>https://joplinapp.org/help/apps/sync/e2ee</Text>
<Text style={styles.warningText}>https://joplinapp.org/help/apps/sync/e2ee</Text>
</TouchableOpacity>
</View>
{unknownKeyFormatMessage}
<Text
style={styles.titleText}
accessibilityRole='header'
@@ -3,7 +3,7 @@ import { _ } from '../../locale';
import BaseItem, { EncryptedItemsStats } from '../../models/BaseItem';
import useAsyncEffect, { AsyncEffectEvent } from '../../hooks/useAsyncEffect';
import { MasterKeyEntity } from '../../services/e2ee/types';
import { findMasterKeyPassword, getMasterPasswordStatus, loadMasterKeysFromSettings, masterPasswordIsValid, MasterPasswordStatus } from '../../services/e2ee/utils';
import { findMasterKeyPassword, getMasterPasswordStatus, isKnownEncryptionMethod, loadMasterKeysFromSettings, masterPasswordIsValid, MasterPasswordStatus } from '../../services/e2ee/utils';
import EncryptionService from '../../services/e2ee/EncryptionService';
import { masterKeyEnabled, setMasterKeyEnabled } from '../../services/synchronizer/syncInfoUtils';
import MasterKey from '../../models/MasterKey';
@@ -212,11 +212,29 @@ export const useNeedMasterPassword = (passwordChecks: PasswordChecks, masterKeys
const mk = masterKeys.find(mk => mk.id === mkId);
if (!mk) continue;
if (!masterKeyEnabled(mk)) continue;
if (!valid) return true;
if (!valid && isKnownEncryptionMethod(mk.encryption_method)) return true;
}
return false;
};
const useHasUnknownEncryptionMethodKeys = (masterKeys: MasterKeyEntity[]) => {
for (const mk of masterKeys) {
if (mk && masterKeyEnabled(mk) && !isKnownEncryptionMethod(mk.encryption_method)) {
return true;
}
}
return false;
};
export const useHasUnknownEncryptionMethodKeysMessage = (masterKeys: MasterKeyEntity[]) => {
const hasUnknownEncryptionMethodKeys = useHasUnknownEncryptionMethodKeys(masterKeys);
if (!hasUnknownEncryptionMethodKeys) return '';
return [
_('One or more encryption keys are stored in an unknown format.'),
_('These keys were most likely created by a newer version of Joplin. Please check that Joplin has been updated to the latest version.'),
].join('\n\n');
};
export const determineKeyPassword = (masterKeyId: string, masterPasswordKeys: PasswordChecks, masterPassword: string, passwords: Record<string, string>): string => {
if (masterPasswordKeys[masterKeyId]) return masterPassword;
return passwords[masterKeyId];
@@ -286,7 +286,10 @@ export default class EncryptionService {
}
public masterKeysThatNeedUpgrading(masterKeys: MasterKeyEntity[]) {
return MasterKey.allWithoutEncryptionMethod(masterKeys, [this.defaultMasterKeyEncryptionMethod_, EncryptionMethod.Custom]);
const keysWithNonDefaultMethods = MasterKey.allWithoutEncryptionMethod(masterKeys, [
this.defaultMasterKeyEncryptionMethod_, EncryptionMethod.Custom,
]);
return keysWithNonDefaultMethods.filter(key => key.encryption_method in EncryptionMethod);
}
public async reencryptMasterKey(model: MasterKeyEntity, decryptionPassword: string, encryptionPassword: string, decryptOptions: EncryptOptions = null, encryptOptions: EncryptOptions = null): Promise<MasterKeyEntity> {
+15 -1
View File
@@ -1,10 +1,11 @@
import { afterAllCleanUp, setupDatabaseAndSynchronizer, switchClient, encryptionService, expectNotThrow, expectThrow, kvStore, msleep } from '../../testing/test-utils';
import MasterKey from '../../models/MasterKey';
import { activeMasterKeySanityCheck, migrateMasterPassword, migratePpk, resetMasterPassword, showMissingMasterKeyMessage, updateMasterPassword } from './utils';
import { activeMasterKeySanityCheck, migrateMasterPassword, migratePpk, resetMasterPassword, showMissingMasterKeyMessage, showUnknownKeyFormatBanner, updateMasterPassword } from './utils';
import { localSyncInfo, masterKeyById, masterKeyEnabled, saveLocalSyncInfo, setActiveMasterKeyId, setMasterKeyEnabled, setPpk } from '../synchronizer/syncInfoUtils';
import Setting from '../../models/Setting';
import { generateKeyPair, generateKeyPairWithAlgorithm, getPpkAlgorithm, ppkPasswordIsValid, testing__setPpkMigrations_ } from './ppk/ppk';
import { PublicKeyAlgorithm } from './types';
import { EncryptionMethod } from './EncryptionService';
describe('e2ee/utils', () => {
@@ -43,6 +44,19 @@ describe('e2ee/utils', () => {
expect(showMissingMasterKeyMessage(syncInfo, [mk1.id, mk2.id])).toBe(false);
});
it.each([
[EncryptionMethod.KeyV1, false],
[1234, true],
[{ toString: 'throws if converted to string with String(...)' }, true],
])('should decide whether to show the unknown key format banner (encryption method: %j, should show: %j)', async (encryptionMethod, shouldShow) => {
await MasterKey.save({
...await encryptionService().generateMasterKey('111111'),
encryption_method: encryptionMethod as unknown as number,
});
expect(showUnknownKeyFormatBanner(localSyncInfo())).toBe(shouldShow);
});
it('should do ppk migration', async () => {
const { reset } = testing__setPpkMigrations_([PublicKeyAlgorithm.RsaV1, PublicKeyAlgorithm.RsaV2]);
+30 -6
View File
@@ -3,13 +3,15 @@ import BaseItem from '../../models/BaseItem';
import MasterKey from '../../models/MasterKey';
import Setting from '../../models/Setting';
import { MasterKeyEntity } from './types';
import EncryptionService from './EncryptionService';
import EncryptionService, { EncryptionMethod } from './EncryptionService';
import { getActiveMasterKey, getActiveMasterKeyId, localSyncInfo, masterKeyEnabled, saveLocalSyncInfo, setActiveMasterKeyId, setEncryptionEnabled, SyncInfo } from '../synchronizer/syncInfoUtils';
import JoplinError from '../../JoplinError';
import { generateKeyPair, pkReencryptPrivateKey, ppkPasswordIsValid, shouldUpdatePpk } from './ppk/ppk';
import KvStore from '../KvStore';
import Folder from '../../models/Folder';
import ShareService from '../share/ShareService';
import { hasOwnProperty } from '@joplin/utils/object';
import { _ } from '../../locale';
const logger = Logger.create('e2ee/utils');
@@ -135,6 +137,11 @@ export const migratePpk = async () => {
saveLocalSyncInfo(syncInfo);
};
export const isKnownEncryptionMethod = (method: EncryptionMethod|number) => {
if (typeof method !== 'number') return false;
return hasOwnProperty(EncryptionMethod, String(method));
};
// All master keys normally should be decrypted with the master password, however
// previously any master key could be encrypted with any password, so to support
// this legacy case, we first check if the MK decrypts with the master password.
@@ -202,8 +209,18 @@ export const activeMasterKeySanityCheck = () => {
setActiveMasterKeyId(latestMasterKey.id);
};
export const showUnknownKeyFormatBanner = (syncInfo: SyncInfo) => {
for (const masterKey of syncInfo.masterKeys) {
if (masterKeyEnabled(masterKey) && !isKnownEncryptionMethod(masterKey.encryption_method)) {
return true;
}
}
return false;
};
export function showMissingMasterKeyMessage(syncInfo: SyncInfo, notLoadedMasterKeys: string[]) {
if (!syncInfo.masterKeys.length) return false;
if (showUnknownKeyFormatBanner(syncInfo)) return false;
notLoadedMasterKeys = notLoadedMasterKeys.slice();
@@ -341,12 +358,18 @@ export enum MasterPasswordStatus {
NotSet = 2,
Invalid = 3,
Valid = 4,
Unsupported = 5,
}
export async function getMasterPasswordStatus(password: string = null): Promise<MasterPasswordStatus> {
password = password === null ? getMasterPassword(false) : password;
if (!password) return MasterPasswordStatus.NotSet;
const defaultKey = getDefaultMasterKey();
if (defaultKey && !isKnownEncryptionMethod(defaultKey.encryption_method)) {
return MasterPasswordStatus.Unsupported;
}
const isValid = await masterPasswordIsValid(password);
return isValid ? MasterPasswordStatus.Valid : MasterPasswordStatus.Invalid;
}
@@ -357,11 +380,12 @@ export async function checkHasMasterPasswordEncryptedData(syncInfo: SyncInfo = n
}
const masterPasswordStatusMessages = {
[MasterPasswordStatus.Unknown]: 'Checking...',
[MasterPasswordStatus.Loaded]: 'Loaded',
[MasterPasswordStatus.NotSet]: 'Not set',
[MasterPasswordStatus.Valid]: '✓ ' + 'Valid',
[MasterPasswordStatus.Invalid]: '❌ ' + 'Invalid',
[MasterPasswordStatus.Unknown]: _('Checking...'),
[MasterPasswordStatus.Loaded]: _('Loaded'),
[MasterPasswordStatus.NotSet]: _('Not set'),
[MasterPasswordStatus.Valid]: `✓ ${_('Valid')}`,
[MasterPasswordStatus.Invalid]: `❌ ${_('Invalid')}`,
[MasterPasswordStatus.Unsupported]: `❌ ${_('Unsupported key format')}`,
};
export function getMasterPasswordStatusMessage(status: MasterPasswordStatus): string {