2021-09-10 20:05:47 +02:00
import shim from '../../shim' ;
import { _ } from '../../locale' ;
import BaseItem , { EncryptedItemsStats } from '../../models/BaseItem' ;
import useAsyncEffect , { AsyncEffectEvent } from '../../hooks/useAsyncEffect' ;
import { MasterKeyEntity } from '../../services/e2ee/types' ;
2021-10-03 17:00:49 +02:00
import { findMasterKeyPassword , getMasterPasswordStatus , masterPasswordIsValid , MasterPasswordStatus } from '../../services/e2ee/utils' ;
2021-09-10 20:05:47 +02:00
import EncryptionService from '../../services/e2ee/EncryptionService' ;
import { masterKeyEnabled , setMasterKeyEnabled } from '../../services/synchronizer/syncInfoUtils' ;
import MasterKey from '../../models/MasterKey' ;
import { reg } from '../../registry' ;
import Setting from '../../models/Setting' ;
const { useCallback , useEffect , useState } = shim . react ( ) ;
type PasswordChecks = Record < string , boolean > ;
export const useStats = ( ) = > {
const [ stats , setStats ] = useState < EncryptedItemsStats > ( { encrypted : null , total : null } ) ;
const [ statsUpdateTime , setStatsUpdateTime ] = useState < number > ( 0 ) ;
useAsyncEffect ( async ( event : AsyncEffectEvent ) = > {
const r = await BaseItem . encryptedItemsStats ( ) ;
if ( event . cancelled ) return ;
2021-10-03 17:00:49 +02:00
setStats ( stats = > {
if ( JSON . stringify ( stats ) === JSON . stringify ( r ) ) return stats ;
return r ;
} ) ;
2021-09-10 20:05:47 +02:00
} , [ statsUpdateTime ] ) ;
useEffect ( ( ) = > {
const iid = shim . setInterval ( ( ) = > {
setStatsUpdateTime ( Date . now ( ) ) ;
} , 3000 ) ;
return ( ) = > {
2021-10-03 17:00:49 +02:00
shim . clearInterval ( iid ) ;
2021-09-10 20:05:47 +02:00
} ;
} , [ ] ) ;
return stats ;
} ;
export const decryptedStatText = ( stats : EncryptedItemsStats ) = > {
const doneCount = stats . encrypted !== null ? stats . total - stats . encrypted : '-' ;
const totalCount = stats . total !== null ? stats . total : '-' ;
const result = _ ( 'Decrypted items: %s / %s' , doneCount , totalCount ) ;
return result ;
} ;
2021-10-03 17:00:49 +02:00
export const enableEncryptionConfirmationMessages = ( _masterKey : MasterKeyEntity , hasMasterPassword : boolean ) = > {
const msg = [ _ ( 'Enabling encryption means *all* your notes and attachments are going to be re-synchronised and sent encrypted to the sync target.' ) ] ;
2021-09-10 20:05:47 +02:00
2021-10-03 17:00:49 +02:00
if ( hasMasterPassword ) {
msg . push ( _ ( 'To continue, please enter your master password below.' ) ) ;
} else {
msg . push ( _ ( 'Do not lose the password as, for security purposes, this will be the *only* way to decrypt the data! To enable encryption, please enter your password below.' ) ) ;
2021-09-10 20:05:47 +02:00
}
2021-10-03 17:00:49 +02:00
// if (masterKey) msg.push(_('Encryption will be enabled using the master key created on %s', time.unixMsToLocalDateTime(masterKey.created_time)));
return msg ;
2021-09-10 20:05:47 +02:00
} ;
export const reencryptData = async ( ) = > {
const ok = confirm ( _ ( 'Please confirm that you would like to re-encrypt your complete database.' ) ) ;
if ( ! ok ) return ;
await BaseItem . forceSyncAll ( ) ;
void reg . waitForSyncFinishedThenSync ( ) ;
Setting . setValue ( 'encryption.shouldReencrypt' , Setting . SHOULD_REENCRYPT_NO ) ;
alert ( _ ( 'Your data is going to be re-encrypted and synced again.' ) ) ;
} ;
export const dontReencryptData = ( ) = > {
Setting . setValue ( 'encryption.shouldReencrypt' , Setting . SHOULD_REENCRYPT_NO ) ;
} ;
export const useToggleShowDisabledMasterKeys = ( ) = > {
const [ showDisabledMasterKeys , setShowDisabledMasterKeys ] = useState < boolean > ( false ) ;
const toggleShowDisabledMasterKeys = ( ) = > {
setShowDisabledMasterKeys ( ( current ) = > ! current ) ;
} ;
return { showDisabledMasterKeys , toggleShowDisabledMasterKeys } ;
} ;
export const onToggleEnabledClick = ( mk : MasterKeyEntity ) = > {
setMasterKeyEnabled ( mk . id , ! masterKeyEnabled ( mk ) ) ;
} ;
export const onSavePasswordClick = ( mk : MasterKeyEntity , passwords : Record < string , string > ) = > {
const password = passwords [ mk . id ] ;
if ( ! password ) {
Setting . deleteObjectValue ( 'encryption.passwordCache' , mk . id ) ;
} else {
Setting . setObjectValue ( 'encryption.passwordCache' , mk . id , password ) ;
}
2021-10-17 18:20:59 +02:00
// When setting a master key password, if the master password is not set, we
// assume that this password is the master password. If it turns out it's
// not, it's always possible to change it in the UI.
if ( password && ! Setting . value ( 'encryption.masterPassword' ) ) {
Setting . setValue ( 'encryption.masterPassword' , password ) ;
}
2021-09-10 20:05:47 +02:00
} ;
export const onMasterPasswordSave = ( masterPasswordInput : string ) = > {
Setting . setValue ( 'encryption.masterPassword' , masterPasswordInput ) ;
} ;
export const useInputMasterPassword = ( masterKeys : MasterKeyEntity [ ] , activeMasterKeyId : string ) = > {
const [ inputMasterPassword , setInputMasterPassword ] = useState < string > ( '' ) ;
const onMasterPasswordSave = useCallback ( async ( ) = > {
Setting . setValue ( 'encryption.masterPassword' , inputMasterPassword ) ;
2021-10-03 17:00:49 +02:00
if ( ! ( await masterPasswordIsValid ( inputMasterPassword , masterKeys . find ( mk = > mk . id === activeMasterKeyId ) ) ) ) {
2021-09-10 20:05:47 +02:00
alert ( 'Password is invalid. Please try again.' ) ;
}
2022-08-19 13:10:04 +02:00
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
2021-09-10 20:05:47 +02:00
} , [ inputMasterPassword ] ) ;
const onMasterPasswordChange = useCallback ( ( password : string ) = > {
setInputMasterPassword ( password ) ;
} , [ ] ) ;
return { inputMasterPassword , onMasterPasswordSave , onMasterPasswordChange } ;
} ;
export const useInputPasswords = ( propsPasswords : Record < string , string > ) = > {
const [ inputPasswords , setInputPasswords ] = useState < Record < string , string > > ( propsPasswords ) ;
useEffect ( ( ) = > {
setInputPasswords ( propsPasswords ) ;
} , [ propsPasswords ] ) ;
const onInputPasswordChange = useCallback ( ( mk : MasterKeyEntity , password : string ) = > {
setInputPasswords ( current = > {
return {
. . . current ,
[ mk . id ] : password ,
} ;
} ) ;
} , [ ] ) ;
return { inputPasswords , onInputPasswordChange } ;
} ;
export const usePasswordChecker = ( masterKeys : MasterKeyEntity [ ] , activeMasterKeyId : string , masterPassword : string , passwords : Record < string , string > ) = > {
const [ passwordChecks , setPasswordChecks ] = useState < PasswordChecks > ( { } ) ;
2021-10-17 18:20:59 +02:00
// "masterPasswordKeys" are the master key that can be decrypted with the
// master password. It should be all of them normally, but in previous
// versions it was possible to have different passwords for different keys,
// so we need this for backward compatibility.
2021-09-10 20:05:47 +02:00
const [ masterPasswordKeys , setMasterPasswordKeys ] = useState < PasswordChecks > ( { } ) ;
2021-10-03 17:00:49 +02:00
const [ masterPasswordStatus , setMasterPasswordStatus ] = useState < MasterPasswordStatus > ( MasterPasswordStatus . Unknown ) ;
2021-09-10 20:05:47 +02:00
useAsyncEffect ( async ( event : AsyncEffectEvent ) = > {
const newPasswordChecks : PasswordChecks = { } ;
const newMasterPasswordKeys : PasswordChecks = { } ;
for ( let i = 0 ; i < masterKeys . length ; i ++ ) {
const mk = masterKeys [ i ] ;
const password = await findMasterKeyPassword ( EncryptionService . instance ( ) , mk , passwords ) ;
const ok = password ? await EncryptionService . instance ( ) . checkMasterKeyPassword ( mk , password ) : false ;
newPasswordChecks [ mk . id ] = ok ;
newMasterPasswordKeys [ mk . id ] = password === masterPassword ;
}
2021-10-03 17:00:49 +02:00
newPasswordChecks [ 'master' ] = masterPassword ? await masterPasswordIsValid ( masterPassword , masterKeys . find ( mk = > mk . id === activeMasterKeyId ) ) : true ;
2021-09-10 20:05:47 +02:00
if ( event . cancelled ) return ;
2021-10-03 17:00:49 +02:00
setPasswordChecks ( passwordChecks = > {
if ( JSON . stringify ( newPasswordChecks ) === JSON . stringify ( passwordChecks ) ) return passwordChecks ;
return newPasswordChecks ;
} ) ;
setMasterPasswordKeys ( masterPasswordKeys = > {
if ( JSON . stringify ( newMasterPasswordKeys ) === JSON . stringify ( masterPasswordKeys ) ) return masterPasswordKeys ;
return newMasterPasswordKeys ;
} ) ;
setMasterPasswordStatus ( await getMasterPasswordStatus ( masterPassword ) ) ;
2021-09-10 20:05:47 +02:00
} , [ masterKeys , masterPassword ] ) ;
2021-10-03 17:00:49 +02:00
return { passwordChecks , masterPasswordKeys , masterPasswordStatus } ;
2021-09-10 20:05:47 +02:00
} ;
2021-10-17 20:08:34 +02:00
export const useNeedMasterPassword = ( passwordChecks : PasswordChecks , masterKeys : MasterKeyEntity [ ] ) = > {
for ( const [ mkId , valid ] of Object . entries ( passwordChecks ) ) {
const mk = masterKeys . find ( mk = > mk . id === mkId ) ;
if ( ! mk ) continue ;
if ( ! masterKeyEnabled ( mk ) ) continue ;
if ( ! valid ) return true ;
}
return false ;
} ;
2021-11-10 17:40:32 +02:00
export const determineKeyPassword = ( masterKeyId : string , masterPasswordKeys : PasswordChecks , masterPassword : string , passwords : Record < string , string > ) : string = > {
if ( masterPasswordKeys [ masterKeyId ] ) return masterPassword ;
return passwords [ masterKeyId ] ;
} ;
export const upgradeMasterKey = async ( masterKey : MasterKeyEntity , password : string ) : Promise < string > = > {
if ( ! password ) {
2021-09-10 20:05:47 +02:00
return _ ( 'Please enter your password in the master key list below before upgrading the key.' ) ;
}
try {
2021-11-10 17:40:32 +02:00
// Just re-encrypt the master key, but using the new encryption method
// (which would be the default).
2021-10-03 17:00:49 +02:00
const newMasterKey = await EncryptionService . instance ( ) . reencryptMasterKey ( masterKey , password , password ) ;
2021-09-10 20:05:47 +02:00
await MasterKey . save ( newMasterKey ) ;
void reg . waitForSyncFinishedThenSync ( ) ;
return _ ( 'The master key has been upgraded successfully!' ) ;
} catch ( error ) {
return _ ( 'Could not upgrade master key: %s' , error . message ) ;
}
} ;