From a77535456c330fe37766ee9e008fda5330743d39 Mon Sep 17 00:00:00 2001 From: Mo Date: Sun, 23 Jul 2023 15:54:31 -0500 Subject: [PATCH] refactor: application dependency management (#2363) --- .../Domain/Server/SharedVaultInvites/Paths.ts | 1 + .../SharedVaultInvitesServer.ts | 4 + .../SharedVaultInvitesServerInterface.ts | 1 + .../src/Domain/Operator/001/Operator001.ts | 20 +- .../src/Domain/Operator/004/Operator004.ts | 36 +- .../Asymmetric/AsymmetricDecrypt.spec.ts | 10 +- .../UseCase/Asymmetric/AsymmetricDecrypt.ts | 2 +- .../Asymmetric/AsymmetricDecryptOwnMessage.ts | 51 + .../Asymmetric/AsymmetricEncrypt.spec.ts | 6 +- .../UseCase/Asymmetric/AsymmetricEncrypt.ts | 4 +- ...AsymmetricSignatureVerificationDetached.ts | 2 +- .../AsymmetricStringGetAdditionalData.ts | 20 + ...ratorManager.ts => EncryptionOperators.ts} | 3 +- .../Operator/EncryptionOperatorsInterface.ts | 8 + .../OperatorInterface/OperatorInterface.ts | 16 +- .../src/Domain/Operator/OperatorWrapper.ts | 6 +- .../src/Domain/Service/Functions.ts | 30 - packages/encryption/src/Domain/index.ts | 20 +- .../Item/Mutator/DecryptedItemMutator.ts | 2 +- .../AsymmetricMessagePayloadType.ts | 1 - .../AsymmetricMessageSharedVaultInvite.ts | 10 +- .../AsymmetricMessageTrustedContactShare.ts | 2 +- .../Content/TrustedContactContent.ts | 11 + .../Mutator/TrustedContactMutator.spec.ts | 93 + .../Mutator/TrustedContactMutator.ts | 27 + .../PublicKeySet/ContactPublicKeySet.ts | 71 +- .../ContactPublicKeySetInterface.ts | 11 +- .../ContactPublicKeySetJsonInterface.ts | 1 - .../PublicKeySet/FindPublicKeySetResult.ts | 8 - .../TrustedContact/TrustedContact.spec.ts | 109 + .../Syncable/TrustedContact/TrustedContact.ts | 51 +- .../TrustedContact/TrustedContactContent.ts | 11 - .../TrustedContact/TrustedContactInterface.ts | 11 +- .../TrustedContact/TrustedContactMutator.ts | 26 - .../Types/PortablePublicKeySet.ts} | 2 +- .../Types/PublicKeyTrustStatus.ts | 5 + .../Domain/Utilities/Item/ItemGenerator.ts | 2 +- packages/models/src/Domain/index.ts | 6 +- .../AsymmetricMessageServerHash.ts | 1 + ...erface.ts => LegacyApiServiceInterface.ts} | 4 +- .../Application/ApplicationInterface.ts | 9 +- .../AsymmetricMessageService.spec.ts | 290 ++- .../AsymmetricMessageService.ts | 298 ++- .../GetAsymmetricMessageTrustedPayload.ts | 23 - .../GetAsymmetricMessageUntrustedPayload.ts | 17 - ...etricMessages.ts => GetInboundMessages.ts} | 2 +- ...tricMessages.ts => GetOutboundMessages.ts} | 2 +- .../UseCase/GetReplaceabilityIdentifier.ts | 17 + .../UseCase/GetTrustedPayload.ts | 22 + .../UseCase/GetUntrustedPayload.ts | 21 + ...sage.ts => HandleRootKeyChangedMessage.ts} | 18 +- ....ts => ProcessAcceptedVaultInvite.spec.ts} | 41 +- ...ssage.ts => ProcessAcceptedVaultInvite.ts} | 12 +- .../UseCase/ResendAllMessages.ts | 58 + .../UseCase/ResendMessage.ts | 58 + ...metricMessageUseCase.ts => SendMessage.ts} | 11 +- .../UseCase/SendOwnContactChangeMessage.ts | 31 +- ...ice.spec.ts => FilesBackupService.spec.ts} | 20 +- ...BackupService.ts => FilesBackupService.ts} | 28 +- .../Challenge/ChallengeServiceInterface.ts | 6 + .../src/Domain/Contacts/ContactService.ts | 111 +- .../Contacts/ContactServiceInterface.ts | 11 +- .../{Managers => }/SelfContactManager.ts | 70 +- ...ustedContact.ts => CreateOrEditContact.ts} | 28 +- ...UpdateTrustedContact.ts => EditContact.ts} | 6 +- .../Domain/Contacts/UseCase/FindContact.ts | 38 + .../Contacts/UseCase/FindTrustedContact.ts | 29 - .../Domain/Contacts/UseCase/GetAllContacts.ts | 11 + .../Contacts/UseCase/HandleKeyPairChange.ts | 31 + .../Contacts/UseCase/ReplaceContactData.ts | 55 + .../UseCase/{ => Types}/FindContactQuery.ts | 2 +- .../Types/ItemSignatureValidationResult.ts | 6 + .../UseCase/ValidateItemSigner.spec.ts | 73 +- .../Contacts/UseCase/ValidateItemSigner.ts | 75 +- .../UseCase/ValidateItemSignerResult.ts | 1 - .../Encryption/EncryptionProviderInterface.ts | 48 +- .../Domain/Encryption/EncryptionService.ts | 231 +-- .../src/Domain/Encryption/Functions.ts | 3 +- .../UseCase/Asymmetric/DecryptMessage.spec.ts | 178 ++ .../UseCase/Asymmetric/DecryptMessage.ts | 44 + .../UseCase/Asymmetric/DecryptOwnMessage.ts | 31 + .../UseCase/Asymmetric/EncryptMessage.ts | 28 + .../Asymmetric/GetMessageAdditionalData.ts | 13 + .../DecryptBackupFile.ts} | 6 +- .../ItemsKey/CreateNewDefaultItemsKey.ts | 10 +- .../ItemsKey/CreateNewItemsKeyWithRollback.ts | 32 +- .../UseCase/ItemsKey/FindDefaultItemsKey.ts | 33 + .../DecryptErroredPayloads.ts | 15 +- .../DecryptPayload.ts | 6 +- .../DecryptPayloadWithKeyLookup.ts | 18 +- .../EncryptPayload.ts | 8 +- .../EncryptPayloadWithKeyLookup.ts | 13 +- .../Domain/Feature/FeaturesClientInterface.ts | 1 + .../src/Domain/Files/FileService.spec.ts | 9 +- .../services/src/Domain/Files/FileService.ts | 11 +- .../Domain/History/HistoryServiceInterface.ts | 3 +- .../Domain/HomeServer/HomeServerService.ts | 41 +- .../ItemsEncryption.ts | 45 +- .../Domain/KeySystem/KeySystemKeyManager.ts | 31 +- .../KeySystemKeyManagerInterface.ts | 1 + .../src/Domain/Mutator/ImportDataUseCase.ts | 10 +- .../Protection/ProtectionClientInterface.ts | 15 +- .../src/Domain/RootKeyManager}/KeyMode.ts | 0 .../RootKeyManager.ts | 32 +- .../RootKeyManagerEvent.ts | 0 .../ValidateAccountPasswordResult.ts | 0 .../ValidatePasscodeResult.ts | 0 .../src/Domain/Service/AbstractService.ts | 14 +- .../Service/ApplicationServiceInterface.ts | 2 - .../Domain/Session/SessionsClientInterface.ts | 15 +- .../Session/UserKeyPairChangedEventData.ts | 13 +- .../SharedVaults/SharedVaultService.spec.ts | 109 + .../Domain/SharedVaults/SharedVaultService.ts | 345 ++-- .../SharedVaultServiceInterface.ts | 3 +- .../UseCase/AcceptTrustedSharedVaultInvite.ts | 29 - .../SharedVaults/UseCase/AcceptVaultInvite.ts | 23 + .../UseCase/ConvertToSharedVault.ts | 13 +- .../SharedVaults/UseCase/CreateSharedVault.ts | 21 +- .../UseCase/DeleteExternalSharedVault.ts | 21 +- .../SharedVaults/UseCase/DeleteSharedVault.ts | 14 +- .../UseCase/GetSharedVaultTrustedContacts.ts | 23 - .../SharedVaults/UseCase/GetVaultContacts.ts | 23 + ...etSharedVaultUsers.ts => GetVaultUsers.ts} | 2 +- .../UseCase/InviteContactToSharedVault.ts | 63 - .../SharedVaults/UseCase/InviteToVault.ts | 135 ++ .../SharedVaults/UseCase/LeaveSharedVault.ts | 22 +- ...NotifySharedVaultUsersOfRootKeyRotation.ts | 62 - .../UseCase/NotifyVaultUsersOfKeyRotation.ts | 116 ++ .../UseCase/RemoveSharedVaultMember.ts | 2 +- .../UseCase/ReuploadAllInvites.ts | 86 + .../SharedVaults/UseCase/ReuploadInvite.ts | 57 + ...ploadSharedVaultInvitesAfterKeyRotation.ts | 144 -- .../UseCase/ReuploadVaultInvites.ts | 86 + ...dSharedVaultMetadataChangedMessageToAll.ts | 100 - ...ndSharedVaultRootKeyChangedMessageToAll.ts | 103 - .../UseCase/SendVaultDataChangedMessage.ts | 108 + ...ultInviteUseCase.ts => SendVaultInvite.ts} | 11 +- .../UseCase/SendVaultKeyChangedMessage.ts | 114 + ...ShareContactWithAllMembersOfSharedVault.ts | 78 - .../UseCase/ShareContactWithVault.ts | 85 + .../Domain/Storage/StorageServiceInterface.ts | 4 + .../Subscription/SubscriptionManager.ts | 14 +- .../src/Domain}/Sync/SyncOpStatus.ts | 6 +- .../src/Domain/Sync/SyncServiceInterface.ts | 13 + .../src/Domain/UseCase/RemoveItemsLocally.ts | 2 +- .../src/Domain/User/UserClientInterface.ts | 22 +- .../src/Domain/User/UserService.spec.ts | 2 +- .../services/src/Domain/User/UserService.ts | 25 +- .../Vaults/UseCase/ChangeVaultKeyOptions.ts | 32 +- .../src/Domain/Vaults/UseCase/CreateVault.ts | 8 +- .../src/Domain/Vaults/UseCase/DeleteVault.ts | 12 +- .../src/Domain/Vaults/UseCase/GetVault.ts | 22 +- .../Domain/Vaults/UseCase/MoveItemsToVault.ts | 2 +- ...otateVaultRootKey.ts => RotateVaultKey.ts} | 17 +- .../src/Domain/Vaults/VaultService.ts | 75 +- packages/services/src/Domain/index.ts | 153 +- .../src/Common/PureCryptoInterface.ts | 2 +- packages/sncrypto-web/src/crypto.ts | 2 +- packages/sncrypto-web/test/crypto.test.js | 2 +- packages/snjs/lib/Application/Application.ts | 1840 ++++++----------- .../Application/Dependencies/Dependencies.ts | 1211 +++++++++++ .../lib/Application/Dependencies/Types.ts | 147 ++ .../Application/Dependencies/isDeinitable.ts | 14 + .../UseCase/GetRevision/GetRevision.spec.ts | 9 +- .../Domain/UseCase/GetRevision/GetRevision.ts | 3 +- .../SignInWithRecoveryCodes.spec.ts | 75 +- .../SignInWithRecoveryCodes.ts | 3 +- .../snjs/lib/Migrations/MigrationServices.ts | 10 +- .../lib/Services/Actions/ActionsService.ts | 17 +- packages/snjs/lib/Services/Api/ApiService.ts | 6 +- .../Services/Api/WebsocketsService.spec.ts | 4 +- .../lib/Services/Api/WebsocketsService.ts | 2 +- .../Services/Challenge/ChallengeService.ts | 6 +- .../ComponentManager/ComponentManager.spec.ts | 10 +- .../ComponentManager/ComponentManager.ts | 4 +- .../ComponentManager/ComponentViewer.ts | 4 +- .../Services/Features/FeaturesService.spec.ts | 28 +- .../lib/Services/Features/FeaturesService.ts | 29 +- .../DownloadRemoteThirdPartyFeature.ts | 8 +- .../lib/Services/History/HistoryManager.ts | 2 +- .../KeyRecovery/KeyRecoveryOperation.ts | 8 +- .../KeyRecovery/KeyRecoveryService.ts | 36 +- .../snjs/lib/Services/Listed/ListedService.ts | 18 +- packages/snjs/lib/Services/Mfa/MfaService.ts | 8 +- .../Services/Migration/MigrationService.ts | 21 +- .../Preferences/PreferencesService.ts | 37 +- .../Services/Protection/ProtectionService.ts | 23 +- .../lib/Services/Session/SessionManager.ts | 46 +- .../Services/Settings/SNSettingsService.ts | 10 +- .../Services/Singleton/SingletonManager.ts | 24 +- .../Services/Storage/DiskStorageService.ts | 183 +- .../lib/Services/Sync/Account/Operation.ts | 4 +- .../lib/Services/Sync/SyncClientInterface.ts | 14 - .../snjs/lib/Services/Sync/SyncService.ts | 27 +- packages/snjs/lib/Services/Sync/index.ts | 2 - packages/snjs/lib/index.ts | 1 + packages/snjs/mocha/001.test.js | 6 +- packages/snjs/mocha/002.test.js | 6 +- packages/snjs/mocha/003.test.js | 6 +- packages/snjs/mocha/004.test.js | 18 +- packages/snjs/mocha/TestRegistry/BaseTests.js | 2 +- .../snjs/mocha/TestRegistry/VaultTests.js | 2 + packages/snjs/mocha/actions.test.js | 21 +- packages/snjs/mocha/application.test.js | 26 +- packages/snjs/mocha/auth-fringe-cases.test.js | 18 +- packages/snjs/mocha/auth.test.js | 84 +- packages/snjs/mocha/backups.test.js | 10 +- packages/snjs/mocha/device_auth.test.js | 38 +- packages/snjs/mocha/features.test.js | 24 +- packages/snjs/mocha/files.test.js | 19 +- packages/snjs/mocha/history.test.js | 64 +- .../snjs/mocha/key_recovery_service.test.js | 128 +- packages/snjs/mocha/keys.test.js | 232 +-- packages/snjs/mocha/lib/AppContext.js | 136 +- packages/snjs/mocha/lib/Collaboration.js | 10 +- packages/snjs/mocha/lib/Utils.js | 4 +- packages/snjs/mocha/lib/factory.js | 61 +- packages/snjs/mocha/lib/fake_web_crypto.js | 2 +- packages/snjs/mocha/mfa_service.test.js | 4 +- .../snjs/mocha/migrations/migration.test.js | 36 +- .../mocha/migrations/tags-to-folders.test.js | 2 +- .../snjs/mocha/model_tests/appmodels.test.js | 88 +- .../snjs/mocha/model_tests/importing.test.js | 92 +- packages/snjs/mocha/model_tests/items.test.js | 16 +- .../snjs/mocha/model_tests/mapping.test.js | 32 +- .../snjs/mocha/model_tests/notes_tags.test.js | 88 +- .../mocha/model_tests/performance.test.js | 10 +- packages/snjs/mocha/mutator_service.test.js | 2 +- .../snjs/mocha/payload_encryption.test.js | 14 +- packages/snjs/mocha/preferences.test.js | 2 +- packages/snjs/mocha/protection.test.js | 10 +- packages/snjs/mocha/protocol.test.js | 38 +- packages/snjs/mocha/recovery.test.js | 16 +- packages/snjs/mocha/session.test.js | 90 +- packages/snjs/mocha/settings.test.js | 2 +- packages/snjs/mocha/singletons.test.js | 81 +- packages/snjs/mocha/storage.test.js | 90 +- packages/snjs/mocha/subscriptions.test.js | 19 +- .../snjs/mocha/sync_tests/conflicting.test.js | 188 +- .../snjs/mocha/sync_tests/integrity.test.js | 20 +- .../snjs/mocha/sync_tests/notes_tags.test.js | 84 +- .../snjs/mocha/sync_tests/offline.test.js | 28 +- packages/snjs/mocha/sync_tests/online.test.js | 268 +-- packages/snjs/mocha/upgrading.test.js | 66 +- .../mocha/vaults/asymmetric-messages.test.js | 129 +- packages/snjs/mocha/vaults/contacts.test.js | 16 + packages/snjs/mocha/vaults/crypto.test.js | 29 +- packages/snjs/mocha/vaults/invites.test.js | 22 +- .../snjs/mocha/vaults/key_rotation.test.js | 2 +- .../snjs/mocha/vaults/keypair-change.test.js | 41 + .../snjs/mocha/vaults/permissions.test.js | 2 +- .../snjs/mocha/vaults/shared_vaults.test.js | 16 +- packages/snjs/mocha/vaults/signatures.test.js | 33 + .../Application/Device/WebOrDesktopDevice.ts | 2 +- .../javascripts/Application/WebApplication.ts | 29 +- .../AccountMenu/AdvancedOptions.tsx | 2 +- .../AccountMenu/GeneralAccountMenu.tsx | 2 +- .../WorkspaceSwitcherMenu.tsx | 2 +- .../ChallengeModal/ChallengeModal.tsx | 2 +- .../ChangeEditor/ChangeEditorMenu.tsx | 4 +- .../ChangeEditor/ChangeEditorMultipleMenu.tsx | 4 +- .../javascripts/Components/Footer/Footer.tsx | 2 +- .../Components/NoteView/NoteView.tsx | 6 +- .../NotesOptions/Listed/ListedMenuItem.tsx | 2 +- .../OtherSessionsSignOut.tsx | 2 +- .../PasswordWizard/PasswordWizard.tsx | 12 +- .../Panes/Account/ChangeEmail/ChangeEmail.tsx | 2 +- .../Preferences/Panes/Account/Email/Email.tsx | 2 +- .../SubscriptionSharing/InvitationsList.tsx | 4 +- .../SubscriptionSharing/Invite/Invite.tsx | 12 +- .../Preferences/Panes/Account/Sync.tsx | 2 +- .../Preferences/Panes/Backups/DataBackups.tsx | 6 +- .../Panes/Backups/EmailBackups.tsx | 2 +- .../Panes/Backups/Files/BackupsDropZone.tsx | 8 +- .../General/Advanced/OfflineSubscription.tsx | 10 +- .../General/Advanced/Packages/Section.tsx | 4 +- .../Preferences/Panes/Listed/Listed.tsx | 4 +- .../Panes/Security/BiometricsLock.tsx | 2 +- .../Panes/Security/ErroredItems.tsx | 4 +- .../Panes/Security/MultitaskingPrivacy.tsx | 2 +- .../Preferences/Panes/Security/Privacy.tsx | 2 +- .../Vaults/Contacts/EditContactModal.tsx | 12 +- .../PurchaseFlow/Panes/CreateAccount.tsx | 2 +- .../Components/PurchaseFlow/Panes/SignIn.tsx | 2 +- .../SuperEditor/SuperNoteConverter.tsx | 2 +- .../SuperEditor/SuperNoteImporter.tsx | 2 +- .../Components/Tags/TagsSection.tsx | 2 +- .../web/src/javascripts/Constants/Strings.ts | 2 +- .../Controllers/FilesController.ts | 2 +- .../Navigation/NavigationController.ts | 2 +- .../Controllers/Navigation/Utils.ts | 2 +- .../NoteHistory/NoteHistoryController.ts | 12 +- .../Controllers/NoteSyncController.ts | 2 +- .../NotesController/NotesController.ts | 4 +- .../PurchaseFlow/PurchaseFlowController.ts | 6 +- .../Controllers/ViewControllerManager.ts | 2 +- .../Event/ApplicationEventObserver.spec.ts | 6 +- .../Event/ApplicationEventObserver.ts | 4 +- .../javascripts/Utils/ManageSubscription.ts | 6 +- 299 files changed, 7415 insertions(+), 4890 deletions(-) create mode 100644 packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecryptOwnMessage.ts create mode 100644 packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricStringGetAdditionalData.ts rename packages/encryption/src/Domain/Operator/{OperatorManager.ts => EncryptionOperators.ts} (87%) create mode 100644 packages/encryption/src/Domain/Operator/EncryptionOperatorsInterface.ts delete mode 100644 packages/encryption/src/Domain/Service/Functions.ts create mode 100644 packages/models/src/Domain/Syncable/TrustedContact/Content/TrustedContactContent.ts create mode 100644 packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.spec.ts create mode 100644 packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.ts delete mode 100644 packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/FindPublicKeySetResult.ts create mode 100644 packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.spec.ts delete mode 100644 packages/models/src/Domain/Syncable/TrustedContact/TrustedContactContent.ts delete mode 100644 packages/models/src/Domain/Syncable/TrustedContact/TrustedContactMutator.ts rename packages/{encryption/src/Domain/Operator/Types/PublicKeySet.ts => models/src/Domain/Syncable/TrustedContact/Types/PortablePublicKeySet.ts} (52%) create mode 100644 packages/models/src/Domain/Syncable/TrustedContact/Types/PublicKeyTrustStatus.ts rename packages/services/src/Domain/Api/{ApiServiceInterface.ts => LegacyApiServiceInterface.ts} (83%) delete mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageTrustedPayload.ts delete mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageUntrustedPayload.ts rename packages/services/src/Domain/AsymmetricMessage/UseCase/{GetInboundAsymmetricMessages.ts => GetInboundMessages.ts} (92%) rename packages/services/src/Domain/AsymmetricMessage/UseCase/{GetOutboundAsymmetricMessages.ts => GetOutboundMessages.ts} (92%) create mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/GetReplaceabilityIdentifier.ts create mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/GetTrustedPayload.ts create mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/GetUntrustedPayload.ts rename packages/services/src/Domain/AsymmetricMessage/UseCase/{HandleTrustedSharedVaultRootKeyChangedMessage.ts => HandleRootKeyChangedMessage.ts} (63%) rename packages/services/src/Domain/AsymmetricMessage/UseCase/{HandleTrustedSharedVaultInviteMessage.spec.ts => ProcessAcceptedVaultInvite.spec.ts} (56%) rename packages/services/src/Domain/AsymmetricMessage/UseCase/{HandleTrustedSharedVaultInviteMessage.ts => ProcessAcceptedVaultInvite.ts} (84%) create mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/ResendAllMessages.ts create mode 100644 packages/services/src/Domain/AsymmetricMessage/UseCase/ResendMessage.ts rename packages/services/src/Domain/AsymmetricMessage/UseCase/{SendAsymmetricMessageUseCase.ts => SendMessage.ts} (55%) rename packages/services/src/Domain/Backups/{BackupService.spec.ts => FilesBackupService.spec.ts} (86%) rename packages/services/src/Domain/Backups/{BackupService.ts => FilesBackupService.ts} (95%) rename packages/services/src/Domain/Contacts/{Managers => }/SelfContactManager.ts (55%) rename packages/services/src/Domain/Contacts/UseCase/{CreateOrEditTrustedContact.ts => CreateOrEditContact.ts} (57%) rename packages/services/src/Domain/Contacts/UseCase/{UpdateTrustedContact.ts => EditContact.ts} (82%) create mode 100644 packages/services/src/Domain/Contacts/UseCase/FindContact.ts delete mode 100644 packages/services/src/Domain/Contacts/UseCase/FindTrustedContact.ts create mode 100644 packages/services/src/Domain/Contacts/UseCase/GetAllContacts.ts create mode 100644 packages/services/src/Domain/Contacts/UseCase/HandleKeyPairChange.ts create mode 100644 packages/services/src/Domain/Contacts/UseCase/ReplaceContactData.ts rename packages/services/src/Domain/Contacts/UseCase/{ => Types}/FindContactQuery.ts (59%) create mode 100644 packages/services/src/Domain/Contacts/UseCase/Types/ItemSignatureValidationResult.ts delete mode 100644 packages/services/src/Domain/Contacts/UseCase/ValidateItemSignerResult.ts rename packages/{encryption/src/Domain/Service => services/src/Domain}/Encryption/EncryptionProviderInterface.ts (70%) create mode 100644 packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.spec.ts create mode 100644 packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.ts create mode 100644 packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptOwnMessage.ts create mode 100644 packages/services/src/Domain/Encryption/UseCase/Asymmetric/EncryptMessage.ts create mode 100644 packages/services/src/Domain/Encryption/UseCase/Asymmetric/GetMessageAdditionalData.ts rename packages/services/src/Domain/Encryption/{DecryptBackupFileUseCase.ts => UseCase/DecryptBackupFile.ts} (98%) create mode 100644 packages/services/src/Domain/Encryption/UseCase/ItemsKey/FindDefaultItemsKey.ts rename packages/services/src/Domain/Encryption/UseCase/{RootEncryption => TypeA}/DecryptErroredPayloads.ts (69%) rename packages/services/src/Domain/Encryption/UseCase/{RootEncryption => TypeA}/DecryptPayload.ts (85%) rename packages/services/src/Domain/Encryption/UseCase/{RootEncryption => TypeA}/DecryptPayloadWithKeyLookup.ts (73%) rename packages/services/src/Domain/Encryption/UseCase/{RootEncryption => TypeA}/EncryptPayload.ts (69%) rename packages/services/src/Domain/Encryption/UseCase/{RootEncryption => TypeA}/EncryptPayloadWithKeyLookup.ts (73%) rename packages/services/src/Domain/{Encryption => ItemsEncryption}/ItemsEncryption.ts (86%) rename packages/{encryption/src/Domain/Service => services/src/Domain/KeySystem}/KeySystemKeyManagerInterface.ts (94%) rename packages/{encryption/src/Domain/Service/RootKey => services/src/Domain/RootKeyManager}/KeyMode.ts (100%) rename packages/services/src/Domain/{Encryption/RootKey => RootKeyManager}/RootKeyManager.ts (94%) rename packages/services/src/Domain/{Encryption/RootKey => RootKeyManager}/RootKeyManagerEvent.ts (100%) rename packages/services/src/Domain/{Encryption/RootKey => RootKeyManager}/ValidateAccountPasswordResult.ts (100%) rename packages/services/src/Domain/{Encryption/RootKey => RootKeyManager}/ValidatePasscodeResult.ts (100%) create mode 100644 packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/AcceptTrustedSharedVaultInvite.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/AcceptVaultInvite.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultTrustedContacts.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/GetVaultContacts.ts rename packages/services/src/Domain/SharedVaults/UseCase/{GetSharedVaultUsers.ts => GetVaultUsers.ts} (92%) delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/InviteContactToSharedVault.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/InviteToVault.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/NotifySharedVaultUsersOfRootKeyRotation.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/ReuploadAllInvites.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/ReuploadInvite.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/ReuploadSharedVaultInvitesAfterKeyRotation.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/ReuploadVaultInvites.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultMetadataChangedMessageToAll.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultRootKeyChangedMessageToAll.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/SendVaultDataChangedMessage.ts rename packages/services/src/Domain/SharedVaults/UseCase/{SendSharedVaultInviteUseCase.ts => SendVaultInvite.ts} (67%) create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/SendVaultKeyChangedMessage.ts delete mode 100644 packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithAllMembersOfSharedVault.ts create mode 100644 packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithVault.ts rename packages/{snjs/lib/Services => services/src/Domain}/Sync/SyncOpStatus.ts (92%) rename packages/services/src/Domain/Vaults/UseCase/{RotateVaultRootKey.ts => RotateVaultKey.ts} (81%) create mode 100644 packages/snjs/lib/Application/Dependencies/Dependencies.ts create mode 100644 packages/snjs/lib/Application/Dependencies/Types.ts create mode 100644 packages/snjs/lib/Application/Dependencies/isDeinitable.ts delete mode 100644 packages/snjs/lib/Services/Sync/SyncClientInterface.ts create mode 100644 packages/snjs/mocha/vaults/keypair-change.test.js create mode 100644 packages/snjs/mocha/vaults/signatures.test.js diff --git a/packages/api/src/Domain/Server/SharedVaultInvites/Paths.ts b/packages/api/src/Domain/Server/SharedVaultInvites/Paths.ts index 3220745fd..2177c8aba 100644 --- a/packages/api/src/Domain/Server/SharedVaultInvites/Paths.ts +++ b/packages/api/src/Domain/Server/SharedVaultInvites/Paths.ts @@ -13,4 +13,5 @@ export const SharedVaultInvitesPaths = { `/v1/shared-vaults/${sharedVaultUuid}/invites/${inviteUuid}`, deleteAllSharedVaultInvites: (sharedVaultUuid: string) => `/v1/shared-vaults/${sharedVaultUuid}/invites`, deleteAllInboundInvites: '/v1/shared-vaults/invites/inbound', + deleteAllOutboundInvites: '/v1/shared-vaults/invites/outbound', } diff --git a/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServer.ts b/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServer.ts index 85e13a01e..a012c1ec9 100644 --- a/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServer.ts +++ b/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServer.ts @@ -72,4 +72,8 @@ export class SharedVaultInvitesServer implements SharedVaultInvitesServerInterfa deleteAllInboundInvites(): Promise> { return this.httpService.delete(SharedVaultInvitesPaths.deleteAllInboundInvites) } + + deleteAllOutboundInvites(): Promise> { + return this.httpService.delete(SharedVaultInvitesPaths.deleteAllOutboundInvites) + } } diff --git a/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServerInterface.ts b/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServerInterface.ts index f5fa2a498..fc3b9670c 100644 --- a/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServerInterface.ts +++ b/packages/api/src/Domain/Server/SharedVaultInvites/SharedVaultInvitesServerInterface.ts @@ -32,4 +32,5 @@ export interface SharedVaultInvitesServerInterface { ): Promise> deleteInvite(params: DeleteInviteRequestParams): Promise> deleteAllInboundInvites(): Promise> + deleteAllOutboundInvites(): Promise> } diff --git a/packages/encryption/src/Domain/Operator/001/Operator001.ts b/packages/encryption/src/Domain/Operator/001/Operator001.ts index fd67dcbd7..5c0671ea7 100644 --- a/packages/encryption/src/Domain/Operator/001/Operator001.ts +++ b/packages/encryption/src/Domain/Operator/001/Operator001.ts @@ -13,6 +13,7 @@ import { KeySystemRootKeyInterface, RootKeyInterface, KeySystemRootKeyParamsInterface, + PortablePublicKeySet, } from '@standardnotes/models' import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common' import { firstHalfOfString, secondHalfOfString, splitString, UuidGenerator } from '@standardnotes/utils' @@ -28,11 +29,12 @@ import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData' import { LegacyAttachedData } from '../../Types/LegacyAttachedData' import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedAuthenticatedData' import { OperatorInterface } from '../OperatorInterface/OperatorInterface' -import { PublicKeySet } from '../Types/PublicKeySet' + import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult' import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult' import { AsyncOperatorInterface } from '../OperatorInterface/AsyncOperatorInterface' -import { ContentType } from '@standardnotes/domain-core' +import { ContentType, Result } from '@standardnotes/domain-core' +import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData' const NO_IV = '00000000000000000000000000000000' @@ -272,11 +274,23 @@ export class SNProtocolOperator001 implements OperatorInterface, AsyncOperatorIn throw new Error('Method not implemented.') } + asymmetricDecryptOwnMessage(_dto: { + message: string + ownPrivateKey: string + recipientPublicKey: string + }): Result { + throw new Error('Method not implemented.') + } + asymmetricSignatureVerifyDetached(_encryptedString: string): AsymmetricSignatureVerificationDetachedResult { throw new Error('Method not implemented.') } - getSenderPublicKeySetFromAsymmetricallyEncryptedString(_string: string): PublicKeySet { + asymmetricStringGetAdditionalData(_dto: { encryptedString: string }): Result { + throw new Error('Method not implemented.') + } + + getSenderPublicKeySetFromAsymmetricallyEncryptedString(_string: string): PortablePublicKeySet { throw new Error('Method not implemented.') } } diff --git a/packages/encryption/src/Domain/Operator/004/Operator004.ts b/packages/encryption/src/Domain/Operator/004/Operator004.ts index bbfa98a6b..228c5b95d 100644 --- a/packages/encryption/src/Domain/Operator/004/Operator004.ts +++ b/packages/encryption/src/Domain/Operator/004/Operator004.ts @@ -12,6 +12,7 @@ import { KeySystemIdentifier, RootKeyInterface, KeySystemRootKeyParamsInterface, + PortablePublicKeySet, } from '@standardnotes/models' import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common' import { HexString, PkcKeyPair, PureCryptoInterface, Utf8String } from '@standardnotes/sncrypto-common' @@ -30,9 +31,9 @@ import { OperatorInterface } from '../OperatorInterface/OperatorInterface' import { AsymmetricallyEncryptedString } from '../Types/Types' import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData' import { V004AsymmetricStringComponents } from './V004AlgorithmTypes' -import { AsymmetricEncryptUseCase } from './UseCase/Asymmetric/AsymmetricEncrypt' +import { AsymmetricEncrypt004 } from './UseCase/Asymmetric/AsymmetricEncrypt' import { ParseConsistentBase64JsonPayloadUseCase } from './UseCase/Utils/ParseConsistentBase64JsonPayload' -import { AsymmetricDecryptUseCase } from './UseCase/Asymmetric/AsymmetricDecrypt' +import { AsymmetricDecrypt004 } from './UseCase/Asymmetric/AsymmetricDecrypt' import { GenerateDecryptedParametersUseCase } from './UseCase/Symmetric/GenerateDecryptedParameters' import { GenerateEncryptedParametersUseCase } from './UseCase/Symmetric/GenerateEncryptedParameters' import { DeriveRootKeyUseCase } from './UseCase/RootKey/DeriveRootKey' @@ -41,14 +42,15 @@ import { CreateRootKeyUseCase } from './UseCase/RootKey/CreateRootKey' import { UuidGenerator } from '@standardnotes/utils' import { CreateKeySystemItemsKeyUseCase } from './UseCase/KeySystem/CreateKeySystemItemsKey' import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult' -import { PublicKeySet } from '../Types/PublicKeySet' import { CreateRandomKeySystemRootKey } from './UseCase/KeySystem/CreateRandomKeySystemRootKey' import { CreateUserInputKeySystemRootKey } from './UseCase/KeySystem/CreateUserInputKeySystemRootKey' import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult' -import { AsymmetricSignatureVerificationDetachedUseCase } from './UseCase/Asymmetric/AsymmetricSignatureVerificationDetached' +import { AsymmetricSignatureVerificationDetached004 } from './UseCase/Asymmetric/AsymmetricSignatureVerificationDetached' import { DeriveKeySystemRootKeyUseCase } from './UseCase/KeySystem/DeriveKeySystemRootKey' import { SyncOperatorInterface } from '../OperatorInterface/SyncOperatorInterface' -import { ContentType } from '@standardnotes/domain-core' +import { ContentType, Result } from '@standardnotes/domain-core' +import { AsymmetricStringGetAdditionalData004 } from './UseCase/Asymmetric/AsymmetricStringGetAdditionalData' +import { AsymmetricDecryptOwnMessage004 } from './UseCase/Asymmetric/AsymmetricDecryptOwnMessage' export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInterface { constructor(protected readonly crypto: PureCryptoInterface) {} @@ -167,7 +169,7 @@ export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInt senderSigningKeyPair: PkcKeyPair recipientPublicKey: HexString }): AsymmetricallyEncryptedString { - const usecase = new AsymmetricEncryptUseCase(this.crypto) + const usecase = new AsymmetricEncrypt004(this.crypto) return usecase.execute(dto) } @@ -175,18 +177,34 @@ export class SNProtocolOperator004 implements OperatorInterface, SyncOperatorInt stringToDecrypt: AsymmetricallyEncryptedString recipientSecretKey: HexString }): AsymmetricDecryptResult | null { - const usecase = new AsymmetricDecryptUseCase(this.crypto) + const usecase = new AsymmetricDecrypt004(this.crypto) + return usecase.execute(dto) + } + + asymmetricDecryptOwnMessage(dto: { + message: AsymmetricallyEncryptedString + ownPrivateKey: HexString + recipientPublicKey: HexString + }): Result { + const usecase = new AsymmetricDecryptOwnMessage004(this.crypto) return usecase.execute(dto) } asymmetricSignatureVerifyDetached( encryptedString: AsymmetricallyEncryptedString, ): AsymmetricSignatureVerificationDetachedResult { - const usecase = new AsymmetricSignatureVerificationDetachedUseCase(this.crypto) + const usecase = new AsymmetricSignatureVerificationDetached004(this.crypto) return usecase.execute({ encryptedString }) } - getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet { + asymmetricStringGetAdditionalData(dto: { + encryptedString: AsymmetricallyEncryptedString + }): Result { + const usecase = new AsymmetricStringGetAdditionalData004(this.crypto) + return usecase.execute(dto) + } + + getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PortablePublicKeySet { const [_, __, ___, additionalDataString] = string.split(':') const parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto) const additionalData = parseBase64Usecase.execute(additionalDataString) diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.spec.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.spec.ts index 58ae00d0f..c1a070065 100644 --- a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.spec.ts +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.spec.ts @@ -1,27 +1,27 @@ import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common' import { getMockedCrypto } from '../../MockedCrypto' -import { AsymmetricDecryptUseCase } from './AsymmetricDecrypt' -import { AsymmetricEncryptUseCase } from './AsymmetricEncrypt' +import { AsymmetricDecrypt004 } from './AsymmetricDecrypt' +import { AsymmetricEncrypt004 } from './AsymmetricEncrypt' import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes' import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' describe('asymmetric decrypt use case', () => { let crypto: PureCryptoInterface - let usecase: AsymmetricDecryptUseCase + let usecase: AsymmetricDecrypt004 let recipientKeyPair: PkcKeyPair let senderKeyPair: PkcKeyPair let senderSigningKeyPair: PkcKeyPair beforeEach(() => { crypto = getMockedCrypto() - usecase = new AsymmetricDecryptUseCase(crypto) + usecase = new AsymmetricDecrypt004(crypto) recipientKeyPair = crypto.sodiumCryptoBoxSeedKeypair('recipient-seedling') senderKeyPair = crypto.sodiumCryptoBoxSeedKeypair('sender-seedling') senderSigningKeyPair = crypto.sodiumCryptoSignSeedKeypair('sender-signing-seedling') }) const getEncryptedString = () => { - const encryptUsecase = new AsymmetricEncryptUseCase(crypto) + const encryptUsecase = new AsymmetricEncrypt004(crypto) const result = encryptUsecase.execute({ stringToEncrypt: 'foobar', diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.ts index 2cd3c899b..3899b8eab 100644 --- a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.ts +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecrypt.ts @@ -5,7 +5,7 @@ import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsisten import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' import { AsymmetricDecryptResult } from '../../../Types/AsymmetricDecryptResult' -export class AsymmetricDecryptUseCase { +export class AsymmetricDecrypt004 { private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto) constructor(private readonly crypto: PureCryptoInterface) {} diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecryptOwnMessage.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecryptOwnMessage.ts new file mode 100644 index 000000000..27838e9aa --- /dev/null +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricDecryptOwnMessage.ts @@ -0,0 +1,51 @@ +import { HexString, PureCryptoInterface } from '@standardnotes/sncrypto-common' +import { AsymmetricallyEncryptedString } from '../../../Types/Types' +import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes' +import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload' +import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' +import { AsymmetricDecryptResult } from '../../../Types/AsymmetricDecryptResult' +import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core' + +export class AsymmetricDecryptOwnMessage004 implements SyncUseCaseInterface { + private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto) + + constructor(private readonly crypto: PureCryptoInterface) {} + + execute(dto: { + message: AsymmetricallyEncryptedString + ownPrivateKey: HexString + recipientPublicKey: HexString + }): Result { + const [_, nonce, ciphertext, additionalDataString] = dto.message.split(':') + + const additionalData = this.parseBase64Usecase.execute(additionalDataString) + + try { + const plaintext = this.crypto.sodiumCryptoBoxEasyDecrypt( + ciphertext, + nonce, + dto.recipientPublicKey, + dto.ownPrivateKey, + ) + + if (!plaintext) { + return Result.fail('Could not decrypt message') + } + + const signatureVerified = this.crypto.sodiumCryptoSignVerify( + ciphertext, + additionalData.signingData.signature, + additionalData.signingData.publicKey, + ) + + return Result.ok({ + plaintext, + signatureVerified, + signaturePublicKey: additionalData.signingData.publicKey, + senderPublicKey: additionalData.senderPublicKey, + }) + } catch (error) { + return Result.fail('Could not decrypt message') + } + } +} diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.spec.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.spec.ts index aee4a58ca..bc84c3dee 100644 --- a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.spec.ts +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.spec.ts @@ -1,20 +1,20 @@ import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common' import { getMockedCrypto } from '../../MockedCrypto' -import { AsymmetricEncryptUseCase } from './AsymmetricEncrypt' +import { AsymmetricEncrypt004 } from './AsymmetricEncrypt' import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes' import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload' import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' describe('asymmetric encrypt use case', () => { let crypto: PureCryptoInterface - let usecase: AsymmetricEncryptUseCase + let usecase: AsymmetricEncrypt004 let encryptionKeyPair: PkcKeyPair let signingKeyPair: PkcKeyPair let parseBase64Usecase: ParseConsistentBase64JsonPayloadUseCase beforeEach(() => { crypto = getMockedCrypto() - usecase = new AsymmetricEncryptUseCase(crypto) + usecase = new AsymmetricEncrypt004(crypto) encryptionKeyPair = crypto.sodiumCryptoBoxSeedKeypair('seedling') signingKeyPair = crypto.sodiumCryptoSignSeedKeypair('seedling') parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(crypto) diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.ts index 5a51523b4..c9ffa3135 100644 --- a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.ts +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricEncrypt.ts @@ -5,7 +5,7 @@ import { V004AsymmetricCiphertextPrefix, V004AsymmetricStringComponents } from ' import { CreateConsistentBase64JsonPayloadUseCase } from '../Utils/CreateConsistentBase64JsonPayload' import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' -export class AsymmetricEncryptUseCase { +export class AsymmetricEncrypt004 { private base64DataUsecase = new CreateConsistentBase64JsonPayloadUseCase(this.crypto) constructor(private readonly crypto: PureCryptoInterface) {} @@ -21,8 +21,8 @@ export class AsymmetricEncryptUseCase { const ciphertext = this.crypto.sodiumCryptoBoxEasyEncrypt( dto.stringToEncrypt, nonce, - dto.senderKeyPair.privateKey, dto.recipientPublicKey, + dto.senderKeyPair.privateKey, ) const additionalData: AsymmetricItemAdditionalData = { diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricSignatureVerificationDetached.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricSignatureVerificationDetached.ts index 36e272ad5..22ae4576c 100644 --- a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricSignatureVerificationDetached.ts +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricSignatureVerificationDetached.ts @@ -5,7 +5,7 @@ import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsisten import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' import { AsymmetricSignatureVerificationDetachedResult } from '../../../Types/AsymmetricSignatureVerificationDetachedResult' -export class AsymmetricSignatureVerificationDetachedUseCase { +export class AsymmetricSignatureVerificationDetached004 { private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto) constructor(private readonly crypto: PureCryptoInterface) {} diff --git a/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricStringGetAdditionalData.ts b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricStringGetAdditionalData.ts new file mode 100644 index 000000000..80bc3da85 --- /dev/null +++ b/packages/encryption/src/Domain/Operator/004/UseCase/Asymmetric/AsymmetricStringGetAdditionalData.ts @@ -0,0 +1,20 @@ +import { PureCryptoInterface } from '@standardnotes/sncrypto-common' +import { AsymmetricallyEncryptedString } from '../../../Types/Types' +import { V004AsymmetricStringComponents } from '../../V004AlgorithmTypes' +import { ParseConsistentBase64JsonPayloadUseCase } from '../Utils/ParseConsistentBase64JsonPayload' +import { AsymmetricItemAdditionalData } from '../../../../Types/EncryptionAdditionalData' +import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core' + +export class AsymmetricStringGetAdditionalData004 implements SyncUseCaseInterface { + private parseBase64Usecase = new ParseConsistentBase64JsonPayloadUseCase(this.crypto) + + constructor(private readonly crypto: PureCryptoInterface) {} + + execute(dto: { encryptedString: AsymmetricallyEncryptedString }): Result { + const [_, __, ___, additionalDataString] = dto.encryptedString.split(':') + + const additionalData = this.parseBase64Usecase.execute(additionalDataString) + + return Result.ok(additionalData) + } +} diff --git a/packages/encryption/src/Domain/Operator/OperatorManager.ts b/packages/encryption/src/Domain/Operator/EncryptionOperators.ts similarity index 87% rename from packages/encryption/src/Domain/Operator/OperatorManager.ts rename to packages/encryption/src/Domain/Operator/EncryptionOperators.ts index 82b710ad9..d4d5b6ca3 100644 --- a/packages/encryption/src/Domain/Operator/OperatorManager.ts +++ b/packages/encryption/src/Domain/Operator/EncryptionOperators.ts @@ -2,8 +2,9 @@ import { ProtocolVersion, ProtocolVersionLatest } from '@standardnotes/common' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { createOperatorForVersion } from './Functions' import { AnyOperatorInterface } from './OperatorInterface/TypeCheck' +import { EncryptionOperatorsInterface } from './EncryptionOperatorsInterface' -export class OperatorManager { +export class EncryptionOperators implements EncryptionOperatorsInterface { private operators: Record = {} constructor(private crypto: PureCryptoInterface) { diff --git a/packages/encryption/src/Domain/Operator/EncryptionOperatorsInterface.ts b/packages/encryption/src/Domain/Operator/EncryptionOperatorsInterface.ts new file mode 100644 index 000000000..7123bb64c --- /dev/null +++ b/packages/encryption/src/Domain/Operator/EncryptionOperatorsInterface.ts @@ -0,0 +1,8 @@ +import { ProtocolVersion } from '@standardnotes/common' +import { AnyOperatorInterface } from './OperatorInterface/TypeCheck' + +export interface EncryptionOperatorsInterface { + operatorForVersion(version: ProtocolVersion): AnyOperatorInterface + defaultOperator(): AnyOperatorInterface + deinit(): void +} diff --git a/packages/encryption/src/Domain/Operator/OperatorInterface/OperatorInterface.ts b/packages/encryption/src/Domain/Operator/OperatorInterface/OperatorInterface.ts index 1b51975e8..018923470 100644 --- a/packages/encryption/src/Domain/Operator/OperatorInterface/OperatorInterface.ts +++ b/packages/encryption/src/Domain/Operator/OperatorInterface/OperatorInterface.ts @@ -6,6 +6,7 @@ import { KeySystemRootKeyInterface, KeySystemIdentifier, KeySystemRootKeyParamsInterface, + PortablePublicKeySet, } from '@standardnotes/models' import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams' import { EncryptedOutputParameters } from '../../Types/EncryptedParameters' @@ -15,8 +16,9 @@ import { RootKeyEncryptedAuthenticatedData } from '../../Types/RootKeyEncryptedA import { HexString, PkcKeyPair } from '@standardnotes/sncrypto-common' import { AsymmetricallyEncryptedString } from '../Types/Types' import { AsymmetricDecryptResult } from '../Types/AsymmetricDecryptResult' -import { PublicKeySet } from '../Types/PublicKeySet' import { AsymmetricSignatureVerificationDetachedResult } from '../Types/AsymmetricSignatureVerificationDetachedResult' +import { AsymmetricItemAdditionalData } from '../../Types/EncryptionAdditionalData' +import { Result } from '@standardnotes/domain-core' /**w * An operator is responsible for performing crypto operations, such as generating keys @@ -92,11 +94,21 @@ export interface OperatorInterface { recipientSecretKey: HexString }): AsymmetricDecryptResult | null + asymmetricDecryptOwnMessage(dto: { + message: AsymmetricallyEncryptedString + ownPrivateKey: HexString + recipientPublicKey: HexString + }): Result + asymmetricSignatureVerifyDetached( encryptedString: AsymmetricallyEncryptedString, ): AsymmetricSignatureVerificationDetachedResult - getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet + asymmetricStringGetAdditionalData(dto: { + encryptedString: AsymmetricallyEncryptedString + }): Result + + getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PortablePublicKeySet versionForAsymmetricallyEncryptedString(encryptedString: string): ProtocolVersion } diff --git a/packages/encryption/src/Domain/Operator/OperatorWrapper.ts b/packages/encryption/src/Domain/Operator/OperatorWrapper.ts index c2aed9c59..2b207deae 100644 --- a/packages/encryption/src/Domain/Operator/OperatorWrapper.ts +++ b/packages/encryption/src/Domain/Operator/OperatorWrapper.ts @@ -13,14 +13,14 @@ import { ErrorDecryptingParameters, } from '../Types/EncryptedParameters' import { DecryptedParameters } from '../Types/DecryptedParameters' -import { OperatorManager } from './OperatorManager' import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { isAsyncOperator } from './OperatorInterface/TypeCheck' +import { EncryptionOperatorsInterface } from './EncryptionOperatorsInterface' export async function encryptPayload( payload: DecryptedPayloadInterface, key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | RootKeyInterface, - operatorManager: OperatorManager, + operatorManager: EncryptionOperatorsInterface, signingKeyPair: PkcKeyPair | undefined, ): Promise { const operator = operatorManager.operatorForVersion(key.keyVersion) @@ -42,7 +42,7 @@ export async function encryptPayload( export async function decryptPayload( payload: EncryptedPayloadInterface, key: ItemsKeyInterface | KeySystemItemsKeyInterface | KeySystemRootKeyInterface | RootKeyInterface, - operatorManager: OperatorManager, + operatorManager: EncryptionOperatorsInterface, ): Promise | ErrorDecryptingParameters> { const operator = operatorManager.operatorForVersion(payload.version) diff --git a/packages/encryption/src/Domain/Service/Functions.ts b/packages/encryption/src/Domain/Service/Functions.ts deleted file mode 100644 index d8daf60c1..000000000 --- a/packages/encryption/src/Domain/Service/Functions.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ItemsKeyInterface } from '@standardnotes/models' - -export function findDefaultItemsKey(itemsKeys: ItemsKeyInterface[]): ItemsKeyInterface | undefined { - if (itemsKeys.length === 1) { - return itemsKeys[0] - } - - const defaultKeys = itemsKeys.filter((key) => { - return key.isDefault - }) - - if (defaultKeys.length === 0) { - return undefined - } - - if (defaultKeys.length === 1) { - return defaultKeys[0] - } - - /** - * Prioritize one that is synced, as neverSynced keys will likely be deleted after - * DownloadFirst sync. - */ - const syncedKeys = defaultKeys.filter((key) => !key.neverSynced) - if (syncedKeys.length > 0) { - return syncedKeys[0] - } - - return undefined -} diff --git a/packages/encryption/src/Domain/index.ts b/packages/encryption/src/Domain/index.ts index 569c72a2f..1240f81c0 100644 --- a/packages/encryption/src/Domain/index.ts +++ b/packages/encryption/src/Domain/index.ts @@ -1,42 +1,30 @@ export * from './Algorithm' export * from './Backups/BackupFileType' - export * from './Keys/ItemsKey/ItemsKey' export * from './Keys/ItemsKey/ItemsKeyMutator' export * from './Keys/ItemsKey/Registration' - export * from './Keys/KeySystemItemsKey/KeySystemItemsKey' export * from './Keys/KeySystemItemsKey/KeySystemItemsKeyMutator' export * from './Keys/KeySystemItemsKey/Registration' - export * from './Keys/RootKey/Functions' export * from './Keys/RootKey/KeyParamsFunctions' export * from './Keys/RootKey/ProtocolVersionForKeyParams' export * from './Keys/RootKey/RootKey' export * from './Keys/RootKey/RootKeyParams' export * from './Keys/RootKey/ValidKeyParamsKeys' - export * from './Keys/Utils/KeyRecoveryStrings' - export * from './Operator/001/Operator001' export * from './Operator/002/Operator002' export * from './Operator/003/Operator003' export * from './Operator/004/Operator004' export * from './Operator/004/V004AlgorithmHelpers' - +export * from './Operator/EncryptionOperators' +export * from './Operator/EncryptionOperatorsInterface' export * from './Operator/Functions' export * from './Operator/OperatorInterface/OperatorInterface' -export * from './Operator/OperatorManager' export * from './Operator/OperatorWrapper' -export * from './Operator/Types/PublicKeySet' export * from './Operator/Types/AsymmetricSignatureVerificationDetachedResult' export * from './Operator/Types/Types' - -export * from './Service/Encryption/EncryptionProviderInterface' -export * from './Service/KeySystemKeyManagerInterface' -export * from './Service/Functions' -export * from './Service/RootKey/KeyMode' - export * from './Split/AbstractKeySplit' export * from './Split/EncryptionSplit' export * from './Split/EncryptionTypeSplit' @@ -44,11 +32,9 @@ export * from './Split/Functions' export * from './Split/KeyedDecryptionSplit' export * from './Split/KeyedEncryptionSplit' export * from './StandardException' - -export * from './Types/EncryptedParameters' export * from './Types/DecryptedParameters' +export * from './Types/EncryptedParameters' export * from './Types/ItemAuthenticatedData' export * from './Types/LegacyAttachedData' export * from './Types/RootKeyEncryptedAuthenticatedData' - export * from './Username/PrivateUsername' diff --git a/packages/models/src/Domain/Abstract/Item/Mutator/DecryptedItemMutator.ts b/packages/models/src/Domain/Abstract/Item/Mutator/DecryptedItemMutator.ts index 492a78cf5..9cd2358fb 100644 --- a/packages/models/src/Domain/Abstract/Item/Mutator/DecryptedItemMutator.ts +++ b/packages/models/src/Domain/Abstract/Item/Mutator/DecryptedItemMutator.ts @@ -23,7 +23,7 @@ export class DecryptedItemMutator< this.mutableContent = mutableCopy } - public override getResult() { + public override getResult(): DecryptedPayloadInterface { if (this.type === MutationType.NonDirtying) { return this.immutablePayload.copy({ content: this.mutableContent, diff --git a/packages/models/src/Domain/Runtime/AsymmetricMessage/AsymmetricMessagePayloadType.ts b/packages/models/src/Domain/Runtime/AsymmetricMessage/AsymmetricMessagePayloadType.ts index 5b4f200c5..00f7851cf 100644 --- a/packages/models/src/Domain/Runtime/AsymmetricMessage/AsymmetricMessagePayloadType.ts +++ b/packages/models/src/Domain/Runtime/AsymmetricMessage/AsymmetricMessagePayloadType.ts @@ -3,7 +3,6 @@ export enum AsymmetricMessagePayloadType { SharedVaultRootKeyChanged = 'shared-vault-root-key-changed', SenderKeypairChanged = 'sender-keypair-changed', SharedVaultMetadataChanged = 'shared-vault-metadata-changed', - /** * Shared Vault Invites conform to the asymmetric message protocol, but are sent via the dedicated * SharedVaultInvite model and not the AsymmetricMessage model on the server side. diff --git a/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite.ts b/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite.ts index 0d798fddf..cd87231ab 100644 --- a/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite.ts +++ b/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageSharedVaultInvite.ts @@ -1,13 +1,19 @@ import { KeySystemRootKeyContentSpecialized } from '../../../Syncable/KeySystemRootKey/KeySystemRootKeyContent' -import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/TrustedContactContent' +import { ContactPublicKeySetJsonInterface } from '../../../Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetJsonInterface' import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon' import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType' +export type VaultInviteDelegatedContact = { + name?: string + contactUuid: string + publicKeySet: ContactPublicKeySetJsonInterface +} + export type AsymmetricMessageSharedVaultInvite = { type: AsymmetricMessagePayloadType.SharedVaultInvite data: AsymmetricMessageDataCommon & { rootKey: KeySystemRootKeyContentSpecialized - trustedContacts: TrustedContactContentSpecialized[] + trustedContacts: VaultInviteDelegatedContact[] metadata: { name: string description?: string diff --git a/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare.ts b/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare.ts index 9196df598..2b7ac8a51 100644 --- a/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare.ts +++ b/packages/models/src/Domain/Runtime/AsymmetricMessage/MessageTypes/AsymmetricMessageTrustedContactShare.ts @@ -1,4 +1,4 @@ -import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/TrustedContactContent' +import { TrustedContactContentSpecialized } from '../../../Syncable/TrustedContact/Content/TrustedContactContent' import { AsymmetricMessageDataCommon } from '../AsymmetricMessageDataCommon' import { AsymmetricMessagePayloadType } from '../AsymmetricMessagePayloadType' diff --git a/packages/models/src/Domain/Syncable/TrustedContact/Content/TrustedContactContent.ts b/packages/models/src/Domain/Syncable/TrustedContact/Content/TrustedContactContent.ts new file mode 100644 index 000000000..462561ab3 --- /dev/null +++ b/packages/models/src/Domain/Syncable/TrustedContact/Content/TrustedContactContent.ts @@ -0,0 +1,11 @@ +import { ItemContent } from '../../../Abstract/Content/ItemContent' +import { ContactPublicKeySetJsonInterface } from '../PublicKeySet/ContactPublicKeySetJsonInterface' + +export type TrustedContactContentSpecialized = { + name: string + contactUuid: string + publicKeySet: ContactPublicKeySetJsonInterface + isMe: boolean +} + +export type TrustedContactContent = TrustedContactContentSpecialized & ItemContent diff --git a/packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.spec.ts b/packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.spec.ts new file mode 100644 index 000000000..dcafe99ac --- /dev/null +++ b/packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.spec.ts @@ -0,0 +1,93 @@ +import { ContentType } from '@standardnotes/domain-core' +import { DecryptedPayload, PayloadTimestampDefaults } from '../../../Abstract/Payload' +import { TrustedContact } from '../TrustedContact' +import { TrustedContactInterface } from '../TrustedContactInterface' +import { TrustedContactMutator } from './TrustedContactMutator' +import { ContactPublicKeySet } from '../PublicKeySet/ContactPublicKeySet' +import { FillItemContentSpecialized } from '../../../Abstract/Content/ItemContent' +import { TrustedContactContentSpecialized } from '../Content/TrustedContactContent' +import { MutationType } from '../../../Abstract/Item' +import { PortablePublicKeySet } from '../Types/PortablePublicKeySet' +import { ContactPublicKeySetInterface } from '../PublicKeySet/ContactPublicKeySetInterface' + +function createMockPublicKeySetChain(): ContactPublicKeySetInterface { + const nMinusOne = new ContactPublicKeySet({ + encryption: 'encryption-public-key-n-1', + signing: 'signing-public-key-n-1', + timestamp: new Date(-1), + previousKeySet: undefined, + }) + + const root = new ContactPublicKeySet({ + encryption: 'encryption-public-key', + signing: 'signing-public-key', + timestamp: new Date(), + previousKeySet: nMinusOne, + }) + + return root +} + +describe('TrustedContactMutator', () => { + let contact: TrustedContactInterface + let mutator: TrustedContactMutator + + beforeEach(() => { + contact = new TrustedContact( + new DecryptedPayload({ + uuid: 'item-uuid', + content_type: ContentType.TYPES.TrustedContact, + ...PayloadTimestampDefaults(), + content: FillItemContentSpecialized({ + name: 'test', + contactUuid: 'contact-uuid', + isMe: true, + publicKeySet: createMockPublicKeySetChain(), + }), + }), + ) + + mutator = new TrustedContactMutator(contact, MutationType.UpdateUserTimestamps) + }) + + it('should set name', () => { + mutator.name = 'new name' + + const result = mutator.getResult() + + expect(result.content.name).toEqual('new name') + }) + + it('should add public key', () => { + const currentKeySet = contact.publicKeySet + + const newKeySet: PortablePublicKeySet = { + encryption: 'new-encryption-public-key', + signing: 'new-signing-public-key', + } + + mutator.addPublicKey(newKeySet) + + const result = new TrustedContact(mutator.getResult()) + + expect(result.publicKeySet.encryption).toEqual(newKeySet.encryption) + expect(result.publicKeySet.signing).toEqual(newKeySet.signing) + expect(result.publicKeySet.previousKeySet).toEqual(currentKeySet) + }) + + it('should replace public key set', () => { + const replacement = new ContactPublicKeySet({ + encryption: 'encryption-public-key-replacement', + signing: 'signing-public-key-replacement', + timestamp: new Date(), + previousKeySet: undefined, + }) + + mutator.replacePublicKeySet(replacement.asJson()) + + const result = new TrustedContact(mutator.getResult()) + + expect(result.publicKeySet.encryption).toEqual(replacement.encryption) + expect(result.publicKeySet.signing).toEqual(replacement.signing) + }) +}) diff --git a/packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.ts b/packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.ts new file mode 100644 index 000000000..d27802a18 --- /dev/null +++ b/packages/models/src/Domain/Syncable/TrustedContact/Mutator/TrustedContactMutator.ts @@ -0,0 +1,27 @@ +import { DecryptedItemMutator } from '../../../Abstract/Item' +import { TrustedContactContent } from '../Content/TrustedContactContent' +import { TrustedContactInterface } from '../TrustedContactInterface' +import { ContactPublicKeySet } from '../PublicKeySet/ContactPublicKeySet' +import { PortablePublicKeySet } from '../Types/PortablePublicKeySet' +import { ContactPublicKeySetJsonInterface } from '../PublicKeySet/ContactPublicKeySetJsonInterface' + +export class TrustedContactMutator extends DecryptedItemMutator { + set name(newName: string) { + this.mutableContent.name = newName + } + + addPublicKey(keySet: PortablePublicKeySet): void { + const newKey = new ContactPublicKeySet({ + encryption: keySet.encryption, + signing: keySet.signing, + timestamp: new Date(), + previousKeySet: this.immutableItem.publicKeySet, + }) + + this.mutableContent.publicKeySet = newKey + } + + replacePublicKeySet(publicKeySet: ContactPublicKeySetJsonInterface): void { + this.mutableContent.publicKeySet = publicKeySet + } +} diff --git a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet.ts b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet.ts index c7ea7e622..287a3b93b 100644 --- a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet.ts +++ b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet.ts @@ -5,36 +5,18 @@ export class ContactPublicKeySet implements ContactPublicKeySetInterface { encryption: string signing: string timestamp: Date - isRevoked: boolean - previousKeySet?: ContactPublicKeySet + previousKeySet?: ContactPublicKeySetInterface - constructor( - encryption: string, - signing: string, - timestamp: Date, - isRevoked: boolean, - previousKeySet: ContactPublicKeySet | undefined, - ) { - this.encryption = encryption - this.signing = signing - this.timestamp = timestamp - this.isRevoked = isRevoked - this.previousKeySet = previousKeySet - } - - public findKeySet(params: { - targetEncryptionPublicKey: string - targetSigningPublicKey: string - }): ContactPublicKeySetInterface | undefined { - if (this.encryption === params.targetEncryptionPublicKey && this.signing === params.targetSigningPublicKey) { - return this - } - - if (this.previousKeySet) { - return this.previousKeySet.findKeySet(params) - } - - return undefined + constructor(dto: { + encryption: string + signing: string + timestamp: Date + previousKeySet: ContactPublicKeySetInterface | undefined + }) { + this.encryption = dto.encryption + this.signing = dto.signing + this.timestamp = dto.timestamp + this.previousKeySet = dto.previousKeySet } public findKeySetWithSigningKey(signingKey: string): ContactPublicKeySetInterface | undefined { @@ -61,13 +43,30 @@ export class ContactPublicKeySet implements ContactPublicKeySetInterface { return undefined } + asJson(): ContactPublicKeySetJsonInterface { + return { + encryption: this.encryption, + signing: this.signing, + timestamp: this.timestamp, + previousKeySet: this.previousKeySet ? this.previousKeySet.asJson() : undefined, + } + } + + mutableCopy(): ContactPublicKeySetInterface { + return new ContactPublicKeySet({ + encryption: this.encryption, + signing: this.signing, + timestamp: this.timestamp, + previousKeySet: this.previousKeySet ? ContactPublicKeySet.FromJson(this.previousKeySet.asJson()) : undefined, + }) + } + static FromJson(json: ContactPublicKeySetJsonInterface): ContactPublicKeySetInterface { - return new ContactPublicKeySet( - json.encryption, - json.signing, - new Date(json.timestamp), - json.isRevoked, - json.previousKeySet ? ContactPublicKeySet.FromJson(json.previousKeySet) : undefined, - ) + return new ContactPublicKeySet({ + encryption: json.encryption, + signing: json.signing, + timestamp: new Date(json.timestamp), + previousKeySet: json.previousKeySet ? ContactPublicKeySet.FromJson(json.previousKeySet) : undefined, + }) } } diff --git a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface.ts b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface.ts index 031aeb779..1013cbe25 100644 --- a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface.ts +++ b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface.ts @@ -1,15 +1,14 @@ +import { ContactPublicKeySetJsonInterface } from './ContactPublicKeySetJsonInterface' + export interface ContactPublicKeySetInterface { encryption: string signing: string timestamp: Date - isRevoked: boolean previousKeySet?: ContactPublicKeySetInterface - findKeySet(params: { - targetEncryptionPublicKey: string - targetSigningPublicKey: string - }): ContactPublicKeySetInterface | undefined - findKeySetWithPublicKey(publicKey: string): ContactPublicKeySetInterface | undefined findKeySetWithSigningKey(signingKey: string): ContactPublicKeySetInterface | undefined + + asJson(): ContactPublicKeySetJsonInterface + mutableCopy(): ContactPublicKeySetInterface } diff --git a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetJsonInterface.ts b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetJsonInterface.ts index 667ba4526..6d3f9ca96 100644 --- a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetJsonInterface.ts +++ b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetJsonInterface.ts @@ -2,6 +2,5 @@ export interface ContactPublicKeySetJsonInterface { encryption: string signing: string timestamp: Date - isRevoked: boolean previousKeySet?: ContactPublicKeySetJsonInterface } diff --git a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/FindPublicKeySetResult.ts b/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/FindPublicKeySetResult.ts deleted file mode 100644 index 9860a7549..000000000 --- a/packages/models/src/Domain/Syncable/TrustedContact/PublicKeySet/FindPublicKeySetResult.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ContactPublicKeySetInterface } from './ContactPublicKeySetInterface' - -export type FindPublicKeySetResult = - | { - publicKeySet: ContactPublicKeySetInterface - current: boolean - } - | undefined diff --git a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.spec.ts b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.spec.ts new file mode 100644 index 000000000..f1fa95241 --- /dev/null +++ b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.spec.ts @@ -0,0 +1,109 @@ +import { ContentType } from '@standardnotes/domain-core' +import { DecryptedPayload, PayloadTimestampDefaults } from '../../Abstract/Payload' +import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet' +import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface' +import { TrustedContact } from './TrustedContact' +import { TrustedContactInterface } from './TrustedContactInterface' +import { FillItemContentSpecialized } from '../../Abstract/Content/ItemContent' +import { ConflictStrategy } from '../../Abstract/Item' +import { TrustedContactContentSpecialized } from './Content/TrustedContactContent' +import { PublicKeyTrustStatus } from './Types/PublicKeyTrustStatus' + +function createMockPublicKeySetChain(): ContactPublicKeySetInterface { + const nMinusOne = new ContactPublicKeySet({ + encryption: 'encryption-public-key-n-1', + signing: 'signing-public-key-n-1', + timestamp: new Date(-1), + previousKeySet: undefined, + }) + + const root = new ContactPublicKeySet({ + encryption: 'encryption-public-key', + signing: 'signing-public-key', + timestamp: new Date(), + previousKeySet: nMinusOne, + }) + + return root +} + +function CreateContact(params: { + name?: string + contactUuid?: string + isMe?: boolean + publicKeySet?: ContactPublicKeySetInterface +}): TrustedContactInterface { + return new TrustedContact( + new DecryptedPayload({ + uuid: 'item-uuid', + content_type: ContentType.TYPES.TrustedContact, + ...PayloadTimestampDefaults(), + content: FillItemContentSpecialized({ + name: params.name ?? 'test', + contactUuid: params.contactUuid ?? 'contact-uuid', + isMe: params.isMe ?? false, + publicKeySet: params.publicKeySet ?? createMockPublicKeySetChain(), + }), + }), + ) +} + +describe('isSingleton', () => { + it('should be true', () => { + const contact = CreateContact({}) + + expect(contact.isSingleton).toEqual(true) + }) +}) + +describe('strategyWhenConflictingWithItem', () => { + it('should be KeepBase', () => { + const contact = CreateContact({}) + + expect(contact.strategyWhenConflictingWithItem({} as unknown as TrustedContactInterface)).toEqual( + ConflictStrategy.KeepBase, + ) + }) +}) + +describe('TrustedContact', () => { + describe('getTrustStatusForPublicKey', () => { + it('should be trusted if key set is root', () => { + const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() }) + + expect(contact.getTrustStatusForPublicKey('encryption-public-key')).toEqual(PublicKeyTrustStatus.Trusted) + }) + + it('should be semi-trusted if key set is previous', () => { + const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() }) + + expect(contact.getTrustStatusForPublicKey('encryption-public-key-n-1')).toEqual(PublicKeyTrustStatus.Previous) + }) + + it('should return not trusted if public key is not found', () => { + const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() }) + + expect(contact.getTrustStatusForPublicKey('not-found-public-key')).toEqual(PublicKeyTrustStatus.NotTrusted) + }) + }) + + describe('getTrustStatusForSigningPublicKey', () => { + it('should be trusted if key set is root', () => { + const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() }) + + expect(contact.getTrustStatusForSigningPublicKey('signing-public-key')).toEqual(PublicKeyTrustStatus.Trusted) + }) + + it('should be semi-trusted if key set is previous', () => { + const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() }) + + expect(contact.getTrustStatusForSigningPublicKey('signing-public-key-n-1')).toEqual(PublicKeyTrustStatus.Previous) + }) + + it('should return not trusted if public key is not found', () => { + const contact = CreateContact({ publicKeySet: createMockPublicKeySetChain() }) + + expect(contact.getTrustStatusForSigningPublicKey('not-found-public-key')).toEqual(PublicKeyTrustStatus.NotTrusted) + }) + }) +}) diff --git a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.ts b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.ts index 0bafb968f..3e5fb0f84 100644 --- a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.ts +++ b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContact.ts @@ -1,12 +1,12 @@ import { ConflictStrategy, DecryptedItem, DecryptedItemInterface } from '../../Abstract/Item' import { DecryptedPayloadInterface } from '../../Abstract/Payload' import { HistoryEntryInterface } from '../../Runtime/History' -import { TrustedContactContent } from './TrustedContactContent' +import { TrustedContactContent } from './Content/TrustedContactContent' import { TrustedContactInterface } from './TrustedContactInterface' -import { FindPublicKeySetResult } from './PublicKeySet/FindPublicKeySetResult' import { ContactPublicKeySet } from './PublicKeySet/ContactPublicKeySet' import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface' import { Predicate } from '../../Runtime/Predicate/Predicate' +import { PublicKeyTrustStatus } from './Types/PublicKeyTrustStatus' export class TrustedContact extends DecryptedItem implements TrustedContactInterface { static singletonPredicate = new Predicate('isMe', '=', true) @@ -33,39 +33,36 @@ export class TrustedContact extends DecryptedItem impleme return TrustedContact.singletonPredicate } - public findKeySet(params: { - targetEncryptionPublicKey: string - targetSigningPublicKey: string - }): FindPublicKeySetResult { - const set = this.publicKeySet.findKeySet(params) - if (!set) { - return undefined - } - - return { - publicKeySet: set, - current: set === this.publicKeySet, - } + hasCurrentOrPreviousSigningPublicKey(signingPublicKey: string): boolean { + return this.publicKeySet.findKeySetWithSigningKey(signingPublicKey) !== undefined } - isPublicKeyTrusted(encryptionPublicKey: string): boolean { - const keySet = this.publicKeySet.findKeySetWithPublicKey(encryptionPublicKey) - - if (keySet && !keySet.isRevoked) { - return true + getTrustStatusForPublicKey(encryptionPublicKey: string): PublicKeyTrustStatus { + if (this.publicKeySet.encryption === encryptionPublicKey) { + return PublicKeyTrustStatus.Trusted } - return false + const previous = this.publicKeySet.findKeySetWithPublicKey(encryptionPublicKey) + + if (previous) { + return PublicKeyTrustStatus.Previous + } + + return PublicKeyTrustStatus.NotTrusted } - isSigningKeyTrusted(signingKey: string): boolean { - const keySet = this.publicKeySet.findKeySetWithSigningKey(signingKey) - - if (keySet && !keySet.isRevoked) { - return true + getTrustStatusForSigningPublicKey(signingPublicKey: string): PublicKeyTrustStatus { + if (this.publicKeySet.signing === signingPublicKey) { + return PublicKeyTrustStatus.Trusted } - return false + const previous = this.publicKeySet.findKeySetWithSigningKey(signingPublicKey) + + if (previous) { + return PublicKeyTrustStatus.Previous + } + + return PublicKeyTrustStatus.NotTrusted } override strategyWhenConflictingWithItem( diff --git a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactContent.ts b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactContent.ts deleted file mode 100644 index 2cc5406d5..000000000 --- a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactContent.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ItemContent } from '../../Abstract/Content/ItemContent' -import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface' - -export type TrustedContactContentSpecialized = { - name: string - contactUuid: string - publicKeySet: ContactPublicKeySetInterface - isMe: boolean -} - -export type TrustedContactContent = TrustedContactContentSpecialized & ItemContent diff --git a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactInterface.ts b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactInterface.ts index 5cc5841d5..a2a37d877 100644 --- a/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactInterface.ts +++ b/packages/models/src/Domain/Syncable/TrustedContact/TrustedContactInterface.ts @@ -1,7 +1,7 @@ import { DecryptedItemInterface } from '../../Abstract/Item/Interfaces/DecryptedItem' -import { FindPublicKeySetResult } from './PublicKeySet/FindPublicKeySetResult' -import { TrustedContactContent } from './TrustedContactContent' +import { TrustedContactContent } from './Content/TrustedContactContent' import { ContactPublicKeySetInterface } from './PublicKeySet/ContactPublicKeySetInterface' +import { PublicKeyTrustStatus } from './Types/PublicKeyTrustStatus' export interface TrustedContactInterface extends DecryptedItemInterface { name: string @@ -9,8 +9,7 @@ export interface TrustedContactInterface extends DecryptedItemInterface { - set name(newName: string) { - this.mutableContent.name = newName - } - - addPublicKey(params: { encryption: string; signing: string }): void { - const newKey = new ContactPublicKeySet( - params.encryption, - params.signing, - new Date(), - false, - this.immutableItem.publicKeySet, - ) - - this.mutableContent.publicKeySet = newKey - } - - replacePublicKeySet(publicKeySet: ContactPublicKeySet): void { - this.mutableContent.publicKeySet = publicKeySet - } -} diff --git a/packages/encryption/src/Domain/Operator/Types/PublicKeySet.ts b/packages/models/src/Domain/Syncable/TrustedContact/Types/PortablePublicKeySet.ts similarity index 52% rename from packages/encryption/src/Domain/Operator/Types/PublicKeySet.ts rename to packages/models/src/Domain/Syncable/TrustedContact/Types/PortablePublicKeySet.ts index 20c35d9c3..d633ac53b 100644 --- a/packages/encryption/src/Domain/Operator/Types/PublicKeySet.ts +++ b/packages/models/src/Domain/Syncable/TrustedContact/Types/PortablePublicKeySet.ts @@ -1,4 +1,4 @@ -export type PublicKeySet = { +export type PortablePublicKeySet = { encryption: string signing: string } diff --git a/packages/models/src/Domain/Syncable/TrustedContact/Types/PublicKeyTrustStatus.ts b/packages/models/src/Domain/Syncable/TrustedContact/Types/PublicKeyTrustStatus.ts new file mode 100644 index 000000000..0c7081802 --- /dev/null +++ b/packages/models/src/Domain/Syncable/TrustedContact/Types/PublicKeyTrustStatus.ts @@ -0,0 +1,5 @@ +export enum PublicKeyTrustStatus { + Trusted = 'Trusted', + Previous = 'Previous', + NotTrusted = 'NotTrusted', +} diff --git a/packages/models/src/Domain/Utilities/Item/ItemGenerator.ts b/packages/models/src/Domain/Utilities/Item/ItemGenerator.ts index 5d42898ac..4fe9d5966 100644 --- a/packages/models/src/Domain/Utilities/Item/ItemGenerator.ts +++ b/packages/models/src/Domain/Utilities/Item/ItemGenerator.ts @@ -27,7 +27,7 @@ import { EncryptedItemInterface } from '../../Abstract/Item/Interfaces/Encrypted import { DeletedItemInterface } from '../../Abstract/Item/Interfaces/DeletedItem' import { SmartViewMutator } from '../../Syncable/SmartView' import { TrustedContact } from '../../Syncable/TrustedContact/TrustedContact' -import { TrustedContactMutator } from '../../Syncable/TrustedContact/TrustedContactMutator' +import { TrustedContactMutator } from '../../Syncable/TrustedContact/Mutator/TrustedContactMutator' import { KeySystemRootKey } from '../../Syncable/KeySystemRootKey/KeySystemRootKey' import { KeySystemRootKeyMutator } from '../../Syncable/KeySystemRootKey/KeySystemRootKeyMutator' import { VaultListing } from '../../Syncable/VaultListing/VaultListing' diff --git a/packages/models/src/Domain/index.ts b/packages/models/src/Domain/index.ts index 8836d2685..e3655e265 100644 --- a/packages/models/src/Domain/index.ts +++ b/packages/models/src/Domain/index.ts @@ -97,11 +97,13 @@ export * from './Syncable/Theme' export * from './Syncable/UserPrefs' export * from './Syncable/TrustedContact/TrustedContact' -export * from './Syncable/TrustedContact/TrustedContactMutator' -export * from './Syncable/TrustedContact/TrustedContactContent' +export * from './Syncable/TrustedContact/Mutator/TrustedContactMutator' +export * from './Syncable/TrustedContact/Content/TrustedContactContent' export * from './Syncable/TrustedContact/TrustedContactInterface' export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySetInterface' export * from './Syncable/TrustedContact/PublicKeySet/ContactPublicKeySet' +export * from './Syncable/TrustedContact/Types/PortablePublicKeySet' +export * from './Syncable/TrustedContact/Types/PublicKeyTrustStatus' export * from './Syncable/KeySystemRootKey/KeySystemRootKey' export * from './Syncable/KeySystemRootKey/KeySystemRootKeyMutator' diff --git a/packages/responses/src/Domain/AsymmetricMessage/AsymmetricMessageServerHash.ts b/packages/responses/src/Domain/AsymmetricMessage/AsymmetricMessageServerHash.ts index a22444dcf..ecd386981 100644 --- a/packages/responses/src/Domain/AsymmetricMessage/AsymmetricMessageServerHash.ts +++ b/packages/responses/src/Domain/AsymmetricMessage/AsymmetricMessageServerHash.ts @@ -2,6 +2,7 @@ export interface AsymmetricMessageServerHash { uuid: string user_uuid: string sender_uuid: string + replaceabilityIdentifier?: string encrypted_message: string created_at_timestamp: number updated_at_timestamp: number diff --git a/packages/services/src/Domain/Api/ApiServiceInterface.ts b/packages/services/src/Domain/Api/LegacyApiServiceInterface.ts similarity index 83% rename from packages/services/src/Domain/Api/ApiServiceInterface.ts rename to packages/services/src/Domain/Api/LegacyApiServiceInterface.ts index 4211628a1..bc070c15d 100644 --- a/packages/services/src/Domain/Api/ApiServiceInterface.ts +++ b/packages/services/src/Domain/Api/LegacyApiServiceInterface.ts @@ -6,7 +6,9 @@ import { SNFeatureRepo } from '@standardnotes/models' import { ClientDisplayableError, HttpResponse } from '@standardnotes/responses' import { AnyFeatureDescription } from '@standardnotes/features' -export interface ApiServiceInterface extends AbstractService, FilesApiInterface { +export interface LegacyApiServiceInterface + extends AbstractService, + FilesApiInterface { isThirdPartyHostUsed(): boolean downloadOfflineFeaturesFromRepo( diff --git a/packages/services/src/Domain/Application/ApplicationInterface.ts b/packages/services/src/Domain/Application/ApplicationInterface.ts index fe3cc784d..99ed39023 100644 --- a/packages/services/src/Domain/Application/ApplicationInterface.ts +++ b/packages/services/src/Domain/Application/ApplicationInterface.ts @@ -1,3 +1,5 @@ +import { HistoryServiceInterface } from './../History/HistoryServiceInterface' +import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface' import { PreferenceServiceInterface } from './../Preferences/PreferenceServiceInterface' import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/AsymmetricMessageServiceInterface' import { SyncOptions } from './../Sync/SyncOptions' @@ -34,6 +36,7 @@ import { UserClientInterface } from '../User/UserClientInterface' import { SessionsClientInterface } from '../Session/SessionsClientInterface' import { HomeServerServiceInterface } from '../HomeServer/HomeServerServiceInterface' import { User } from '@standardnotes/responses' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' export interface ApplicationInterface { deinit(mode: DeinitMode, source: DeinitSource): void @@ -107,9 +110,11 @@ export interface ApplicationInterface { get alerts(): AlertService get asymmetric(): AsymmetricMessageServiceInterface get preferences(): PreferenceServiceInterface + get events(): InternalEventBusInterface + get history(): HistoryServiceInterface + get encryption(): EncryptionProviderInterface readonly identifier: ApplicationIdentifier readonly platform: Platform - deviceInterface: DeviceInterface - alertService: AlertService + device: DeviceInterface } diff --git a/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.spec.ts b/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.spec.ts index f85892890..556a7e2d0 100644 --- a/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.spec.ts +++ b/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.spec.ts @@ -1,31 +1,121 @@ +import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface' +import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload' +import { GetInboundMessages } from './UseCase/GetInboundMessages' +import { GetOutboundMessages } from './UseCase/GetOutboundMessages' +import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage' +import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage' +import { GetVault } from './../Vaults/UseCase/GetVault' +import { GetTrustedPayload } from './UseCase/GetTrustedPayload' +import { ReplaceContactData } from './../Contacts/UseCase/ReplaceContactData' +import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts' +import { FindContact } from './../Contacts/UseCase/FindContact' +import { CreateOrEditContact } from './../Contacts/UseCase/CreateOrEditContact' import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' -import { HttpServiceInterface } from '@standardnotes/api' +import { AsymmetricMessageServer } from '@standardnotes/api' import { AsymmetricMessageService } from './AsymmetricMessageService' -import { ContactServiceInterface } from './../Contacts/ContactServiceInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { AsymmetricMessageServerHash } from '@standardnotes/responses' -import { AsymmetricMessagePayloadType } from '@standardnotes/models' +import { + AsymmetricMessagePayloadType, + AsymmetricMessageSenderKeypairChanged, + AsymmetricMessageSharedVaultInvite, + AsymmetricMessageSharedVaultMetadataChanged, + AsymmetricMessageSharedVaultRootKeyChanged, + AsymmetricMessageTrustedContactShare, + KeySystemRootKeyContentSpecialized, + TrustedContactInterface, +} from '@standardnotes/models' describe('AsymmetricMessageService', () => { + let sync: jest.Mocked + let mutator: jest.Mocked + let encryption: jest.Mocked let service: AsymmetricMessageService beforeEach(() => { - const http = {} as jest.Mocked - http.delete = jest.fn() + const messageServer = {} as jest.Mocked + messageServer.deleteMessage = jest.fn() - const encryption = {} as jest.Mocked - const contacts = {} as jest.Mocked - const items = {} as jest.Mocked - const sync = {} as jest.Mocked - const mutator = {} as jest.Mocked + encryption = {} as jest.Mocked + const createOrEditContact = {} as jest.Mocked + const findContact = {} as jest.Mocked + const getAllContacts = {} as jest.Mocked + const replaceContactData = {} as jest.Mocked + const getTrustedPayload = {} as jest.Mocked + const getVault = {} as jest.Mocked + const handleRootKeyChangedMessage = {} as jest.Mocked + const sendOwnContactChangedMessage = {} as jest.Mocked + const getOutboundMessagesUseCase = {} as jest.Mocked + const getInboundMessagesUseCase = {} as jest.Mocked + const getUntrustedPayload = {} as jest.Mocked + + sync = {} as jest.Mocked + sync.sync = jest.fn() + + mutator = {} as jest.Mocked + mutator.changeItem = jest.fn() const eventBus = {} as jest.Mocked eventBus.addEventHandler = jest.fn() - service = new AsymmetricMessageService(http, encryption, contacts, items, mutator, sync, eventBus) + service = new AsymmetricMessageService( + messageServer, + encryption, + mutator, + createOrEditContact, + findContact, + getAllContacts, + replaceContactData, + getTrustedPayload, + getVault, + handleRootKeyChangedMessage, + sendOwnContactChangedMessage, + getOutboundMessagesUseCase, + getInboundMessagesUseCase, + getUntrustedPayload, + eventBus, + ) + }) + + describe('sortServerMessages', () => { + it('should prioritize keypair changed messages over other messages', () => { + const messages: AsymmetricMessageServerHash[] = [ + { + uuid: 'keypair-changed-message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + }, + { + uuid: 'misc-message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 1, + updated_at_timestamp: 1, + }, + ] + + service.getUntrustedMessagePayload = jest.fn() + service.getServerMessageType = jest.fn().mockImplementation((message) => { + if (message.uuid === 'keypair-changed-message') { + return AsymmetricMessagePayloadType.SenderKeypairChanged + } else { + return AsymmetricMessagePayloadType.ContactShare + } + }) + + const sorted = service.sortServerMessages(messages) + expect(sorted[0].uuid).toEqual('keypair-changed-message') + expect(sorted[1].uuid).toEqual('misc-message') + + const reverseSorted = service.sortServerMessages(messages.reverse()) + expect(reverseSorted[0].uuid).toEqual('keypair-changed-message') + expect(reverseSorted[1].uuid).toEqual('misc-message') + }) }) it('should process incoming messages oldest first', async () => { @@ -50,7 +140,9 @@ describe('AsymmetricMessageService', () => { const trustedPayloadMock = { type: AsymmetricMessagePayloadType.ContactShare, data: { recipientUuid: '1' } } - service.getTrustedMessagePayload = jest.fn().mockReturnValue(trustedPayloadMock) + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(trustedPayloadMock) const handleTrustedContactShareMessageMock = jest.fn() service.handleTrustedContactShareMessage = handleTrustedContactShareMessageMock @@ -60,4 +152,174 @@ describe('AsymmetricMessageService', () => { expect(handleTrustedContactShareMessageMock.mock.calls[0][0]).toEqual(messages[1]) expect(handleTrustedContactShareMessageMock.mock.calls[1][0]).toEqual(messages[0]) }) + + it('should handle ContactShare message', async () => { + const message: AsymmetricMessageServerHash = { + uuid: 'message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + } + + const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = { + type: AsymmetricMessagePayloadType.ContactShare, + data: { + recipientUuid: '1', + trustedContact: {} as TrustedContactInterface, + }, + } + + service.handleTrustedContactShareMessage = jest.fn() + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(decryptedMessagePayload) + + await service.handleRemoteReceivedAsymmetricMessages([message]) + + expect(service.handleTrustedContactShareMessage).toHaveBeenCalledWith(message, decryptedMessagePayload) + }) + + it('should handle SenderKeypairChanged message', async () => { + const message: AsymmetricMessageServerHash = { + uuid: 'message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + } + + const decryptedMessagePayload: AsymmetricMessageSenderKeypairChanged = { + type: AsymmetricMessagePayloadType.SenderKeypairChanged, + data: { + recipientUuid: '1', + newEncryptionPublicKey: 'new-encryption-public-key', + newSigningPublicKey: 'new-signing-public-key', + }, + } + + service.handleTrustedSenderKeypairChangedMessage = jest.fn() + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(decryptedMessagePayload) + + await service.handleRemoteReceivedAsymmetricMessages([message]) + + expect(service.handleTrustedSenderKeypairChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload) + }) + + it('should handle SharedVaultRootKeyChanged message', async () => { + const message: AsymmetricMessageServerHash = { + uuid: 'message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + } + + const decryptedMessagePayload: AsymmetricMessageSharedVaultRootKeyChanged = { + type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged, + data: { + recipientUuid: '1', + rootKey: {} as KeySystemRootKeyContentSpecialized, + }, + } + + service.handleTrustedSharedVaultRootKeyChangedMessage = jest.fn() + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(decryptedMessagePayload) + + await service.handleRemoteReceivedAsymmetricMessages([message]) + + expect(service.handleTrustedSharedVaultRootKeyChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload) + }) + + it('should handle SharedVaultMetadataChanged message', async () => { + const message: AsymmetricMessageServerHash = { + uuid: 'message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + } + + const decryptedMessagePayload: AsymmetricMessageSharedVaultMetadataChanged = { + type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged, + data: { + recipientUuid: '1', + sharedVaultUuid: 'shared-vault-uuid', + name: 'Vault name', + description: 'Vault description', + }, + } + + service.handleTrustedVaultMetadataChangedMessage = jest.fn() + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(decryptedMessagePayload) + + await service.handleRemoteReceivedAsymmetricMessages([message]) + + expect(service.handleTrustedVaultMetadataChangedMessage).toHaveBeenCalledWith(message, decryptedMessagePayload) + }) + + it('should throw if message type is SharedVaultInvite', async () => { + const message: AsymmetricMessageServerHash = { + uuid: 'message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + } + + const decryptedMessagePayload: AsymmetricMessageSharedVaultInvite = { + type: AsymmetricMessagePayloadType.SharedVaultInvite, + data: { + recipientUuid: '1', + }, + } as AsymmetricMessageSharedVaultInvite + + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(decryptedMessagePayload) + + await expect(service.handleRemoteReceivedAsymmetricMessages([message])).rejects.toThrow( + 'Shared vault invites payloads are not handled as part of asymmetric messages', + ) + }) + + it('should delete message from server after processing', async () => { + const message: AsymmetricMessageServerHash = { + uuid: 'message', + user_uuid: '1', + sender_uuid: '2', + encrypted_message: 'encrypted_message', + created_at_timestamp: 2, + updated_at_timestamp: 2, + } + + const decryptedMessagePayload: AsymmetricMessageTrustedContactShare = { + type: AsymmetricMessagePayloadType.ContactShare, + data: { + recipientUuid: '1', + trustedContact: {} as TrustedContactInterface, + }, + } + + service.deleteMessageAfterProcessing = jest.fn() + service.handleTrustedContactShareMessage = jest.fn() + service.getTrustedMessagePayload = service.getUntrustedMessagePayload = jest + .fn() + .mockReturnValue(decryptedMessagePayload) + + await service.handleRemoteReceivedAsymmetricMessages([message]) + + expect(service.deleteMessageAfterProcessing).toHaveBeenCalled() + }) }) diff --git a/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.ts b/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.ts index f7609b374..68c6218a2 100644 --- a/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.ts +++ b/packages/services/src/Domain/AsymmetricMessage/AsymmetricMessageService.ts @@ -1,13 +1,11 @@ import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' -import { ContactServiceInterface } from './../Contacts/ContactServiceInterface' import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses' import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface' import { InternalEventInterface } from '../Internal/InternalEventInterface' import { AbstractService } from '../Service/AbstractService' -import { GetAsymmetricMessageTrustedPayload } from './UseCase/GetAsymmetricMessageTrustedPayload' -import { EncryptionProviderInterface } from '@standardnotes/encryption' +import { GetTrustedPayload } from './UseCase/GetTrustedPayload' import { AsymmetricMessageSharedVaultRootKeyChanged, AsymmetricMessagePayloadType, @@ -16,61 +14,68 @@ import { AsymmetricMessagePayload, AsymmetricMessageSharedVaultMetadataChanged, VaultListingMutator, + MutationType, + PayloadEmitSource, + VaultListingInterface, } from '@standardnotes/models' -import { HandleTrustedSharedVaultRootKeyChangedMessage } from './UseCase/HandleTrustedSharedVaultRootKeyChangedMessage' -import { ItemManagerInterface } from '../Item/ItemManagerInterface' -import { SyncServiceInterface } from '../Sync/SyncServiceInterface' +import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage' import { SessionEvent } from '../Session/SessionEvent' -import { AsymmetricMessageServer, HttpServiceInterface } from '@standardnotes/api' +import { AsymmetricMessageServer } from '@standardnotes/api' import { UserKeyPairChangedEventData } from '../Session/UserKeyPairChangedEventData' import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage' -import { GetOutboundAsymmetricMessages } from './UseCase/GetOutboundAsymmetricMessages' -import { GetInboundAsymmetricMessages } from './UseCase/GetInboundAsymmetricMessages' -import { GetVaultUseCase } from '../Vaults/UseCase/GetVault' +import { GetOutboundMessages } from './UseCase/GetOutboundMessages' +import { GetInboundMessages } from './UseCase/GetInboundMessages' +import { GetVault } from '../Vaults/UseCase/GetVault' import { AsymmetricMessageServiceInterface } from './AsymmetricMessageServiceInterface' +import { GetUntrustedPayload } from './UseCase/GetUntrustedPayload' +import { FindContact } from '../Contacts/UseCase/FindContact' +import { CreateOrEditContact } from '../Contacts/UseCase/CreateOrEditContact' +import { ReplaceContactData } from '../Contacts/UseCase/ReplaceContactData' +import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' export class AsymmetricMessageService extends AbstractService implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface { - private messageServer: AsymmetricMessageServer - constructor( - http: HttpServiceInterface, + private messageServer: AsymmetricMessageServer, private encryption: EncryptionProviderInterface, - private contacts: ContactServiceInterface, - private items: ItemManagerInterface, private mutator: MutatorClientInterface, - private sync: SyncServiceInterface, + private _createOrEditContact: CreateOrEditContact, + private _findContact: FindContact, + private _getAllContacts: GetAllContacts, + private _replaceContactData: ReplaceContactData, + private _getTrustedPayload: GetTrustedPayload, + private _getVault: GetVault, + private _handleRootKeyChangedMessage: HandleRootKeyChangedMessage, + private _sendOwnContactChangedMessage: SendOwnContactChangeMessage, + private _getOutboundMessagesUseCase: GetOutboundMessages, + private _getInboundMessagesUseCase: GetInboundMessages, + private _getUntrustedPayload: GetUntrustedPayload, eventBus: InternalEventBusInterface, ) { super(eventBus) - - this.messageServer = new AsymmetricMessageServer(http) - - eventBus.addEventHandler(this, SyncEvent.ReceivedAsymmetricMessages) - eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged) } async handleEvent(event: InternalEventInterface): Promise { - if (event.type === SessionEvent.UserKeyPairChanged) { - void this.messageServer.deleteAllInboundMessages() - void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData) - } - - if (event.type === SyncEvent.ReceivedAsymmetricMessages) { - void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData) + switch (event.type) { + case SessionEvent.UserKeyPairChanged: + void this.messageServer.deleteAllInboundMessages() + void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData) + break + case SyncEvent.ReceivedAsymmetricMessages: + void this.handleRemoteReceivedAsymmetricMessages(event.payload as SyncEventReceivedAsymmetricMessagesData) + break } } public async getOutboundMessages(): Promise { - const usecase = new GetOutboundAsymmetricMessages(this.messageServer) - return usecase.execute() + return this._getOutboundMessagesUseCase.execute() } public async getInboundMessages(): Promise { - const usecase = new GetInboundAsymmetricMessages(this.messageServer) - return usecase.execute() + return this._getInboundMessagesUseCase.execute() } public async downloadAndProcessInboundMessages(): Promise { @@ -83,118 +88,223 @@ export class AsymmetricMessageService } private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise { - if (!data.oldKeyPair || !data.oldSigningKeyPair) { + if (!data.previous) { return } - const useCase = new SendOwnContactChangeMessage(this.encryption, this.messageServer) + const contacts = this._getAllContacts.execute() + if (contacts.isFailed()) { + return + } - const contacts = this.contacts.getAllContacts() - - for (const contact of contacts) { + for (const contact of contacts.getValue()) { if (contact.isMe) { continue } - await useCase.execute({ - senderOldKeyPair: data.oldKeyPair, - senderOldSigningKeyPair: data.oldSigningKeyPair, - senderNewKeyPair: data.newKeyPair, - senderNewSigningKeyPair: data.newSigningKeyPair, + await this._sendOwnContactChangedMessage.execute({ + senderOldKeyPair: data.previous.encryption, + senderOldSigningKeyPair: data.previous.signing, + senderNewKeyPair: data.current.encryption, + senderNewSigningKeyPair: data.current.signing, contact, }) } } + sortServerMessages(messages: AsymmetricMessageServerHash[]): AsymmetricMessageServerHash[] { + const SortedPriorityTypes = [AsymmetricMessagePayloadType.SenderKeypairChanged] + + const priority: AsymmetricMessageServerHash[] = [] + const regular: AsymmetricMessageServerHash[] = [] + + const allMessagesOldestFirst = messages.slice().sort((a, b) => a.created_at_timestamp - b.created_at_timestamp) + + const messageTypeMap: Record = {} + + for (const message of allMessagesOldestFirst) { + const messageType = this.getServerMessageType(message) + if (!messageType) { + continue + } + + messageTypeMap[message.uuid] = messageType + + if (SortedPriorityTypes.includes(messageType)) { + priority.push(message) + } else { + regular.push(message) + } + } + + const sortedPriority = priority.sort((a, b) => { + const typeA = messageTypeMap[a.uuid] + const typeB = messageTypeMap[b.uuid] + + if (typeA !== typeB) { + return SortedPriorityTypes.indexOf(typeA) - SortedPriorityTypes.indexOf(typeB) + } + + return a.created_at_timestamp - b.created_at_timestamp + }) + + const regularMessagesOldestFirst = regular.sort((a, b) => a.created_at_timestamp - b.created_at_timestamp) + + return [...sortedPriority, ...regularMessagesOldestFirst] + } + + getServerMessageType(message: AsymmetricMessageServerHash): AsymmetricMessagePayloadType | undefined { + const result = this.getUntrustedMessagePayload(message) + + if (!result) { + return undefined + } + + return result.type + } + async handleRemoteReceivedAsymmetricMessages(messages: AsymmetricMessageServerHash[]): Promise { if (messages.length === 0) { return } - const sortedMessages = messages.slice().sort((a, b) => a.created_at_timestamp - b.created_at_timestamp) + const sortedMessages = this.sortServerMessages(messages) for (const message of sortedMessages) { - const trustedMessagePayload = this.getTrustedMessagePayload(message) - if (!trustedMessagePayload) { + const trustedPayload = this.getTrustedMessagePayload(message) + if (!trustedPayload) { continue } - if (trustedMessagePayload.data.recipientUuid !== message.user_uuid) { - continue - } - - if (trustedMessagePayload.type === AsymmetricMessagePayloadType.ContactShare) { - await this.handleTrustedContactShareMessage(message, trustedMessagePayload) - } else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) { - await this.handleTrustedSenderKeypairChangedMessage(message, trustedMessagePayload) - } else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultRootKeyChanged) { - await this.handleTrustedSharedVaultRootKeyChangedMessage(message, trustedMessagePayload) - } else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultMetadataChanged) { - await this.handleVaultMetadataChangedMessage(message, trustedMessagePayload) - } else if (trustedMessagePayload.type === AsymmetricMessagePayloadType.SharedVaultInvite) { - throw new Error('Shared vault invites payloads are not handled as part of asymmetric messages') - } - - await this.deleteMessageAfterProcessing(message) + await this.handleTrustedMessageResult(message, trustedPayload) } } - getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined { - const useCase = new GetAsymmetricMessageTrustedPayload(this.encryption, this.contacts) - - return useCase.execute({ - privateKey: this.encryption.getKeyPair().privateKey, - message, - }) - } - - private async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise { - await this.messageServer.deleteMessage({ messageUuid: message.uuid }) - } - - async handleVaultMetadataChangedMessage( - _message: AsymmetricMessageServerHash, - trustedPayload: AsymmetricMessageSharedVaultMetadataChanged, + private async handleTrustedMessageResult( + message: AsymmetricMessageServerHash, + payload: AsymmetricMessagePayload, ): Promise { - const vault = new GetVaultUseCase(this.items).execute({ sharedVaultUuid: trustedPayload.data.sharedVaultUuid }) - if (!vault) { + if (payload.data.recipientUuid !== message.user_uuid) { return } - await this.mutator.changeItem(vault, (mutator) => { - mutator.name = trustedPayload.data.name - mutator.description = trustedPayload.data.description + if (payload.type === AsymmetricMessagePayloadType.ContactShare) { + await this.handleTrustedContactShareMessage(message, payload) + } else if (payload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) { + await this.handleTrustedSenderKeypairChangedMessage(message, payload) + } else if (payload.type === AsymmetricMessagePayloadType.SharedVaultRootKeyChanged) { + await this.handleTrustedSharedVaultRootKeyChangedMessage(message, payload) + } else if (payload.type === AsymmetricMessagePayloadType.SharedVaultMetadataChanged) { + await this.handleTrustedVaultMetadataChangedMessage(message, payload) + } else if (payload.type === AsymmetricMessagePayloadType.SharedVaultInvite) { + throw new Error('Shared vault invites payloads are not handled as part of asymmetric messages') + } + + await this.deleteMessageAfterProcessing(message) + } + + getUntrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined { + const result = this._getUntrustedPayload.execute({ + privateKey: this.encryption.getKeyPair().privateKey, + message, }) + + if (result.isFailed()) { + return undefined + } + + return result.getValue() + } + + getTrustedMessagePayload(message: AsymmetricMessageServerHash): AsymmetricMessagePayload | undefined { + const contact = this._findContact.execute({ userUuid: message.sender_uuid }) + if (contact.isFailed()) { + return undefined + } + + const result = this._getTrustedPayload.execute({ + privateKey: this.encryption.getKeyPair().privateKey, + sender: contact.getValue(), + message, + }) + + if (result.isFailed()) { + return undefined + } + + return result.getValue() + } + + async deleteMessageAfterProcessing(message: AsymmetricMessageServerHash): Promise { + await this.messageServer.deleteMessage({ messageUuid: message.uuid }) + } + + async handleTrustedVaultMetadataChangedMessage( + _message: AsymmetricMessageServerHash, + trustedPayload: AsymmetricMessageSharedVaultMetadataChanged, + ): Promise { + const vault = this._getVault.execute({ + sharedVaultUuid: trustedPayload.data.sharedVaultUuid, + }) + if (vault.isFailed()) { + return + } + + await this.mutator.changeItem( + vault.getValue(), + (mutator) => { + mutator.name = trustedPayload.data.name + mutator.description = trustedPayload.data.description + }, + MutationType.UpdateUserTimestamps, + PayloadEmitSource.RemoteRetrieved, + ) } async handleTrustedContactShareMessage( _message: AsymmetricMessageServerHash, trustedPayload: AsymmetricMessageTrustedContactShare, ): Promise { - await this.contacts.createOrUpdateTrustedContactFromContactShare(trustedPayload.data.trustedContact) + if (trustedPayload.data.trustedContact.isMe) { + return + } + + await this._replaceContactData.execute(trustedPayload.data.trustedContact) } - private async handleTrustedSenderKeypairChangedMessage( + async handleTrustedSenderKeypairChangedMessage( message: AsymmetricMessageServerHash, trustedPayload: AsymmetricMessageSenderKeypairChanged, ): Promise { - await this.contacts.createOrEditTrustedContact({ + await this._createOrEditContact.execute({ contactUuid: message.sender_uuid, publicKey: trustedPayload.data.newEncryptionPublicKey, signingPublicKey: trustedPayload.data.newSigningPublicKey, }) } - private async handleTrustedSharedVaultRootKeyChangedMessage( + async handleTrustedSharedVaultRootKeyChangedMessage( _message: AsymmetricMessageServerHash, trustedPayload: AsymmetricMessageSharedVaultRootKeyChanged, ): Promise { - const useCase = new HandleTrustedSharedVaultRootKeyChangedMessage( - this.mutator, - this.items, - this.sync, - this.encryption, - ) - await useCase.execute(trustedPayload) + await this._handleRootKeyChangedMessage.execute(trustedPayload) + } + + public override deinit(): void { + super.deinit() + ;(this.messageServer as unknown) = undefined + ;(this.encryption as unknown) = undefined + ;(this.mutator as unknown) = undefined + ;(this._createOrEditContact as unknown) = undefined + ;(this._findContact as unknown) = undefined + ;(this._getAllContacts as unknown) = undefined + ;(this._replaceContactData as unknown) = undefined + ;(this._getTrustedPayload as unknown) = undefined + ;(this._getVault as unknown) = undefined + ;(this._handleRootKeyChangedMessage as unknown) = undefined + ;(this._sendOwnContactChangedMessage as unknown) = undefined + ;(this._getOutboundMessagesUseCase as unknown) = undefined + ;(this._getInboundMessagesUseCase as unknown) = undefined + ;(this._getUntrustedPayload as unknown) = undefined } } diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageTrustedPayload.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageTrustedPayload.ts deleted file mode 100644 index 99258e02e..000000000 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageTrustedPayload.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { AsymmetricMessageServerHash } from '@standardnotes/responses' -import { AsymmetricMessagePayload } from '@standardnotes/models' - -export class GetAsymmetricMessageTrustedPayload { - constructor(private encryption: EncryptionProviderInterface, private contacts: ContactServiceInterface) {} - - execute(dto: { privateKey: string; message: AsymmetricMessageServerHash }): M | undefined { - const trustedContact = this.contacts.findTrustedContact(dto.message.sender_uuid) - if (!trustedContact) { - return undefined - } - - const decryptionResult = this.encryption.asymmetricallyDecryptMessage({ - encryptedString: dto.message.encrypted_message, - trustedSender: trustedContact, - privateKey: dto.privateKey, - }) - - return decryptionResult - } -} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageUntrustedPayload.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageUntrustedPayload.ts deleted file mode 100644 index a69d228cb..000000000 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetAsymmetricMessageUntrustedPayload.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { AsymmetricMessageServerHash } from '@standardnotes/responses' -import { AsymmetricMessagePayload } from '@standardnotes/models' - -export class GetAsymmetricMessageUntrustedPayload { - constructor(private encryption: EncryptionProviderInterface) {} - - execute(dto: { privateKey: string; message: AsymmetricMessageServerHash }): M | undefined { - const decryptionResult = this.encryption.asymmetricallyDecryptMessage({ - encryptedString: dto.message.encrypted_message, - trustedSender: undefined, - privateKey: dto.privateKey, - }) - - return decryptionResult - } -} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetInboundAsymmetricMessages.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetInboundMessages.ts similarity index 92% rename from packages/services/src/Domain/AsymmetricMessage/UseCase/GetInboundAsymmetricMessages.ts rename to packages/services/src/Domain/AsymmetricMessage/UseCase/GetInboundMessages.ts index 011d186b2..9b4aefcd8 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetInboundAsymmetricMessages.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetInboundMessages.ts @@ -1,7 +1,7 @@ import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses' import { AsymmetricMessageServerInterface } from '@standardnotes/api' -export class GetInboundAsymmetricMessages { +export class GetInboundMessages { constructor(private messageServer: AsymmetricMessageServerInterface) {} async execute(): Promise { diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetOutboundAsymmetricMessages.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetOutboundMessages.ts similarity index 92% rename from packages/services/src/Domain/AsymmetricMessage/UseCase/GetOutboundAsymmetricMessages.ts rename to packages/services/src/Domain/AsymmetricMessage/UseCase/GetOutboundMessages.ts index a1fe86f05..99c58874a 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetOutboundAsymmetricMessages.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetOutboundMessages.ts @@ -1,7 +1,7 @@ import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses' import { AsymmetricMessageServerInterface } from '@standardnotes/api' -export class GetOutboundAsymmetricMessages { +export class GetOutboundMessages { constructor(private messageServer: AsymmetricMessageServerInterface) {} async execute(): Promise { diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetReplaceabilityIdentifier.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetReplaceabilityIdentifier.ts new file mode 100644 index 000000000..fbde06eab --- /dev/null +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetReplaceabilityIdentifier.ts @@ -0,0 +1,17 @@ +import { AsymmetricMessagePayloadType } from '@standardnotes/models' + +const TypesUsingReplaceableIdentifiers = [ + AsymmetricMessagePayloadType.SharedVaultRootKeyChanged, + AsymmetricMessagePayloadType.SharedVaultMetadataChanged, +] + +export function GetReplaceabilityIdentifier( + type: AsymmetricMessagePayloadType, + sharedVaultUuid: string, + keySystemIdentifier: string, +): string | undefined { + if (!TypesUsingReplaceableIdentifiers.includes(type)) { + return undefined + } + return [type, sharedVaultUuid, keySystemIdentifier].join(':') +} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetTrustedPayload.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetTrustedPayload.ts new file mode 100644 index 000000000..894760066 --- /dev/null +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetTrustedPayload.ts @@ -0,0 +1,22 @@ +import { AsymmetricMessageServerHash } from '@standardnotes/responses' +import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models' +import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage' +import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core' + +export class GetTrustedPayload implements SyncUseCaseInterface { + constructor(private decryptMessage: DecryptMessage) {} + + execute(dto: { + privateKey: string + message: AsymmetricMessageServerHash + sender: TrustedContactInterface + }): Result { + const result = this.decryptMessage.execute({ + message: dto.message.encrypted_message, + sender: dto.sender, + privateKey: dto.privateKey, + }) + + return result + } +} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/GetUntrustedPayload.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetUntrustedPayload.ts new file mode 100644 index 000000000..8b9fe9b42 --- /dev/null +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/GetUntrustedPayload.ts @@ -0,0 +1,21 @@ +import { AsymmetricMessageServerHash } from '@standardnotes/responses' +import { AsymmetricMessagePayload } from '@standardnotes/models' +import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage' +import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core' + +export class GetUntrustedPayload implements SyncUseCaseInterface { + constructor(private decryptMessage: DecryptMessage) {} + + execute(dto: { + privateKey: string + message: AsymmetricMessageServerHash + }): Result { + const result = this.decryptMessage.execute({ + message: dto.message.encrypted_message, + sender: undefined, + privateKey: dto.privateKey, + }) + + return result + } +} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultRootKeyChangedMessage.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleRootKeyChangedMessage.ts similarity index 63% rename from packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultRootKeyChangedMessage.ts rename to packages/services/src/Domain/AsymmetricMessage/UseCase/HandleRootKeyChangedMessage.ts index b2cc1ed22..042b4039b 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultRootKeyChangedMessage.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleRootKeyChangedMessage.ts @@ -1,5 +1,4 @@ -import { ItemManagerInterface } from './../../Item/ItemManagerInterface' -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' +import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' import { KeySystemRootKeyInterface, @@ -7,18 +6,19 @@ import { FillItemContent, KeySystemRootKeyContent, VaultListingMutator, + VaultListingInterface, } from '@standardnotes/models' import { ContentType } from '@standardnotes/domain-core' -import { GetVaultUseCase } from '../../Vaults/UseCase/GetVault' -import { EncryptionProviderInterface } from '@standardnotes/encryption' +import { GetVault } from '../../Vaults/UseCase/GetVault' +import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface' -export class HandleTrustedSharedVaultRootKeyChangedMessage { +export class HandleRootKeyChangedMessage { constructor( private mutator: MutatorClientInterface, - private items: ItemManagerInterface, private sync: SyncServiceInterface, private encryption: EncryptionProviderInterface, + private getVault: GetVault, ) {} async execute(message: AsymmetricMessageSharedVaultRootKeyChanged): Promise { @@ -30,9 +30,9 @@ export class HandleTrustedSharedVaultRootKeyChangedMessage { true, ) - const vault = new GetVaultUseCase(this.items).execute({ keySystemIdentifier: rootKeyContent.systemIdentifier }) - if (vault) { - await this.mutator.changeItem(vault, (mutator) => { + const vault = this.getVault.execute({ keySystemIdentifier: rootKeyContent.systemIdentifier }) + if (!vault.isFailed()) { + await this.mutator.changeItem(vault.getValue(), (mutator) => { mutator.rootKeyParams = rootKeyContent.keyParams }) } diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage.spec.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite.spec.ts similarity index 56% rename from packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage.spec.ts rename to packages/services/src/Domain/AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite.spec.ts index 1b1375bec..b96fc30ef 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage.spec.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite.spec.ts @@ -1,7 +1,7 @@ -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' -import { HandleTrustedSharedVaultInviteMessage } from './HandleTrustedSharedVaultInviteMessage' +import { CreateOrEditContact } from './../../Contacts/UseCase/CreateOrEditContact' +import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' +import { ProcessAcceptedVaultInvite } from './ProcessAcceptedVaultInvite' import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' import { ContentType } from '@standardnotes/domain-core' import { AsymmetricMessagePayloadType, @@ -9,32 +9,25 @@ import { KeySystemRootKeyContent, } from '@standardnotes/models' -describe('HandleTrustedSharedVaultInviteMessage', () => { - let mutatorMock: jest.Mocked - let syncServiceMock: jest.Mocked - let contactServiceMock: jest.Mocked +describe('ProcessAcceptedVaultInvite', () => { + let mutator: jest.Mocked + let sync: jest.Mocked + let createOrEditContact: jest.Mocked beforeEach(() => { - mutatorMock = { - createItem: jest.fn(), - } as any + mutator = {} as jest.Mocked + mutator.createItem = jest.fn() - syncServiceMock = { - sync: jest.fn(), - } as any + sync = {} as jest.Mocked + sync.sync = jest.fn() - contactServiceMock = { - createOrEditTrustedContact: jest.fn(), - } as any + createOrEditContact = {} as jest.Mocked + createOrEditContact.execute = jest.fn() }) it('should create root key before creating vault listing so that propagated vault listings do not appear as locked', async () => { - const handleTrustedSharedVaultInviteMessage = new HandleTrustedSharedVaultInviteMessage( - mutatorMock, - syncServiceMock, - contactServiceMock, - ) - + const handleTrustedSharedVaultInviteMessage = new ProcessAcceptedVaultInvite(mutator, sync, createOrEditContact) + createOrEditContact const testMessage = { type: AsymmetricMessagePayloadType.SharedVaultInvite, data: { @@ -54,11 +47,11 @@ describe('HandleTrustedSharedVaultInviteMessage', () => { await handleTrustedSharedVaultInviteMessage.execute(testMessage, sharedVaultUuid, senderUuid) - const keySystemRootKeyCallIndex = mutatorMock.createItem.mock.calls.findIndex( + const keySystemRootKeyCallIndex = mutator.createItem.mock.calls.findIndex( ([contentType]) => contentType === ContentType.TYPES.KeySystemRootKey, ) - const vaultListingCallIndex = mutatorMock.createItem.mock.calls.findIndex( + const vaultListingCallIndex = mutator.createItem.mock.calls.findIndex( ([contentType]) => contentType === ContentType.TYPES.VaultListing, ) diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite.ts similarity index 84% rename from packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage.ts rename to packages/services/src/Domain/AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite.ts index da8b491b3..7eb80e9f5 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite.ts @@ -1,4 +1,3 @@ -import { ContactServiceInterface } from './../../Contacts/ContactServiceInterface' import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' import { KeySystemRootKeyInterface, @@ -11,12 +10,13 @@ import { } from '@standardnotes/models' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' import { ContentType } from '@standardnotes/domain-core' +import { CreateOrEditContact } from '../../Contacts/UseCase/CreateOrEditContact' -export class HandleTrustedSharedVaultInviteMessage { +export class ProcessAcceptedVaultInvite { constructor( private mutator: MutatorClientInterface, private sync: SyncServiceInterface, - private contacts: ContactServiceInterface, + private createOrEditContact: CreateOrEditContact, ) {} async execute( @@ -47,11 +47,7 @@ export class HandleTrustedSharedVaultInviteMessage { await this.mutator.createItem(ContentType.TYPES.VaultListing, FillItemContentSpecialized(content), true) for (const contact of trustedContacts) { - if (contact.isMe) { - throw new Error('Should not receive isMe contact from invite') - } - - await this.contacts.createOrEditTrustedContact({ + await this.createOrEditContact.execute({ name: contact.name, contactUuid: contact.contactUuid, publicKey: contact.publicKeySet.encryption, diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/ResendAllMessages.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/ResendAllMessages.ts new file mode 100644 index 000000000..f1966434b --- /dev/null +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/ResendAllMessages.ts @@ -0,0 +1,58 @@ +import { AsymmetricMessageServerHash, isErrorResponse } from '@standardnotes/responses' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { AsymmetricMessageServerInterface } from '@standardnotes/api' +import { ResendMessage } from './ResendMessage' +import { FindContact } from '../../Contacts/UseCase/FindContact' + +export class ResendAllMessages implements UseCaseInterface { + constructor( + private resendMessage: ResendMessage, + private messageServer: AsymmetricMessageServerInterface, + private findContact: FindContact, + ) {} + + async execute(params: { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + previousKeys?: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + }): Promise> { + const messages = await this.messageServer.getOutboundUserMessages() + + if (isErrorResponse(messages)) { + return Result.fail('Failed to get outbound user messages') + } + + const errors: string[] = [] + + for (const message of messages.data.messages) { + const recipient = this.findContact.execute({ userUuid: message.user_uuid }) + if (recipient) { + errors.push(`Contact not found for invite ${message.user_uuid}`) + continue + } + + await this.resendMessage.execute({ + keys: params.keys, + previousKeys: params.previousKeys, + message: message, + recipient, + }) + + await this.messageServer.deleteMessage({ + messageUuid: message.uuid, + }) + } + + if (errors.length > 0) { + return Result.fail(errors.join(', ')) + } + + return Result.ok() + } +} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/ResendMessage.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/ResendMessage.ts new file mode 100644 index 000000000..acc5ac3f6 --- /dev/null +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/ResendMessage.ts @@ -0,0 +1,58 @@ +import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage' +import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models' +import { AsymmetricMessageServerHash } from '@standardnotes/responses' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' +import { SendMessage } from './SendMessage' + +export class ResendMessage implements UseCaseInterface { + constructor( + private decryptOwnMessage: DecryptOwnMessage, + private sendMessage: SendMessage, + private encryptMessage: EncryptMessage, + ) {} + + async execute(params: { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + previousKeys?: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + recipient: TrustedContactInterface + message: AsymmetricMessageServerHash + }): Promise> { + const decryptionResult = this.decryptOwnMessage.execute({ + message: params.message.encrypted_message, + privateKey: params.previousKeys?.encryption.privateKey ?? params.keys.encryption.privateKey, + recipientPublicKey: params.recipient.publicKeySet.encryption, + }) + + if (decryptionResult.isFailed()) { + return Result.fail(decryptionResult.getError()) + } + + const decryptedMessage = decryptionResult.getValue() + + const encryptedMessage = this.encryptMessage.execute({ + message: decryptedMessage, + keys: params.keys, + recipientPublicKey: params.recipient.publicKeySet.encryption, + }) + + if (encryptedMessage.isFailed()) { + return Result.fail(encryptedMessage.getError()) + } + + const sendMessageResult = await this.sendMessage.execute({ + recipientUuid: params.recipient.contactUuid, + encryptedMessage: encryptedMessage.getValue(), + replaceabilityIdentifier: params.message.replaceabilityIdentifier, + }) + + return sendMessageResult + } +} diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/SendMessage.ts similarity index 55% rename from packages/services/src/Domain/AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase.ts rename to packages/services/src/Domain/AsymmetricMessage/UseCase/SendMessage.ts index 96cf6dfc9..aa2f5f2f0 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/SendMessage.ts @@ -1,14 +1,15 @@ -import { ClientDisplayableError, isErrorResponse, AsymmetricMessageServerHash } from '@standardnotes/responses' +import { isErrorResponse, AsymmetricMessageServerHash, getErrorFromErrorResponse } from '@standardnotes/responses' import { AsymmetricMessageServerInterface } from '@standardnotes/api' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' -export class SendAsymmetricMessageUseCase { +export class SendMessage implements UseCaseInterface { constructor(private messageServer: AsymmetricMessageServerInterface) {} async execute(params: { recipientUuid: string encryptedMessage: string replaceabilityIdentifier: string | undefined - }): Promise { + }): Promise> { const response = await this.messageServer.createMessage({ recipientUuid: params.recipientUuid, encryptedMessage: params.encryptedMessage, @@ -16,9 +17,9 @@ export class SendAsymmetricMessageUseCase { }) if (isErrorResponse(response)) { - return ClientDisplayableError.FromNetworkError(response) + return Result.fail(getErrorFromErrorResponse(response).message) } - return response.data.message + return Result.ok(response.data.message) } } diff --git a/packages/services/src/Domain/AsymmetricMessage/UseCase/SendOwnContactChangeMessage.ts b/packages/services/src/Domain/AsymmetricMessage/UseCase/SendOwnContactChangeMessage.ts index 3c3fe9efc..b103414f1 100644 --- a/packages/services/src/Domain/AsymmetricMessage/UseCase/SendOwnContactChangeMessage.ts +++ b/packages/services/src/Domain/AsymmetricMessage/UseCase/SendOwnContactChangeMessage.ts @@ -1,16 +1,16 @@ -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses' +import { AsymmetricMessageServerHash } from '@standardnotes/responses' import { TrustedContactInterface, AsymmetricMessagePayloadType, AsymmetricMessageSenderKeypairChanged, } from '@standardnotes/models' -import { AsymmetricMessageServer } from '@standardnotes/api' import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { SendAsymmetricMessageUseCase } from './SendAsymmetricMessageUseCase' +import { SendMessage } from './SendMessage' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' -export class SendOwnContactChangeMessage { - constructor(private encryption: EncryptionProviderInterface, private messageServer: AsymmetricMessageServer) {} +export class SendOwnContactChangeMessage implements UseCaseInterface { + constructor(private encryptMessage: EncryptMessage, private sendMessage: SendMessage) {} async execute(params: { senderOldKeyPair: PkcKeyPair @@ -18,7 +18,7 @@ export class SendOwnContactChangeMessage { senderNewKeyPair: PkcKeyPair senderNewSigningKeyPair: PkcKeyPair contact: TrustedContactInterface - }): Promise { + }): Promise> { const message: AsymmetricMessageSenderKeypairChanged = { type: AsymmetricMessagePayloadType.SenderKeypairChanged, data: { @@ -28,17 +28,22 @@ export class SendOwnContactChangeMessage { }, } - const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({ + const encryptedMessage = this.encryptMessage.execute({ message: message, - senderKeyPair: params.senderOldKeyPair, - senderSigningKeyPair: params.senderOldSigningKeyPair, + keys: { + encryption: params.senderOldKeyPair, + signing: params.senderOldSigningKeyPair, + }, recipientPublicKey: params.contact.publicKeySet.encryption, }) - const sendMessageUseCase = new SendAsymmetricMessageUseCase(this.messageServer) - const sendMessageResult = await sendMessageUseCase.execute({ + if (encryptedMessage.isFailed()) { + return Result.fail(encryptedMessage.getError()) + } + + const sendMessageResult = await this.sendMessage.execute({ recipientUuid: params.contact.contactUuid, - encryptedMessage, + encryptedMessage: encryptedMessage.getValue(), replaceabilityIdentifier: undefined, }) diff --git a/packages/services/src/Domain/Backups/BackupService.spec.ts b/packages/services/src/Domain/Backups/FilesBackupService.spec.ts similarity index 86% rename from packages/services/src/Domain/Backups/BackupService.spec.ts rename to packages/services/src/Domain/Backups/FilesBackupService.spec.ts index c9ef4ee3e..3644b5ce0 100644 --- a/packages/services/src/Domain/Backups/BackupService.spec.ts +++ b/packages/services/src/Domain/Backups/FilesBackupService.spec.ts @@ -1,20 +1,20 @@ -import { HistoryServiceInterface } from './../History/HistoryServiceInterface' -import { PayloadManagerInterface } from './../Payloads/PayloadManagerInterface' -import { StorageServiceInterface } from './../Storage/StorageServiceInterface' -import { SessionsClientInterface } from './../Session/SessionsClientInterface' -import { StatusServiceInterface } from './../Status/StatusServiceInterface' -import { FilesBackupService } from './BackupService' +import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface' +import { LegacyApiServiceInterface } from '../Api/LegacyApiServiceInterface' +import { HistoryServiceInterface } from '../History/HistoryServiceInterface' +import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface' +import { StorageServiceInterface } from '../Storage/StorageServiceInterface' +import { SessionsClientInterface } from '../Session/SessionsClientInterface' +import { StatusServiceInterface } from '../Status/StatusServiceInterface' +import { FilesBackupService } from './FilesBackupService' import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { InternalEventBusInterface } from '..' import { AlertService } from '../Alert/AlertService' -import { ApiServiceInterface } from '../Api/ApiServiceInterface' import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { DirectoryManagerInterface, FileBackupsDevice } from '@standardnotes/files' describe('backup service', () => { - let apiService: ApiServiceInterface + let apiService: LegacyApiServiceInterface let itemManager: ItemManagerInterface let syncService: SyncServiceInterface let alertService: AlertService @@ -30,7 +30,7 @@ describe('backup service', () => { let history: HistoryServiceInterface beforeEach(() => { - apiService = {} as jest.Mocked + apiService = {} as jest.Mocked apiService.addEventObserver = jest.fn() apiService.createUserFileValetToken = jest.fn() apiService.downloadFile = jest.fn() diff --git a/packages/services/src/Domain/Backups/BackupService.ts b/packages/services/src/Domain/Backups/FilesBackupService.ts similarity index 95% rename from packages/services/src/Domain/Backups/BackupService.ts rename to packages/services/src/Domain/Backups/FilesBackupService.ts index 1bf82febb..1cccda2cc 100644 --- a/packages/services/src/Domain/Backups/BackupService.ts +++ b/packages/services/src/Domain/Backups/FilesBackupService.ts @@ -1,6 +1,9 @@ +import { InternalEventInterface } from './../Internal/InternalEventInterface' +import { ApplicationStageChangedEventPayload } from '../Event/ApplicationStageChangedEventPayload' +import { ApplicationEvent } from '../Event/ApplicationEvent' +import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface' import { NoteType } from '@standardnotes/features' -import { ApplicationStage } from './../Application/ApplicationStage' -import { EncryptionProviderInterface } from '@standardnotes/encryption' +import { ApplicationStage } from '../Application/ApplicationStage' import { PayloadEmitSource, FileItem, @@ -34,12 +37,16 @@ import { SessionsClientInterface } from '../Session/SessionsClientInterface' import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface' import { HistoryServiceInterface } from '../History/HistoryServiceInterface' import { ContentType } from '@standardnotes/domain-core' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' const PlaintextBackupsDirectoryName = 'Plaintext Backups' export const TextBackupsDirectoryName = 'Text Backups' export const FileBackupsDirectoryName = 'File Backups' -export class FilesBackupService extends AbstractService implements BackupServiceInterface { +export class FilesBackupService + extends AbstractService + implements BackupServiceInterface, InternalEventHandlerInterface +{ private filesObserverDisposer: () => void private notesObserverDisposer: () => void private tagsObserverDisposer: () => void @@ -98,6 +105,15 @@ export class FilesBackupService extends AbstractService implements BackupService }) } + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.Launched_10) { + void this.automaticallyEnableTextBackupsIfPreferenceNotSet() + } + } + } + setSuperConverter(converter: SuperConverterServiceInterface): void { this.markdownConverter = converter } @@ -143,12 +159,6 @@ export class FilesBackupService extends AbstractService implements BackupService ;(this.session as unknown) = undefined } - override async handleApplicationStage(stage: ApplicationStage): Promise { - if (stage === ApplicationStage.Launched_10) { - void this.automaticallyEnableTextBackupsIfPreferenceNotSet() - } - } - private async automaticallyEnableTextBackupsIfPreferenceNotSet(): Promise { if (this.storage.getValue(StorageKey.TextBackupsEnabled) == undefined) { this.storage.setValue(StorageKey.TextBackupsEnabled, true) diff --git a/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts b/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts index 10020248e..51a31946a 100644 --- a/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts +++ b/packages/services/src/Domain/Challenge/ChallengeServiceInterface.ts @@ -10,6 +10,12 @@ import { ChallengeReason } from './Types/ChallengeReason' import { ChallengeObserver } from './Types/ChallengeObserver' export interface ChallengeServiceInterface extends AbstractService { + sendChallenge: (challenge: ChallengeInterface) => void + submitValuesForChallenge(challenge: ChallengeInterface, values: ChallengeValue[]): Promise + cancelChallenge(challenge: ChallengeInterface): void + + isPasscodeLocked(): Promise + /** * Resolves when the challenge has been completed. * For non-validated challenges, will resolve when the first value is submitted. diff --git a/packages/services/src/Domain/Contacts/ContactService.ts b/packages/services/src/Domain/Contacts/ContactService.ts index 0f20fbd62..c7b13aa1c 100644 --- a/packages/services/src/Domain/Contacts/ContactService.ts +++ b/packages/services/src/Domain/Contacts/ContactService.ts @@ -1,81 +1,62 @@ import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' -import { ApplicationStage } from './../Application/ApplicationStage' -import { SingletonManagerInterface } from './../Singleton/SingletonManagerInterface' import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData' import { SessionEvent } from './../Session/SessionEvent' import { InternalEventInterface } from './../Internal/InternalEventInterface' import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses' -import { - TrustedContactContent, - TrustedContactContentSpecialized, - TrustedContactInterface, - FillItemContent, - TrustedContactMutator, - DecryptedItemInterface, -} from '@standardnotes/models' +import { TrustedContactInterface, TrustedContactMutator, DecryptedItemInterface } from '@standardnotes/models' import { AbstractService } from '../Service/AbstractService' import { SyncServiceInterface } from '../Sync/SyncServiceInterface' -import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { SessionsClientInterface } from '../Session/SessionsClientInterface' import { ContactServiceEvent, ContactServiceInterface } from '../Contacts/ContactServiceInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { UserClientInterface } from '../User/UserClientInterface' import { CollaborationIDData, Version1CollaborationId } from './CollaborationID' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { ValidateItemSignerUseCase } from './UseCase/ValidateItemSigner' -import { ValidateItemSignerResult } from './UseCase/ValidateItemSignerResult' -import { FindTrustedContactUseCase } from './UseCase/FindTrustedContact' -import { SelfContactManager } from './Managers/SelfContactManager' -import { CreateOrEditTrustedContactUseCase } from './UseCase/CreateOrEditTrustedContact' -import { UpdateTrustedContactUseCase } from './UseCase/UpdateTrustedContact' -import { ContentType } from '@standardnotes/domain-core' +import { ValidateItemSigner } from './UseCase/ValidateItemSigner' +import { ItemSignatureValidationResult } from './UseCase/Types/ItemSignatureValidationResult' +import { FindContact } from './UseCase/FindContact' +import { SelfContactManager } from './SelfContactManager' +import { CreateOrEditContact } from './UseCase/CreateOrEditContact' +import { EditContact } from './UseCase/EditContact' +import { GetAllContacts } from './UseCase/GetAllContacts' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' export class ContactService extends AbstractService implements ContactServiceInterface, InternalEventHandlerInterface { - private selfContactManager: SelfContactManager - constructor( private sync: SyncServiceInterface, - private items: ItemManagerInterface, private mutator: MutatorClientInterface, private session: SessionsClientInterface, private crypto: PureCryptoInterface, private user: UserClientInterface, + private selfContactManager: SelfContactManager, private encryption: EncryptionProviderInterface, - singletons: SingletonManagerInterface, + private _findContact: FindContact, + private _getAllContacts: GetAllContacts, + private _createOrEditContact: CreateOrEditContact, + private _editContact: EditContact, + private _validateItemSigner: ValidateItemSigner, eventBus: InternalEventBusInterface, ) { super(eventBus) - this.selfContactManager = new SelfContactManager(sync, items, mutator, session, singletons) - eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged) } - public override async handleApplicationStage(stage: ApplicationStage): Promise { - await super.handleApplicationStage(stage) - await this.selfContactManager.handleApplicationStage(stage) - } - async handleEvent(event: InternalEventInterface): Promise { if (event.type === SessionEvent.UserKeyPairChanged) { const data = event.payload as UserKeyPairChangedEventData await this.selfContactManager.updateWithNewPublicKeySet({ - encryption: data.newKeyPair.publicKey, - signing: data.newSigningKeyPair.publicKey, + encryption: data.current.encryption.publicKey, + signing: data.current.signing.publicKey, }) } } - private get userUuid(): string { - return this.session.getSureUser().uuid - } - getSelfContact(): TrustedContactInterface | undefined { return this.selfContactManager.selfContact } @@ -170,38 +151,11 @@ export class ContactService contact: TrustedContactInterface, params: { name: string; publicKey: string; signingPublicKey: string }, ): Promise { - const usecase = new UpdateTrustedContactUseCase(this.mutator, this.sync) - const updatedContact = await usecase.execute(contact, params) + const updatedContact = await this._editContact.execute(contact, params) return updatedContact } - async createOrUpdateTrustedContactFromContactShare( - data: TrustedContactContentSpecialized, - ): Promise { - if (data.contactUuid === this.userUuid) { - throw new Error('Cannot receive self from contact share') - } - - let contact = this.findTrustedContact(data.contactUuid) - if (contact) { - contact = await this.mutator.changeItem(contact, (mutator) => { - mutator.name = data.name - mutator.replacePublicKeySet(data.publicKeySet) - }) - } else { - contact = await this.mutator.createItem( - ContentType.TYPES.TrustedContact, - FillItemContent(data), - true, - ) - } - - await this.sync.sync() - - return contact - } - async createOrEditTrustedContact(params: { name?: string contactUuid: string @@ -209,8 +163,7 @@ export class ContactService signingPublicKey: string isMe?: boolean }): Promise { - const usecase = new CreateOrEditTrustedContactUseCase(this.items, this.mutator, this.sync) - const contact = await usecase.execute(params) + const contact = await this._createOrEditContact.execute(params) return contact } @@ -224,12 +177,15 @@ export class ContactService } getAllContacts(): TrustedContactInterface[] { - return this.items.getItems(ContentType.TYPES.TrustedContact) + return this._getAllContacts.execute().getValue() } findTrustedContact(userUuid: string): TrustedContactInterface | undefined { - const usecase = new FindTrustedContactUseCase(this.items) - return usecase.execute({ userUuid }) + const result = this._findContact.execute({ userUuid }) + if (result.isFailed()) { + return undefined + } + return result.getValue() } findTrustedContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined { @@ -249,16 +205,23 @@ export class ContactService }) } - isItemAuthenticallySigned(item: DecryptedItemInterface): ValidateItemSignerResult { - const usecase = new ValidateItemSignerUseCase(this.items) - return usecase.execute(item) + isItemAuthenticallySigned(item: DecryptedItemInterface): ItemSignatureValidationResult { + return this._validateItemSigner.execute(item) } override deinit(): void { super.deinit() - this.selfContactManager.deinit() ;(this.sync as unknown) = undefined - ;(this.items as unknown) = undefined + ;(this.mutator as unknown) = undefined + ;(this.session as unknown) = undefined + ;(this.crypto as unknown) = undefined + ;(this.user as unknown) = undefined ;(this.selfContactManager as unknown) = undefined + ;(this.encryption as unknown) = undefined + ;(this._findContact as unknown) = undefined + ;(this._getAllContacts as unknown) = undefined + ;(this._createOrEditContact as unknown) = undefined + ;(this._editContact as unknown) = undefined + ;(this._validateItemSigner as unknown) = undefined } } diff --git a/packages/services/src/Domain/Contacts/ContactServiceInterface.ts b/packages/services/src/Domain/Contacts/ContactServiceInterface.ts index 6a24825bf..ff3010ce0 100644 --- a/packages/services/src/Domain/Contacts/ContactServiceInterface.ts +++ b/packages/services/src/Domain/Contacts/ContactServiceInterface.ts @@ -1,11 +1,7 @@ -import { - DecryptedItemInterface, - TrustedContactContentSpecialized, - TrustedContactInterface, -} from '@standardnotes/models' +import { DecryptedItemInterface, TrustedContactInterface } from '@standardnotes/models' import { AbstractService } from '../Service/AbstractService' import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses' -import { ValidateItemSignerResult } from './UseCase/ValidateItemSignerResult' +import { ItemSignatureValidationResult } from './UseCase/Types/ItemSignatureValidationResult' export enum ContactServiceEvent {} @@ -26,7 +22,6 @@ export interface ContactServiceInterface extends AbstractService - createOrUpdateTrustedContactFromContactShare(data: TrustedContactContentSpecialized): Promise editTrustedContactFromCollaborationID( contact: TrustedContactInterface, params: { name: string; collaborationID: string }, @@ -39,5 +34,5 @@ export interface ContactServiceInterface extends AbstractService void)[] = [] constructor( - private sync: SyncServiceInterface, - private items: ItemManagerInterface, - private mutator: MutatorClientInterface, + sync: SyncServiceInterface, + items: ItemManagerInterface, private session: SessionsClientInterface, private singletons: SingletonManagerInterface, + private createOrEditContact: CreateOrEditContact, ) { this.eventDisposers.push( sync.addEventObserver((event) => { @@ -43,11 +48,26 @@ export class SelfContactManager { } }), ) + + this.eventDisposers.push( + items.addObserver(ContentType.TYPES.TrustedContact, () => { + const updatedReference = this.singletons.findSingleton( + ContentType.TYPES.TrustedContact, + TrustedContact.singletonPredicate, + ) + if (updatedReference) { + this.selfContact = updatedReference + } + }), + ) } - public async handleApplicationStage(stage: ApplicationStage): Promise { - if (stage === ApplicationStage.LoadedDatabase_12) { - this.loadSelfContactFromDatabase() + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.LoadedDatabase_12) { + this.loadSelfContactFromDatabase() + } } } @@ -62,7 +82,7 @@ export class SelfContactManager { ) } - public async updateWithNewPublicKeySet(publicKeySet: PublicKeySet) { + public async updateWithNewPublicKeySet(publicKeySet: PortablePublicKeySet) { if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) { return } @@ -71,9 +91,8 @@ export class SelfContactManager { return } - const usecase = new CreateOrEditTrustedContactUseCase(this.items, this.mutator, this.sync) - await usecase.execute({ - name: 'Me', + await this.createOrEditContact.execute({ + name: SelfContactName, contactUuid: this.selfContact.contactUuid, publicKey: publicKeySet.encryption, signingPublicKey: publicKeySet.signing, @@ -104,13 +123,12 @@ export class SelfContactManager { this.isReloadingSelfContact = true const content: TrustedContactContentSpecialized = { - name: 'Me', + name: SelfContactName, isMe: true, contactUuid: this.session.getSureUser().uuid, publicKeySet: ContactPublicKeySet.FromJson({ encryption: this.session.getPublicKey(), signing: this.session.getSigningPublicKey(), - isRevoked: false, timestamp: new Date(), }), } @@ -126,10 +144,8 @@ export class SelfContactManager { deinit() { this.eventDisposers.forEach((disposer) => disposer()) - ;(this.sync as unknown) = undefined - ;(this.items as unknown) = undefined - ;(this.mutator as unknown) = undefined ;(this.session as unknown) = undefined ;(this.singletons as unknown) = undefined + ;(this.createOrEditContact as unknown) = undefined } } diff --git a/packages/services/src/Domain/Contacts/UseCase/CreateOrEditTrustedContact.ts b/packages/services/src/Domain/Contacts/UseCase/CreateOrEditContact.ts similarity index 57% rename from packages/services/src/Domain/Contacts/UseCase/CreateOrEditTrustedContact.ts rename to packages/services/src/Domain/Contacts/UseCase/CreateOrEditContact.ts index efd571a6f..b289e49ab 100644 --- a/packages/services/src/Domain/Contacts/UseCase/CreateOrEditTrustedContact.ts +++ b/packages/services/src/Domain/Contacts/UseCase/CreateOrEditContact.ts @@ -1,6 +1,5 @@ -import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' -import { ItemManagerInterface } from './../../Item/ItemManagerInterface' +import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' +import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' import { ContactPublicKeySet, FillItemContent, @@ -8,16 +7,17 @@ import { TrustedContactContentSpecialized, TrustedContactInterface, } from '@standardnotes/models' -import { FindTrustedContactUseCase } from './FindTrustedContact' +import { FindContact } from './FindContact' import { UnknownContactName } from '../UnknownContactName' -import { UpdateTrustedContactUseCase } from './UpdateTrustedContact' +import { EditContact } from './EditContact' import { ContentType } from '@standardnotes/domain-core' -export class CreateOrEditTrustedContactUseCase { +export class CreateOrEditContact { constructor( - private items: ItemManagerInterface, private mutator: MutatorClientInterface, private sync: SyncServiceInterface, + private findContact: FindContact, + private editContact: EditContact, ) {} async execute(params: { @@ -27,13 +27,14 @@ export class CreateOrEditTrustedContactUseCase { signingPublicKey: string isMe?: boolean }): Promise { - const findUsecase = new FindTrustedContactUseCase(this.items) - const existingContact = findUsecase.execute({ userUuid: params.contactUuid }) + const existingContact = this.findContact.execute({ userUuid: params.contactUuid }) - if (existingContact) { - const updateUsecase = new UpdateTrustedContactUseCase(this.mutator, this.sync) - await updateUsecase.execute(existingContact, { ...params, name: params.name ?? existingContact.name }) - return existingContact + if (!existingContact.isFailed()) { + await this.editContact.execute(existingContact.getValue(), { + ...params, + name: params.name ?? existingContact.getValue().name, + }) + return existingContact.getValue() } const content: TrustedContactContentSpecialized = { @@ -41,7 +42,6 @@ export class CreateOrEditTrustedContactUseCase { publicKeySet: ContactPublicKeySet.FromJson({ encryption: params.publicKey, signing: params.signingPublicKey, - isRevoked: false, timestamp: new Date(), }), contactUuid: params.contactUuid, diff --git a/packages/services/src/Domain/Contacts/UseCase/UpdateTrustedContact.ts b/packages/services/src/Domain/Contacts/UseCase/EditContact.ts similarity index 82% rename from packages/services/src/Domain/Contacts/UseCase/UpdateTrustedContact.ts rename to packages/services/src/Domain/Contacts/UseCase/EditContact.ts index a071b1bba..ac8a0db0f 100644 --- a/packages/services/src/Domain/Contacts/UseCase/UpdateTrustedContact.ts +++ b/packages/services/src/Domain/Contacts/UseCase/EditContact.ts @@ -1,8 +1,8 @@ -import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' +import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' +import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' import { TrustedContactInterface, TrustedContactMutator } from '@standardnotes/models' -export class UpdateTrustedContactUseCase { +export class EditContact { constructor(private mutator: MutatorClientInterface, private sync: SyncServiceInterface) {} async execute( diff --git a/packages/services/src/Domain/Contacts/UseCase/FindContact.ts b/packages/services/src/Domain/Contacts/UseCase/FindContact.ts new file mode 100644 index 000000000..f53af789b --- /dev/null +++ b/packages/services/src/Domain/Contacts/UseCase/FindContact.ts @@ -0,0 +1,38 @@ +import { Predicate, TrustedContactInterface } from '@standardnotes/models' +import { ItemManagerInterface } from '../../Item/ItemManagerInterface' +import { FindContactQuery } from './Types/FindContactQuery' +import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core' + +export class FindContact implements SyncUseCaseInterface { + constructor(private items: ItemManagerInterface) {} + + execute(query: FindContactQuery): Result { + if ('userUuid' in query && query.userUuid) { + const contact = this.items.itemsMatchingPredicate( + ContentType.TYPES.TrustedContact, + new Predicate('contactUuid', '=', query.userUuid), + )[0] + + if (contact) { + return Result.ok(contact) + } else { + return Result.fail('Contact not found') + } + } + + if ('signingPublicKey' in query && query.signingPublicKey) { + const allContacts = this.items.getItems(ContentType.TYPES.TrustedContact) + const contact = allContacts.find((contact) => + contact.hasCurrentOrPreviousSigningPublicKey(query.signingPublicKey), + ) + + if (contact) { + return Result.ok(contact) + } else { + return Result.fail('Contact not found') + } + } + + throw new Error('Invalid query') + } +} diff --git a/packages/services/src/Domain/Contacts/UseCase/FindTrustedContact.ts b/packages/services/src/Domain/Contacts/UseCase/FindTrustedContact.ts deleted file mode 100644 index 703d44ec4..000000000 --- a/packages/services/src/Domain/Contacts/UseCase/FindTrustedContact.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Predicate, TrustedContactInterface } from '@standardnotes/models' -import { ItemManagerInterface } from './../../Item/ItemManagerInterface' -import { FindContactQuery } from './FindContactQuery' -import { ContentType } from '@standardnotes/domain-core' - -export class FindTrustedContactUseCase { - constructor(private items: ItemManagerInterface) {} - - execute(query: FindContactQuery): TrustedContactInterface | undefined { - if ('userUuid' in query && query.userUuid) { - return this.items.itemsMatchingPredicate( - ContentType.TYPES.TrustedContact, - new Predicate('contactUuid', '=', query.userUuid), - )[0] - } - - if ('signingPublicKey' in query && query.signingPublicKey) { - const allContacts = this.items.getItems(ContentType.TYPES.TrustedContact) - return allContacts.find((contact) => contact.isSigningKeyTrusted(query.signingPublicKey)) - } - - if ('publicKey' in query && query.publicKey) { - const allContacts = this.items.getItems(ContentType.TYPES.TrustedContact) - return allContacts.find((contact) => contact.isPublicKeyTrusted(query.publicKey)) - } - - throw new Error('Invalid query') - } -} diff --git a/packages/services/src/Domain/Contacts/UseCase/GetAllContacts.ts b/packages/services/src/Domain/Contacts/UseCase/GetAllContacts.ts new file mode 100644 index 000000000..5d8f41aee --- /dev/null +++ b/packages/services/src/Domain/Contacts/UseCase/GetAllContacts.ts @@ -0,0 +1,11 @@ +import { TrustedContactInterface } from '@standardnotes/models' +import { ItemManagerInterface } from '../../Item/ItemManagerInterface' +import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core' + +export class GetAllContacts implements SyncUseCaseInterface { + constructor(private items: ItemManagerInterface) {} + + execute(): Result { + return Result.ok(this.items.getItems(ContentType.TYPES.TrustedContact)) + } +} diff --git a/packages/services/src/Domain/Contacts/UseCase/HandleKeyPairChange.ts b/packages/services/src/Domain/Contacts/UseCase/HandleKeyPairChange.ts new file mode 100644 index 000000000..d8abcb24a --- /dev/null +++ b/packages/services/src/Domain/Contacts/UseCase/HandleKeyPairChange.ts @@ -0,0 +1,31 @@ +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { ReuploadAllInvites } from '../../SharedVaults/UseCase/ReuploadAllInvites' +import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages' + +export class HandleKeyPairChange implements UseCaseInterface { + constructor(private reuploadAllInvites: ReuploadAllInvites, private resendAllMessages: ResendAllMessages) {} + + async execute(dto: { + newKeys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + previousKeys?: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + }): Promise> { + await this.reuploadAllInvites.execute({ + keys: dto.newKeys, + previousKeys: dto.previousKeys, + }) + + await this.resendAllMessages.execute({ + keys: dto.newKeys, + previousKeys: dto.previousKeys, + }) + + return Result.ok() + } +} diff --git a/packages/services/src/Domain/Contacts/UseCase/ReplaceContactData.ts b/packages/services/src/Domain/Contacts/UseCase/ReplaceContactData.ts new file mode 100644 index 000000000..06b34aa3f --- /dev/null +++ b/packages/services/src/Domain/Contacts/UseCase/ReplaceContactData.ts @@ -0,0 +1,55 @@ +import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' +import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' +import { + FillItemContent, + MutationType, + PayloadEmitSource, + TrustedContactContent, + TrustedContactContentSpecialized, + TrustedContactInterface, + TrustedContactMutator, +} from '@standardnotes/models' +import { FindContact } from './FindContact' +import { ContentType, Result, UseCaseInterface } from '@standardnotes/domain-core' + +export class ReplaceContactData implements UseCaseInterface { + constructor( + private mutator: MutatorClientInterface, + private sync: SyncServiceInterface, + private findContact: FindContact, + ) {} + + async execute(data: TrustedContactContentSpecialized): Promise> { + const contactResult = this.findContact.execute({ userUuid: data.contactUuid }) + if (contactResult.isFailed()) { + const newContact = await this.mutator.createItem( + ContentType.TYPES.TrustedContact, + FillItemContent(data), + true, + ) + + await this.sync.sync() + + return Result.ok(newContact) + } + + const existingContact = contactResult.getValue() + if (existingContact.isMe) { + return Result.fail('Cannot replace data for me contact') + } + + const updatedContact = await this.mutator.changeItem( + existingContact, + (mutator) => { + mutator.name = data.name + mutator.replacePublicKeySet(data.publicKeySet) + }, + MutationType.UpdateUserTimestamps, + PayloadEmitSource.RemoteRetrieved, + ) + + await this.sync.sync() + + return Result.ok(updatedContact) + } +} diff --git a/packages/services/src/Domain/Contacts/UseCase/FindContactQuery.ts b/packages/services/src/Domain/Contacts/UseCase/Types/FindContactQuery.ts similarity index 59% rename from packages/services/src/Domain/Contacts/UseCase/FindContactQuery.ts rename to packages/services/src/Domain/Contacts/UseCase/Types/FindContactQuery.ts index 31a2bf652..79563af3f 100644 --- a/packages/services/src/Domain/Contacts/UseCase/FindContactQuery.ts +++ b/packages/services/src/Domain/Contacts/UseCase/Types/FindContactQuery.ts @@ -1 +1 @@ -export type FindContactQuery = { userUuid: string } | { signingPublicKey: string } | { publicKey: string } +export type FindContactQuery = { userUuid: string } | { signingPublicKey: string } diff --git a/packages/services/src/Domain/Contacts/UseCase/Types/ItemSignatureValidationResult.ts b/packages/services/src/Domain/Contacts/UseCase/Types/ItemSignatureValidationResult.ts new file mode 100644 index 000000000..36b561879 --- /dev/null +++ b/packages/services/src/Domain/Contacts/UseCase/Types/ItemSignatureValidationResult.ts @@ -0,0 +1,6 @@ +export enum ItemSignatureValidationResult { + NotApplicable = 'NotApplicable', + Trusted = 'Trusted', + SignedWithNonCurrentKey = 'SignedWithNonCurrentKey', + NotTrusted = 'NotTrusted', +} diff --git a/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.spec.ts b/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.spec.ts index 6c6c5aa5a..876efc01c 100644 --- a/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.spec.ts +++ b/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.spec.ts @@ -2,21 +2,26 @@ import { DecryptedItemInterface, PayloadSource, PersistentSignatureData, + PublicKeyTrustStatus, TrustedContactInterface, } from '@standardnotes/models' -import { ItemManagerInterface } from './../../Item/ItemManagerInterface' -import { ValidateItemSignerUseCase } from './ValidateItemSigner' +import { ValidateItemSigner } from './ValidateItemSigner' +import { FindContact } from './FindContact' +import { ItemSignatureValidationResult } from './Types/ItemSignatureValidationResult' +import { Result } from '@standardnotes/domain-core' describe('validate item signer use case', () => { - let usecase: ValidateItemSignerUseCase - let items: ItemManagerInterface + let usecase: ValidateItemSigner + let findContact: FindContact const trustedContact = {} as jest.Mocked - trustedContact.isSigningKeyTrusted = jest.fn().mockReturnValue(true) + trustedContact.getTrustStatusForSigningPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted) beforeEach(() => { - items = {} as jest.Mocked - usecase = new ValidateItemSignerUseCase(items) + findContact = {} as jest.Mocked + findContact.execute = jest.fn().mockReturnValue(Result.ok(trustedContact)) + + usecase = new ValidateItemSigner(findContact) }) const createItem = (params: { @@ -42,7 +47,7 @@ describe('validate item signer use case', () => { describe('has last edited by uuid', () => { describe('trusted contact not found', () => { beforeEach(() => { - items.itemsMatchingPredicate = jest.fn().mockReturnValue([]) + findContact.execute = jest.fn().mockReturnValue(Result.fail('Not found')) }) it('should return invalid signing is required', () => { @@ -53,7 +58,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return not applicable signing is not required', () => { @@ -64,15 +69,11 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) }) describe('trusted contact found for last editor', () => { - beforeEach(() => { - items.itemsMatchingPredicate = jest.fn().mockReturnValue([trustedContact]) - }) - describe('does not have signature data', () => { it('should return not applicable if the item was just recently created', () => { const item = createItem({ @@ -83,7 +84,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) it('should return not applicable if the item was just recently saved', () => { @@ -95,7 +96,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) it('should return invalid if signing is required', () => { @@ -106,7 +107,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return not applicable if signing is not required', () => { @@ -117,7 +118,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) }) @@ -133,7 +134,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return not applicable if signing is not required', () => { @@ -146,7 +147,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) }) @@ -164,7 +165,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return invalid if signature result passes and a trusted contact is NOT found for signature public key', () => { @@ -180,10 +181,10 @@ describe('validate item signer use case', () => { } as jest.Mocked, }) - items.itemsMatchingPredicate = jest.fn().mockReturnValue([]) + findContact.execute = jest.fn().mockReturnValue(Result.fail('Not found')) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return valid if signature result passes and a trusted contact is found for signature public key', () => { @@ -199,10 +200,10 @@ describe('validate item signer use case', () => { } as jest.Mocked, }) - items.itemsMatchingPredicate = jest.fn().mockReturnValue([trustedContact]) + findContact.execute = jest.fn().mockReturnValue(Result.ok(trustedContact)) const result = usecase.execute(item) - expect(result).toEqual('yes') + expect(result).toEqual(ItemSignatureValidationResult.Trusted) }) }) }) @@ -220,7 +221,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) it('should return not applicable if the item was just recently saved', () => { @@ -232,7 +233,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) it('should return invalid if signing is required', () => { @@ -243,7 +244,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return not applicable if signing is not required', () => { @@ -254,7 +255,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) }) @@ -270,7 +271,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return not applicable if signing is not required', () => { @@ -283,7 +284,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('not-applicable') + expect(result).toEqual(ItemSignatureValidationResult.NotApplicable) }) }) @@ -301,7 +302,7 @@ describe('validate item signer use case', () => { }) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return invalid if signature result passes and a trusted contact is NOT found for signature public key', () => { @@ -317,10 +318,10 @@ describe('validate item signer use case', () => { } as jest.Mocked, }) - items.getItems = jest.fn().mockReturnValue([]) + findContact.execute = jest.fn().mockReturnValue(Result.fail('Not found')) const result = usecase.execute(item) - expect(result).toEqual('no') + expect(result).toEqual(ItemSignatureValidationResult.NotTrusted) }) it('should return valid if signature result passes and a trusted contact is found for signature public key', () => { @@ -336,10 +337,8 @@ describe('validate item signer use case', () => { } as jest.Mocked, }) - items.getItems = jest.fn().mockReturnValue([trustedContact]) - const result = usecase.execute(item) - expect(result).toEqual('yes') + expect(result).toEqual(ItemSignatureValidationResult.Trusted) }) }) }) diff --git a/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.ts b/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.ts index 3d5449d1f..3753a9ae2 100644 --- a/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.ts +++ b/packages/services/src/Domain/Contacts/UseCase/ValidateItemSigner.ts @@ -1,15 +1,12 @@ -import { ItemManagerInterface } from './../../Item/ItemManagerInterface' import { doesPayloadRequireSigning } from '@standardnotes/encryption/src/Domain/Operator/004/V004AlgorithmHelpers' -import { DecryptedItemInterface, PayloadSource } from '@standardnotes/models' -import { ValidateItemSignerResult } from './ValidateItemSignerResult' -import { FindTrustedContactUseCase } from './FindTrustedContact' +import { DecryptedItemInterface, PayloadSource, PublicKeyTrustStatus } from '@standardnotes/models' +import { ItemSignatureValidationResult } from './Types/ItemSignatureValidationResult' +import { FindContact } from './FindContact' -export class ValidateItemSignerUseCase { - private findContactUseCase = new FindTrustedContactUseCase(this.items) +export class ValidateItemSigner { + constructor(private findContact: FindContact) {} - constructor(private items: ItemManagerInterface) {} - - execute(item: DecryptedItemInterface): ValidateItemSignerResult { + execute(item: DecryptedItemInterface): ItemSignatureValidationResult { const uuidOfLastEditor = item.last_edited_by_uuid if (uuidOfLastEditor) { return this.validateSignatureWithLastEditedByUuid(item, uuidOfLastEditor) @@ -29,15 +26,15 @@ export class ValidateItemSignerUseCase { private validateSignatureWithLastEditedByUuid( item: DecryptedItemInterface, uuidOfLastEditor: string, - ): ValidateItemSignerResult { + ): ItemSignatureValidationResult { const requiresSignature = doesPayloadRequireSigning(item) - const trustedContact = this.findContactUseCase.execute({ userUuid: uuidOfLastEditor }) - if (!trustedContact) { + const trustedContact = this.findContact.execute({ userUuid: uuidOfLastEditor }) + if (trustedContact.isFailed()) { if (requiresSignature) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } else { - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } } @@ -46,38 +43,41 @@ export class ValidateItemSignerUseCase { this.isItemLocallyCreatedAndDoesNotRequireSignature(item) || this.isItemResutOfRemoteSaveAndDoesNotRequireSignature(item) ) { - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } if (requiresSignature) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } const signatureData = item.signatureData if (!signatureData.result) { if (signatureData.required) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } const signatureResult = signatureData.result if (!signatureResult.passes) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } const signerPublicKey = signatureResult.publicKey - if (trustedContact.isSigningKeyTrusted(signerPublicKey)) { - return 'yes' + const trustStatus = trustedContact.getValue().getTrustStatusForSigningPublicKey(signerPublicKey) + if (trustStatus === PublicKeyTrustStatus.Trusted) { + return ItemSignatureValidationResult.Trusted + } else if (trustStatus === PublicKeyTrustStatus.Previous) { + return ItemSignatureValidationResult.SignedWithNonCurrentKey } - return 'no' + return ItemSignatureValidationResult.NotTrusted } - private validateSignatureWithNoLastEditedByUuid(item: DecryptedItemInterface): ValidateItemSignerResult { + private validateSignatureWithNoLastEditedByUuid(item: DecryptedItemInterface): ItemSignatureValidationResult { const requiresSignature = doesPayloadRequireSigning(item) if (!item.signatureData) { @@ -85,38 +85,45 @@ export class ValidateItemSignerUseCase { this.isItemLocallyCreatedAndDoesNotRequireSignature(item) || this.isItemResutOfRemoteSaveAndDoesNotRequireSignature(item) ) { - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } if (requiresSignature) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } const signatureData = item.signatureData if (!signatureData.result) { if (signatureData.required) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } - return 'not-applicable' + return ItemSignatureValidationResult.NotApplicable } const signatureResult = signatureData.result if (!signatureResult.passes) { - return 'no' + return ItemSignatureValidationResult.NotTrusted } const signerPublicKey = signatureResult.publicKey - const trustedContact = this.findContactUseCase.execute({ signingPublicKey: signerPublicKey }) + const trustedContact = this.findContact.execute({ signingPublicKey: signerPublicKey }) - if (trustedContact) { - return 'yes' + if (trustedContact.isFailed()) { + return ItemSignatureValidationResult.NotTrusted } - return 'no' + const trustStatus = trustedContact.getValue().getTrustStatusForSigningPublicKey(signerPublicKey) + if (trustStatus === PublicKeyTrustStatus.Trusted) { + return ItemSignatureValidationResult.Trusted + } else if (trustStatus === PublicKeyTrustStatus.Previous) { + return ItemSignatureValidationResult.SignedWithNonCurrentKey + } + + return ItemSignatureValidationResult.NotTrusted } } diff --git a/packages/services/src/Domain/Contacts/UseCase/ValidateItemSignerResult.ts b/packages/services/src/Domain/Contacts/UseCase/ValidateItemSignerResult.ts deleted file mode 100644 index 6d28bd992..000000000 --- a/packages/services/src/Domain/Contacts/UseCase/ValidateItemSignerResult.ts +++ /dev/null @@ -1 +0,0 @@ -export type ValidateItemSignerResult = 'not-applicable' | 'yes' | 'no' diff --git a/packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts b/packages/services/src/Domain/Encryption/EncryptionProviderInterface.ts similarity index 70% rename from packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts rename to packages/services/src/Domain/Encryption/EncryptionProviderInterface.ts index 39d62fb4c..a07ef756f 100644 --- a/packages/encryption/src/Domain/Service/Encryption/EncryptionProviderInterface.ts +++ b/packages/services/src/Domain/Encryption/EncryptionProviderInterface.ts @@ -1,4 +1,11 @@ -import { AsymmetricSignatureVerificationDetachedResult } from '../../Operator/Types/AsymmetricSignatureVerificationDetachedResult' +import { + AsymmetricSignatureVerificationDetachedResult, + SNRootKeyParams, + KeyedDecryptionSplit, + KeyedEncryptionSplit, + ItemAuthenticatedData, + AsymmetricallyEncryptedString, +} from '@standardnotes/encryption' import { KeyParamsOrigination, ProtocolVersion } from '@standardnotes/common' import { BackupFile, @@ -9,23 +16,14 @@ import { RootKeyInterface, KeySystemIdentifier, KeySystemItemsKeyInterface, - AsymmetricMessagePayload, KeySystemRootKeyInterface, KeySystemRootKeyParamsInterface, - TrustedContactInterface, + PortablePublicKeySet, } from '@standardnotes/models' -import { ClientDisplayableError } from '@standardnotes/responses' -import { SNRootKeyParams } from '../../Keys/RootKey/RootKeyParams' -import { KeyedDecryptionSplit } from '../../Split/KeyedDecryptionSplit' -import { KeyedEncryptionSplit } from '../../Split/KeyedEncryptionSplit' -import { ItemAuthenticatedData } from '../../Types/ItemAuthenticatedData' import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { PublicKeySet } from '../../Operator/Types/PublicKeySet' -import { KeySystemKeyManagerInterface } from '../KeySystemKeyManagerInterface' -import { AsymmetricallyEncryptedString } from '../../Operator/Types/Types' export interface EncryptionProviderInterface { - keys: KeySystemKeyManagerInterface + initialize(): Promise encryptSplitSingle(split: KeyedEncryptionSplit): Promise encryptSplit(split: KeyedEncryptionSplit): Promise @@ -51,10 +49,12 @@ export interface EncryptionProviderInterface { isVersionNewerThanLibraryVersion(version: ProtocolVersion): boolean platformSupportsKeyDerivation(keyParams: SNRootKeyParams): boolean - decryptBackupFile( - file: BackupFile, - password?: string, - ): Promise + getPasswordCreatedDate(): Date | undefined + getEncryptionDisplayName(): Promise + upgradeAvailable(): Promise + + createEncryptedBackupFile(): Promise + createDecryptedBackupFile(): BackupFile getUserVersion(): ProtocolVersion | undefined hasAccount(): boolean @@ -75,6 +75,7 @@ export interface EncryptionProviderInterface { decryptErroredPayloads(): Promise deleteWorkspaceSpecificKeyStateFromDevice(): Promise + unwrapRootKey(wrappingKey: RootKeyInterface): Promise computeRootKey(password: string, keyParams: SNRootKeyParams): Promise computeWrappingKey(passcode: string): Promise hasRootKeyEncryptionSource(): boolean @@ -110,24 +111,11 @@ export interface EncryptionProviderInterface { rootKeyToken: string, ): KeySystemItemsKeyInterface - reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise - getKeyPair(): PkcKeyPair getSigningKeyPair(): PkcKeyPair - asymmetricallyEncryptMessage(dto: { - message: AsymmetricMessagePayload - senderKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - recipientPublicKey: string - }): string - asymmetricallyDecryptMessage(dto: { - encryptedString: AsymmetricallyEncryptedString - trustedSender: TrustedContactInterface | undefined - privateKey: string - }): M | undefined asymmetricSignatureVerifyDetached( encryptedString: AsymmetricallyEncryptedString, ): AsymmetricSignatureVerificationDetachedResult - getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: string): PublicKeySet + getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: string): PortablePublicKeySet } diff --git a/packages/services/src/Domain/Encryption/EncryptionService.ts b/packages/services/src/Domain/Encryption/EncryptionService.ts index db0a4d905..c1119c6ce 100644 --- a/packages/services/src/Domain/Encryption/EncryptionService.ts +++ b/packages/services/src/Domain/Encryption/EncryptionService.ts @@ -1,3 +1,4 @@ +import { FindDefaultItemsKey } from './UseCase/ItemsKey/FindDefaultItemsKey' import { InternalEventInterface } from './../Internal/InternalEventInterface' import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface' import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' @@ -5,27 +6,22 @@ import { CreateAnyKeyParams, CreateEncryptionSplitWithKeyLookup, encryptedInputParametersFromPayload, - EncryptionProviderInterface, ErrorDecryptingParameters, - findDefaultItemsKey, FindPayloadInDecryptionSplit, FindPayloadInEncryptionSplit, isErrorDecryptingParameters, ItemAuthenticatedData, KeyedDecryptionSplit, KeyedEncryptionSplit, - KeyMode, LegacyAttachedData, - OperatorManager, RootKeyEncryptedAuthenticatedData, SplitPayloadsByEncryptionType, V001Algorithm, V002Algorithm, - PublicKeySet, EncryptedOutputParameters, - KeySystemKeyManagerInterface, AsymmetricSignatureVerificationDetachedResult, AsymmetricallyEncryptedString, + EncryptionOperatorsInterface, } from '@standardnotes/encryption' import { BackupFile, @@ -42,13 +38,11 @@ import { RootKeyInterface, KeySystemItemsKeyInterface, KeySystemIdentifier, - AsymmetricMessagePayload, KeySystemRootKeyInterface, KeySystemRootKeyParamsInterface, - TrustedContactInterface, + PortablePublicKeySet, RootKeyParamsInterface, } from '@standardnotes/models' -import { ClientDisplayableError } from '@standardnotes/responses' import { PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common' import { extendArray, @@ -60,7 +54,6 @@ import { } from '@standardnotes/utils' import { AnyKeyParamsContent, - ApplicationIdentifier, compareVersions, isVersionLessThanOrEqualTo, KeyParamsOrigination, @@ -70,28 +63,27 @@ import { } from '@standardnotes/common' import { AbstractService } from '../Service/AbstractService' -import { ItemsEncryptionService } from './ItemsEncryption' +import { ItemsEncryptionService } from '../ItemsEncryption/ItemsEncryption' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface' -import { DeviceInterface } from '../Device/DeviceInterface' -import { StorageServiceInterface } from '../Storage/StorageServiceInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { SyncEvent } from '../Event/SyncEvent' -import { DecryptBackupFileUseCase } from './DecryptBackupFileUseCase' import { EncryptionServiceEvent } from './EncryptionServiceEvent' import { DecryptedParameters } from '@standardnotes/encryption/src/Domain/Types/DecryptedParameters' -import { RootKeyManager } from './RootKey/RootKeyManager' -import { RootKeyManagerEvent } from './RootKey/RootKeyManagerEvent' -import { CreateNewItemsKeyWithRollbackUseCase } from './UseCase/ItemsKey/CreateNewItemsKeyWithRollback' -import { DecryptErroredRootPayloadsUseCase } from './UseCase/RootEncryption/DecryptErroredPayloads' -import { CreateNewDefaultItemsKeyUseCase } from './UseCase/ItemsKey/CreateNewDefaultItemsKey' -import { RootKeyDecryptPayloadUseCase } from './UseCase/RootEncryption/DecryptPayload' -import { RootKeyDecryptPayloadWithKeyLookupUseCase } from './UseCase/RootEncryption/DecryptPayloadWithKeyLookup' -import { RootKeyEncryptPayloadWithKeyLookupUseCase } from './UseCase/RootEncryption/EncryptPayloadWithKeyLookup' -import { RootKeyEncryptPayloadUseCase } from './UseCase/RootEncryption/EncryptPayload' -import { ValidateAccountPasswordResult } from './RootKey/ValidateAccountPasswordResult' -import { ValidatePasscodeResult } from './RootKey/ValidatePasscodeResult' +import { RootKeyManager } from '../RootKeyManager/RootKeyManager' +import { RootKeyManagerEvent } from '../RootKeyManager/RootKeyManagerEvent' +import { CreateNewItemsKeyWithRollback } from './UseCase/ItemsKey/CreateNewItemsKeyWithRollback' +import { DecryptErroredTypeAPayloads } from './UseCase/TypeA/DecryptErroredPayloads' +import { CreateNewDefaultItemsKey } from './UseCase/ItemsKey/CreateNewDefaultItemsKey' +import { DecryptTypeAPayload } from './UseCase/TypeA/DecryptPayload' +import { DecryptTypeAPayloadWithKeyLookup } from './UseCase/TypeA/DecryptPayloadWithKeyLookup' +import { EncryptTypeAPayloadWithKeyLookup } from './UseCase/TypeA/EncryptPayloadWithKeyLookup' +import { EncryptTypeAPayload } from './UseCase/TypeA/EncryptPayload' +import { ValidateAccountPasswordResult } from '../RootKeyManager/ValidateAccountPasswordResult' +import { ValidatePasscodeResult } from '../RootKeyManager/ValidatePasscodeResult' import { ContentType } from '@standardnotes/domain-core' +import { EncryptionProviderInterface } from './EncryptionProviderInterface' +import { KeyMode } from '../RootKeyManager/KeyMode' /** * The encryption service is responsible for the encryption and decryption of payloads, and @@ -124,40 +116,28 @@ export class EncryptionService extends AbstractService implements EncryptionProviderInterface, InternalEventHandlerInterface { - private operators: OperatorManager - private readonly itemsEncryption: ItemsEncryptionService - private readonly rootKeyManager: RootKeyManager - constructor( private items: ItemManagerInterface, private mutator: MutatorClientInterface, private payloads: PayloadManagerInterface, - public device: DeviceInterface, - private storage: StorageServiceInterface, - public readonly keys: KeySystemKeyManagerInterface, - identifier: ApplicationIdentifier, - public crypto: PureCryptoInterface, + private operators: EncryptionOperatorsInterface, + private itemsEncryption: ItemsEncryptionService, + private rootKeyManager: RootKeyManager, + private crypto: PureCryptoInterface, + private _createNewItemsKeyWithRollback: CreateNewItemsKeyWithRollback, + private _findDefaultItemsKey: FindDefaultItemsKey, + private _decryptErroredRootPayloads: DecryptErroredTypeAPayloads, + private _rootKeyEncryptPayloadWithKeyLookup: EncryptTypeAPayloadWithKeyLookup, + private _rootKeyEncryptPayload: EncryptTypeAPayload, + private _rootKeyDecryptPayload: DecryptTypeAPayload, + private _rootKeyDecryptPayloadWithKeyLookup: DecryptTypeAPayloadWithKeyLookup, + private _createDefaultItemsKey: CreateNewDefaultItemsKey, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) - this.crypto = crypto - - this.operators = new OperatorManager(crypto) - - this.rootKeyManager = new RootKeyManager( - device, - storage, - items, - mutator, - this.operators, - identifier, - internalEventBus, - ) internalEventBus.addEventHandler(this, RootKeyManagerEvent.RootKeyManagerKeyStatusChanged) - this.itemsEncryption = new ItemsEncryptionService(items, payloads, storage, this.operators, keys, internalEventBus) - UuidGenerator.SetGenerator(this.crypto.generateUUID) } @@ -168,25 +148,21 @@ export class EncryptionService } } - public override async blockDeinit(): Promise { - await Promise.all([this.rootKeyManager.blockDeinit(), this.itemsEncryption.blockDeinit()]) - - return super.blockDeinit() - } - public override deinit(): void { ;(this.items as unknown) = undefined ;(this.payloads as unknown) = undefined - ;(this.device as unknown) = undefined - ;(this.storage as unknown) = undefined - ;(this.crypto as unknown) = undefined ;(this.operators as unknown) = undefined - - this.itemsEncryption.deinit() ;(this.itemsEncryption as unknown) = undefined - - this.rootKeyManager.deinit() ;(this.rootKeyManager as unknown) = undefined + ;(this.crypto as unknown) = undefined + ;(this._createNewItemsKeyWithRollback as unknown) = undefined + ;(this._findDefaultItemsKey as unknown) = undefined + ;(this._decryptErroredRootPayloads as unknown) = undefined + ;(this._rootKeyEncryptPayloadWithKeyLookup as unknown) = undefined + ;(this._rootKeyEncryptPayload as unknown) = undefined + ;(this._rootKeyDecryptPayload as unknown) = undefined + ;(this._rootKeyDecryptPayloadWithKeyLookup as unknown) = undefined + ;(this._createDefaultItemsKey as unknown) = undefined super.deinit() } @@ -217,7 +193,7 @@ export class EncryptionService return !!this.getRootKey()?.signingKeyPair } - public async initialize() { + public async initialize(): Promise { await this.rootKeyManager.initialize() } @@ -250,7 +226,7 @@ export class EncryptionService return this.rootKeyManager.getUserVersion() } - public async upgradeAvailable() { + public async upgradeAvailable(): Promise { const accountUpgradeAvailable = this.accountUpgradeAvailable() const passcodeUpgradeAvailable = await this.passcodeUpgradeAvailable() return accountUpgradeAvailable || passcodeUpgradeAvailable @@ -268,31 +244,12 @@ export class EncryptionService await this.rootKeyManager.reencryptApplicableItemsAfterUserRootKeyChange() } - /** - * When the key system root key changes, we must re-encrypt all vault items keys - * with this new key system root key (by simply re-syncing). - */ - public async reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise { - const keySystemItemsKeys = this.keys.getKeySystemItemsKeys(keySystemIdentifier) - if (keySystemItemsKeys.length > 0) { - await this.mutator.setItemsDirty(keySystemItemsKeys) - } - } - public async createNewItemsKeyWithRollback(): Promise<() => Promise> { - const usecase = new CreateNewItemsKeyWithRollbackUseCase( - this.mutator, - this.items, - this.storage, - this.operators, - this.rootKeyManager, - ) - return usecase.execute() + return this._createNewItemsKeyWithRollback.execute() } public async decryptErroredPayloads(): Promise { - const usecase = new DecryptErroredRootPayloadsUseCase(this.payloads, this.operators, this.keys, this.rootKeyManager) - await usecase.execute() + await this._decryptErroredRootPayloads.execute() await this.itemsEncryption.decryptErroredItemPayloads() } @@ -326,18 +283,10 @@ export class EncryptionService usesKeySystemRootKeyWithKeyLookup, } = split - const rootKeyEncryptWithKeyLookupUsecase = new RootKeyEncryptPayloadWithKeyLookupUseCase( - this.operators, - this.keys, - this.rootKeyManager, - ) - - const rootKeyEncryptUsecase = new RootKeyEncryptPayloadUseCase(this.operators) - const signingKeyPair = this.hasSigningKeyPair() ? this.getSigningKeyPair() : undefined if (usesRootKey) { - const rootKeyEncrypted = await rootKeyEncryptUsecase.executeMany( + const rootKeyEncrypted = await this._rootKeyEncryptPayload.executeMany( usesRootKey.items, usesRootKey.key, signingKeyPair, @@ -346,7 +295,7 @@ export class EncryptionService } if (usesRootKeyWithKeyLookup) { - const rootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany( + const rootKeyEncrypted = await this._rootKeyEncryptPayloadWithKeyLookup.executeMany( usesRootKeyWithKeyLookup.items, signingKeyPair, ) @@ -354,7 +303,7 @@ export class EncryptionService } if (usesKeySystemRootKey) { - const keySystemRootKeyEncrypted = await rootKeyEncryptUsecase.executeMany( + const keySystemRootKeyEncrypted = await this._rootKeyEncryptPayload.executeMany( usesKeySystemRootKey.items, usesKeySystemRootKey.key, signingKeyPair, @@ -363,7 +312,7 @@ export class EncryptionService } if (usesKeySystemRootKeyWithKeyLookup) { - const keySystemRootKeyEncrypted = await rootKeyEncryptWithKeyLookupUsecase.executeMany( + const keySystemRootKeyEncrypted = await this._rootKeyEncryptPayloadWithKeyLookup.executeMany( usesKeySystemRootKeyWithKeyLookup.items, signingKeyPair, ) @@ -423,32 +372,26 @@ export class EncryptionService usesKeySystemRootKeyWithKeyLookup, } = split - const rootKeyDecryptUseCase = new RootKeyDecryptPayloadUseCase(this.operators) - - const rootKeyDecryptWithKeyLookupUsecase = new RootKeyDecryptPayloadWithKeyLookupUseCase( - this.operators, - this.keys, - this.rootKeyManager, - ) - if (usesRootKey) { - const rootKeyDecrypted = await rootKeyDecryptUseCase.executeMany(usesRootKey.items, usesRootKey.key) + const rootKeyDecrypted = await this._rootKeyDecryptPayload.executeMany(usesRootKey.items, usesRootKey.key) extendArray(resultParams, rootKeyDecrypted) } if (usesRootKeyWithKeyLookup) { - const rootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany(usesRootKeyWithKeyLookup.items) + const rootKeyDecrypted = await this._rootKeyDecryptPayloadWithKeyLookup.executeMany( + usesRootKeyWithKeyLookup.items, + ) extendArray(resultParams, rootKeyDecrypted) } if (usesKeySystemRootKey) { - const keySystemRootKeyDecrypted = await rootKeyDecryptUseCase.executeMany( + const keySystemRootKeyDecrypted = await this._rootKeyDecryptPayload.executeMany( usesKeySystemRootKey.items, usesKeySystemRootKey.key, ) extendArray(resultParams, keySystemRootKeyDecrypted) } if (usesKeySystemRootKeyWithKeyLookup) { - const keySystemRootKeyDecrypted = await rootKeyDecryptWithKeyLookupUsecase.executeMany( + const keySystemRootKeyDecrypted = await this._rootKeyDecryptPayloadWithKeyLookup.executeMany( usesKeySystemRootKeyWithKeyLookup.items, ) extendArray(resultParams, keySystemRootKeyDecrypted) @@ -640,56 +583,6 @@ export class EncryptionService .createKeySystemItemsKey(uuid, keySystemIdentifier, sharedVaultUuid, rootKeyToken) } - asymmetricallyEncryptMessage(dto: { - message: AsymmetricMessagePayload - senderKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - recipientPublicKey: string - }): AsymmetricallyEncryptedString { - const operator = this.operators.defaultOperator() - const encrypted = operator.asymmetricEncrypt({ - stringToEncrypt: JSON.stringify(dto.message), - senderKeyPair: dto.senderKeyPair, - senderSigningKeyPair: dto.senderSigningKeyPair, - recipientPublicKey: dto.recipientPublicKey, - }) - return encrypted - } - - asymmetricallyDecryptMessage(dto: { - encryptedString: AsymmetricallyEncryptedString - trustedSender: TrustedContactInterface | undefined - privateKey: string - }): M | undefined { - const defaultOperator = this.operators.defaultOperator() - const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.encryptedString) - const keyOperator = this.operators.operatorForVersion(version) - const decryptedResult = keyOperator.asymmetricDecrypt({ - stringToDecrypt: dto.encryptedString, - recipientSecretKey: dto.privateKey, - }) - - if (!decryptedResult) { - return undefined - } - - if (!decryptedResult.signatureVerified) { - return undefined - } - - if (dto.trustedSender) { - if (!dto.trustedSender.isPublicKeyTrusted(decryptedResult.senderPublicKey)) { - return undefined - } - - if (!dto.trustedSender.isSigningKeyTrusted(decryptedResult.signaturePublicKey)) { - return undefined - } - } - - return JSON.parse(decryptedResult.plaintext) - } - asymmetricSignatureVerifyDetached( encryptedString: AsymmetricallyEncryptedString, ): AsymmetricSignatureVerificationDetachedResult { @@ -699,7 +592,7 @@ export class EncryptionService return keyOperator.asymmetricSignatureVerifyDetached(encryptedString) } - getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PublicKeySet { + getSenderPublicKeySetFromAsymmetricallyEncryptedString(string: AsymmetricallyEncryptedString): PortablePublicKeySet { const defaultOperator = this.operators.defaultOperator() const version = defaultOperator.versionForAsymmetricallyEncryptedString(string) @@ -707,15 +600,6 @@ export class EncryptionService return keyOperator.getSenderPublicKeySetFromAsymmetricallyEncryptedString(string) } - public async decryptBackupFile( - file: BackupFile, - password?: string, - ): Promise)[]> { - const usecase = new DecryptBackupFileUseCase(this) - const result = await usecase.execute(file, password) - return result - } - /** * Creates a key params object from a raw object * @param keyParams - The raw key params object to create a KeyParams object from @@ -800,7 +684,7 @@ export class EncryptionService * If so, they must generate the unwrapping key by getting our saved wrapping key keyParams. * After unwrapping, the root key is automatically loaded. */ - public async unwrapRootKey(wrappingKey: RootKeyInterface) { + public async unwrapRootKey(wrappingKey: RootKeyInterface): Promise { return this.rootKeyManager.unwrapRootKey(wrappingKey) } /** @@ -902,7 +786,7 @@ export class EncryptionService * A new root key based items key is needed if our default items key content * isnt equal to our current root key */ - const defaultItemsKey = findDefaultItemsKey(this.itemsEncryption.getItemsKeys()) + const defaultItemsKey = this._findDefaultItemsKey.execute(this.itemsEncryption.getItemsKeys()).getValue() /** Shouldn't be undefined, but if it is, we'll take the corrective action */ if (!defaultItemsKey) { @@ -913,8 +797,7 @@ export class EncryptionService } public async createNewDefaultItemsKey(): Promise { - const usecase = new CreateNewDefaultItemsKeyUseCase(this.mutator, this.items, this.operators, this.rootKeyManager) - return usecase.execute() + return this._createDefaultItemsKey.execute() } public getPasswordCreatedDate(): Date | undefined { @@ -1001,7 +884,7 @@ export class EncryptionService private async handleFullSyncCompletion() { /** Always create a new items key after full sync, if no items key is found */ - const currentItemsKey = findDefaultItemsKey(this.itemsEncryption.getItemsKeys()) + const currentItemsKey = this._findDefaultItemsKey.execute(this.itemsEncryption.getItemsKeys()).getValue() if (!currentItemsKey) { await this.createNewDefaultItemsKey() if (this.rootKeyManager.getKeyMode() === KeyMode.WrapperOnly) { diff --git a/packages/services/src/Domain/Encryption/Functions.ts b/packages/services/src/Domain/Encryption/Functions.ts index aa3ef03e8..11fa6b9a3 100644 --- a/packages/services/src/Domain/Encryption/Functions.ts +++ b/packages/services/src/Domain/Encryption/Functions.ts @@ -5,11 +5,12 @@ import { ItemsKeyContent, RootKeyInterface, } from '@standardnotes/models' -import { EncryptionProviderInterface, KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption' +import { KeyRecoveryStrings, SNRootKeyParams } from '@standardnotes/encryption' import { ChallengeServiceInterface } from '../Challenge/ChallengeServiceInterface' import { ChallengePrompt } from '../Challenge/Prompt/ChallengePrompt' import { ChallengeReason } from '../Challenge/Types/ChallengeReason' import { ChallengeValidation } from '../Challenge/Types/ChallengeValidation' +import { EncryptionProviderInterface } from './EncryptionProviderInterface' export async function DecryptItemsKeyWithUserFallback( itemsKey: EncryptedPayloadInterface, diff --git a/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.spec.ts b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.spec.ts new file mode 100644 index 000000000..95ff45158 --- /dev/null +++ b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.spec.ts @@ -0,0 +1,178 @@ +import { + ContactPublicKeySet, + ContactPublicKeySetInterface, + PublicKeyTrustStatus, + TrustedContactInterface, +} from '@standardnotes/models' +import { DecryptMessage } from './DecryptMessage' +import { OperatorInterface, EncryptionOperatorsInterface } from '@standardnotes/encryption' +import { ProtocolVersion } from '@standardnotes/common' + +function createMockPublicKeySetChain(): ContactPublicKeySetInterface { + const nMinusOne = new ContactPublicKeySet({ + encryption: 'encryption-public-key-n-1', + signing: 'signing-public-key-n-1', + timestamp: new Date(-1), + previousKeySet: undefined, + }) + + const root = new ContactPublicKeySet({ + encryption: 'encryption-public-key', + signing: 'signing-public-key', + timestamp: new Date(), + previousKeySet: nMinusOne, + }) + + return root +} + +describe('DecryptMessage', () => { + let usecase: DecryptMessage + let operator: jest.Mocked + + beforeEach(() => { + operator = {} as jest.Mocked + operator.versionForAsymmetricallyEncryptedString = jest.fn().mockReturnValue(ProtocolVersion.V004) + + const operators = {} as jest.Mocked + operators.defaultOperator = jest.fn().mockReturnValue(operator) + operators.operatorForVersion = jest.fn().mockReturnValue(operator) + + usecase = new DecryptMessage(operators) + }) + + it('should fail if fails to decrypt', () => { + operator.asymmetricDecrypt = jest.fn().mockReturnValue(null) + const result = usecase.execute({ + message: 'encrypted', + sender: undefined, + privateKey: 'private-key', + }) + + expect(result.isFailed()).toEqual(true) + expect(result.getError()).toEqual('Failed to decrypt message') + }) + + it('should fail if signature is invalid', () => { + operator.asymmetricDecrypt = jest.fn().mockReturnValue({ + plaintext: 'decrypted', + signatureVerified: false, + signaturePublicKey: 'signing-public-key', + senderPublicKey: 'encryption-public-key', + }) + + const result = usecase.execute({ + message: 'encrypted', + sender: undefined, + privateKey: 'private-key', + }) + + expect(result.isFailed()).toEqual(true) + expect(result.getError()).toEqual('Failed to verify signature') + }) + + describe('with trusted sender', () => { + it('should fail if encryption public key is not trusted', () => { + operator.asymmetricDecrypt = jest.fn().mockReturnValue({ + plaintext: 'decrypted', + signatureVerified: true, + signaturePublicKey: 'signing-public-key', + senderPublicKey: 'encryption-public-key', + }) + + const senderContact = { + name: 'Other', + contactUuid: '456', + publicKeySet: createMockPublicKeySetChain(), + isMe: false, + } as jest.Mocked + + senderContact.getTrustStatusForPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.NotTrusted) + + const result = usecase.execute({ + message: 'encrypted', + sender: senderContact, + privateKey: 'private-key', + }) + + expect(result.isFailed()).toEqual(true) + expect(result.getError()).toEqual('Sender public key is not trusted') + }) + + it('should fail if signing public key is not trusted', () => { + operator.asymmetricDecrypt = jest.fn().mockReturnValue({ + plaintext: 'decrypted', + signatureVerified: true, + signaturePublicKey: 'signing-public-key', + senderPublicKey: 'encryption-public-key', + }) + + const senderContact = { + name: 'Other', + contactUuid: '456', + publicKeySet: createMockPublicKeySetChain(), + isMe: false, + } as jest.Mocked + + senderContact.getTrustStatusForPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted) + senderContact.getTrustStatusForSigningPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.NotTrusted) + + const result = usecase.execute({ + message: 'encrypted', + sender: senderContact, + privateKey: 'private-key', + }) + + expect(result.isFailed()).toEqual(true) + expect(result.getError()).toEqual('Signature public key is not trusted') + }) + + it('should succeed with valid signature and encryption key', () => { + operator.asymmetricDecrypt = jest.fn().mockReturnValue({ + plaintext: '{"foo": "bar"}', + signatureVerified: true, + signaturePublicKey: 'signing-public-key', + senderPublicKey: 'encryption-public-key', + }) + + const senderContact = { + name: 'Other', + contactUuid: '456', + publicKeySet: createMockPublicKeySetChain(), + isMe: false, + } as jest.Mocked + + senderContact.getTrustStatusForPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted) + senderContact.getTrustStatusForSigningPublicKey = jest.fn().mockReturnValue(PublicKeyTrustStatus.Trusted) + + const result = usecase.execute({ + message: 'encrypted', + sender: senderContact, + privateKey: 'private-key', + }) + + expect(result.isFailed()).toEqual(false) + expect(result.getValue()).toEqual({ foo: 'bar' }) + }) + }) + + describe('without trusted sender', () => { + it('should succeed with valid signature and encryption key', () => { + operator.asymmetricDecrypt = jest.fn().mockReturnValue({ + plaintext: '{"foo": "bar"}', + signatureVerified: true, + signaturePublicKey: 'signing-public-key', + senderPublicKey: 'encryption-public-key', + }) + + const result = usecase.execute({ + message: 'encrypted', + sender: undefined, + privateKey: 'private-key', + }) + + expect(result.isFailed()).toEqual(false) + expect(result.getValue()).toEqual({ foo: 'bar' }) + }) + }) +}) diff --git a/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.ts b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.ts new file mode 100644 index 000000000..7ca5f64f4 --- /dev/null +++ b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptMessage.ts @@ -0,0 +1,44 @@ +import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core' +import { EncryptionOperatorsInterface } from '@standardnotes/encryption' +import { AsymmetricMessagePayload, PublicKeyTrustStatus, TrustedContactInterface } from '@standardnotes/models' + +export class DecryptMessage implements SyncUseCaseInterface { + constructor(private operators: EncryptionOperatorsInterface) {} + + execute(dto: { + message: string + sender: TrustedContactInterface | undefined + privateKey: string + }): Result { + const defaultOperator = this.operators.defaultOperator() + const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.message) + const keyOperator = this.operators.operatorForVersion(version) + + const decryptedResult = keyOperator.asymmetricDecrypt({ + stringToDecrypt: dto.message, + recipientSecretKey: dto.privateKey, + }) + + if (!decryptedResult) { + return Result.fail('Failed to decrypt message') + } + + if (!decryptedResult.signatureVerified) { + return Result.fail('Failed to verify signature') + } + + if (dto.sender) { + const publicKeyTrustStatus = dto.sender.getTrustStatusForPublicKey(decryptedResult.senderPublicKey) + if (publicKeyTrustStatus !== PublicKeyTrustStatus.Trusted) { + return Result.fail('Sender public key is not trusted') + } + + const signingKeyTrustStatus = dto.sender.getTrustStatusForSigningPublicKey(decryptedResult.signaturePublicKey) + if (signingKeyTrustStatus !== PublicKeyTrustStatus.Trusted) { + return Result.fail('Signature public key is not trusted') + } + } + + return Result.ok(JSON.parse(decryptedResult.plaintext)) + } +} diff --git a/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptOwnMessage.ts b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptOwnMessage.ts new file mode 100644 index 000000000..a24c1dabc --- /dev/null +++ b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/DecryptOwnMessage.ts @@ -0,0 +1,31 @@ +import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core' +import { EncryptionOperatorsInterface } from '@standardnotes/encryption' +import { AsymmetricMessagePayload } from '@standardnotes/models' + +export class DecryptOwnMessage implements SyncUseCaseInterface { + constructor(private operators: EncryptionOperatorsInterface) {} + + execute(dto: { message: string; privateKey: string; recipientPublicKey: string }): Result { + const defaultOperator = this.operators.defaultOperator() + const version = defaultOperator.versionForAsymmetricallyEncryptedString(dto.message) + const keyOperator = this.operators.operatorForVersion(version) + + const result = keyOperator.asymmetricDecryptOwnMessage({ + message: dto.message, + ownPrivateKey: dto.privateKey, + recipientPublicKey: dto.recipientPublicKey, + }) + + if (result.isFailed()) { + return Result.fail(result.getError()) + } + + const decryptedObject = result.getValue() + + if (!decryptedObject.signatureVerified) { + return Result.fail('Failed to verify signature') + } + + return Result.ok(JSON.parse(decryptedObject.plaintext)) + } +} diff --git a/packages/services/src/Domain/Encryption/UseCase/Asymmetric/EncryptMessage.ts b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/EncryptMessage.ts new file mode 100644 index 000000000..7641c83e5 --- /dev/null +++ b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/EncryptMessage.ts @@ -0,0 +1,28 @@ +import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core' +import { EncryptionOperatorsInterface } from '@standardnotes/encryption' +import { AsymmetricMessagePayload } from '@standardnotes/models' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' + +export class EncryptMessage implements SyncUseCaseInterface { + constructor(private operators: EncryptionOperatorsInterface) {} + + execute(dto: { + message: AsymmetricMessagePayload + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + recipientPublicKey: string + }): Result { + const operator = this.operators.defaultOperator() + + const encrypted = operator.asymmetricEncrypt({ + stringToEncrypt: JSON.stringify(dto.message), + senderKeyPair: dto.keys.encryption, + senderSigningKeyPair: dto.keys.signing, + recipientPublicKey: dto.recipientPublicKey, + }) + + return Result.ok(encrypted) + } +} diff --git a/packages/services/src/Domain/Encryption/UseCase/Asymmetric/GetMessageAdditionalData.ts b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/GetMessageAdditionalData.ts new file mode 100644 index 000000000..00fc585bd --- /dev/null +++ b/packages/services/src/Domain/Encryption/UseCase/Asymmetric/GetMessageAdditionalData.ts @@ -0,0 +1,13 @@ +import { SyncUseCaseInterface, Result } from '@standardnotes/domain-core' +import { AsymmetricallyEncryptedString, EncryptionOperatorsInterface } from '@standardnotes/encryption' +import { AsymmetricItemAdditionalData } from '@standardnotes/encryption/src/Domain/Types/EncryptionAdditionalData' + +export class GetMessageAdditionalData implements SyncUseCaseInterface { + constructor(private operators: EncryptionOperatorsInterface) {} + + execute(dto: { message: AsymmetricallyEncryptedString }): Result { + const operator = this.operators.defaultOperator() + + return operator.asymmetricStringGetAdditionalData({ encryptedString: dto.message }) + } +} diff --git a/packages/services/src/Domain/Encryption/DecryptBackupFileUseCase.ts b/packages/services/src/Domain/Encryption/UseCase/DecryptBackupFile.ts similarity index 98% rename from packages/services/src/Domain/Encryption/DecryptBackupFileUseCase.ts rename to packages/services/src/Domain/Encryption/UseCase/DecryptBackupFile.ts index f7d2782fb..9848173a8 100644 --- a/packages/services/src/Domain/Encryption/DecryptBackupFileUseCase.ts +++ b/packages/services/src/Domain/Encryption/UseCase/DecryptBackupFile.ts @@ -37,10 +37,10 @@ import { } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' import { extendArray } from '@standardnotes/utils' -import { EncryptionService } from './EncryptionService' +import { EncryptionService } from '../EncryptionService' import { ContentType } from '@standardnotes/domain-core' -export class DecryptBackupFileUseCase { +export class DecryptBackupFile { constructor(private encryption: EncryptionService) {} async execute( @@ -53,7 +53,7 @@ export class DecryptBackupFileUseCase { } else if (isDecryptedTransferPayload(item)) { return new DecryptedPayload(item) } else { - throw Error('Unhandled case in decryptBackupFile') + throw Error('Unhandled case in DecryptBackupFile') } }) diff --git a/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey.ts b/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey.ts index 63c437fc0..8e52a6afc 100644 --- a/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey.ts +++ b/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey.ts @@ -1,4 +1,4 @@ -import { OperatorManager } from '@standardnotes/encryption' +import { EncryptionOperatorsInterface } from '@standardnotes/encryption' import { ProtocolVersionLastNonrootItemsKey, ProtocolVersionLatest, compareVersions } from '@standardnotes/common' import { CreateDecryptedItemFromPayload, @@ -12,7 +12,7 @@ import { import { UuidGenerator } from '@standardnotes/utils' import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface' import { ItemManagerInterface } from '../../../Item/ItemManagerInterface' -import { RootKeyManager } from '../../RootKey/RootKeyManager' +import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager' import { ContentType } from '@standardnotes/domain-core' /** @@ -20,11 +20,11 @@ import { ContentType } from '@standardnotes/domain-core' * Consumer must call sync. If the protocol version <= 003, only one items key should be created, * and its .itemsKey value should be equal to the root key masterKey value. */ -export class CreateNewDefaultItemsKeyUseCase { +export class CreateNewDefaultItemsKey { constructor( private mutator: MutatorClientInterface, private items: ItemManagerInterface, - private operatorManager: OperatorManager, + private operators: EncryptionOperatorsInterface, private rootKeyManager: RootKeyManager, ) {} @@ -48,7 +48,7 @@ export class CreateNewDefaultItemsKeyUseCase { itemTemplate = CreateDecryptedItemFromPayload(payload) } else { /** Create independent items key */ - itemTemplate = this.operatorManager.operatorForVersion(operatorVersion).createItemsKey() + itemTemplate = this.operators.operatorForVersion(operatorVersion).createItemsKey() } const itemsKeys = this.items.getDisplayableItemsKeys() diff --git a/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback.ts b/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback.ts index f9ef0710a..bee554b26 100644 --- a/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback.ts +++ b/packages/services/src/Domain/Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback.ts @@ -1,35 +1,25 @@ -import { StorageServiceInterface } from './../../../Storage/StorageServiceInterface' -import { ItemsKeyMutator, OperatorManager, findDefaultItemsKey } from '@standardnotes/encryption' +import { ItemsKeyMutator } from '@standardnotes/encryption' import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface' import { ItemManagerInterface } from '../../../Item/ItemManagerInterface' -import { RootKeyManager } from '../../RootKey/RootKeyManager' -import { CreateNewDefaultItemsKeyUseCase } from './CreateNewDefaultItemsKey' -import { RemoveItemsLocallyUseCase } from '../../../UseCase/RemoveItemsLocally' - -export class CreateNewItemsKeyWithRollbackUseCase { - private createDefaultItemsKeyUseCase = new CreateNewDefaultItemsKeyUseCase( - this.mutator, - this.items, - this.operatorManager, - this.rootKeyManager, - ) - - private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage) +import { CreateNewDefaultItemsKey } from './CreateNewDefaultItemsKey' +import { RemoveItemsLocally } from '../../../UseCase/RemoveItemsLocally' +import { FindDefaultItemsKey } from './FindDefaultItemsKey' +export class CreateNewItemsKeyWithRollback { constructor( private mutator: MutatorClientInterface, private items: ItemManagerInterface, - private storage: StorageServiceInterface, - private operatorManager: OperatorManager, - private rootKeyManager: RootKeyManager, + private createDefaultItemsKey: CreateNewDefaultItemsKey, + private removeItemsLocally: RemoveItemsLocally, + private findDefaultItemsKey: FindDefaultItemsKey, ) {} async execute(): Promise<() => Promise> { - const currentDefaultItemsKey = findDefaultItemsKey(this.items.getDisplayableItemsKeys()) - const newDefaultItemsKey = await this.createDefaultItemsKeyUseCase.execute() + const currentDefaultItemsKey = this.findDefaultItemsKey.execute(this.items.getDisplayableItemsKeys()).getValue() + const newDefaultItemsKey = await this.createDefaultItemsKey.execute() const rollback = async () => { - await this.removeItemsLocallyUsecase.execute([newDefaultItemsKey]) + await this.removeItemsLocally.execute([newDefaultItemsKey]) if (currentDefaultItemsKey) { await this.mutator.changeItem(currentDefaultItemsKey, (mutator) => { diff --git a/packages/services/src/Domain/Encryption/UseCase/ItemsKey/FindDefaultItemsKey.ts b/packages/services/src/Domain/Encryption/UseCase/ItemsKey/FindDefaultItemsKey.ts new file mode 100644 index 000000000..46194af11 --- /dev/null +++ b/packages/services/src/Domain/Encryption/UseCase/ItemsKey/FindDefaultItemsKey.ts @@ -0,0 +1,33 @@ +import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core' +import { ItemsKeyInterface } from '@standardnotes/models' + +export class FindDefaultItemsKey implements SyncUseCaseInterface { + execute(itemsKeys: ItemsKeyInterface[]): Result { + if (itemsKeys.length === 1) { + return Result.ok(itemsKeys[0]) + } + + const defaultKeys = itemsKeys.filter((key) => { + return key.isDefault + }) + + if (defaultKeys.length === 0) { + return Result.ok(undefined) + } + + if (defaultKeys.length === 1) { + return Result.ok(defaultKeys[0]) + } + + /** + * Prioritize one that is synced, as neverSynced keys will likely be deleted after + * DownloadFirst sync. + */ + const syncedKeys = defaultKeys.filter((key) => !key.neverSynced) + if (syncedKeys.length > 0) { + return Result.ok(syncedKeys[0]) + } + + return Result.ok(undefined) + } +} diff --git a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptErroredPayloads.ts b/packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptErroredPayloads.ts similarity index 69% rename from packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptErroredPayloads.ts rename to packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptErroredPayloads.ts index ebfa9c93e..56a6845eb 100644 --- a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptErroredPayloads.ts +++ b/packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptErroredPayloads.ts @@ -6,15 +6,16 @@ import { PayloadEmitSource, SureFindPayload, } from '@standardnotes/models' -import { PayloadManagerInterface } from './../../../Payloads/PayloadManagerInterface' -import { KeySystemKeyManagerInterface, OperatorManager, isErrorDecryptingParameters } from '@standardnotes/encryption' -import { RootKeyDecryptPayloadWithKeyLookupUseCase } from './DecryptPayloadWithKeyLookup' -import { RootKeyManager } from '../../RootKey/RootKeyManager' +import { PayloadManagerInterface } from '../../../Payloads/PayloadManagerInterface' +import { isErrorDecryptingParameters, EncryptionOperatorsInterface } from '@standardnotes/encryption' +import { DecryptTypeAPayloadWithKeyLookup } from './DecryptPayloadWithKeyLookup' +import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager' +import { KeySystemKeyManagerInterface } from '../../../KeySystem/KeySystemKeyManagerInterface' -export class DecryptErroredRootPayloadsUseCase { +export class DecryptErroredTypeAPayloads { constructor( private payloads: PayloadManagerInterface, - private operatorManager: OperatorManager, + private operatorManager: EncryptionOperatorsInterface, private keySystemKeyManager: KeySystemKeyManagerInterface, private rootKeyManager: RootKeyManager, ) {} @@ -28,7 +29,7 @@ export class DecryptErroredRootPayloadsUseCase { return } - const usecase = new RootKeyDecryptPayloadWithKeyLookupUseCase( + const usecase = new DecryptTypeAPayloadWithKeyLookup( this.operatorManager, this.keySystemKeyManager, this.rootKeyManager, diff --git a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptPayload.ts b/packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptPayload.ts similarity index 85% rename from packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptPayload.ts rename to packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptPayload.ts index 18d84bac4..5e7f31309 100644 --- a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptPayload.ts +++ b/packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptPayload.ts @@ -1,8 +1,8 @@ import { DecryptedParameters, ErrorDecryptingParameters, - OperatorManager, decryptPayload, + EncryptionOperatorsInterface, } from '@standardnotes/encryption' import { EncryptedPayloadInterface, @@ -11,8 +11,8 @@ import { RootKeyInterface, } from '@standardnotes/models' -export class RootKeyDecryptPayloadUseCase { - constructor(private operatorManager: OperatorManager) {} +export class DecryptTypeAPayload { + constructor(private operatorManager: EncryptionOperatorsInterface) {} async executeOne( payload: EncryptedPayloadInterface, diff --git a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptPayloadWithKeyLookup.ts b/packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptPayloadWithKeyLookup.ts similarity index 73% rename from packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptPayloadWithKeyLookup.ts rename to packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptPayloadWithKeyLookup.ts index 1611756cd..2745b05b2 100644 --- a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/DecryptPayloadWithKeyLookup.ts +++ b/packages/services/src/Domain/Encryption/UseCase/TypeA/DecryptPayloadWithKeyLookup.ts @@ -1,9 +1,4 @@ -import { - DecryptedParameters, - ErrorDecryptingParameters, - KeySystemKeyManagerInterface, - OperatorManager, -} from '@standardnotes/encryption' +import { DecryptedParameters, ErrorDecryptingParameters, EncryptionOperatorsInterface } from '@standardnotes/encryption' import { ContentTypeUsesKeySystemRootKeyEncryption, EncryptedPayloadInterface, @@ -12,12 +7,13 @@ import { RootKeyInterface, } from '@standardnotes/models' -import { RootKeyDecryptPayloadUseCase } from './DecryptPayload' -import { RootKeyManager } from '../../RootKey/RootKeyManager' +import { DecryptTypeAPayload } from './DecryptPayload' +import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager' +import { KeySystemKeyManagerInterface } from '../../../KeySystem/KeySystemKeyManagerInterface' -export class RootKeyDecryptPayloadWithKeyLookupUseCase { +export class DecryptTypeAPayloadWithKeyLookup { constructor( - private operatorManager: OperatorManager, + private operators: EncryptionOperatorsInterface, private keySystemKeyManager: KeySystemKeyManagerInterface, private rootKeyManager: RootKeyManager, ) {} @@ -43,7 +39,7 @@ export class RootKeyDecryptPayloadWithKeyLookupUseCase { } } - const usecase = new RootKeyDecryptPayloadUseCase(this.operatorManager) + const usecase = new DecryptTypeAPayload(this.operators) return usecase.executeOne(payload, key) } diff --git a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/EncryptPayload.ts b/packages/services/src/Domain/Encryption/UseCase/TypeA/EncryptPayload.ts similarity index 69% rename from packages/services/src/Domain/Encryption/UseCase/RootEncryption/EncryptPayload.ts rename to packages/services/src/Domain/Encryption/UseCase/TypeA/EncryptPayload.ts index 09a3453c9..23fe5052f 100644 --- a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/EncryptPayload.ts +++ b/packages/services/src/Domain/Encryption/UseCase/TypeA/EncryptPayload.ts @@ -1,16 +1,16 @@ -import { EncryptedOutputParameters, OperatorManager, encryptPayload } from '@standardnotes/encryption' +import { EncryptedOutputParameters, EncryptionOperatorsInterface, encryptPayload } from '@standardnotes/encryption' import { DecryptedPayloadInterface, KeySystemRootKeyInterface, RootKeyInterface } from '@standardnotes/models' import { PkcKeyPair } from '@standardnotes/sncrypto-common' -export class RootKeyEncryptPayloadUseCase { - constructor(private operatorManager: OperatorManager) {} +export class EncryptTypeAPayload { + constructor(private operators: EncryptionOperatorsInterface) {} async executeOne( payload: DecryptedPayloadInterface, key: RootKeyInterface | KeySystemRootKeyInterface, signingKeyPair?: PkcKeyPair, ): Promise { - return encryptPayload(payload, key, this.operatorManager, signingKeyPair) + return encryptPayload(payload, key, this.operators, signingKeyPair) } async executeMany( diff --git a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/EncryptPayloadWithKeyLookup.ts b/packages/services/src/Domain/Encryption/UseCase/TypeA/EncryptPayloadWithKeyLookup.ts similarity index 73% rename from packages/services/src/Domain/Encryption/UseCase/RootEncryption/EncryptPayloadWithKeyLookup.ts rename to packages/services/src/Domain/Encryption/UseCase/TypeA/EncryptPayloadWithKeyLookup.ts index 1b693610e..e23fac7d7 100644 --- a/packages/services/src/Domain/Encryption/UseCase/RootEncryption/EncryptPayloadWithKeyLookup.ts +++ b/packages/services/src/Domain/Encryption/UseCase/TypeA/EncryptPayloadWithKeyLookup.ts @@ -1,4 +1,4 @@ -import { EncryptedOutputParameters, KeySystemKeyManagerInterface, OperatorManager } from '@standardnotes/encryption' +import { EncryptedOutputParameters, EncryptionOperatorsInterface } from '@standardnotes/encryption' import { ContentTypeUsesKeySystemRootKeyEncryption, DecryptedPayloadInterface, @@ -7,12 +7,13 @@ import { } from '@standardnotes/models' import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { RootKeyEncryptPayloadUseCase } from './EncryptPayload' -import { RootKeyManager } from '../../RootKey/RootKeyManager' +import { EncryptTypeAPayload } from './EncryptPayload' +import { RootKeyManager } from '../../../RootKeyManager/RootKeyManager' +import { KeySystemKeyManagerInterface } from '../../../KeySystem/KeySystemKeyManagerInterface' -export class RootKeyEncryptPayloadWithKeyLookupUseCase { +export class EncryptTypeAPayloadWithKeyLookup { constructor( - private operatorManager: OperatorManager, + private operators: EncryptionOperatorsInterface, private keySystemKeyManager: KeySystemKeyManagerInterface, private rootKeyManager: RootKeyManager, ) {} @@ -35,7 +36,7 @@ export class RootKeyEncryptPayloadWithKeyLookupUseCase { throw Error('Attempting root key encryption with no root key') } - const usecase = new RootKeyEncryptPayloadUseCase(this.operatorManager) + const usecase = new EncryptTypeAPayload(this.operators) return usecase.executeOne(payload, key, signingKeyPair) } diff --git a/packages/services/src/Domain/Feature/FeaturesClientInterface.ts b/packages/services/src/Domain/Feature/FeaturesClientInterface.ts index 2d0563e38..0b9435fcb 100644 --- a/packages/services/src/Domain/Feature/FeaturesClientInterface.ts +++ b/packages/services/src/Domain/Feature/FeaturesClientInterface.ts @@ -5,6 +5,7 @@ import { FeatureStatus } from './FeatureStatus' import { SetOfflineFeaturesFunctionResponse } from './SetOfflineFeaturesFunctionResponse' export interface FeaturesClientInterface { + initializeFromDisk(): void getFeatureStatus(featureId: FeatureIdentifier, options?: { inContextOfItem?: DecryptedItemInterface }): FeatureStatus hasMinimumRole(role: string): boolean diff --git a/packages/services/src/Domain/Files/FileService.spec.ts b/packages/services/src/Domain/Files/FileService.spec.ts index 49b674484..0d861884a 100644 --- a/packages/services/src/Domain/Files/FileService.spec.ts +++ b/packages/services/src/Domain/Files/FileService.spec.ts @@ -1,18 +1,19 @@ +import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface' +import { LegacyApiServiceInterface } from './../Api/LegacyApiServiceInterface' import { PureCryptoInterface, StreamEncryptor } from '@standardnotes/sncrypto-common' import { FileItem } from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { ChallengeServiceInterface } from '../Challenge' import { InternalEventBusInterface, MutatorClientInterface } from '..' import { AlertService } from '../Alert/AlertService' -import { ApiServiceInterface } from '../Api/ApiServiceInterface' + import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { FileService } from './FileService' import { BackupServiceInterface } from '@standardnotes/files' import { HttpServiceInterface } from '@standardnotes/api' describe('fileService', () => { - let apiService: ApiServiceInterface + let apiService: LegacyApiServiceInterface let itemManager: ItemManagerInterface let mutator: MutatorClientInterface let syncService: SyncServiceInterface @@ -26,7 +27,7 @@ describe('fileService', () => { let http: HttpServiceInterface beforeEach(() => { - apiService = {} as jest.Mocked + apiService = {} as jest.Mocked apiService.addEventObserver = jest.fn() apiService.createUserFileValetToken = jest.fn() apiService.deleteFile = jest.fn().mockReturnValue({}) diff --git a/packages/services/src/Domain/Files/FileService.ts b/packages/services/src/Domain/Files/FileService.ts index 2dc982fbf..4ed66a0b4 100644 --- a/packages/services/src/Domain/Files/FileService.ts +++ b/packages/services/src/Domain/Files/FileService.ts @@ -20,7 +20,7 @@ import { } from '@standardnotes/models' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { spaceSeparatedStrings, UuidGenerator } from '@standardnotes/utils' -import { EncryptionProviderInterface, SNItemsKey } from '@standardnotes/encryption' +import { SNItemsKey } from '@standardnotes/encryption' import { DownloadAndDecryptFileOperation, EncryptAndUploadFileOperation, @@ -50,6 +50,7 @@ import { DecryptItemsKeyWithUserFallback } from '../Encryption/Functions' import { log, LoggingDomain } from '../Logging' import { SharedVaultServer, SharedVaultServerInterface, HttpServiceInterface } from '@standardnotes/api' import { ContentType } from '@standardnotes/domain-core' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' const OneHundredMb = 100 * 1_000_000 @@ -60,7 +61,7 @@ export class FileService extends AbstractService implements FilesClientInterface constructor( private api: FilesApiInterface, private mutator: MutatorClientInterface, - private syncService: SyncServiceInterface, + private sync: SyncServiceInterface, private encryptor: EncryptionProviderInterface, private challengor: ChallengeServiceInterface, http: HttpServiceInterface, @@ -80,7 +81,7 @@ export class FileService extends AbstractService implements FilesClientInterface ;(this.encryptedCache as unknown) = undefined ;(this.api as unknown) = undefined ;(this.encryptor as unknown) = undefined - ;(this.syncService as unknown) = undefined + ;(this.sync as unknown) = undefined ;(this.alertService as unknown) = undefined ;(this.challengor as unknown) = undefined ;(this.crypto as unknown) = undefined @@ -270,7 +271,7 @@ export class FileService extends AbstractService implements FilesClientInterface operation.vault, ) - await this.syncService.sync() + await this.sync.sync() return file } @@ -401,7 +402,7 @@ export class FileService extends AbstractService implements FilesClientInterface } await this.mutator.setItemToBeDeleted(file) - await this.syncService.sync() + await this.sync.sync() return undefined } diff --git a/packages/services/src/Domain/History/HistoryServiceInterface.ts b/packages/services/src/Domain/History/HistoryServiceInterface.ts index dbfe65a23..b7d9b50d0 100644 --- a/packages/services/src/Domain/History/HistoryServiceInterface.ts +++ b/packages/services/src/Domain/History/HistoryServiceInterface.ts @@ -1,5 +1,6 @@ -import { HistoryMap } from '@standardnotes/models' +import { HistoryEntry, HistoryMap, SNNote } from '@standardnotes/models' export interface HistoryServiceInterface { getHistoryMapCopy(): HistoryMap + sessionHistoryForItem(item: SNNote): HistoryEntry[] } diff --git a/packages/services/src/Domain/HomeServer/HomeServerService.ts b/packages/services/src/Domain/HomeServer/HomeServerService.ts index aa99028e5..599698a4d 100644 --- a/packages/services/src/Domain/HomeServer/HomeServerService.ts +++ b/packages/services/src/Domain/HomeServer/HomeServerService.ts @@ -1,3 +1,7 @@ +import { InternalEventInterface } from './../Internal/InternalEventInterface' +import { ApplicationStageChangedEventPayload } from './../Event/ApplicationStageChangedEventPayload' +import { ApplicationEvent } from './../Event/ApplicationEvent' +import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface' import { Result } from '@standardnotes/domain-core' import { ApplicationStage } from '../Application/ApplicationStage' @@ -10,7 +14,10 @@ import { HomeServerServiceInterface } from './HomeServerServiceInterface' import { HomeServerEnvironmentConfiguration } from './HomeServerEnvironmentConfiguration' import { HomeServerStatus } from './HomeServerStatus' -export class HomeServerService extends AbstractService implements HomeServerServiceInterface { +export class HomeServerService + extends AbstractService + implements HomeServerServiceInterface, InternalEventHandlerInterface +{ private readonly HOME_SERVER_DATA_DIRECTORY_NAME = '.homeserver' constructor( @@ -20,26 +27,28 @@ export class HomeServerService extends AbstractService implements HomeServerServ super(internalEventBus) } + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + + switch (stage) { + case ApplicationStage.StorageDecrypted_09: { + await this.setHomeServerDataLocationOnDevice() + break + } + case ApplicationStage.Launched_10: { + await this.startHomeServerIfItIsEnabled() + break + } + } + } + } + override deinit() { ;(this.desktopDevice as unknown) = undefined super.deinit() } - override async handleApplicationStage(stage: ApplicationStage) { - await super.handleApplicationStage(stage) - - switch (stage) { - case ApplicationStage.StorageDecrypted_09: { - await this.setHomeServerDataLocationOnDevice() - break - } - case ApplicationStage.Launched_10: { - await this.startHomeServerIfItIsEnabled() - break - } - } - } - async getHomeServerStatus(): Promise { const isHomeServerRunning = await this.desktopDevice.isHomeServerRunning() diff --git a/packages/services/src/Domain/Encryption/ItemsEncryption.ts b/packages/services/src/Domain/ItemsEncryption/ItemsEncryption.ts similarity index 86% rename from packages/services/src/Domain/Encryption/ItemsEncryption.ts rename to packages/services/src/Domain/ItemsEncryption/ItemsEncryption.ts index 00a62b8bb..6b5aa504e 100644 --- a/packages/services/src/Domain/Encryption/ItemsEncryption.ts +++ b/packages/services/src/Domain/ItemsEncryption/ItemsEncryption.ts @@ -1,15 +1,14 @@ +import { FindDefaultItemsKey } from './../Encryption/UseCase/ItemsKey/FindDefaultItemsKey' import { ProtocolVersion } from '@standardnotes/common' import { DecryptedParameters, ErrorDecryptingParameters, - findDefaultItemsKey, isErrorDecryptingParameters, - OperatorManager, StandardException, encryptPayload, decryptPayload, EncryptedOutputParameters, - KeySystemKeyManagerInterface, + EncryptionOperatorsInterface, } from '@standardnotes/encryption' import { ContentTypeUsesKeySystemRootKeyEncryption, @@ -33,22 +32,24 @@ import { AbstractService } from '../Service/AbstractService' import { StorageServiceInterface } from '../Storage/StorageServiceInterface' import { PkcKeyPair } from '@standardnotes/sncrypto-common' import { ContentType } from '@standardnotes/domain-core' +import { KeySystemKeyManagerInterface } from '../KeySystem/KeySystemKeyManagerInterface' export class ItemsEncryptionService extends AbstractService { private removeItemsObserver!: () => void public userVersion?: ProtocolVersion constructor( - private itemManager: ItemManagerInterface, - private payloadManager: PayloadManagerInterface, - private storageService: StorageServiceInterface, - private operatorManager: OperatorManager, + private items: ItemManagerInterface, + private payloads: PayloadManagerInterface, + private storage: StorageServiceInterface, + private operators: EncryptionOperatorsInterface, private keys: KeySystemKeyManagerInterface, + private _findDefaultItemsKey: FindDefaultItemsKey, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) - this.removeItemsObserver = this.itemManager.addObserver([ContentType.TYPES.ItemsKey], ({ changed, inserted }) => { + this.removeItemsObserver = this.items.addObserver([ContentType.TYPES.ItemsKey], ({ changed, inserted }) => { if (changed.concat(inserted).length > 0) { void this.decryptErroredItemPayloads() } @@ -56,10 +57,10 @@ export class ItemsEncryptionService extends AbstractService { } public override deinit(): void { - ;(this.itemManager as unknown) = undefined - ;(this.payloadManager as unknown) = undefined - ;(this.storageService as unknown) = undefined - ;(this.operatorManager as unknown) = undefined + ;(this.items as unknown) = undefined + ;(this.payloads as unknown) = undefined + ;(this.storage as unknown) = undefined + ;(this.operators as unknown) = undefined ;(this.keys as unknown) = undefined this.removeItemsObserver() ;(this.removeItemsObserver as unknown) = undefined @@ -72,22 +73,20 @@ export class ItemsEncryptionService extends AbstractService { * disk using latest encryption status. */ async repersistAllItems(): Promise { - const items = this.itemManager.items + const items = this.items.items const payloads = items.map((item) => item.payload) - return this.storageService.savePayloads(payloads) + return this.storage.savePayloads(payloads) } public getItemsKeys(): ItemsKeyInterface[] { - return this.itemManager.getDisplayableItemsKeys() + return this.items.getDisplayableItemsKeys() } public itemsKeyForEncryptedPayload( payload: EncryptedPayloadInterface, ): ItemsKeyInterface | KeySystemItemsKeyInterface | undefined { const itemsKeys = this.getItemsKeys() - const keySystemItemsKeys = this.itemManager.getItems( - ContentType.TYPES.KeySystemItemsKey, - ) + const keySystemItemsKeys = this.items.getItems(ContentType.TYPES.KeySystemItemsKey) return [...itemsKeys, ...keySystemItemsKeys].find( (key) => key.uuid === payload.items_key_id || key.duplicateOf === payload.items_key_id, @@ -95,7 +94,7 @@ export class ItemsEncryptionService extends AbstractService { } public getDefaultItemsKey(): ItemsKeyInterface | undefined { - return findDefaultItemsKey(this.getItemsKeys()) + return this._findDefaultItemsKey.execute(this.getItemsKeys()).getValue() } private keyToUseForItemEncryption( @@ -173,7 +172,7 @@ export class ItemsEncryptionService extends AbstractService { throw Error('Attempting to encrypt payload with no UuidGenerator.') } - return encryptPayload(payload, key, this.operatorManager, signingKeyPair) + return encryptPayload(payload, key, this.operators, signingKeyPair) } public async encryptPayloads( @@ -218,7 +217,7 @@ export class ItemsEncryptionService extends AbstractService { } } - return decryptPayload(payload, key, this.operatorManager) + return decryptPayload(payload, key, this.operators) } public async decryptPayloadsWithKeyLookup( @@ -235,7 +234,7 @@ export class ItemsEncryptionService extends AbstractService { } public async decryptErroredItemPayloads(): Promise { - const erroredItemPayloads = this.payloadManager.invalidPayloads.filter( + const erroredItemPayloads = this.payloads.invalidPayloads.filter( (i) => !ContentTypeUsesRootKeyEncryption(i.content_type) && !ContentTypeUsesKeySystemRootKeyEncryption(i.content_type), ) @@ -260,7 +259,7 @@ export class ItemsEncryptionService extends AbstractService { } }) - await this.payloadManager.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged) + await this.payloads.emitPayloads(decryptedPayloads, PayloadEmitSource.LocalChanged) } /** diff --git a/packages/services/src/Domain/KeySystem/KeySystemKeyManager.ts b/packages/services/src/Domain/KeySystem/KeySystemKeyManager.ts index dd2d9b4cd..db65c747f 100644 --- a/packages/services/src/Domain/KeySystem/KeySystemKeyManager.ts +++ b/packages/services/src/Domain/KeySystem/KeySystemKeyManager.ts @@ -1,3 +1,4 @@ +import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface' import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' import { ApplicationStage } from './../Application/ApplicationStage' import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface' @@ -16,13 +17,19 @@ import { VaultListingInterface, } from '@standardnotes/models' import { ItemManagerInterface } from './../Item/ItemManagerInterface' -import { KeySystemKeyManagerInterface } from '@standardnotes/encryption' import { AbstractService } from '../Service/AbstractService' import { ContentType } from '@standardnotes/domain-core' +import { InternalEventInterface } from '../Internal/InternalEventInterface' +import { ApplicationEvent } from '../Event/ApplicationEvent' +import { ApplicationStageChangedEventPayload } from '../Event/ApplicationStageChangedEventPayload' +import { KeySystemKeyManagerInterface } from './KeySystemKeyManagerInterface' const RootKeyStorageKeyPrefix = 'key-system-root-key-' -export class KeySystemKeyManager extends AbstractService implements KeySystemKeyManagerInterface { +export class KeySystemKeyManager + extends AbstractService + implements KeySystemKeyManagerInterface, InternalEventHandlerInterface +{ private rootKeyMemoryCache: Record = {} constructor( @@ -34,9 +41,12 @@ export class KeySystemKeyManager extends AbstractService implements KeySystemKey super(eventBus) } - public override async handleApplicationStage(stage: ApplicationStage): Promise { - if (stage === ApplicationStage.StorageDecrypted_09) { - this.loadRootKeysFromStorage() + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.StorageDecrypted_09) { + this.loadRootKeysFromStorage() + } } } @@ -59,6 +69,17 @@ export class KeySystemKeyManager extends AbstractService implements KeySystemKey return `${RootKeyStorageKeyPrefix}${systemIdentifier}` } + /** + * When the key system root key changes, we must re-encrypt all vault items keys + * with this new key system root key (by simply re-syncing). + */ + public async reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise { + const keySystemItemsKeys = this.getKeySystemItemsKeys(keySystemIdentifier) + if (keySystemItemsKeys.length > 0) { + await this.mutator.setItemsDirty(keySystemItemsKeys) + } + } + public intakeNonPersistentKeySystemRootKey( key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode, diff --git a/packages/encryption/src/Domain/Service/KeySystemKeyManagerInterface.ts b/packages/services/src/Domain/KeySystem/KeySystemKeyManagerInterface.ts similarity index 94% rename from packages/encryption/src/Domain/Service/KeySystemKeyManagerInterface.ts rename to packages/services/src/Domain/KeySystem/KeySystemKeyManagerInterface.ts index 38afcaade..ec367c81d 100644 --- a/packages/encryption/src/Domain/Service/KeySystemKeyManagerInterface.ts +++ b/packages/services/src/Domain/KeySystem/KeySystemKeyManagerInterface.ts @@ -21,6 +21,7 @@ export interface KeySystemKeyManagerInterface { keyIdentifier: string, ): KeySystemRootKeyInterface | undefined getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined + reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise intakeNonPersistentKeySystemRootKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void undoIntakeNonPersistentKeySystemRootKey(systemIdentifier: KeySystemIdentifier): void diff --git a/packages/services/src/Domain/Mutator/ImportDataUseCase.ts b/packages/services/src/Domain/Mutator/ImportDataUseCase.ts index 2852401de..199be7065 100644 --- a/packages/services/src/Domain/Mutator/ImportDataUseCase.ts +++ b/packages/services/src/Domain/Mutator/ImportDataUseCase.ts @@ -1,3 +1,4 @@ +import { DecryptBackupFile } from '../Encryption/UseCase/DecryptBackupFile' import { HistoryServiceInterface } from '../History/HistoryServiceInterface' import { ChallengeServiceInterface } from '../Challenge/ChallengeServiceInterface' import { PayloadManagerInterface } from '../Payloads/PayloadManagerInterface' @@ -18,9 +19,9 @@ import { isEncryptedTransferPayload, } from '@standardnotes/models' import { ClientDisplayableError } from '@standardnotes/responses' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { Challenge, ChallengePrompt, ChallengeReason, ChallengeValidation } from '../Challenge' import { ContentType } from '@standardnotes/domain-core' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' const Strings = { UnsupportedBackupFileVersion: @@ -42,12 +43,13 @@ export type ImportDataReturnType = export class ImportDataUseCase { constructor( private itemManager: ItemManagerInterface, - private syncService: SyncServiceInterface, + private sync: SyncServiceInterface, private protectionService: ProtectionsClientInterface, private encryption: EncryptionProviderInterface, private payloadManager: PayloadManagerInterface, private challengeService: ChallengeServiceInterface, private historyService: HistoryServiceInterface, + private _decryptBackFile: DecryptBackupFile, ) {} /** @@ -107,7 +109,7 @@ export class ImportDataUseCase { } }) - const decryptedPayloadsOrError = await this.encryption.decryptBackupFile(data, password) + const decryptedPayloadsOrError = await this._decryptBackFile.execute(data, password) if (decryptedPayloadsOrError instanceof ClientDisplayableError) { return { error: decryptedPayloadsOrError } @@ -131,7 +133,7 @@ export class ImportDataUseCase { this.historyService.getHistoryMapCopy(), ) - const promise = this.syncService.sync() + const promise = this.sync.sync() if (awaitSync) { await promise diff --git a/packages/services/src/Domain/Protection/ProtectionClientInterface.ts b/packages/services/src/Domain/Protection/ProtectionClientInterface.ts index 2be79c934..e7a97e29e 100644 --- a/packages/services/src/Domain/Protection/ProtectionClientInterface.ts +++ b/packages/services/src/Domain/Protection/ProtectionClientInterface.ts @@ -1,9 +1,10 @@ import { DecryptedItem, DecryptedItemInterface, FileItem, SNNote } from '@standardnotes/models' -import { ChallengeReason } from '../Challenge' +import { ChallengeInterface, ChallengeReason } from '../Challenge' import { MobileUnlockTiming } from './MobileUnlockTiming' import { TimingDisplayOption } from './TimingDisplayOption' export interface ProtectionsClientInterface { + createLaunchChallenge(): ChallengeInterface | undefined authorizeProtectedActionForItems(files: T[], challengeReason: ChallengeReason): Promise authorizeItemAccess(item: DecryptedItem): Promise getMobileBiometricsTiming(): MobileUnlockTiming | undefined @@ -17,6 +18,7 @@ export interface ProtectionsClientInterface { hasBiometricsEnabled(): boolean enableBiometrics(): boolean disableBiometrics(): Promise + authorizeAction( reason: ChallengeReason, dto: { fallBackToAccountPassword: boolean; requireAccountPassword: boolean; forcePrompt: boolean }, @@ -25,6 +27,12 @@ export interface ProtectionsClientInterface { authorizeRemovingPasscode(): Promise authorizeChangingPasscode(): Promise authorizeFileImport(): Promise + authorizeSessionRevoking(): Promise + authorizeAutolockIntervalChange(): Promise + authorizeSearchingProtectedNotesText(): Promise + authorizeBackupCreation(): Promise + authorizeMfaDisable(): Promise + protectItems(items: I[]): Promise unprotectItems(items: I[], reason: ChallengeReason): Promise protectNote(note: SNNote): Promise @@ -33,4 +41,9 @@ export interface ProtectionsClientInterface { unprotectNotes(notes: SNNote[]): Promise protectFile(file: FileItem): Promise unprotectFile(file: FileItem): Promise + + hasProtectionSources(): boolean + hasUnprotectedAccessSession(): boolean + getSessionExpiryDate(): Date + clearSession(): Promise } diff --git a/packages/encryption/src/Domain/Service/RootKey/KeyMode.ts b/packages/services/src/Domain/RootKeyManager/KeyMode.ts similarity index 100% rename from packages/encryption/src/Domain/Service/RootKey/KeyMode.ts rename to packages/services/src/Domain/RootKeyManager/KeyMode.ts diff --git a/packages/services/src/Domain/Encryption/RootKey/RootKeyManager.ts b/packages/services/src/Domain/RootKeyManager/RootKeyManager.ts similarity index 94% rename from packages/services/src/Domain/Encryption/RootKey/RootKeyManager.ts rename to packages/services/src/Domain/RootKeyManager/RootKeyManager.ts index 3720cd027..789cee74b 100644 --- a/packages/services/src/Domain/Encryption/RootKey/RootKeyManager.ts +++ b/packages/services/src/Domain/RootKeyManager/RootKeyManager.ts @@ -6,12 +6,11 @@ import { ProtocolVersionLatest, } from '@standardnotes/common' import { - KeyMode, CreateNewRootKey, CreateAnyKeyParams, SNRootKey, isErrorDecryptingParameters, - OperatorManager, + EncryptionOperatorsInterface, } from '@standardnotes/encryption' import { ContentTypesUsingRootKeyEncryption, @@ -25,19 +24,20 @@ import { RootKeyInterface, RootKeyParamsInterface, } from '@standardnotes/models' -import { DeviceInterface } from '../../Device/DeviceInterface' -import { InternalEventBusInterface } from '../../Internal/InternalEventBusInterface' -import { StorageKey } from '../../Storage/StorageKeys' -import { StorageServiceInterface } from '../../Storage/StorageServiceInterface' -import { StorageValueModes } from '../../Storage/StorageTypes' -import { RootKeyEncryptPayloadUseCase } from '../UseCase/RootEncryption/EncryptPayload' -import { RootKeyDecryptPayloadUseCase } from '../UseCase/RootEncryption/DecryptPayload' -import { AbstractService } from '../../Service/AbstractService' -import { ItemManagerInterface } from '../../Item/ItemManagerInterface' -import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' +import { DeviceInterface } from '../Device/DeviceInterface' +import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' +import { StorageKey } from '../Storage/StorageKeys' +import { StorageServiceInterface } from '../Storage/StorageServiceInterface' +import { StorageValueModes } from '../Storage/StorageTypes' +import { EncryptTypeAPayload } from '../Encryption/UseCase/TypeA/EncryptPayload' +import { DecryptTypeAPayload } from '../Encryption/UseCase/TypeA/DecryptPayload' +import { AbstractService } from '../Service/AbstractService' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { MutatorClientInterface } from '../Mutator/MutatorClientInterface' import { RootKeyManagerEvent } from './RootKeyManagerEvent' import { ValidatePasscodeResult } from './ValidatePasscodeResult' import { ValidateAccountPasswordResult } from './ValidateAccountPasswordResult' +import { KeyMode } from './KeyMode' export class RootKeyManager extends AbstractService { private rootKey?: RootKeyInterface @@ -49,7 +49,7 @@ export class RootKeyManager extends AbstractService { private storage: StorageServiceInterface, private items: ItemManagerInterface, private mutator: MutatorClientInterface, - private operators: OperatorManager, + private operators: EncryptionOperatorsInterface, private identifier: ApplicationIdentifier, eventBus: InternalEventBusInterface, ) { @@ -249,7 +249,7 @@ export class RootKeyManager extends AbstractService { const payload = new DecryptedPayload(value) - const usecase = new RootKeyEncryptPayloadUseCase(this.operators) + const usecase = new EncryptTypeAPayload(this.operators) const wrappedKey = await usecase.executeOne(payload, wrappingKey) const wrappedKeyPayload = new EncryptedPayload({ ...payload.ejected(), @@ -273,7 +273,7 @@ export class RootKeyManager extends AbstractService { const wrappedKey = this.getWrappedRootKey() const payload = new EncryptedPayload(wrappedKey) - const usecase = new RootKeyDecryptPayloadUseCase(this.operators) + const usecase = new DecryptTypeAPayload(this.operators) const decrypted = await usecase.executeOne(payload, wrappingKey) if (isErrorDecryptingParameters(decrypted)) { @@ -433,7 +433,7 @@ export class RootKeyManager extends AbstractService { * by attempting to decrypt account keys. */ const wrappedKeyPayload = new EncryptedPayload(wrappedRootKey) - const usecase = new RootKeyDecryptPayloadUseCase(this.operators) + const usecase = new DecryptTypeAPayload(this.operators) const decrypted = await usecase.executeOne(wrappedKeyPayload, wrappingKey) return !isErrorDecryptingParameters(decrypted) } else { diff --git a/packages/services/src/Domain/Encryption/RootKey/RootKeyManagerEvent.ts b/packages/services/src/Domain/RootKeyManager/RootKeyManagerEvent.ts similarity index 100% rename from packages/services/src/Domain/Encryption/RootKey/RootKeyManagerEvent.ts rename to packages/services/src/Domain/RootKeyManager/RootKeyManagerEvent.ts diff --git a/packages/services/src/Domain/Encryption/RootKey/ValidateAccountPasswordResult.ts b/packages/services/src/Domain/RootKeyManager/ValidateAccountPasswordResult.ts similarity index 100% rename from packages/services/src/Domain/Encryption/RootKey/ValidateAccountPasswordResult.ts rename to packages/services/src/Domain/RootKeyManager/ValidateAccountPasswordResult.ts diff --git a/packages/services/src/Domain/Encryption/RootKey/ValidatePasscodeResult.ts b/packages/services/src/Domain/RootKeyManager/ValidatePasscodeResult.ts similarity index 100% rename from packages/services/src/Domain/Encryption/RootKey/ValidatePasscodeResult.ts rename to packages/services/src/Domain/RootKeyManager/ValidatePasscodeResult.ts diff --git a/packages/services/src/Domain/Service/AbstractService.ts b/packages/services/src/Domain/Service/AbstractService.ts index 266cf9b6b..fb5e4326d 100644 --- a/packages/services/src/Domain/Service/AbstractService.ts +++ b/packages/services/src/Domain/Service/AbstractService.ts @@ -4,7 +4,6 @@ import { log, removeFromArray } from '@standardnotes/utils' import { EventObserver } from '../Event/EventObserver' import { ApplicationServiceInterface } from './ApplicationServiceInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' -import { ApplicationStage } from '../Application/ApplicationStage' import { InternalEventPublishStrategy } from '../Internal/InternalEventPublishStrategy' import { DiagnosticInfo } from '../Diagnostics/ServiceDiagnostics' @@ -93,19 +92,14 @@ export abstract class AbstractService return promise } - /** - * Application instances will call this function directly when they arrive - * at a certain migratory state. - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async handleApplicationStage(_stage: ApplicationStage): Promise { - // optional override - } - getServiceName(): string { return this.constructor.name } + isApplicationService(): true { + return true + } + log(..._args: unknown[]): void { if (this.loggingEnabled) { // eslint-disable-next-line prefer-rest-params diff --git a/packages/services/src/Domain/Service/ApplicationServiceInterface.ts b/packages/services/src/Domain/Service/ApplicationServiceInterface.ts index 91f4658ea..7bd254a20 100644 --- a/packages/services/src/Domain/Service/ApplicationServiceInterface.ts +++ b/packages/services/src/Domain/Service/ApplicationServiceInterface.ts @@ -1,4 +1,3 @@ -import { ApplicationStage } from '../Application/ApplicationStage' import { ServiceDiagnostics } from '../Diagnostics/ServiceDiagnostics' import { EventObserver } from '../Event/EventObserver' @@ -7,6 +6,5 @@ export interface ApplicationServiceInterface extends ServiceDiagnostics { addEventObserver(observer: EventObserver): () => void blockDeinit(): Promise deinit(): void - handleApplicationStage(stage: ApplicationStage): Promise log(message: string, ...args: unknown[]): void } diff --git a/packages/services/src/Domain/Session/SessionsClientInterface.ts b/packages/services/src/Domain/Session/SessionsClientInterface.ts index 0e9c82bdb..89a3085be 100644 --- a/packages/services/src/Domain/Session/SessionsClientInterface.ts +++ b/packages/services/src/Domain/Session/SessionsClientInterface.ts @@ -2,7 +2,14 @@ import { UserRegistrationResponseBody } from '@standardnotes/api' import { ProtocolVersion } from '@standardnotes/common' import { SNRootKey } from '@standardnotes/encryption' import { RootKeyInterface } from '@standardnotes/models' -import { SessionBody, SignInResponse, User, HttpResponse } from '@standardnotes/responses' +import { + SessionBody, + SignInResponse, + User, + HttpResponse, + SessionListEntry, + SessionListResponse, +} from '@standardnotes/responses' import { Base64String } from '@standardnotes/sncrypto-common' import { SessionManagerResponse } from './SessionManagerResponse' @@ -11,12 +18,18 @@ export interface SessionsClientInterface { getWorkspaceDisplayIdentifier(): string populateSessionFromDemoShareToken(token: Base64String): Promise + initializeFromDisk(): Promise + getUser(): User | undefined isSignedIn(): boolean get userUuid(): string getSureUser(): User isSignedIntoFirstPartyServer(): boolean + getSessionsList(): Promise> + revokeSession(sessionId: string): Promise> + revokeAllOtherSessions(): Promise + isCurrentSessionReadOnly(): boolean | undefined register(email: string, password: string, ephemeral: boolean): Promise signIn( diff --git a/packages/services/src/Domain/Session/UserKeyPairChangedEventData.ts b/packages/services/src/Domain/Session/UserKeyPairChangedEventData.ts index 9a56d6a20..5eaf9c49b 100644 --- a/packages/services/src/Domain/Session/UserKeyPairChangedEventData.ts +++ b/packages/services/src/Domain/Session/UserKeyPairChangedEventData.ts @@ -1,9 +1,12 @@ import { PkcKeyPair } from '@standardnotes/sncrypto-common' export type UserKeyPairChangedEventData = { - oldKeyPair: PkcKeyPair | undefined - oldSigningKeyPair: PkcKeyPair | undefined - - newKeyPair: PkcKeyPair - newSigningKeyPair: PkcKeyPair + current: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + previous?: { + encryption: PkcKeyPair + signing: PkcKeyPair + } } diff --git a/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts b/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts new file mode 100644 index 000000000..a14ee4764 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/SharedVaultService.spec.ts @@ -0,0 +1,109 @@ +import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface' +import { GetVaultUsers } from './UseCase/GetVaultUsers' +import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember' +import { DeleteSharedVault } from './UseCase/DeleteSharedVault' +import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault' +import { ShareContactWithVault } from './UseCase/ShareContactWithVault' +import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault' +import { LeaveVault } from './UseCase/LeaveSharedVault' +import { InviteToVault } from './UseCase/InviteToVault' +import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite' +import { GetVaultContacts } from './UseCase/GetVaultContacts' +import { GetAllContacts } from './../Contacts/UseCase/GetAllContacts' +import { FindContact } from './../Contacts/UseCase/FindContact' +import { GetUntrustedPayload } from './../AsymmetricMessage/UseCase/GetUntrustedPayload' +import { GetTrustedPayload } from './../AsymmetricMessage/UseCase/GetTrustedPayload' +import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage' +import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation' +import { HandleKeyPairChange } from './../Contacts/UseCase/HandleKeyPairChange' +import { CreateSharedVault } from './UseCase/CreateSharedVault' +import { GetVault } from './../Vaults/UseCase/GetVault' +import { SharedVaultInvitesServer } from '@standardnotes/api' +import { SharedVaultService } from './SharedVaultService' +import { SyncServiceInterface } from '../Sync/SyncServiceInterface' +import { ItemManagerInterface } from '../Item/ItemManagerInterface' +import { SessionsClientInterface } from '../Session/SessionsClientInterface' +import { VaultServiceInterface } from '../Vaults/VaultServiceInterface' +import { InternalEventBusInterface } from '../..' +import { ContactPublicKeySetInterface, TrustedContactInterface } from '@standardnotes/models' + +describe('SharedVaultService', () => { + let service: SharedVaultService + + beforeEach(() => { + const sync = {} as jest.Mocked + sync.addEventObserver = jest.fn() + + const items = {} as jest.Mocked + items.addObserver = jest.fn() + + const encryption = {} as jest.Mocked + const session = {} as jest.Mocked + const vaults = {} as jest.Mocked + const invitesServer = {} as jest.Mocked + const getVault = {} as jest.Mocked + const createSharedVaultUseCase = {} as jest.Mocked + const handleKeyPairChange = {} as jest.Mocked + const notifyVaultUsersOfKeyRotation = {} as jest.Mocked + const sendVaultDataChangeMessage = {} as jest.Mocked + const getTrustedPayload = {} as jest.Mocked + const getUntrustedPayload = {} as jest.Mocked + const findContact = {} as jest.Mocked + const getAllContacts = {} as jest.Mocked + const getVaultContacts = {} as jest.Mocked + const acceptVaultInvite = {} as jest.Mocked + const inviteToVault = {} as jest.Mocked + const leaveVault = {} as jest.Mocked + const deleteThirdPartyVault = {} as jest.Mocked + const shareContactWithVault = {} as jest.Mocked + const convertToSharedVault = {} as jest.Mocked + const deleteSharedVaultUseCase = {} as jest.Mocked + const removeVaultMember = {} as jest.Mocked + const getSharedVaultUsersUseCase = {} as jest.Mocked + + const eventBus = {} as jest.Mocked + eventBus.addEventHandler = jest.fn() + + service = new SharedVaultService( + sync, + items, + encryption, + session, + vaults, + invitesServer, + getVault, + createSharedVaultUseCase, + handleKeyPairChange, + notifyVaultUsersOfKeyRotation, + sendVaultDataChangeMessage, + getTrustedPayload, + getUntrustedPayload, + findContact, + getAllContacts, + getVaultContacts, + acceptVaultInvite, + inviteToVault, + leaveVault, + deleteThirdPartyVault, + shareContactWithVault, + convertToSharedVault, + deleteSharedVaultUseCase, + removeVaultMember, + getSharedVaultUsersUseCase, + eventBus, + ) + }) + + describe('shareContactWithVaults', () => { + it('should throw if attempting to share self contact', async () => { + const contact = { + name: 'Other', + contactUuid: '456', + publicKeySet: {} as ContactPublicKeySetInterface, + isMe: true, + } as jest.Mocked + + await expect(service.shareContactWithVaults(contact)).rejects.toThrow('Cannot share self contact') + }) + }) +}) diff --git a/packages/services/src/Domain/SharedVaults/SharedVaultService.ts b/packages/services/src/Domain/SharedVaults/SharedVaultService.ts index 7bca6bfb0..6e66ca935 100644 --- a/packages/services/src/Domain/SharedVaults/SharedVaultService.ts +++ b/packages/services/src/Domain/SharedVaults/SharedVaultService.ts @@ -1,6 +1,5 @@ -import { MutatorClientInterface } from './../Mutator/MutatorClientInterface' -import { StorageServiceInterface } from './../Storage/StorageServiceInterface' -import { InviteContactToSharedVaultUseCase } from './UseCase/InviteContactToSharedVault' +import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData' +import { InviteToVault } from './UseCase/InviteToVault' import { ClientDisplayableError, SharedVaultInviteServerHash, @@ -10,17 +9,7 @@ import { SharedVaultPermission, UserEventType, } from '@standardnotes/responses' -import { - HttpServiceInterface, - SharedVaultServerInterface, - SharedVaultUsersServerInterface, - SharedVaultInvitesServerInterface, - SharedVaultUsersServer, - SharedVaultInvitesServer, - SharedVaultServer, - AsymmetricMessageServerInterface, - AsymmetricMessageServer, -} from '@standardnotes/api' +import { SharedVaultInvitesServer } from '@standardnotes/api' import { DecryptedItemInterface, PayloadEmitSource, @@ -32,61 +21,72 @@ import { } from '@standardnotes/models' import { SharedVaultServiceInterface } from './SharedVaultServiceInterface' import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { GetSharedVaultUsersUseCase } from './UseCase/GetSharedVaultUsers' -import { RemoveVaultMemberUseCase } from './UseCase/RemoveSharedVaultMember' +import { GetVaultUsers } from './UseCase/GetVaultUsers' +import { RemoveVaultMember } from './UseCase/RemoveSharedVaultMember' import { AbstractService } from '../Service/AbstractService' import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface' import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { SessionsClientInterface } from '../Session/SessionsClientInterface' -import { ContactServiceInterface } from '../Contacts/ContactServiceInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from '../Event/SyncEvent' import { SessionEvent } from '../Session/SessionEvent' import { InternalEventInterface } from '../Internal/InternalEventInterface' -import { FilesClientInterface } from '@standardnotes/files' -import { LeaveVaultUseCase } from './UseCase/LeaveSharedVault' +import { LeaveVault } from './UseCase/LeaveSharedVault' import { VaultServiceInterface } from '../Vaults/VaultServiceInterface' import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent' -import { DeleteExternalSharedVaultUseCase } from './UseCase/DeleteExternalSharedVault' -import { DeleteSharedVaultUseCase } from './UseCase/DeleteSharedVault' +import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault' +import { DeleteSharedVault } from './UseCase/DeleteSharedVault' import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent' -import { AcceptTrustedSharedVaultInvite } from './UseCase/AcceptTrustedSharedVaultInvite' -import { GetAsymmetricMessageTrustedPayload } from '../AsymmetricMessage/UseCase/GetAsymmetricMessageTrustedPayload' +import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite' +import { GetTrustedPayload } from '../AsymmetricMessage/UseCase/GetTrustedPayload' import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord' -import { GetAsymmetricMessageUntrustedPayload } from '../AsymmetricMessage/UseCase/GetAsymmetricMessageUntrustedPayload' -import { ShareContactWithAllMembersOfSharedVaultUseCase } from './UseCase/ShareContactWithAllMembersOfSharedVault' -import { GetSharedVaultTrustedContacts } from './UseCase/GetSharedVaultTrustedContacts' -import { NotifySharedVaultUsersOfRootKeyRotationUseCase } from './UseCase/NotifySharedVaultUsersOfRootKeyRotation' -import { CreateSharedVaultUseCase } from './UseCase/CreateSharedVault' -import { SendSharedVaultMetadataChangedMessageToAll } from './UseCase/SendSharedVaultMetadataChangedMessageToAll' -import { ConvertToSharedVaultUseCase } from './UseCase/ConvertToSharedVault' -import { GetVaultUseCase } from '../Vaults/UseCase/GetVault' -import { ContentType } from '@standardnotes/domain-core' +import { GetUntrustedPayload } from '../AsymmetricMessage/UseCase/GetUntrustedPayload' +import { ShareContactWithVault } from './UseCase/ShareContactWithVault' +import { GetVaultContacts } from './UseCase/GetVaultContacts' +import { NotifyVaultUsersOfKeyRotation } from './UseCase/NotifyVaultUsersOfKeyRotation' +import { CreateSharedVault } from './UseCase/CreateSharedVault' +import { SendVaultDataChangedMessage } from './UseCase/SendVaultDataChangedMessage' +import { ConvertToSharedVault } from './UseCase/ConvertToSharedVault' +import { GetVault } from '../Vaults/UseCase/GetVault' +import { ContentType, Result } from '@standardnotes/domain-core' +import { HandleKeyPairChange } from '../Contacts/UseCase/HandleKeyPairChange' +import { FindContact } from '../Contacts/UseCase/FindContact' +import { GetAllContacts } from '../Contacts/UseCase/GetAllContacts' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' export class SharedVaultService extends AbstractService implements SharedVaultServiceInterface, InternalEventHandlerInterface { - private server: SharedVaultServerInterface - private usersServer: SharedVaultUsersServerInterface - private invitesServer: SharedVaultInvitesServerInterface - private messageServer: AsymmetricMessageServerInterface - private pendingInvites: Record = {} constructor( - http: HttpServiceInterface, private sync: SyncServiceInterface, private items: ItemManagerInterface, - private mutator: MutatorClientInterface, private encryption: EncryptionProviderInterface, private session: SessionsClientInterface, - private contacts: ContactServiceInterface, - private files: FilesClientInterface, private vaults: VaultServiceInterface, - private storage: StorageServiceInterface, + private invitesServer: SharedVaultInvitesServer, + private getVault: GetVault, + private createSharedVaultUseCase: CreateSharedVault, + private handleKeyPairChange: HandleKeyPairChange, + private notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation, + private sendVaultDataChangeMessage: SendVaultDataChangedMessage, + private getTrustedPayload: GetTrustedPayload, + private getUntrustedPayload: GetUntrustedPayload, + private findContact: FindContact, + private getAllContacts: GetAllContacts, + private getVaultContacts: GetVaultContacts, + private acceptVaultInvite: AcceptVaultInvite, + private inviteToVault: InviteToVault, + private leaveVault: LeaveVault, + private deleteThirdPartyVault: DeleteThirdPartyVault, + private shareContactWithVault: ShareContactWithVault, + private convertToSharedVault: ConvertToSharedVault, + private deleteSharedVaultUseCase: DeleteSharedVault, + private removeVaultMember: RemoveVaultMember, + private getSharedVaultUsersUseCase: GetVaultUsers, eventBus: InternalEventBusInterface, ) { super(eventBus) @@ -95,21 +95,6 @@ export class SharedVaultService eventBus.addEventHandler(this, UserEventServiceEvent.UserEventReceived) eventBus.addEventHandler(this, VaultServiceEvent.VaultRootKeyRotated) - this.server = new SharedVaultServer(http) - this.usersServer = new SharedVaultUsersServer(http) - this.invitesServer = new SharedVaultInvitesServer(http) - this.messageServer = new AsymmetricMessageServer(http) - - this.eventDisposers.push( - sync.addEventObserver(async (event, data) => { - if (event === SyncEvent.ReceivedSharedVaultInvites) { - void this.processInboundInvites(data as SyncEventReceivedSharedVaultInvitesData) - } else if (event === SyncEvent.ReceivedRemoteSharedVaults) { - void this.notifyCollaborationStatusChanged() - } - }), - ) - this.eventDisposers.push( items.addObserver( ContentType.TYPES.TrustedContact, @@ -138,26 +123,32 @@ export class SharedVaultService async handleEvent(event: InternalEventInterface): Promise { if (event.type === SessionEvent.UserKeyPairChanged) { void this.invitesServer.deleteAllInboundInvites() + + const eventData = event.payload as UserKeyPairChangedEventData + + void this.handleKeyPairChange.execute({ + newKeys: eventData.current, + previousKeys: eventData.previous, + }) } else if (event.type === UserEventServiceEvent.UserEventReceived) { await this.handleUserEvent(event.payload as UserEventServiceEventPayload) } else if (event.type === VaultServiceEvent.VaultRootKeyRotated) { const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated] await this.handleVaultRootKeyRotatedEvent(payload.vault) + } else if (event.type === SyncEvent.ReceivedSharedVaultInvites) { + await this.processInboundInvites(event.payload as SyncEventReceivedSharedVaultInvitesData) + } else if (event.type === SyncEvent.ReceivedRemoteSharedVaults) { + void this.notifyCollaborationStatusChanged() } } private async handleUserEvent(event: UserEventServiceEventPayload): Promise { if (event.eventPayload.eventType === UserEventType.RemovedFromSharedVault) { - const vault = new GetVaultUseCase(this.items).execute({ sharedVaultUuid: event.eventPayload.sharedVaultUuid }) - if (vault) { - const useCase = new DeleteExternalSharedVaultUseCase( - this.items, - this.mutator, - this.encryption, - this.storage, - this.sync, - ) - await useCase.execute(vault) + const vault = this.getVault.execute({ + sharedVaultUuid: event.eventPayload.sharedVaultUuid, + }) + if (!vault.isFailed()) { + await this.deleteThirdPartyVault.execute(vault.getValue()) } } else if (event.eventPayload.eventType === UserEventType.SharedVaultItemRemoved) { const item = this.items.findItem(event.eventPayload.itemUuid) @@ -176,15 +167,14 @@ export class SharedVaultService return } - const usecase = new NotifySharedVaultUsersOfRootKeyRotationUseCase( - this.usersServer, - this.invitesServer, - this.messageServer, - this.encryption, - this.contacts, - ) - - await usecase.execute({ sharedVault: vault, userUuid: this.session.getSureUser().uuid }) + await this.notifyVaultUsersOfKeyRotation.execute({ + sharedVault: vault, + senderUuid: this.session.getSureUser().uuid, + keys: { + encryption: this.encryption.getKeyPair(), + signing: this.encryption.getSigningKeyPair(), + }, + }) } async createSharedVault(dto: { @@ -193,16 +183,7 @@ export class SharedVaultService userInputtedPassword: string | undefined storagePreference?: KeySystemRootKeyStorageMode }): Promise { - const usecase = new CreateSharedVaultUseCase( - this.encryption, - this.items, - this.mutator, - this.sync, - this.files, - this.server, - ) - - return usecase.execute({ + return this.createSharedVaultUseCase.execute({ vaultName: dto.name, vaultDescription: dto.description, userInputtedPassword: dto.userInputtedPassword, @@ -213,9 +194,7 @@ export class SharedVaultService async convertVaultToSharedVault( vault: VaultListingInterface, ): Promise { - const usecase = new ConvertToSharedVaultUseCase(this.items, this.mutator, this.sync, this.files, this.server) - - return usecase.execute({ vault }) + return this.convertToSharedVault.execute({ vault }) } public getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[] { @@ -228,7 +207,12 @@ export class SharedVaultService } private findSharedVault(sharedVaultUuid: string): SharedVaultListingInterface | undefined { - return this.getAllSharedVaults().find((vault) => vault.sharing.sharedVaultUuid === sharedVaultUuid) + const result = this.getVault.execute({ sharedVaultUuid }) + if (result.isFailed()) { + return undefined + } + + return result.getValue() } public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean { @@ -256,7 +240,11 @@ export class SharedVaultService private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise { for (const contact of contacts) { - await this.shareContactWithUserAdministeredSharedVaults(contact) + if (contact.isMe) { + continue + } + + await this.shareContactWithVaults(contact) } } @@ -266,18 +254,13 @@ export class SharedVaultService continue } - const usecase = new SendSharedVaultMetadataChangedMessageToAll( - this.encryption, - this.contacts, - this.usersServer, - this.messageServer, - ) - - await usecase.execute({ + await this.sendVaultDataChangeMessage.execute({ vault, senderUuid: this.session.getSureUser().uuid, - senderEncryptionKeyPair: this.encryption.getKeyPair(), - senderSigningKeyPair: this.encryption.getSigningKeyPair(), + keys: { + encryption: this.encryption.getKeyPair(), + signing: this.encryption.getSigningKeyPair(), + }, }) } } @@ -326,8 +309,7 @@ export class SharedVaultService } public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise { - const useCase = new DeleteSharedVaultUseCase(this.server, this.items, this.mutator, this.sync, this.encryption) - return useCase.execute({ sharedVault }) + return this.deleteSharedVaultUseCase.execute({ sharedVault }) } private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise { @@ -342,39 +324,34 @@ export class SharedVaultService } for (const invite of invites) { - const trustedMessageUseCase = new GetAsymmetricMessageTrustedPayload( - this.encryption, - this.contacts, - ) + const sender = this.findContact.execute({ userUuid: invite.sender_uuid }) + if (!sender.isFailed()) { + const trustedMessage = this.getTrustedPayload.execute({ + message: invite, + privateKey: this.encryption.getKeyPair().privateKey, + sender: sender.getValue(), + }) - const trustedMessage = trustedMessageUseCase.execute({ - message: invite, - privateKey: this.encryption.getKeyPair().privateKey, - }) + if (!trustedMessage.isFailed()) { + this.pendingInvites[invite.uuid] = { + invite, + message: trustedMessage.getValue(), + trusted: true, + } - if (trustedMessage) { - this.pendingInvites[invite.uuid] = { - invite, - message: trustedMessage, - trusted: true, + continue } - - continue } - const untrustedMessageUseCase = new GetAsymmetricMessageUntrustedPayload( - this.encryption, - ) - - const untrustedMessage = untrustedMessageUseCase.execute({ + const untrustedMessage = this.getUntrustedPayload.execute({ message: invite, privateKey: this.encryption.getKeyPair().privateKey, }) - if (untrustedMessage) { + if (!untrustedMessage.isFailed()) { this.pendingInvites[invite.uuid] = { invite, - message: untrustedMessage, + message: untrustedMessage.getValue(), trusted: false, } } @@ -392,8 +369,7 @@ export class SharedVaultService throw new Error('Cannot accept untrusted invite') } - const useCase = new AcceptTrustedSharedVaultInvite(this.invitesServer, this.mutator, this.sync, this.contacts) - await useCase.execute({ invite: pendingInvite.invite, message: pendingInvite.message }) + await this.acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message }) delete this.pendingInvites[pendingInvite.invite.uuid] @@ -416,35 +392,38 @@ export class SharedVaultService return [] } - const contacts = this.contacts.getAllContacts() - return contacts.filter((contact) => { + const contacts = this.getAllContacts.execute() + if (contacts.isFailed()) { + return [] + } + return contacts.getValue().filter((contact) => { const isContactAlreadyInVault = users.some((user) => user.user_uuid === contact.contactUuid) return !isContactAlreadyInVault }) } private async getSharedVaultContacts(sharedVault: SharedVaultListingInterface): Promise { - const usecase = new GetSharedVaultTrustedContacts(this.contacts, this.usersServer) - const contacts = await usecase.execute(sharedVault) - if (!contacts) { + const contacts = await this.getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid) + if (contacts.isFailed()) { return [] } - return contacts + return contacts.getValue() } async inviteContactToSharedVault( sharedVault: SharedVaultListingInterface, contact: TrustedContactInterface, permissions: SharedVaultPermission, - ): Promise { + ): Promise> { const sharedVaultContacts = await this.getSharedVaultContacts(sharedVault) - const useCase = new InviteContactToSharedVaultUseCase(this.encryption, this.invitesServer) - - const result = await useCase.execute({ - senderKeyPair: this.encryption.getKeyPair(), - senderSigningKeyPair: this.encryption.getSigningKeyPair(), + const result = await this.inviteToVault.execute({ + keys: { + encryption: this.encryption.getKeyPair(), + signing: this.encryption.getSigningKeyPair(), + }, + senderUuid: this.session.getSureUser().uuid, sharedVault, recipient: contact, sharedVaultContacts, @@ -470,8 +449,10 @@ export class SharedVaultService throw new Error('Cannot remove user from locked vault') } - const useCase = new RemoveVaultMemberUseCase(this.usersServer) - const result = await useCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid, userUuid }) + const result = await this.removeVaultMember.execute({ + sharedVaultUuid: sharedVault.sharing.sharedVaultUuid, + userUuid, + }) if (isClientDisplayableError(result)) { return result } @@ -482,15 +463,7 @@ export class SharedVaultService } async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise { - const useCase = new LeaveVaultUseCase( - this.usersServer, - this.items, - this.mutator, - this.encryption, - this.storage, - this.sync, - ) - const result = await useCase.execute({ + const result = await this.leaveVault.execute({ sharedVault: sharedVault, userUuid: this.session.getSureUser().uuid, }) @@ -505,28 +478,22 @@ export class SharedVaultService async getSharedVaultUsers( sharedVault: SharedVaultListingInterface, ): Promise { - const useCase = new GetSharedVaultUsersUseCase(this.usersServer) - return useCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid }) + return this.getSharedVaultUsersUseCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid }) } - private async shareContactWithUserAdministeredSharedVaults(contact: TrustedContactInterface): Promise { - const sharedVaults = this.getAllSharedVaults() + async shareContactWithVaults(contact: TrustedContactInterface): Promise { + if (contact.isMe) { + throw new Error('Cannot share self contact') + } - const useCase = new ShareContactWithAllMembersOfSharedVaultUseCase( - this.contacts, - this.encryption, - this.usersServer, - this.messageServer, - ) + const ownedVaults = this.getAllSharedVaults().filter(this.isCurrentUserSharedVaultAdmin.bind(this)) - for (const vault of sharedVaults) { - if (!this.isCurrentUserSharedVaultAdmin(vault)) { - continue - } - - await useCase.execute({ - senderKeyPair: this.encryption.getKeyPair(), - senderSigningKeyPair: this.encryption.getSigningKeyPair(), + for (const vault of ownedVaults) { + await this.shareContactWithVault.execute({ + keys: { + encryption: this.encryption.getKeyPair(), + signing: this.encryption.getSigningKeyPair(), + }, sharedVault: vault, contactToShare: contact, senderUserUuid: this.session.getSureUser().uuid, @@ -539,9 +506,9 @@ export class SharedVaultService return undefined } - const contact = this.contacts.findTrustedContact(item.last_edited_by_uuid) + const contact = this.findContact.execute({ userUuid: item.last_edited_by_uuid }) - return contact + return contact.isFailed() ? undefined : contact.getValue() } getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined { @@ -549,23 +516,37 @@ export class SharedVaultService return undefined } - const contact = this.contacts.findTrustedContact(item.user_uuid) + const contact = this.findContact.execute({ userUuid: item.user_uuid }) - return contact + return contact.isFailed() ? undefined : contact.getValue() } override deinit(): void { super.deinit() - ;(this.contacts as unknown) = undefined - ;(this.encryption as unknown) = undefined - ;(this.files as unknown) = undefined - ;(this.invitesServer as unknown) = undefined - ;(this.items as unknown) = undefined - ;(this.messageServer as unknown) = undefined - ;(this.server as unknown) = undefined - ;(this.session as unknown) = undefined ;(this.sync as unknown) = undefined - ;(this.usersServer as unknown) = undefined + ;(this.items as unknown) = undefined + ;(this.encryption as unknown) = undefined + ;(this.session as unknown) = undefined ;(this.vaults as unknown) = undefined + ;(this.invitesServer as unknown) = undefined + ;(this.getVault as unknown) = undefined + ;(this.createSharedVaultUseCase as unknown) = undefined + ;(this.handleKeyPairChange as unknown) = undefined + ;(this.notifyVaultUsersOfKeyRotation as unknown) = undefined + ;(this.sendVaultDataChangeMessage as unknown) = undefined + ;(this.getTrustedPayload as unknown) = undefined + ;(this.getUntrustedPayload as unknown) = undefined + ;(this.findContact as unknown) = undefined + ;(this.getAllContacts as unknown) = undefined + ;(this.getVaultContacts as unknown) = undefined + ;(this.acceptVaultInvite as unknown) = undefined + ;(this.inviteToVault as unknown) = undefined + ;(this.leaveVault as unknown) = undefined + ;(this.deleteThirdPartyVault as unknown) = undefined + ;(this.shareContactWithVault as unknown) = undefined + ;(this.convertToSharedVault as unknown) = undefined + ;(this.deleteSharedVaultUseCase as unknown) = undefined + ;(this.removeVaultMember as unknown) = undefined + ;(this.getSharedVaultUsersUseCase as unknown) = undefined } } diff --git a/packages/services/src/Domain/SharedVaults/SharedVaultServiceInterface.ts b/packages/services/src/Domain/SharedVaults/SharedVaultServiceInterface.ts index 8de042513..fd40fe38b 100644 --- a/packages/services/src/Domain/SharedVaults/SharedVaultServiceInterface.ts +++ b/packages/services/src/Domain/SharedVaults/SharedVaultServiceInterface.ts @@ -14,6 +14,7 @@ import { import { AbstractService } from '../Service/AbstractService' import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent' import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord' +import { Result } from '@standardnotes/domain-core' export interface SharedVaultServiceInterface extends AbstractService { @@ -31,7 +32,7 @@ export interface SharedVaultServiceInterface sharedVault: SharedVaultListingInterface, contact: TrustedContactInterface, permissions: SharedVaultPermission, - ): Promise + ): Promise> removeUserFromSharedVault( sharedVault: SharedVaultListingInterface, userUuid: string, diff --git a/packages/services/src/Domain/SharedVaults/UseCase/AcceptTrustedSharedVaultInvite.ts b/packages/services/src/Domain/SharedVaults/UseCase/AcceptTrustedSharedVaultInvite.ts deleted file mode 100644 index 55ef52c67..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/AcceptTrustedSharedVaultInvite.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' -import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' -import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models' -import { SharedVaultInvitesServerInterface } from '@standardnotes/api' -import { SharedVaultInviteServerHash } from '@standardnotes/responses' -import { HandleTrustedSharedVaultInviteMessage } from '../../AsymmetricMessage/UseCase/HandleTrustedSharedVaultInviteMessage' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' - -export class AcceptTrustedSharedVaultInvite { - constructor( - private vaultInvitesServer: SharedVaultInvitesServerInterface, - private mutator: MutatorClientInterface, - private sync: SyncServiceInterface, - private contacts: ContactServiceInterface, - ) {} - - async execute(dto: { - invite: SharedVaultInviteServerHash - message: AsymmetricMessageSharedVaultInvite - }): Promise { - const useCase = new HandleTrustedSharedVaultInviteMessage(this.mutator, this.sync, this.contacts) - await useCase.execute(dto.message, dto.invite.shared_vault_uuid, dto.invite.sender_uuid) - - await this.vaultInvitesServer.acceptInvite({ - sharedVaultUuid: dto.invite.shared_vault_uuid, - inviteUuid: dto.invite.uuid, - }) - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/AcceptVaultInvite.ts b/packages/services/src/Domain/SharedVaults/UseCase/AcceptVaultInvite.ts new file mode 100644 index 000000000..cf92201ea --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/AcceptVaultInvite.ts @@ -0,0 +1,23 @@ +import { AsymmetricMessageSharedVaultInvite } from '@standardnotes/models' +import { SharedVaultInvitesServerInterface } from '@standardnotes/api' +import { SharedVaultInviteServerHash } from '@standardnotes/responses' +import { ProcessAcceptedVaultInvite } from '../../AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite' + +export class AcceptVaultInvite { + constructor( + private inviteServer: SharedVaultInvitesServerInterface, + private processInvite: ProcessAcceptedVaultInvite, + ) {} + + async execute(dto: { + invite: SharedVaultInviteServerHash + message: AsymmetricMessageSharedVaultInvite + }): Promise { + await this.processInvite.execute(dto.message, dto.invite.shared_vault_uuid, dto.invite.sender_uuid) + + await this.inviteServer.acceptInvite({ + sharedVaultUuid: dto.invite.shared_vault_uuid, + inviteUuid: dto.invite.uuid, + }) + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ConvertToSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/ConvertToSharedVault.ts index e6cce9687..4842c9b07 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/ConvertToSharedVault.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/ConvertToSharedVault.ts @@ -1,19 +1,16 @@ -import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' import { SharedVaultListingInterface, VaultListingInterface, VaultListingMutator } from '@standardnotes/models' import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' import { SharedVaultServerInterface } from '@standardnotes/api' import { ItemManagerInterface } from '../../Item/ItemManagerInterface' -import { MoveItemsToVaultUseCase } from '../../Vaults/UseCase/MoveItemsToVault' -import { FilesClientInterface } from '@standardnotes/files' +import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' -export class ConvertToSharedVaultUseCase { +export class ConvertToSharedVault { constructor( private items: ItemManagerInterface, private mutator: MutatorClientInterface, - private sync: SyncServiceInterface, - private files: FilesClientInterface, private sharedVaultServer: SharedVaultServerInterface, + private moveItemsToVault: MoveItemsToVault, ) {} async execute(dto: { vault: VaultListingInterface }): Promise { @@ -39,8 +36,8 @@ export class ConvertToSharedVaultUseCase { ) const vaultItems = this.items.itemsBelongingToKeySystem(sharedVaultListing.systemIdentifier) - const moveToVaultUsecase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files) - await moveToVaultUsecase.execute({ vault: sharedVaultListing, items: vaultItems }) + + await this.moveItemsToVault.execute({ vault: sharedVaultListing, items: vaultItems }) return sharedVaultListing as SharedVaultListingInterface } diff --git a/packages/services/src/Domain/SharedVaults/UseCase/CreateSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/CreateSharedVault.ts index d13282db1..f36ef933b 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/CreateSharedVault.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/CreateSharedVault.ts @@ -1,4 +1,3 @@ -import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' import { KeySystemRootKeyStorageMode, SharedVaultListingInterface, @@ -6,22 +5,19 @@ import { VaultListingMutator, } from '@standardnotes/models' import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { SharedVaultServerInterface } from '@standardnotes/api' import { ItemManagerInterface } from '../../Item/ItemManagerInterface' -import { CreateVaultUseCase } from '../../Vaults/UseCase/CreateVault' -import { MoveItemsToVaultUseCase } from '../../Vaults/UseCase/MoveItemsToVault' -import { FilesClientInterface } from '@standardnotes/files' +import { CreateVault } from '../../Vaults/UseCase/CreateVault' +import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' -export class CreateSharedVaultUseCase { +export class CreateSharedVault { constructor( - private encryption: EncryptionProviderInterface, private items: ItemManagerInterface, private mutator: MutatorClientInterface, - private sync: SyncServiceInterface, - private files: FilesClientInterface, private sharedVaultServer: SharedVaultServerInterface, + private createVault: CreateVault, + private moveItemsToVault: MoveItemsToVault, ) {} async execute(dto: { @@ -30,8 +26,7 @@ export class CreateSharedVaultUseCase { userInputtedPassword: string | undefined storagePreference: KeySystemRootKeyStorageMode }): Promise { - const usecase = new CreateVaultUseCase(this.mutator, this.encryption, this.sync) - const privateVault = await usecase.execute({ + const privateVault = await this.createVault.execute({ vaultName: dto.vaultName, vaultDescription: dto.vaultDescription, userInputtedPassword: dto.userInputtedPassword, @@ -56,8 +51,8 @@ export class CreateSharedVaultUseCase { ) const vaultItems = this.items.itemsBelongingToKeySystem(sharedVaultListing.systemIdentifier) - const moveToVaultUsecase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files) - await moveToVaultUsecase.execute({ vault: sharedVaultListing, items: vaultItems }) + + await this.moveItemsToVault.execute({ vault: sharedVaultListing, items: vaultItems }) return sharedVaultListing as SharedVaultListingInterface } diff --git a/packages/services/src/Domain/SharedVaults/UseCase/DeleteExternalSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/DeleteExternalSharedVault.ts index 6295874aa..beaeb2716 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/DeleteExternalSharedVault.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/DeleteExternalSharedVault.ts @@ -1,26 +1,23 @@ import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' -import { StorageServiceInterface } from '../../Storage/StorageServiceInterface' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' -import { RemoveItemsLocallyUseCase } from '../../UseCase/RemoveItemsLocally' - -export class DeleteExternalSharedVaultUseCase { - private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage) +import { RemoveItemsLocally } from '../../UseCase/RemoveItemsLocally' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' +export class DeleteThirdPartyVault { constructor( private items: ItemManagerInterface, private mutator: MutatorClientInterface, - private encryption: EncryptionProviderInterface, - private storage: StorageServiceInterface, + private keys: KeySystemKeyManagerInterface, private sync: SyncServiceInterface, + private removeItemsLocally: RemoveItemsLocally, ) {} async execute(vault: VaultListingInterface): Promise { await this.deleteDataSharedByVaultUsers(vault) await this.deleteDataOwnedByThisUser(vault) - await this.encryption.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier) + await this.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier) void this.sync.sync({ sourceDescription: 'Not awaiting due to this event handler running from sync response' }) } @@ -34,13 +31,13 @@ export class DeleteExternalSharedVaultUseCase { this.items.allTrackedItems().filter((item) => item.key_system_identifier === vault.systemIdentifier) ) - const itemsKeys = this.encryption.keys.getKeySystemItemsKeys(vault.systemIdentifier) + const itemsKeys = this.keys.getKeySystemItemsKeys(vault.systemIdentifier) - await this.removeItemsLocallyUsecase.execute([...vaultItems, ...itemsKeys]) + await this.removeItemsLocally.execute([...vaultItems, ...itemsKeys]) } private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise { - const rootKeys = this.encryption.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier) + const rootKeys = this.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier) await this.mutator.setItemsToBeDeleted(rootKeys) await this.mutator.setItemToBeDeleted(vault) diff --git a/packages/services/src/Domain/SharedVaults/UseCase/DeleteSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/DeleteSharedVault.ts index 3950c75b2..328a237ad 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/DeleteSharedVault.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/DeleteSharedVault.ts @@ -1,19 +1,14 @@ -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' import { SharedVaultServerInterface } from '@standardnotes/api' -import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { SharedVaultListingInterface } from '@standardnotes/models' import { SyncServiceInterface } from '../../Sync/SyncServiceInterface' -import { DeleteVaultUseCase } from '../../Vaults/UseCase/DeleteVault' -import { EncryptionProviderInterface } from '@standardnotes/encryption' +import { DeleteVault } from '../../Vaults/UseCase/DeleteVault' -export class DeleteSharedVaultUseCase { +export class DeleteSharedVault { constructor( private sharedVaultServer: SharedVaultServerInterface, - private items: ItemManagerInterface, - private mutator: MutatorClientInterface, private sync: SyncServiceInterface, - private encryption: EncryptionProviderInterface, + private deleteVault: DeleteVault, ) {} async execute(params: { sharedVault: SharedVaultListingInterface }): Promise { @@ -25,8 +20,7 @@ export class DeleteSharedVaultUseCase { return ClientDisplayableError.FromString(`Failed to delete vault ${response}`) } - const deleteUsecase = new DeleteVaultUseCase(this.items, this.mutator, this.encryption) - await deleteUsecase.execute(params.sharedVault) + await this.deleteVault.execute(params.sharedVault) await this.sync.sync() } diff --git a/packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultTrustedContacts.ts b/packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultTrustedContacts.ts deleted file mode 100644 index 580b71074..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultTrustedContacts.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { SharedVaultUsersServerInterface } from '@standardnotes/api' -import { GetSharedVaultUsersUseCase } from './GetSharedVaultUsers' -import { SharedVaultListingInterface, TrustedContactInterface } from '@standardnotes/models' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { isNotUndefined } from '@standardnotes/utils' - -export class GetSharedVaultTrustedContacts { - constructor( - private contacts: ContactServiceInterface, - private sharedVaultUsersServer: SharedVaultUsersServerInterface, - ) {} - - async execute(vault: SharedVaultListingInterface): Promise { - const useCase = new GetSharedVaultUsersUseCase(this.sharedVaultUsersServer) - const users = await useCase.execute({ sharedVaultUuid: vault.sharing.sharedVaultUuid }) - if (!users) { - return undefined - } - - const contacts = users.map((user) => this.contacts.findTrustedContact(user.user_uuid)).filter(isNotUndefined) - return contacts - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/GetVaultContacts.ts b/packages/services/src/Domain/SharedVaults/UseCase/GetVaultContacts.ts new file mode 100644 index 000000000..eea864bc8 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/GetVaultContacts.ts @@ -0,0 +1,23 @@ +import { GetVaultUsers } from './GetVaultUsers' +import { TrustedContactInterface } from '@standardnotes/models' +import { isNotUndefined } from '@standardnotes/utils' +import { FindContact } from '../../Contacts/UseCase/FindContact' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' + +export class GetVaultContacts implements UseCaseInterface { + constructor(private findContact: FindContact, private getVaultUsers: GetVaultUsers) {} + + async execute(sharedVaultUuid: string): Promise> { + const users = await this.getVaultUsers.execute({ sharedVaultUuid }) + if (!users) { + return Result.fail('Failed to get vault users') + } + + const contacts = users + .map((user) => this.findContact.execute({ userUuid: user.user_uuid })) + .map((result) => (result.isFailed() ? undefined : result.getValue())) + .filter(isNotUndefined) + + return Result.ok(contacts) + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultUsers.ts b/packages/services/src/Domain/SharedVaults/UseCase/GetVaultUsers.ts similarity index 92% rename from packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultUsers.ts rename to packages/services/src/Domain/SharedVaults/UseCase/GetVaultUsers.ts index eb022c6e4..173faeff0 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/GetSharedVaultUsers.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/GetVaultUsers.ts @@ -1,7 +1,7 @@ import { SharedVaultUserServerHash, isErrorResponse } from '@standardnotes/responses' import { SharedVaultUsersServerInterface } from '@standardnotes/api' -export class GetSharedVaultUsersUseCase { +export class GetVaultUsers { constructor(private vaultUsersServer: SharedVaultUsersServerInterface) {} async execute(params: { sharedVaultUuid: string }): Promise { diff --git a/packages/services/src/Domain/SharedVaults/UseCase/InviteContactToSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/InviteContactToSharedVault.ts deleted file mode 100644 index 264b453e3..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/InviteContactToSharedVault.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { ClientDisplayableError, SharedVaultInviteServerHash, SharedVaultPermission } from '@standardnotes/responses' -import { - TrustedContactInterface, - SharedVaultListingInterface, - AsymmetricMessagePayloadType, -} from '@standardnotes/models' -import { SharedVaultInvitesServerInterface } from '@standardnotes/api' -import { SendSharedVaultInviteUseCase } from './SendSharedVaultInviteUseCase' -import { PkcKeyPair } from '@standardnotes/sncrypto-common' - -export class InviteContactToSharedVaultUseCase { - constructor( - private encryption: EncryptionProviderInterface, - private sharedVaultInviteServer: SharedVaultInvitesServerInterface, - ) {} - - async execute(params: { - senderKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - sharedVault: SharedVaultListingInterface - sharedVaultContacts: TrustedContactInterface[] - recipient: TrustedContactInterface - permissions: SharedVaultPermission - }): Promise { - const keySystemRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.sharedVault.systemIdentifier) - if (!keySystemRootKey) { - return ClientDisplayableError.FromString('Cannot add contact; key system root key not found') - } - - const delegatedContacts = params.sharedVaultContacts.filter( - (contact) => !contact.isMe && contact.contactUuid !== params.recipient.contactUuid, - ) - - const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({ - message: { - type: AsymmetricMessagePayloadType.SharedVaultInvite, - data: { - recipientUuid: params.recipient.contactUuid, - rootKey: keySystemRootKey.content, - trustedContacts: delegatedContacts.map((contact) => contact.content), - metadata: { - name: params.sharedVault.name, - description: params.sharedVault.description, - }, - }, - }, - senderKeyPair: params.senderKeyPair, - senderSigningKeyPair: params.senderSigningKeyPair, - recipientPublicKey: params.recipient.publicKeySet.encryption, - }) - - const createInviteUseCase = new SendSharedVaultInviteUseCase(this.sharedVaultInviteServer) - const createInviteResult = await createInviteUseCase.execute({ - sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid, - recipientUuid: params.recipient.contactUuid, - encryptedMessage, - permissions: params.permissions, - }) - - return createInviteResult - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/InviteToVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/InviteToVault.ts new file mode 100644 index 000000000..d010ac676 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/InviteToVault.ts @@ -0,0 +1,135 @@ +import { SharedVaultInviteServerHash, SharedVaultPermission } from '@standardnotes/responses' +import { + TrustedContactInterface, + SharedVaultListingInterface, + AsymmetricMessagePayloadType, + VaultInviteDelegatedContact, +} from '@standardnotes/models' +import { SendVaultInvite } from './SendVaultInvite' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { ShareContactWithVault } from './ShareContactWithVault' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' + +export class InviteToVault implements UseCaseInterface { + constructor( + private keyManager: KeySystemKeyManagerInterface, + private encryptMessage: EncryptMessage, + private sendInvite: SendVaultInvite, + private shareContact: ShareContactWithVault, + ) {} + + async execute(params: { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + senderUuid: string + sharedVault: SharedVaultListingInterface + sharedVaultContacts: TrustedContactInterface[] + recipient: TrustedContactInterface + permissions: SharedVaultPermission + }): Promise> { + const createInviteResult = await this.inviteContact(params) + + if (createInviteResult.isFailed()) { + return createInviteResult + } + + await this.shareContactWithOtherVaultMembers({ + contact: params.recipient, + senderUuid: params.senderUuid, + keys: params.keys, + sharedVault: params.sharedVault, + }) + + return createInviteResult + } + + private async shareContactWithOtherVaultMembers(params: { + contact: TrustedContactInterface + senderUuid: string + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + sharedVault: SharedVaultListingInterface + }): Promise> { + const result = await this.shareContact.execute({ + keys: params.keys, + senderUserUuid: params.senderUuid, + sharedVault: params.sharedVault, + contactToShare: params.contact, + }) + + return result + } + + private async inviteContact(params: { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + sharedVault: SharedVaultListingInterface + sharedVaultContacts: TrustedContactInterface[] + recipient: TrustedContactInterface + permissions: SharedVaultPermission + }): Promise> { + const keySystemRootKey = this.keyManager.getPrimaryKeySystemRootKey(params.sharedVault.systemIdentifier) + if (!keySystemRootKey) { + return Result.fail('Cannot invite contact; key system root key not found') + } + + const meContact = params.sharedVaultContacts.find((contact) => contact.isMe) + if (!meContact) { + return Result.fail('Cannot invite contact; me contact not found') + } + + const meContactContent: VaultInviteDelegatedContact = { + name: undefined, + contactUuid: meContact.contactUuid, + publicKeySet: meContact.publicKeySet, + } + + const delegatedContacts: VaultInviteDelegatedContact[] = params.sharedVaultContacts + .filter((contact) => !contact.isMe && contact.contactUuid !== params.recipient.contactUuid) + .map((contact) => { + return { + name: contact.name, + contactUuid: contact.contactUuid, + publicKeySet: contact.publicKeySet, + } + }) + + const encryptedMessage = this.encryptMessage.execute({ + message: { + type: AsymmetricMessagePayloadType.SharedVaultInvite, + data: { + recipientUuid: params.recipient.contactUuid, + rootKey: keySystemRootKey.content, + trustedContacts: [meContactContent, ...delegatedContacts], + metadata: { + name: params.sharedVault.name, + description: params.sharedVault.description, + }, + }, + }, + keys: params.keys, + recipientPublicKey: params.recipient.publicKeySet.encryption, + }) + + if (encryptedMessage.isFailed()) { + return Result.fail(encryptedMessage.getError()) + } + + const createInviteResult = await this.sendInvite.execute({ + sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid, + recipientUuid: params.recipient.contactUuid, + encryptedMessage: encryptedMessage.getValue(), + permissions: params.permissions, + }) + + return createInviteResult + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/LeaveSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/LeaveSharedVault.ts index 8f3cea34b..355b4c123 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/LeaveSharedVault.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/LeaveSharedVault.ts @@ -1,21 +1,14 @@ -import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface' -import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' -import { StorageServiceInterface } from './../../Storage/StorageServiceInterface' import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' import { SharedVaultUsersServerInterface } from '@standardnotes/api' -import { DeleteExternalSharedVaultUseCase } from './DeleteExternalSharedVault' +import { DeleteThirdPartyVault } from './DeleteExternalSharedVault' import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { SharedVaultListingInterface } from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -export class LeaveVaultUseCase { +export class LeaveVault { constructor( private vaultUserServer: SharedVaultUsersServerInterface, private items: ItemManagerInterface, - private mutator: MutatorClientInterface, - private encryption: EncryptionProviderInterface, - private storage: StorageServiceInterface, - private sync: SyncServiceInterface, + private deleteThirdPartyVault: DeleteThirdPartyVault, ) {} async execute(params: { @@ -36,13 +29,6 @@ export class LeaveVaultUseCase { return ClientDisplayableError.FromString(`Failed to leave vault ${JSON.stringify(response)}`) } - const removeLocalItems = new DeleteExternalSharedVaultUseCase( - this.items, - this.mutator, - this.encryption, - this.storage, - this.sync, - ) - await removeLocalItems.execute(latestVaultListing) + await this.deleteThirdPartyVault.execute(latestVaultListing) } } diff --git a/packages/services/src/Domain/SharedVaults/UseCase/NotifySharedVaultUsersOfRootKeyRotation.ts b/packages/services/src/Domain/SharedVaults/UseCase/NotifySharedVaultUsersOfRootKeyRotation.ts deleted file mode 100644 index 079ddf20a..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/NotifySharedVaultUsersOfRootKeyRotation.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - AsymmetricMessageServerInterface, - SharedVaultInvitesServerInterface, - SharedVaultUsersServerInterface, -} from '@standardnotes/api' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { SharedVaultListingInterface } from '@standardnotes/models' -import { ClientDisplayableError } from '@standardnotes/responses' -import { ReuploadSharedVaultInvitesAfterKeyRotationUseCase } from './ReuploadSharedVaultInvitesAfterKeyRotation' -import { SendSharedVaultRootKeyChangedMessageToAll } from './SendSharedVaultRootKeyChangedMessageToAll' - -export class NotifySharedVaultUsersOfRootKeyRotationUseCase { - constructor( - private sharedVaultUsersServer: SharedVaultUsersServerInterface, - private sharedVaultInvitesServer: SharedVaultInvitesServerInterface, - private messageServer: AsymmetricMessageServerInterface, - private encryption: EncryptionProviderInterface, - private contacts: ContactServiceInterface, - ) {} - - async execute(params: { - sharedVault: SharedVaultListingInterface - userUuid: string - }): Promise { - const errors: ClientDisplayableError[] = [] - const updatePendingInvitesUseCase = new ReuploadSharedVaultInvitesAfterKeyRotationUseCase( - this.encryption, - this.contacts, - this.sharedVaultInvitesServer, - this.sharedVaultUsersServer, - ) - - const updateExistingResults = await updatePendingInvitesUseCase.execute({ - sharedVault: params.sharedVault, - senderUuid: params.userUuid, - senderEncryptionKeyPair: this.encryption.getKeyPair(), - senderSigningKeyPair: this.encryption.getSigningKeyPair(), - }) - - errors.push(...updateExistingResults) - - const shareKeyUseCase = new SendSharedVaultRootKeyChangedMessageToAll( - this.encryption, - this.contacts, - this.sharedVaultUsersServer, - this.messageServer, - ) - - const shareKeyResults = await shareKeyUseCase.execute({ - keySystemIdentifier: params.sharedVault.systemIdentifier, - sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid, - senderUuid: params.userUuid, - senderEncryptionKeyPair: this.encryption.getKeyPair(), - senderSigningKeyPair: this.encryption.getSigningKeyPair(), - }) - - errors.push(...shareKeyResults) - - return errors - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation.ts b/packages/services/src/Domain/SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation.ts new file mode 100644 index 000000000..31bc919cc --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation.ts @@ -0,0 +1,116 @@ +import { SharedVaultInvitesServerInterface } from '@standardnotes/api' +import { AsymmetricMessageSharedVaultInvite, SharedVaultListingInterface } from '@standardnotes/models' +import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses' +import { SendVaultKeyChangedMessage } from './SendVaultKeyChangedMessage' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { InviteToVault } from './InviteToVault' +import { GetVaultContacts } from './GetVaultContacts' +import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage' +import { FindContact } from '../../Contacts/UseCase/FindContact' + +type Params = { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + sharedVault: SharedVaultListingInterface + senderUuid: string +} + +export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface { + constructor( + private findContact: FindContact, + private sendKeyChangedMessage: SendVaultKeyChangedMessage, + private inviteToVault: InviteToVault, + private inviteServer: SharedVaultInvitesServerInterface, + private getVaultContacts: GetVaultContacts, + private decryptOwnMessage: DecryptOwnMessage, + ) {} + + async execute(params: Params): Promise> { + await this.reinvitePendingInvites(params) + + await this.performSendKeyChangeMessage(params) + + return Result.ok() + } + + private async reinvitePendingInvites(params: Params): Promise> { + const existingInvites = await this.getExistingInvites(params.sharedVault.sharing.sharedVaultUuid) + if (existingInvites.isFailed()) { + return existingInvites + } + + await this.deleteAllInvites(params.sharedVault.sharing.sharedVaultUuid) + + const contacts = await this.getVaultContacts.execute(params.sharedVault.sharing.sharedVaultUuid) + + for (const invite of existingInvites.getValue()) { + const recipient = this.findContact.execute({ userUuid: invite.user_uuid }) + if (recipient.isFailed()) { + continue + } + + const decryptedPreviousInvite = this.decryptOwnMessage.execute({ + message: invite.encrypted_message, + privateKey: params.keys.encryption.privateKey, + recipientPublicKey: recipient.getValue().publicKeySet.encryption, + }) + + if (decryptedPreviousInvite.isFailed()) { + return Result.fail(decryptedPreviousInvite.getError()) + } + + await this.inviteToVault.execute({ + keys: params.keys, + sharedVault: params.sharedVault, + sharedVaultContacts: !contacts.isFailed() ? contacts.getValue() : [], + recipient: recipient.getValue(), + permissions: invite.permissions, + senderUuid: params.senderUuid, + }) + } + + return Result.ok() + } + + private async performSendKeyChangeMessage(params: Params): Promise> { + const result = await this.sendKeyChangedMessage.execute({ + keySystemIdentifier: params.sharedVault.systemIdentifier, + sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid, + senderUuid: params.senderUuid, + keys: params.keys, + }) + + if (result.isFailed()) { + return result + } + + return Result.ok() + } + + private async deleteAllInvites(sharedVaultUuid: string): Promise> { + const response = await this.inviteServer.deleteAllSharedVaultInvites({ + sharedVaultUuid: sharedVaultUuid, + }) + + if (isErrorResponse(response)) { + return Result.fail(`Failed to delete existing invites ${response}`) + } + + return Result.ok() + } + + private async getExistingInvites(sharedVaultUuid: string): Promise> { + const response = await this.inviteServer.getOutboundUserInvites() + + if (isErrorResponse(response)) { + return Result.fail(`Failed to get outbound user invites ${response}`) + } + + const invites = response.data.invites + + return Result.ok(invites.filter((invite) => invite.shared_vault_uuid === sharedVaultUuid)) + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/RemoveSharedVaultMember.ts b/packages/services/src/Domain/SharedVaults/UseCase/RemoveSharedVaultMember.ts index abb03f348..290ffd5ab 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/RemoveSharedVaultMember.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/RemoveSharedVaultMember.ts @@ -1,7 +1,7 @@ import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' import { SharedVaultUsersServerInterface } from '@standardnotes/api' -export class RemoveVaultMemberUseCase { +export class RemoveVaultMember { constructor(private vaultUserServer: SharedVaultUsersServerInterface) {} async execute(params: { sharedVaultUuid: string; userUuid: string }): Promise { diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ReuploadAllInvites.ts b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadAllInvites.ts new file mode 100644 index 000000000..206d3217c --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadAllInvites.ts @@ -0,0 +1,86 @@ +import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses' +import { SharedVaultInvitesServerInterface } from '@standardnotes/api' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { ReuploadInvite } from './ReuploadInvite' +import { FindContact } from '../../Contacts/UseCase/FindContact' + +type ReuploadAllInvitesDTO = { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + previousKeys?: { + encryption: PkcKeyPair + signing: PkcKeyPair + } +} + +export class ReuploadAllInvites implements UseCaseInterface { + constructor( + private reuploadInvite: ReuploadInvite, + private findContact: FindContact, + private inviteServer: SharedVaultInvitesServerInterface, + ) {} + + async execute(params: ReuploadAllInvitesDTO): Promise> { + const invites = await this.getExistingInvites() + if (invites.isFailed()) { + return invites + } + + const deleteResult = await this.deleteExistingInvites() + if (deleteResult.isFailed()) { + return deleteResult + } + + const errors: string[] = [] + + for (const invite of invites.getValue()) { + const recipient = this.findContact.execute({ userUuid: invite.user_uuid }) + if (recipient.isFailed()) { + errors.push(`Contact not found for invite ${invite.user_uuid}`) + continue + } + + const result = await this.reuploadInvite.execute({ + keys: params.keys, + previousKeys: params.previousKeys, + recipient: recipient.getValue(), + previousInvite: invite, + }) + + if (result.isFailed()) { + errors.push(result.getError()) + } + } + + if (errors.length > 0) { + return Result.fail(errors.join(', ')) + } + + return Result.ok() + } + + private async getExistingInvites(): Promise> { + const response = await this.inviteServer.getOutboundUserInvites() + + if (isErrorResponse(response)) { + return Result.fail(`Failed to get outbound user invites ${response}`) + } + + const invites = response.data.invites + + return Result.ok(invites) + } + + private async deleteExistingInvites(): Promise> { + const response = await this.inviteServer.deleteAllOutboundInvites() + + if (isErrorResponse(response)) { + return Result.fail(`Failed to delete existing invites ${response}`) + } + + return Result.ok() + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ReuploadInvite.ts b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadInvite.ts new file mode 100644 index 000000000..daa2ad0d0 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadInvite.ts @@ -0,0 +1,57 @@ +import { DecryptOwnMessage } from './../../Encryption/UseCase/Asymmetric/DecryptOwnMessage' +import { AsymmetricMessageSharedVaultInvite, TrustedContactInterface } from '@standardnotes/models' +import { SharedVaultInviteServerHash } from '@standardnotes/responses' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { SendVaultInvite } from './SendVaultInvite' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' + +export class ReuploadInvite implements UseCaseInterface { + constructor( + private decryptOwnMessage: DecryptOwnMessage, + private sendInvite: SendVaultInvite, + private encryptMessage: EncryptMessage, + ) {} + + async execute(params: { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + previousKeys?: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + recipient: TrustedContactInterface + previousInvite: SharedVaultInviteServerHash + }): Promise> { + const decryptedPreviousInvite = this.decryptOwnMessage.execute({ + message: params.previousInvite.encrypted_message, + privateKey: params.previousKeys?.encryption.privateKey ?? params.keys.encryption.privateKey, + recipientPublicKey: params.recipient.publicKeySet.encryption, + }) + + if (decryptedPreviousInvite.isFailed()) { + return Result.fail(decryptedPreviousInvite.getError()) + } + + const encryptedMessage = this.encryptMessage.execute({ + message: decryptedPreviousInvite.getValue(), + keys: params.keys, + recipientPublicKey: params.recipient.publicKeySet.encryption, + }) + + if (encryptedMessage.isFailed()) { + return Result.fail(encryptedMessage.getError()) + } + + const createInviteResult = await this.sendInvite.execute({ + sharedVaultUuid: params.previousInvite.shared_vault_uuid, + recipientUuid: params.recipient.contactUuid, + encryptedMessage: encryptedMessage.getValue(), + permissions: params.previousInvite.permissions, + }) + + return createInviteResult + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ReuploadSharedVaultInvitesAfterKeyRotation.ts b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadSharedVaultInvitesAfterKeyRotation.ts deleted file mode 100644 index 12847005e..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/ReuploadSharedVaultInvitesAfterKeyRotation.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { - KeySystemRootKeyContentSpecialized, - SharedVaultListingInterface, - TrustedContactInterface, -} from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { - ClientDisplayableError, - SharedVaultInviteServerHash, - isClientDisplayableError, - isErrorResponse, -} from '@standardnotes/responses' -import { SharedVaultInvitesServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { InviteContactToSharedVaultUseCase } from './InviteContactToSharedVault' -import { GetSharedVaultTrustedContacts } from './GetSharedVaultTrustedContacts' - -type ReuploadAllSharedVaultInvitesDTO = { - sharedVault: SharedVaultListingInterface - senderUuid: string - senderEncryptionKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair -} - -export class ReuploadSharedVaultInvitesAfterKeyRotationUseCase { - constructor( - private encryption: EncryptionProviderInterface, - private contacts: ContactServiceInterface, - private vaultInvitesServer: SharedVaultInvitesServerInterface, - private vaultUserServer: SharedVaultUsersServerInterface, - ) {} - - async execute(params: ReuploadAllSharedVaultInvitesDTO): Promise { - const keySystemRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.sharedVault.systemIdentifier) - if (!keySystemRootKey) { - throw new Error(`Vault key not found for keySystemIdentifier ${params.sharedVault.systemIdentifier}`) - } - - const existingInvites = await this.getExistingInvites(params.sharedVault.sharing.sharedVaultUuid) - if (isClientDisplayableError(existingInvites)) { - return [existingInvites] - } - - const deleteResult = await this.deleteExistingInvites(params.sharedVault.sharing.sharedVaultUuid) - if (isClientDisplayableError(deleteResult)) { - return [deleteResult] - } - - const vaultContacts = await this.getVaultContacts(params.sharedVault) - if (vaultContacts.length === 0) { - return [] - } - - const errors: ClientDisplayableError[] = [] - - for (const invite of existingInvites) { - const contact = this.contacts.findTrustedContact(invite.user_uuid) - if (!contact) { - errors.push(ClientDisplayableError.FromString(`Contact not found for invite ${invite.user_uuid}`)) - continue - } - - const result = await this.sendNewInvite({ - usecaseDTO: params, - contact: contact, - previousInvite: invite, - keySystemRootKeyData: keySystemRootKey.content, - sharedVaultContacts: vaultContacts, - }) - - if (isClientDisplayableError(result)) { - errors.push(result) - } - } - - return errors - } - - private async getVaultContacts(sharedVault: SharedVaultListingInterface): Promise { - const usecase = new GetSharedVaultTrustedContacts(this.contacts, this.vaultUserServer) - const contacts = await usecase.execute(sharedVault) - if (!contacts) { - return [] - } - - return contacts - } - - private async getExistingInvites( - sharedVaultUuid: string, - ): Promise { - const response = await this.vaultInvitesServer.getOutboundUserInvites() - - if (isErrorResponse(response)) { - return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`) - } - - const invites = response.data.invites - - return invites.filter((invite) => invite.shared_vault_uuid === sharedVaultUuid) - } - - private async deleteExistingInvites(sharedVaultUuid: string): Promise { - const response = await this.vaultInvitesServer.deleteAllSharedVaultInvites({ - sharedVaultUuid: sharedVaultUuid, - }) - - if (isErrorResponse(response)) { - return ClientDisplayableError.FromString(`Failed to delete existing invites ${response}`) - } - } - - private async sendNewInvite(params: { - usecaseDTO: ReuploadAllSharedVaultInvitesDTO - contact: TrustedContactInterface - previousInvite: SharedVaultInviteServerHash - keySystemRootKeyData: KeySystemRootKeyContentSpecialized - sharedVaultContacts: TrustedContactInterface[] - }): Promise { - const signatureResult = this.encryption.asymmetricSignatureVerifyDetached(params.previousInvite.encrypted_message) - if (!signatureResult.signatureVerified) { - return ClientDisplayableError.FromString('Failed to verify signature of previous invite') - } - - if (signatureResult.signaturePublicKey !== params.usecaseDTO.senderSigningKeyPair.publicKey) { - return ClientDisplayableError.FromString('Sender public key does not match signature') - } - - const usecase = new InviteContactToSharedVaultUseCase(this.encryption, this.vaultInvitesServer) - const result = await usecase.execute({ - senderKeyPair: params.usecaseDTO.senderEncryptionKeyPair, - senderSigningKeyPair: params.usecaseDTO.senderSigningKeyPair, - sharedVault: params.usecaseDTO.sharedVault, - sharedVaultContacts: params.sharedVaultContacts, - recipient: params.contact, - permissions: params.previousInvite.permissions, - }) - - if (isClientDisplayableError(result)) { - return result - } - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ReuploadVaultInvites.ts b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadVaultInvites.ts new file mode 100644 index 000000000..b2cff7737 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/ReuploadVaultInvites.ts @@ -0,0 +1,86 @@ +import { SharedVaultListingInterface } from '@standardnotes/models' +import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/responses' +import { SharedVaultInvitesServerInterface } from '@standardnotes/api' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { ReuploadInvite } from './ReuploadInvite' +import { FindContact } from '../../Contacts/UseCase/FindContact' + +type ReuploadVaultInvitesDTO = { + sharedVault: SharedVaultListingInterface + senderUuid: string + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } +} + +export class ReuploadVaultInvites implements UseCaseInterface { + constructor( + private reuploadInvite: ReuploadInvite, + private findContact: FindContact, + private inviteServer: SharedVaultInvitesServerInterface, + ) {} + + async execute(params: ReuploadVaultInvitesDTO): Promise> { + const existingInvites = await this.getExistingInvites(params.sharedVault.sharing.sharedVaultUuid) + if (existingInvites.isFailed()) { + return existingInvites + } + + const deleteResult = await this.deleteExistingInvites(params.sharedVault.sharing.sharedVaultUuid) + if (deleteResult.isFailed()) { + return deleteResult + } + + const errors: string[] = [] + + for (const invite of existingInvites.getValue()) { + const recipient = this.findContact.execute({ userUuid: invite.user_uuid }) + if (recipient.isFailed()) { + errors.push(`Contact not found for invite ${invite.user_uuid}`) + continue + } + + const result = await this.reuploadInvite.execute({ + keys: params.keys, + recipient: recipient.getValue(), + previousInvite: invite, + }) + + if (result.isFailed()) { + errors.push(result.getError()) + } + } + + if (errors.length > 0) { + return Result.fail(errors.join(', ')) + } + + return Result.ok() + } + + private async getExistingInvites(sharedVaultUuid: string): Promise> { + const response = await this.inviteServer.getOutboundUserInvites() + + if (isErrorResponse(response)) { + return Result.fail(`Failed to get outbound user invites ${response}`) + } + + const invites = response.data.invites + + return Result.ok(invites.filter((invite) => invite.shared_vault_uuid === sharedVaultUuid)) + } + + private async deleteExistingInvites(sharedVaultUuid: string): Promise> { + const response = await this.inviteServer.deleteAllSharedVaultInvites({ + sharedVaultUuid: sharedVaultUuid, + }) + + if (isErrorResponse(response)) { + return Result.fail(`Failed to delete existing invites ${response}`) + } + + return Result.ok() + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultMetadataChangedMessageToAll.ts b/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultMetadataChangedMessageToAll.ts deleted file mode 100644 index 2473cc4c9..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultMetadataChangedMessageToAll.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { - AsymmetricMessagePayloadType, - AsymmetricMessageSharedVaultMetadataChanged, - SharedVaultListingInterface, - TrustedContactInterface, -} from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses' -import { AsymmetricMessageServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api' -import { GetSharedVaultUsersUseCase } from './GetSharedVaultUsers' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { SendAsymmetricMessageUseCase } from '../../AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase' - -export class SendSharedVaultMetadataChangedMessageToAll { - constructor( - private encryption: EncryptionProviderInterface, - private contacts: ContactServiceInterface, - private vaultUsersServer: SharedVaultUsersServerInterface, - private messageServer: AsymmetricMessageServerInterface, - ) {} - - async execute(params: { - vault: SharedVaultListingInterface - senderUuid: string - senderEncryptionKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - }): Promise { - const errors: ClientDisplayableError[] = [] - - const getUsersUseCase = new GetSharedVaultUsersUseCase(this.vaultUsersServer) - const users = await getUsersUseCase.execute({ sharedVaultUuid: params.vault.sharing.sharedVaultUuid }) - if (!users) { - return [ClientDisplayableError.FromString('Cannot send metadata changed message; users not found')] - } - - for (const user of users) { - if (user.user_uuid === params.senderUuid) { - continue - } - - const trustedContact = this.contacts.findTrustedContact(user.user_uuid) - if (!trustedContact) { - continue - } - - const sendMessageResult = await this.sendToContact({ - vault: params.vault, - senderKeyPair: params.senderEncryptionKeyPair, - senderSigningKeyPair: params.senderSigningKeyPair, - contact: trustedContact, - }) - - if (isClientDisplayableError(sendMessageResult)) { - errors.push(sendMessageResult) - } - } - - return errors - } - - private async sendToContact(params: { - vault: SharedVaultListingInterface - senderKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - contact: TrustedContactInterface - }): Promise { - const message: AsymmetricMessageSharedVaultMetadataChanged = { - type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged, - data: { - recipientUuid: params.contact.contactUuid, - sharedVaultUuid: params.vault.sharing.sharedVaultUuid, - name: params.vault.name, - description: params.vault.description, - }, - } - - const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({ - message: message, - senderKeyPair: params.senderKeyPair, - senderSigningKeyPair: params.senderSigningKeyPair, - recipientPublicKey: params.contact.publicKeySet.encryption, - }) - - const replaceabilityIdentifier = [ - AsymmetricMessagePayloadType.SharedVaultMetadataChanged, - params.vault.sharing.sharedVaultUuid, - params.vault.systemIdentifier, - ].join(':') - - const sendMessageUseCase = new SendAsymmetricMessageUseCase(this.messageServer) - const sendMessageResult = await sendMessageUseCase.execute({ - recipientUuid: params.contact.contactUuid, - encryptedMessage, - replaceabilityIdentifier, - }) - - return sendMessageResult - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultRootKeyChangedMessageToAll.ts b/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultRootKeyChangedMessageToAll.ts deleted file mode 100644 index 8656afc89..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultRootKeyChangedMessageToAll.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { - AsymmetricMessagePayloadType, - AsymmetricMessageSharedVaultRootKeyChanged, - KeySystemIdentifier, - TrustedContactInterface, -} from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses' -import { AsymmetricMessageServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api' -import { GetSharedVaultUsersUseCase } from './GetSharedVaultUsers' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { SendAsymmetricMessageUseCase } from '../../AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase' - -export class SendSharedVaultRootKeyChangedMessageToAll { - constructor( - private encryption: EncryptionProviderInterface, - private contacts: ContactServiceInterface, - private vaultUsersServer: SharedVaultUsersServerInterface, - private messageServer: AsymmetricMessageServerInterface, - ) {} - - async execute(params: { - keySystemIdentifier: KeySystemIdentifier - sharedVaultUuid: string - senderUuid: string - senderEncryptionKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - }): Promise { - const errors: ClientDisplayableError[] = [] - - const getUsersUseCase = new GetSharedVaultUsersUseCase(this.vaultUsersServer) - const users = await getUsersUseCase.execute({ sharedVaultUuid: params.sharedVaultUuid }) - if (!users) { - return [ClientDisplayableError.FromString('Cannot send root key changed message; users not found')] - } - - for (const user of users) { - if (user.user_uuid === params.senderUuid) { - continue - } - - const trustedContact = this.contacts.findTrustedContact(user.user_uuid) - if (!trustedContact) { - continue - } - - const sendMessageResult = await this.sendToContact({ - keySystemIdentifier: params.keySystemIdentifier, - sharedVaultUuid: params.sharedVaultUuid, - senderKeyPair: params.senderEncryptionKeyPair, - senderSigningKeyPair: params.senderSigningKeyPair, - contact: trustedContact, - }) - - if (isClientDisplayableError(sendMessageResult)) { - errors.push(sendMessageResult) - } - } - - return errors - } - - private async sendToContact(params: { - keySystemIdentifier: KeySystemIdentifier - sharedVaultUuid: string - senderKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - contact: TrustedContactInterface - }): Promise { - const keySystemRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.keySystemIdentifier) - if (!keySystemRootKey) { - throw new Error(`Vault key not found for keySystemIdentifier ${params.keySystemIdentifier}`) - } - - const message: AsymmetricMessageSharedVaultRootKeyChanged = { - type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged, - data: { recipientUuid: params.contact.contactUuid, rootKey: keySystemRootKey.content }, - } - - const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({ - message: message, - senderKeyPair: params.senderKeyPair, - senderSigningKeyPair: params.senderSigningKeyPair, - recipientPublicKey: params.contact.publicKeySet.encryption, - }) - - const replaceabilityIdentifier = [ - AsymmetricMessagePayloadType.SharedVaultRootKeyChanged, - params.sharedVaultUuid, - params.keySystemIdentifier, - ].join(':') - - const sendMessageUseCase = new SendAsymmetricMessageUseCase(this.messageServer) - const sendMessageResult = await sendMessageUseCase.execute({ - recipientUuid: params.contact.contactUuid, - encryptedMessage, - replaceabilityIdentifier, - }) - - return sendMessageResult - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/SendVaultDataChangedMessage.ts b/packages/services/src/Domain/SharedVaults/UseCase/SendVaultDataChangedMessage.ts new file mode 100644 index 000000000..552fd55a9 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/SendVaultDataChangedMessage.ts @@ -0,0 +1,108 @@ +import { + AsymmetricMessagePayloadType, + AsymmetricMessageSharedVaultMetadataChanged, + SharedVaultListingInterface, + TrustedContactInterface, +} from '@standardnotes/models' +import { AsymmetricMessageServerHash } from '@standardnotes/responses' +import { GetVaultUsers } from './GetVaultUsers' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { GetReplaceabilityIdentifier } from '../../AsymmetricMessage/UseCase/GetReplaceabilityIdentifier' +import { FindContact } from '../../Contacts/UseCase/FindContact' + +export class SendVaultDataChangedMessage implements UseCaseInterface { + constructor( + private encryptMessage: EncryptMessage, + private findContact: FindContact, + private getVaultUsers: GetVaultUsers, + private sendMessage: SendMessage, + ) {} + + async execute(params: { + vault: SharedVaultListingInterface + senderUuid: string + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + }): Promise> { + const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.vault.sharing.sharedVaultUuid }) + if (!users) { + return Result.fail('Cannot send metadata changed message; users not found') + } + + const errors: string[] = [] + for (const user of users) { + if (user.user_uuid === params.senderUuid) { + continue + } + + const trustedContact = this.findContact.execute({ userUuid: user.user_uuid }) + if (trustedContact.isFailed()) { + continue + } + + const sendMessageResult = await this.sendToContact({ + vault: params.vault, + keys: params.keys, + contact: trustedContact.getValue(), + }) + + if (sendMessageResult.isFailed()) { + errors.push(sendMessageResult.getError()) + } + } + + if (errors.length > 0) { + return Result.fail(errors.join(', ')) + } + + return Result.ok() + } + + private async sendToContact(params: { + vault: SharedVaultListingInterface + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + contact: TrustedContactInterface + }): Promise> { + const message: AsymmetricMessageSharedVaultMetadataChanged = { + type: AsymmetricMessagePayloadType.SharedVaultMetadataChanged, + data: { + recipientUuid: params.contact.contactUuid, + sharedVaultUuid: params.vault.sharing.sharedVaultUuid, + name: params.vault.name, + description: params.vault.description, + }, + } + + const encryptedMessage = this.encryptMessage.execute({ + message: message, + keys: params.keys, + recipientPublicKey: params.contact.publicKeySet.encryption, + }) + + if (encryptedMessage.isFailed()) { + return Result.fail(encryptedMessage.getError()) + } + + const replaceabilityIdentifier = GetReplaceabilityIdentifier( + AsymmetricMessagePayloadType.SharedVaultMetadataChanged, + params.vault.sharing.sharedVaultUuid, + params.vault.systemIdentifier, + ) + + const sendMessageResult = await this.sendMessage.execute({ + recipientUuid: params.contact.contactUuid, + encryptedMessage: encryptedMessage.getValue(), + replaceabilityIdentifier, + }) + + return sendMessageResult + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultInviteUseCase.ts b/packages/services/src/Domain/SharedVaults/UseCase/SendVaultInvite.ts similarity index 67% rename from packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultInviteUseCase.ts rename to packages/services/src/Domain/SharedVaults/UseCase/SendVaultInvite.ts index 26bc59d57..06afba3a6 100644 --- a/packages/services/src/Domain/SharedVaults/UseCase/SendSharedVaultInviteUseCase.ts +++ b/packages/services/src/Domain/SharedVaults/UseCase/SendVaultInvite.ts @@ -1,12 +1,13 @@ import { - ClientDisplayableError, SharedVaultInviteServerHash, isErrorResponse, SharedVaultPermission, + getErrorFromErrorResponse, } from '@standardnotes/responses' import { SharedVaultInvitesServerInterface } from '@standardnotes/api' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' -export class SendSharedVaultInviteUseCase { +export class SendVaultInvite implements UseCaseInterface { constructor(private vaultInvitesServer: SharedVaultInvitesServerInterface) {} async execute(params: { @@ -14,7 +15,7 @@ export class SendSharedVaultInviteUseCase { recipientUuid: string encryptedMessage: string permissions: SharedVaultPermission - }): Promise { + }): Promise> { const response = await this.vaultInvitesServer.createInvite({ sharedVaultUuid: params.sharedVaultUuid, recipientUuid: params.recipientUuid, @@ -23,9 +24,9 @@ export class SendSharedVaultInviteUseCase { }) if (isErrorResponse(response)) { - return ClientDisplayableError.FromNetworkError(response) + return Result.fail(getErrorFromErrorResponse(response).message) } - return response.data.invite + return Result.ok(response.data.invite) } } diff --git a/packages/services/src/Domain/SharedVaults/UseCase/SendVaultKeyChangedMessage.ts b/packages/services/src/Domain/SharedVaults/UseCase/SendVaultKeyChangedMessage.ts new file mode 100644 index 000000000..fe8769a30 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/SendVaultKeyChangedMessage.ts @@ -0,0 +1,114 @@ +import { + AsymmetricMessagePayloadType, + AsymmetricMessageSharedVaultRootKeyChanged, + KeySystemIdentifier, + TrustedContactInterface, +} from '@standardnotes/models' +import { AsymmetricMessageServerHash } from '@standardnotes/responses' +import { GetVaultUsers } from './GetVaultUsers' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { GetReplaceabilityIdentifier } from '../../AsymmetricMessage/UseCase/GetReplaceabilityIdentifier' +import { FindContact } from '../../Contacts/UseCase/FindContact' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' + +export class SendVaultKeyChangedMessage implements UseCaseInterface { + constructor( + private encryptMessage: EncryptMessage, + private keyManager: KeySystemKeyManagerInterface, + private findContact: FindContact, + private sendMessage: SendMessage, + private getVaultUsers: GetVaultUsers, + ) {} + + async execute(params: { + keySystemIdentifier: KeySystemIdentifier + sharedVaultUuid: string + senderUuid: string + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + }): Promise> { + const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.sharedVaultUuid }) + if (!users) { + return Result.fail('Cannot send root key changed message; users not found') + } + + const errors: string[] = [] + + for (const user of users) { + if (user.user_uuid === params.senderUuid) { + continue + } + + const trustedContact = this.findContact.execute({ userUuid: user.user_uuid }) + if (trustedContact.isFailed()) { + continue + } + + const result = await this.sendToContact({ + keySystemIdentifier: params.keySystemIdentifier, + sharedVaultUuid: params.sharedVaultUuid, + keys: params.keys, + contact: trustedContact.getValue(), + }) + + if (result.isFailed()) { + errors.push(result.getError()) + } + } + + if (errors.length > 0) { + return Result.fail(errors.join(', ')) + } + + return Result.ok() + } + + private async sendToContact(params: { + keySystemIdentifier: KeySystemIdentifier + sharedVaultUuid: string + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + contact: TrustedContactInterface + }): Promise> { + const keySystemRootKey = this.keyManager.getPrimaryKeySystemRootKey(params.keySystemIdentifier) + if (!keySystemRootKey) { + throw new Error(`Vault key not found for keySystemIdentifier ${params.keySystemIdentifier}`) + } + + const message: AsymmetricMessageSharedVaultRootKeyChanged = { + type: AsymmetricMessagePayloadType.SharedVaultRootKeyChanged, + data: { recipientUuid: params.contact.contactUuid, rootKey: keySystemRootKey.content }, + } + + const encryptedMessage = this.encryptMessage.execute({ + message: message, + keys: params.keys, + recipientPublicKey: params.contact.publicKeySet.encryption, + }) + + if (encryptedMessage.isFailed()) { + return Result.fail(encryptedMessage.getError()) + } + + const replaceabilityIdentifier = GetReplaceabilityIdentifier( + AsymmetricMessagePayloadType.SharedVaultRootKeyChanged, + params.sharedVaultUuid, + params.keySystemIdentifier, + ) + + const sendMessageResult = await this.sendMessage.execute({ + recipientUuid: params.contact.contactUuid, + encryptedMessage: encryptedMessage.getValue(), + replaceabilityIdentifier, + }) + + return sendMessageResult + } +} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithAllMembersOfSharedVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithAllMembersOfSharedVault.ts deleted file mode 100644 index 0e6b63a3b..000000000 --- a/packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithAllMembersOfSharedVault.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' -import { - TrustedContactInterface, - SharedVaultListingInterface, - AsymmetricMessagePayloadType, -} from '@standardnotes/models' -import { AsymmetricMessageServerInterface, SharedVaultUsersServerInterface } from '@standardnotes/api' -import { PkcKeyPair } from '@standardnotes/sncrypto-common' -import { ContactServiceInterface } from '../../Contacts/ContactServiceInterface' -import { SendAsymmetricMessageUseCase } from '../../AsymmetricMessage/UseCase/SendAsymmetricMessageUseCase' - -export class ShareContactWithAllMembersOfSharedVaultUseCase { - constructor( - private contacts: ContactServiceInterface, - private encryption: EncryptionProviderInterface, - private sharedVaultUsersServer: SharedVaultUsersServerInterface, - private messageServer: AsymmetricMessageServerInterface, - ) {} - - async execute(params: { - senderKeyPair: PkcKeyPair - senderSigningKeyPair: PkcKeyPair - senderUserUuid: string - sharedVault: SharedVaultListingInterface - contactToShare: TrustedContactInterface - }): Promise { - if (params.sharedVault.sharing.ownerUserUuid !== params.senderUserUuid) { - return ClientDisplayableError.FromString('Cannot share contact; user is not the owner of the shared vault') - } - - const usersResponse = await this.sharedVaultUsersServer.getSharedVaultUsers({ - sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid, - }) - - if (isErrorResponse(usersResponse)) { - return ClientDisplayableError.FromString('Cannot share contact; shared vault users not found') - } - - const users = usersResponse.data.users - if (users.length === 0) { - return - } - - const messageSendUseCase = new SendAsymmetricMessageUseCase(this.messageServer) - - for (const vaultUser of users) { - if (vaultUser.user_uuid === params.senderUserUuid) { - continue - } - - if (vaultUser.user_uuid === params.contactToShare.contactUuid) { - continue - } - - const vaultUserAsContact = this.contacts.findTrustedContact(vaultUser.user_uuid) - if (!vaultUserAsContact) { - continue - } - - const encryptedMessage = this.encryption.asymmetricallyEncryptMessage({ - message: { - type: AsymmetricMessagePayloadType.ContactShare, - data: { recipientUuid: vaultUserAsContact.contactUuid, trustedContact: params.contactToShare.content }, - }, - senderKeyPair: params.senderKeyPair, - senderSigningKeyPair: params.senderSigningKeyPair, - recipientPublicKey: vaultUserAsContact.publicKeySet.encryption, - }) - - await messageSendUseCase.execute({ - recipientUuid: vaultUserAsContact.contactUuid, - encryptedMessage, - replaceabilityIdentifier: undefined, - }) - } - } -} diff --git a/packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithVault.ts b/packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithVault.ts new file mode 100644 index 000000000..74580d876 --- /dev/null +++ b/packages/services/src/Domain/SharedVaults/UseCase/ShareContactWithVault.ts @@ -0,0 +1,85 @@ +import { + TrustedContactInterface, + SharedVaultListingInterface, + AsymmetricMessagePayloadType, +} from '@standardnotes/models' +import { PkcKeyPair } from '@standardnotes/sncrypto-common' +import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage' +import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage' +import { Result, UseCaseInterface } from '@standardnotes/domain-core' +import { FindContact } from '../../Contacts/UseCase/FindContact' +import { GetVaultUsers } from './GetVaultUsers' + +export class ShareContactWithVault implements UseCaseInterface { + constructor( + private findContact: FindContact, + private encryptMessage: EncryptMessage, + private sendMessage: SendMessage, + private getVaultUsers: GetVaultUsers, + ) {} + + async execute(params: { + keys: { + encryption: PkcKeyPair + signing: PkcKeyPair + } + senderUserUuid: string + sharedVault: SharedVaultListingInterface + contactToShare: TrustedContactInterface + }): Promise> { + if (params.sharedVault.sharing.ownerUserUuid !== params.senderUserUuid) { + return Result.fail('Cannot share contact; user is not the owner of the shared vault') + } + + const users = await this.getVaultUsers.execute({ + sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid, + }) + + if (!users) { + return Result.fail('Cannot share contact; shared vault users not found') + } + + if (users.length === 0) { + return Result.ok() + } + + for (const vaultUser of users) { + if (vaultUser.user_uuid === params.senderUserUuid) { + continue + } + + if (vaultUser.user_uuid === params.contactToShare.contactUuid) { + continue + } + + const vaultUserAsContact = this.findContact.execute({ userUuid: vaultUser.user_uuid }) + if (vaultUserAsContact.isFailed()) { + continue + } + + const encryptedMessage = this.encryptMessage.execute({ + message: { + type: AsymmetricMessagePayloadType.ContactShare, + data: { + recipientUuid: vaultUserAsContact.getValue().contactUuid, + trustedContact: params.contactToShare.content, + }, + }, + keys: params.keys, + recipientPublicKey: vaultUserAsContact.getValue().publicKeySet.encryption, + }) + + if (encryptedMessage.isFailed()) { + continue + } + + await this.sendMessage.execute({ + recipientUuid: vaultUserAsContact.getValue().contactUuid, + encryptedMessage: encryptedMessage.getValue(), + replaceabilityIdentifier: undefined, + }) + } + + return Result.ok() + } +} diff --git a/packages/services/src/Domain/Storage/StorageServiceInterface.ts b/packages/services/src/Domain/Storage/StorageServiceInterface.ts index e89e01068..231f4d6d9 100644 --- a/packages/services/src/Domain/Storage/StorageServiceInterface.ts +++ b/packages/services/src/Domain/Storage/StorageServiceInterface.ts @@ -7,6 +7,9 @@ import { import { StoragePersistencePolicies, StorageValueModes } from './StorageTypes' export interface StorageServiceInterface { + initializeFromDisk(): Promise + isStorageWrapped(): boolean + decryptStorage(): Promise getAllRawPayloads(): Promise getAllKeys(mode?: StorageValueModes): string[] getValue(key: string, mode?: StorageValueModes, defaultValue?: T): T @@ -20,4 +23,5 @@ export interface StorageServiceInterface { deletePayloads(payloads: FullyFormedPayloadInterface[]): Promise deletePayloadsWithUuids(uuids: string[]): Promise clearAllPayloads(): Promise + isEphemeralSession(): boolean } diff --git a/packages/services/src/Domain/Subscription/SubscriptionManager.ts b/packages/services/src/Domain/Subscription/SubscriptionManager.ts index ac20a28f1..b4b1bc1ef 100644 --- a/packages/services/src/Domain/Subscription/SubscriptionManager.ts +++ b/packages/services/src/Domain/Subscription/SubscriptionManager.ts @@ -20,6 +20,7 @@ import { Subscription, } from '@standardnotes/responses' import { SubscriptionManagerEvent } from './SubscriptionManagerEvent' +import { ApplicationStageChangedEventPayload } from '../Event/ApplicationStageChangedEventPayload' export class SubscriptionManager extends AbstractService @@ -53,13 +54,14 @@ export class SubscriptionManager case ApplicationEvent.SignedIn: void this.fetchOnlineSubscription() break - } - } - public override async handleApplicationStage(stage: ApplicationStage): Promise { - if (stage === ApplicationStage.StorageDecrypted_09) { - this.onlineSubscription = this.storage.getValue(StorageKey.Subscription) - void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription) + case ApplicationEvent.ApplicationStageChanged: { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.StorageDecrypted_09) { + this.onlineSubscription = this.storage.getValue(StorageKey.Subscription) + void this.notifyEvent(SubscriptionManagerEvent.DidFetchSubscription) + } + } } } diff --git a/packages/snjs/lib/Services/Sync/SyncOpStatus.ts b/packages/services/src/Domain/Sync/SyncOpStatus.ts similarity index 92% rename from packages/snjs/lib/Services/Sync/SyncOpStatus.ts rename to packages/services/src/Domain/Sync/SyncOpStatus.ts index 91ac7df0b..a6836d3f2 100644 --- a/packages/snjs/lib/Services/Sync/SyncOpStatus.ts +++ b/packages/services/src/Domain/Sync/SyncOpStatus.ts @@ -1,4 +1,7 @@ -import { SyncEvent, SyncEventReceiver } from '@standardnotes/services' +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { SyncEventReceiver } from './../Event/SyncEventReceiver' +import { SyncEvent } from './../Event/SyncEvent' const HEALTHY_SYNC_DURATION_THRESHOLD_S = 5 const TIMING_MONITOR_POLL_FREQUENCY_MS = 500 @@ -105,6 +108,7 @@ export class SyncOpStatus { return !!this.error } + // eslint-disable-next-line @typescript-eslint/no-explicit-any setError(error: any): void { this.error = error } diff --git a/packages/services/src/Domain/Sync/SyncServiceInterface.ts b/packages/services/src/Domain/Sync/SyncServiceInterface.ts index 9a8005eba..544897dbb 100644 --- a/packages/services/src/Domain/Sync/SyncServiceInterface.ts +++ b/packages/services/src/Domain/Sync/SyncServiceInterface.ts @@ -4,9 +4,14 @@ import { FullyFormedPayloadInterface } from '@standardnotes/models' import { SyncOptions } from './SyncOptions' import { AbstractService } from '../Service/AbstractService' import { SyncEvent } from '../Event/SyncEvent' +import { SyncOpStatus } from './SyncOpStatus' export interface SyncServiceInterface extends AbstractService { sync(options?: Partial): Promise + isDatabaseLoaded(): boolean + onNewDatabaseCreated(): Promise + loadDatabasePayloads(): Promise + resetSyncState(): void markAllItemsAsNeedingSyncAndPersist(): Promise downloadFirstSync(waitTimeOnFailureMs: number, otherSyncOptions?: Partial): Promise @@ -14,4 +19,12 @@ export interface SyncServiceInterface extends AbstractService { lockSyncing(): void unlockSyncing(): void syncSharedVaultsFromScratch(sharedVaultUuids: string[]): Promise + + setLaunchPriorityUuids(launchPriorityUuids: string[]): void + + isOutOfSync(): boolean + getLastSyncDate(): Date | undefined + getSyncStatus(): SyncOpStatus + + completedOnlineDownloadFirstSync: boolean } diff --git a/packages/services/src/Domain/UseCase/RemoveItemsLocally.ts b/packages/services/src/Domain/UseCase/RemoveItemsLocally.ts index 434a67326..fea5032d2 100644 --- a/packages/services/src/Domain/UseCase/RemoveItemsLocally.ts +++ b/packages/services/src/Domain/UseCase/RemoveItemsLocally.ts @@ -3,7 +3,7 @@ import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { AnyItemInterface } from '@standardnotes/models' import { Uuids } from '@standardnotes/utils' -export class RemoveItemsLocallyUseCase { +export class RemoveItemsLocally { constructor(private readonly items: ItemManagerInterface, private readonly storage: StorageServiceInterface) {} async execute(items: AnyItemInterface[]): Promise { diff --git a/packages/services/src/Domain/User/UserClientInterface.ts b/packages/services/src/Domain/User/UserClientInterface.ts index b03b38002..fef88b566 100644 --- a/packages/services/src/Domain/User/UserClientInterface.ts +++ b/packages/services/src/Domain/User/UserClientInterface.ts @@ -1,15 +1,21 @@ import { Base64String } from '@standardnotes/sncrypto-common' -import { UserRequestType } from '@standardnotes/common' +import { KeyParamsOrigination, UserRequestType } from '@standardnotes/common' import { DeinitSource } from '../Application/DeinitSource' import { UserRegistrationResponseBody } from '@standardnotes/api' import { HttpResponse, SignInResponse } from '@standardnotes/responses' import { AbstractService } from '../Service/AbstractService' import { AccountEventData } from './AccountEventData' import { AccountEvent } from './AccountEvent' +import { CredentialsChangeFunctionResponse } from './CredentialsChangeFunctionResponse' export interface UserClientInterface extends AbstractService { getUserUuid(): string isSignedIn(): boolean + + addPasscode(passcode: string): Promise + removePasscode(): Promise + changePasscode(newPasscode: string, origination?: KeyParamsOrigination): Promise + register( email: string, password: string, @@ -28,6 +34,15 @@ export interface UserClientInterface extends AbstractService + changeCredentials(parameters: { + currentPassword: string + origination: KeyParamsOrigination + validateNewPasswordStrength: boolean + newEmail?: string + newPassword?: string + passcode?: string + }): Promise + signOut(force?: boolean, source?: DeinitSource): Promise submitUserRequest(requestType: UserRequestType): Promise populateSessionFromDemoShareToken(token: Base64String): Promise @@ -36,4 +51,9 @@ export interface UserClientInterface extends AbstractService + performProtocolUpgrade(): Promise<{ + success?: true + canceled?: true + error?: { message: string } + }> } diff --git a/packages/services/src/Domain/User/UserService.spec.ts b/packages/services/src/Domain/User/UserService.spec.ts index 83ba96761..790551905 100644 --- a/packages/services/src/Domain/User/UserService.spec.ts +++ b/packages/services/src/Domain/User/UserService.spec.ts @@ -1,6 +1,6 @@ +import { EncryptionProviderInterface } from './../Encryption/EncryptionProviderInterface' import { UserApiServiceInterface } from '@standardnotes/api' import { UserRequestType } from '@standardnotes/common' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { User } from '@standardnotes/responses' import { diff --git a/packages/services/src/Domain/User/UserService.ts b/packages/services/src/Domain/User/UserService.ts index 85a9bec72..ac4ecfeec 100644 --- a/packages/services/src/Domain/User/UserService.ts +++ b/packages/services/src/Domain/User/UserService.ts @@ -1,5 +1,5 @@ import { Base64String } from '@standardnotes/sncrypto-common' -import { EncryptionProviderInterface, SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' +import { SNRootKey, SNRootKeyParams } from '@standardnotes/encryption' import { HttpResponse, SignInResponse, @@ -36,6 +36,7 @@ import { AccountEventData } from './AccountEventData' import { AccountEvent } from './AccountEvent' import { SignedInOrRegisteredEventPayload } from './SignedInOrRegisteredEventPayload' import { CredentialsChangeFunctionResponse } from './CredentialsChangeFunctionResponse' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' export class UserService extends AbstractService @@ -49,7 +50,7 @@ export class UserService constructor( private sessionManager: SessionsClientInterface, - private syncService: SyncServiceInterface, + private sync: SyncServiceInterface, private storageService: StorageServiceInterface, private itemManager: ItemManagerInterface, private encryptionService: EncryptionProviderInterface, @@ -65,14 +66,14 @@ export class UserService async handleEvent(event: InternalEventInterface): Promise { if (event.type === AccountEvent.SignedInOrRegistered) { const payload = (event.payload as AccountEventData).payload as SignedInOrRegisteredEventPayload - this.syncService.resetSyncState() + this.sync.resetSyncState() await this.storageService.setPersistencePolicy( payload.ephemeral ? StoragePersistencePolicies.Ephemeral : StoragePersistencePolicies.Default, ) if (payload.mergeLocal) { - await this.syncService.markAllItemsAsNeedingSyncAndPersist() + await this.sync.markAllItemsAsNeedingSyncAndPersist() } else { void this.itemManager.removeAllItemsFromMemory() await this.clearDatabase() @@ -80,7 +81,7 @@ export class UserService this.unlockSyncing() - const syncPromise = this.syncService + const syncPromise = this.sync .downloadFirstSync(1_000, { checkIntegrity: payload.checkIntegrity, awaitAll: payload.awaitSync, @@ -102,7 +103,7 @@ export class UserService public override deinit(): void { super.deinit() ;(this.sessionManager as unknown) = undefined - ;(this.syncService as unknown) = undefined + ;(this.sync as unknown) = undefined ;(this.storageService as unknown) = undefined ;(this.itemManager as unknown) = undefined ;(this.encryptionService as unknown) = undefined @@ -514,7 +515,7 @@ export class UserService const key = await this.encryptionService.createRootKey(identifier, passcode, origination) await this.encryptionService.setNewRootKeyWrapper(key) await this.rewriteItemsKeys() - await this.syncService.sync() + await this.sync.sync() } private async removePasscodeWithoutWarning() { @@ -534,15 +535,15 @@ export class UserService const itemsKeys = this.itemManager.getDisplayableItemsKeys() const payloads = itemsKeys.map((key) => key.payloadRepresentation()) await this.storageService.deletePayloads(payloads) - await this.syncService.persistPayloads(payloads) + await this.sync.persistPayloads(payloads) } private lockSyncing(): void { - this.syncService.lockSyncing() + this.sync.lockSyncing() } private unlockSyncing(): void { - this.syncService.unlockSyncing() + this.sync.unlockSyncing() } private clearDatabase(): Promise { @@ -605,7 +606,7 @@ export class UserService const rollback = await this.encryptionService.createNewItemsKeyWithRollback() await this.encryptionService.reencryptApplicableItemsAfterUserRootKeyChange() - await this.syncService.sync({ awaitAll: true }) + await this.sync.sync({ awaitAll: true }) const defaultItemsKey = this.encryptionService.getSureDefaultItemsKey() const itemsKeyWasSynced = !defaultItemsKey.neverSynced @@ -618,7 +619,7 @@ export class UserService }) await this.encryptionService.reencryptApplicableItemsAfterUserRootKeyChange() await rollback() - await this.syncService.sync({ awaitAll: true }) + await this.sync.sync({ awaitAll: true }) return { error: Error(Messages.CredentialsChangeStrings.Failed) } } diff --git a/packages/services/src/Domain/Vaults/UseCase/ChangeVaultKeyOptions.ts b/packages/services/src/Domain/Vaults/UseCase/ChangeVaultKeyOptions.ts index 98600d7e8..b44a4ed12 100644 --- a/packages/services/src/Domain/Vaults/UseCase/ChangeVaultKeyOptions.ts +++ b/packages/services/src/Domain/Vaults/UseCase/ChangeVaultKeyOptions.ts @@ -1,28 +1,24 @@ import { MutatorClientInterface, SyncServiceInterface } from '@standardnotes/services' -import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { KeySystemRootKeyPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface, VaultListingMutator, } from '@standardnotes/models' -import { EncryptionProviderInterface, KeySystemKeyManagerInterface } from '@standardnotes/encryption' import { ChangeVaultOptionsDTO } from '../ChangeVaultOptionsDTO' -import { GetVaultUseCase } from './GetVault' -import { assert } from '@standardnotes/utils' +import { GetVault } from './GetVault' +import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' -export class ChangeVaultKeyOptionsUseCase { +export class ChangeVaultKeyOptions { constructor( - private items: ItemManagerInterface, private mutator: MutatorClientInterface, private sync: SyncServiceInterface, private encryption: EncryptionProviderInterface, + private keys: KeySystemKeyManagerInterface, + private getVault: GetVault, ) {} - private get keys(): KeySystemKeyManagerInterface { - return this.encryption.keys - } - async execute(dto: ChangeVaultOptionsDTO): Promise { const useStorageMode = dto.newKeyStorageMode ?? dto.vault.keyStorageMode @@ -42,9 +38,13 @@ export class ChangeVaultKeyOptionsUseCase { } if (dto.newKeyStorageMode) { - const usecase = new GetVaultUseCase(this.items) - const latestVault = usecase.execute({ keySystemIdentifier: dto.vault.systemIdentifier }) - assert(latestVault) + const result = this.getVault.execute({ keySystemIdentifier: dto.vault.systemIdentifier }) + + if (result.isFailed()) { + throw new Error('Vault not found') + } + + const latestVault = result.getValue() if (latestVault.rootKeyParams.passwordType !== KeySystemRootKeyPasswordType.UserInputted) { throw new Error('Vault uses randomized password and cannot change its storage preference') @@ -80,14 +80,14 @@ export class ChangeVaultKeyOptionsUseCase { if (storageMode === KeySystemRootKeyStorageMode.Synced) { await this.mutator.insertItem(newRootKey, true) } else { - this.encryption.keys.intakeNonPersistentKeySystemRootKey(newRootKey, storageMode) + this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, storageMode) } await this.mutator.changeItem(vault, (mutator) => { mutator.rootKeyParams = newRootKey.keyParams }) - await this.encryption.reencryptKeySystemItemsKeysForVault(vault.systemIdentifier) + await this.keys.reencryptKeySystemItemsKeysForVault(vault.systemIdentifier) } private async changePasswordTypeToRandomized( @@ -108,7 +108,7 @@ export class ChangeVaultKeyOptionsUseCase { await this.mutator.insertItem(newRootKey, true) - await this.encryption.reencryptKeySystemItemsKeysForVault(vault.systemIdentifier) + await this.keys.reencryptKeySystemItemsKeysForVault(vault.systemIdentifier) } private async changeStorageModeToLocalOrEphemeral( diff --git a/packages/services/src/Domain/Vaults/UseCase/CreateVault.ts b/packages/services/src/Domain/Vaults/UseCase/CreateVault.ts index 33dae625c..656211981 100644 --- a/packages/services/src/Domain/Vaults/UseCase/CreateVault.ts +++ b/packages/services/src/Domain/Vaults/UseCase/CreateVault.ts @@ -1,5 +1,4 @@ import { SyncServiceInterface } from './../../Sync/SyncServiceInterface' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { UuidGenerator } from '@standardnotes/utils' import { KeySystemRootKeyParamsInterface, @@ -12,11 +11,14 @@ import { } from '@standardnotes/models' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' import { ContentType } from '@standardnotes/domain-core' +import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' -export class CreateVaultUseCase { +export class CreateVault { constructor( private mutator: MutatorClientInterface, private encryption: EncryptionProviderInterface, + private keys: KeySystemKeyManagerInterface, private sync: SyncServiceInterface, ) {} @@ -107,7 +109,7 @@ export class CreateVaultUseCase { if (dto.storagePreference === KeySystemRootKeyStorageMode.Synced) { await this.mutator.insertItem(newRootKey, true) } else { - this.encryption.keys.intakeNonPersistentKeySystemRootKey(newRootKey, dto.storagePreference) + this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, dto.storagePreference) } return newRootKey diff --git a/packages/services/src/Domain/Vaults/UseCase/DeleteVault.ts b/packages/services/src/Domain/Vaults/UseCase/DeleteVault.ts index 1a9c2b146..34638b011 100644 --- a/packages/services/src/Domain/Vaults/UseCase/DeleteVault.ts +++ b/packages/services/src/Domain/Vaults/UseCase/DeleteVault.ts @@ -1,14 +1,14 @@ import { ClientDisplayableError } from '@standardnotes/responses' import { ItemManagerInterface } from '../../Item/ItemManagerInterface' import { VaultListingInterface } from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' -export class DeleteVaultUseCase { +export class DeleteVault { constructor( private items: ItemManagerInterface, private mutator: MutatorClientInterface, - private encryption: EncryptionProviderInterface, + private keys: KeySystemKeyManagerInterface, ) {} async execute(vault: VaultListingInterface): Promise { @@ -16,12 +16,12 @@ export class DeleteVaultUseCase { throw new Error('Vault system identifier is missing') } - await this.encryption.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier) + await this.keys.deleteNonPersistentSystemRootKeysForVault(vault.systemIdentifier) - const rootKeys = this.encryption.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier) + const rootKeys = this.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier) await this.mutator.setItemsToBeDeleted(rootKeys) - const itemsKeys = this.encryption.keys.getKeySystemItemsKeys(vault.systemIdentifier) + const itemsKeys = this.keys.getKeySystemItemsKeys(vault.systemIdentifier) await this.mutator.setItemsToBeDeleted(itemsKeys) const vaultItems = this.items.itemsBelongingToKeySystem(vault.systemIdentifier) diff --git a/packages/services/src/Domain/Vaults/UseCase/GetVault.ts b/packages/services/src/Domain/Vaults/UseCase/GetVault.ts index 3ca8ced16..68c0cca66 100644 --- a/packages/services/src/Domain/Vaults/UseCase/GetVault.ts +++ b/packages/services/src/Domain/Vaults/UseCase/GetVault.ts @@ -1,17 +1,29 @@ import { VaultListingInterface } from '@standardnotes/models' import { ItemManagerInterface } from './../../Item/ItemManagerInterface' -import { ContentType } from '@standardnotes/domain-core' +import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core' -export class GetVaultUseCase { +export class GetVault implements SyncUseCaseInterface { constructor(private items: ItemManagerInterface) {} - execute(query: { keySystemIdentifier: string } | { sharedVaultUuid: string }): T | undefined { + execute( + query: { keySystemIdentifier: string } | { sharedVaultUuid: string }, + ): Result { const vaults = this.items.getItems(ContentType.TYPES.VaultListing) if ('keySystemIdentifier' in query) { - return vaults.find((listing) => listing.systemIdentifier === query.keySystemIdentifier) as T + const result = vaults.find((listing) => listing.systemIdentifier === query.keySystemIdentifier) as T + if (!result) { + return Result.fail('Vault not found') + } + + return Result.ok(result) } else { - return vaults.find((listing) => listing.sharing?.sharedVaultUuid === query.sharedVaultUuid) as T + const result = vaults.find((listing) => listing.sharing?.sharedVaultUuid === query.sharedVaultUuid) as T + if (!result) { + return Result.fail('Vault not found') + } + + return Result.ok(result) } } } diff --git a/packages/services/src/Domain/Vaults/UseCase/MoveItemsToVault.ts b/packages/services/src/Domain/Vaults/UseCase/MoveItemsToVault.ts index 7181df7c3..c795357ad 100644 --- a/packages/services/src/Domain/Vaults/UseCase/MoveItemsToVault.ts +++ b/packages/services/src/Domain/Vaults/UseCase/MoveItemsToVault.ts @@ -4,7 +4,7 @@ import { DecryptedItemInterface, FileItem, VaultListingInterface } from '@standa import { FilesClientInterface } from '@standardnotes/files' import { ContentType } from '@standardnotes/domain-core' -export class MoveItemsToVaultUseCase { +export class MoveItemsToVault { constructor( private mutator: MutatorClientInterface, private sync: SyncServiceInterface, diff --git a/packages/services/src/Domain/Vaults/UseCase/RotateVaultRootKey.ts b/packages/services/src/Domain/Vaults/UseCase/RotateVaultKey.ts similarity index 81% rename from packages/services/src/Domain/Vaults/UseCase/RotateVaultRootKey.ts rename to packages/services/src/Domain/Vaults/UseCase/RotateVaultKey.ts index 6b10ff6de..d3046116a 100644 --- a/packages/services/src/Domain/Vaults/UseCase/RotateVaultRootKey.ts +++ b/packages/services/src/Domain/Vaults/UseCase/RotateVaultKey.ts @@ -1,5 +1,4 @@ import { UuidGenerator, assert } from '@standardnotes/utils' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses' import { KeySystemIdentifier, @@ -10,16 +9,22 @@ import { VaultListingMutator, } from '@standardnotes/models' import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface' +import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface' +import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface' -export class RotateVaultRootKeyUseCase { - constructor(private mutator: MutatorClientInterface, private encryption: EncryptionProviderInterface) {} +export class RotateVaultKey { + constructor( + private mutator: MutatorClientInterface, + private encryption: EncryptionProviderInterface, + private keys: KeySystemKeyManagerInterface, + ) {} async execute(params: { vault: VaultListingInterface sharedVaultUuid: string | undefined userInputtedPassword: string | undefined }): Promise { - const currentRootKey = this.encryption.keys.getPrimaryKeySystemRootKey(params.vault.systemIdentifier) + const currentRootKey = this.keys.getPrimaryKeySystemRootKey(params.vault.systemIdentifier) if (!currentRootKey) { throw new Error('Cannot rotate key system root key; key system root key not found') } @@ -48,7 +53,7 @@ export class RotateVaultRootKeyUseCase { if (params.vault.keyStorageMode === KeySystemRootKeyStorageMode.Synced) { await this.mutator.insertItem(newRootKey, true) } else { - this.encryption.keys.intakeNonPersistentKeySystemRootKey(newRootKey, params.vault.keyStorageMode) + this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, params.vault.keyStorageMode) } await this.mutator.changeItem(params.vault, (mutator) => { @@ -68,7 +73,7 @@ export class RotateVaultRootKeyUseCase { errors.push(updateKeySystemItemsKeyResult) } - await this.encryption.reencryptKeySystemItemsKeysForVault(params.vault.systemIdentifier) + await this.keys.reencryptKeySystemItemsKeysForVault(params.vault.systemIdentifier) return errors } diff --git a/packages/services/src/Domain/Vaults/VaultService.ts b/packages/services/src/Domain/Vaults/VaultService.ts index b20dedf76..451cb8720 100644 --- a/packages/services/src/Domain/Vaults/VaultService.ts +++ b/packages/services/src/Domain/Vaults/VaultService.ts @@ -12,23 +12,22 @@ import { import { VaultServiceInterface } from './VaultServiceInterface' import { ChangeVaultOptionsDTO } from './ChangeVaultOptionsDTO' import { VaultServiceEvent, VaultServiceEventPayload } from './VaultServiceEvent' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { CreateVaultUseCase } from './UseCase/CreateVault' +import { CreateVault } from './UseCase/CreateVault' import { AbstractService } from '../Service/AbstractService' import { SyncServiceInterface } from '../Sync/SyncServiceInterface' import { ItemManagerInterface } from '../Item/ItemManagerInterface' import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface' import { RemoveItemFromVault } from './UseCase/RemoveItemFromVault' -import { DeleteVaultUseCase } from './UseCase/DeleteVault' -import { MoveItemsToVaultUseCase } from './UseCase/MoveItemsToVault' - -import { RotateVaultRootKeyUseCase } from './UseCase/RotateVaultRootKey' -import { FilesClientInterface } from '@standardnotes/files' -import { GetVaultUseCase } from './UseCase/GetVault' -import { ChangeVaultKeyOptionsUseCase } from './UseCase/ChangeVaultKeyOptions' +import { DeleteVault } from './UseCase/DeleteVault' +import { MoveItemsToVault } from './UseCase/MoveItemsToVault' +import { RotateVaultKey } from './UseCase/RotateVaultKey' +import { GetVault } from './UseCase/GetVault' +import { ChangeVaultKeyOptions } from './UseCase/ChangeVaultKeyOptions' import { MutatorClientInterface } from '../Mutator/MutatorClientInterface' import { AlertService } from '../Alert/AlertService' import { ContentType } from '@standardnotes/domain-core' +import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface' +import { KeySystemKeyManagerInterface } from '../KeySystem/KeySystemKeyManagerInterface' export class VaultService extends AbstractService @@ -41,8 +40,15 @@ export class VaultService private items: ItemManagerInterface, private mutator: MutatorClientInterface, private encryption: EncryptionProviderInterface, - private files: FilesClientInterface, + private keys: KeySystemKeyManagerInterface, private alerts: AlertService, + private _getVault: GetVault, + private _changeVaultKeyOptions: ChangeVaultKeyOptions, + private _moveItemsToVault: MoveItemsToVault, + private _createVault: CreateVault, + private _removeItemFromVaultUseCase: RemoveItemFromVault, + private _deleteVaultUseCase: DeleteVault, + private _rotateVaultKey: RotateVaultKey, eventBus: InternalEventBusInterface, ) { super(eventBus) @@ -67,8 +73,12 @@ export class VaultService } public getVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface | undefined { - const usecase = new GetVaultUseCase(this.items) - return usecase.execute(dto) + const result = this._getVault.execute(dto) + if (result.isFailed()) { + return undefined + } + + return result.getValue() } public getSureVault(dto: { keySystemIdentifier: KeySystemIdentifier }): VaultListingInterface { @@ -108,8 +118,7 @@ export class VaultService userInputtedPassword: string | undefined storagePreference: KeySystemRootKeyStorageMode }): Promise { - const createVault = new CreateVaultUseCase(this.mutator, this.encryption, this.sync) - const result = await createVault.execute({ + const result = await this._createVault.execute({ vaultName: dto.name, vaultDescription: dto.description, userInputtedPassword: dto.userInputtedPassword, @@ -142,8 +151,7 @@ export class VaultService } } - const useCase = new MoveItemsToVaultUseCase(this.mutator, this.sync, this.files) - await useCase.execute({ vault, items: [item, ...linkedFiles] }) + await this._moveItemsToVault.execute({ vault, items: [item, ...linkedFiles] }) return this.items.findSureItem(item.uuid) } @@ -158,8 +166,7 @@ export class VaultService throw new Error('Attempting to remove item from locked vault') } - const useCase = new RemoveItemFromVault(this.mutator, this.sync, this.files) - await useCase.execute({ item }) + await this._removeItemFromVaultUseCase.execute({ item }) return this.items.findSureItem(item.uuid) } @@ -168,8 +175,7 @@ export class VaultService throw new Error('Shared vault must be deleted through SharedVaultService') } - const useCase = new DeleteVaultUseCase(this.items, this.mutator, this.encryption) - const error = await useCase.execute(vault) + const error = await this._deleteVaultUseCase.execute(vault) if (isClientDisplayableError(error)) { return false @@ -198,8 +204,7 @@ export class VaultService throw new Error('Cannot rotate root key of locked vault') } - const useCase = new RotateVaultRootKeyUseCase(this.mutator, this.encryption) - await useCase.execute({ + await this._rotateVaultKey.execute({ vault, sharedVaultUuid: vault.isSharedVaultListing() ? vault.sharing.sharedVaultUuid : undefined, userInputtedPassword: undefined, @@ -232,8 +237,7 @@ export class VaultService throw new Error('Attempting to change vault options on a locked vault') } - const usecase = new ChangeVaultKeyOptionsUseCase(this.items, this.mutator, this.sync, this.encryption) - await usecase.execute(dto) + await this._changeVaultKeyOptions.execute(dto) if (dto.newPasswordType) { await this.notifyEventSync(VaultServiceEvent.VaultRootKeyRotated, { vault: dto.vault }) @@ -249,7 +253,7 @@ export class VaultService throw new Error('Vault uses synced root key and cannot be locked') } - this.encryption.keys.clearMemoryOfKeysRelatedToVault(vault) + this.keys.clearMemoryOfKeysRelatedToVault(vault) this.lockMap.set(vault.uuid, true) void this.notifyEventSync(VaultServiceEvent.VaultLocked, { vault }) @@ -269,12 +273,12 @@ export class VaultService userInputtedPassword: password, }) - this.encryption.keys.intakeNonPersistentKeySystemRootKey(derivedRootKey, vault.keyStorageMode) + this.keys.intakeNonPersistentKeySystemRootKey(derivedRootKey, vault.keyStorageMode) await this.encryption.decryptErroredPayloads() if (this.computeVaultLockState(vault) === 'locked') { - this.encryption.keys.undoIntakeNonPersistentKeySystemRootKey(vault.systemIdentifier) + this.keys.undoIntakeNonPersistentKeySystemRootKey(vault.systemIdentifier) return false } @@ -303,12 +307,12 @@ export class VaultService } private computeVaultLockState(vault: VaultListingInterface): 'locked' | 'unlocked' { - const rootKey = this.encryption.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier) + const rootKey = this.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier) if (!rootKey) { return 'locked' } - const itemsKey = this.encryption.keys.getPrimaryKeySystemItemsKey(vault.systemIdentifier) + const itemsKey = this.keys.getPrimaryKeySystemItemsKey(vault.systemIdentifier) if (!itemsKey) { return 'locked' } @@ -319,7 +323,18 @@ export class VaultService override deinit(): void { super.deinit() ;(this.sync as unknown) = undefined - ;(this.encryption as unknown) = undefined ;(this.items as unknown) = undefined + ;(this.mutator as unknown) = undefined + ;(this.encryption as unknown) = undefined + ;(this.alerts as unknown) = undefined + ;(this._getVault as unknown) = undefined + ;(this._changeVaultKeyOptions as unknown) = undefined + ;(this._moveItemsToVault as unknown) = undefined + ;(this._createVault as unknown) = undefined + ;(this._removeItemFromVaultUseCase as unknown) = undefined + ;(this._deleteVaultUseCase as unknown) = undefined + ;(this._rotateVaultKey as unknown) = undefined + + this.lockMap.clear() } } diff --git a/packages/services/src/Domain/index.ts b/packages/services/src/Domain/index.ts index c27a12ddb..6b9bde1ea 100644 --- a/packages/services/src/Domain/index.ts +++ b/packages/services/src/Domain/index.ts @@ -1,55 +1,53 @@ export * from './Alert/AlertService' - -export * from './Api/ApiServiceInterface' -export * from './Api/ApiServiceEventData' export * from './Api/ApiServiceEvent' +export * from './Api/ApiServiceEventData' +export * from './Api/LegacyApiServiceInterface' export * from './Api/MetaReceivedData' export * from './Api/SessionRefreshedData' - export * from './Application/AppGroupManagedApplication' export * from './Application/ApplicationInterface' export * from './Application/ApplicationStage' export * from './Application/DeinitCallback' export * from './Application/DeinitMode' export * from './Application/DeinitSource' - export * from './AsymmetricMessage/AsymmetricMessageService' export * from './AsymmetricMessage/AsymmetricMessageServiceInterface' - +export * from './AsymmetricMessage/UseCase/GetInboundMessages' +export * from './AsymmetricMessage/UseCase/GetOutboundMessages' +export * from './AsymmetricMessage/UseCase/GetTrustedPayload' +export * from './AsymmetricMessage/UseCase/GetUntrustedPayload' +export * from './AsymmetricMessage/UseCase/HandleRootKeyChangedMessage' +export * from './AsymmetricMessage/UseCase/ProcessAcceptedVaultInvite' +export * from './AsymmetricMessage/UseCase/ResendAllMessages' +export * from './AsymmetricMessage/UseCase/ResendMessage' +export * from './AsymmetricMessage/UseCase/SendMessage' +export * from './AsymmetricMessage/UseCase/SendOwnContactChangeMessage' export * from './Auth/AuthClientInterface' export * from './Auth/AuthManager' - export * from './Authenticator/AuthenticatorClientInterface' export * from './Authenticator/AuthenticatorManager' - -export * from './Backups/BackupService' - +export * from './Backups/FilesBackupService' export * from './Challenge' - export * from './Component/ComponentManagerInterface' export * from './Component/ComponentViewerError' export * from './Component/ComponentViewerInterface' export * from './Component/ComponentViewerItem' - -export * from './Contacts/ContactServiceInterface' export * from './Contacts/ContactService' - -export * from './KeySystem/KeySystemKeyManager' - -export * from './SharedVaults/SharedVaultServiceInterface' -export * from './SharedVaults/SharedVaultService' -export * from './SharedVaults/SharedVaultServiceEvent' -export * from './SharedVaults/PendingSharedVaultInviteRecord' - -export * from './Singleton/SingletonManagerInterface' - -export * from './Vaults/VaultService' -export * from './Vaults/VaultServiceInterface' -export * from './Vaults/VaultServiceEvent' -export * from './Vaults/ChangeVaultOptionsDTO' - +export * from './Contacts/ContactServiceInterface' +export * from './Contacts/SelfContactManager' +export * from './Contacts/UseCase/CreateOrEditContact' +export * from './Contacts/UseCase/EditContact' +export * from './Contacts/UseCase/FindContact' +export * from './Contacts/UseCase/GetAllContacts' +export * from './Contacts/UseCase/HandleKeyPairChange' +export * from './Contacts/UseCase/ReplaceContactData' +export * from './Contacts/UseCase/Types/ItemSignatureValidationResult' +export * from './Contacts/UseCase/ValidateItemSigner' +export * from './Device/DatabaseItemMetadata' export * from './Device/DatabaseItemMetadata' export * from './Device/DatabaseLoadOptions' +export * from './Device/DatabaseLoadOptions' +export * from './Device/DatabaseLoadSorter' export * from './Device/DatabaseLoadSorter' export * from './Device/DesktopDeviceInterface' export * from './Device/DesktopManagerInterface' @@ -58,33 +56,37 @@ export * from './Device/DeviceInterface' export * from './Device/MobileDeviceInterface' export * from './Device/TypeCheck' export * from './Device/WebOrDesktopDeviceInterface' -export * from './Device/DatabaseLoadOptions' -export * from './Device/DatabaseItemMetadata' -export * from './Device/DatabaseLoadSorter' export * from './Diagnostics/ServiceDiagnostics' - -export * from './Encryption/DecryptBackupFileUseCase' +export * from './Encryption/UseCase/DecryptBackupFile' export * from './Encryption/EncryptionService' +export * from './Encryption/EncryptionProviderInterface' export * from './Encryption/EncryptionServiceEvent' export * from './Encryption/Functions' -export * from './Encryption/ItemsEncryption' - +export * from './Encryption/UseCase/Asymmetric/DecryptMessage' +export * from './Encryption/UseCase/Asymmetric/DecryptOwnMessage' +export * from './Encryption/UseCase/Asymmetric/EncryptMessage' +export * from './Encryption/UseCase/Asymmetric/GetMessageAdditionalData' +export * from './Encryption/UseCase/ItemsKey/CreateNewDefaultItemsKey' +export * from './Encryption/UseCase/ItemsKey/CreateNewItemsKeyWithRollback' +export * from './Encryption/UseCase/ItemsKey/FindDefaultItemsKey' +export * from './Encryption/UseCase/TypeA/DecryptErroredPayloads' +export * from './Encryption/UseCase/TypeA/DecryptPayload' +export * from './Encryption/UseCase/TypeA/DecryptPayloadWithKeyLookup' +export * from './Encryption/UseCase/TypeA/EncryptPayload' +export * from './Encryption/UseCase/TypeA/EncryptPayloadWithKeyLookup' export * from './Event/ApplicationEvent' export * from './Event/ApplicationEventCallback' +export * from './Event/ApplicationStageChangedEventPayload' export * from './Event/EventObserver' export * from './Event/SyncEvent' export * from './Event/SyncEventReceiver' export * from './Event/WebAppEvent' -export * from './Event/ApplicationStageChangedEventPayload' - export * from './Feature/FeaturesClientInterface' export * from './Feature/FeaturesEvent' export * from './Feature/FeatureStatus' export * from './Feature/OfflineSubscriptionEntitlements' export * from './Feature/SetOfflineFeaturesFunctionResponse' - export * from './Files/FileService' - export * from './History/HistoryServiceInterface' export * from './HomeServer/HomeServerEnvironmentConfiguration' export * from './HomeServer/HomeServerManagerInterface' @@ -95,76 +97,103 @@ export * from './Integrity/IntegrityApiInterface' export * from './Integrity/IntegrityEvent' export * from './Integrity/IntegrityEventPayload' export * from './Integrity/IntegrityService' - export * from './Internal/InternalEventBus' export * from './Internal/InternalEventBusInterface' export * from './Internal/InternalEventHandlerInterface' export * from './Internal/InternalEventInterface' export * from './Internal/InternalEventPublishStrategy' export * from './Internal/InternalEventType' - export * from './InternalFeatures/InternalFeature' export * from './InternalFeatures/InternalFeatureService' export * from './InternalFeatures/InternalFeatureServiceInterface' - -export * from './Item/StaticItemCounter' export * from './Item/ItemManagerInterface' export * from './Item/ItemRelationshipDirection' export * from './Item/ItemsServerInterface' - -export * from './Mutator/MutatorClientInterface' +export * from './Item/StaticItemCounter' +export * from './ItemsEncryption/ItemsEncryption' +export * from './KeySystem/KeySystemKeyManager' export * from './Mutator/ImportDataUseCase' - +export * from './Mutator/MutatorClientInterface' export * from './Payloads/PayloadManagerInterface' - export * from './Preferences/PreferenceServiceInterface' - export * from './Protection/MobileUnlockTiming' export * from './Protection/ProtectionClientInterface' export * from './Protection/TimingDisplayOption' - export * from './Revision/RevisionClientInterface' export * from './Revision/RevisionManager' - +export * from './RootKeyManager/RootKeyManager' +export * from './RootKeyManager/KeyMode' +export * from './ItemsEncryption/ItemsEncryption' export * from './Service/AbstractService' export * from './Service/ApplicationServiceInterface' +export * from './Session/SessionEvent' export * from './Session/SessionManagerResponse' export * from './Session/SessionsClientInterface' -export * from './Session/SessionEvent' export * from './Session/UserKeyPairChangedEventData' - +export * from './SharedVaults/PendingSharedVaultInviteRecord' +export * from './SharedVaults/SharedVaultService' +export * from './SharedVaults/SharedVaultServiceEvent' +export * from './SharedVaults/SharedVaultServiceInterface' +export * from './SharedVaults/UseCase/AcceptVaultInvite' +export * from './SharedVaults/UseCase/ConvertToSharedVault' +export * from './SharedVaults/UseCase/CreateSharedVault' +export * from './SharedVaults/UseCase/DeleteExternalSharedVault' +export * from './SharedVaults/UseCase/DeleteSharedVault' +export * from './SharedVaults/UseCase/GetVaultContacts' +export * from './SharedVaults/UseCase/GetVaultContacts' +export * from './SharedVaults/UseCase/GetVaultUsers' +export * from './SharedVaults/UseCase/InviteToVault' +export * from './SharedVaults/UseCase/LeaveSharedVault' +export * from './SharedVaults/UseCase/NotifyVaultUsersOfKeyRotation' +export * from './SharedVaults/UseCase/RemoveSharedVaultMember' +export * from './SharedVaults/UseCase/ReuploadAllInvites' +export * from './SharedVaults/UseCase/ReuploadInvite' +export * from './SharedVaults/UseCase/ReuploadVaultInvites' +export * from './SharedVaults/UseCase/SendVaultDataChangedMessage' +export * from './SharedVaults/UseCase/SendVaultInvite' +export * from './SharedVaults/UseCase/SendVaultKeyChangedMessage' +export * from './SharedVaults/UseCase/ShareContactWithVault' +export * from './SharedVaults/UseCase/UpdateSharedVaultInvite' +export * from './Singleton/SingletonManagerInterface' export * from './Status/StatusService' export * from './Status/StatusServiceInterface' - export * from './Storage/InMemoryStore' export * from './Storage/KeyValueStoreInterface' export * from './Storage/StorageKeys' export * from './Storage/StorageServiceInterface' export * from './Storage/StorageTypes' - export * from './Strings/InfoStrings' export * from './Strings/Messages' - export * from './Subscription/AppleIAPProductId' export * from './Subscription/AppleIAPReceipt' -export * from './Subscription/SubscriptionManagerInterface' export * from './Subscription/SubscriptionManager' export * from './Subscription/SubscriptionManagerEvent' - +export * from './Subscription/SubscriptionManagerInterface' export * from './Sync/SyncMode' +export * from './Sync/SyncOpStatus' export * from './Sync/SyncOptions' export * from './Sync/SyncQueueStrategy' export * from './Sync/SyncServiceInterface' export * from './Sync/SyncSource' - -export * from './User/UserClientInterface' -export * from './User/UserClientInterface' -export * from './User/UserService' +export * from './UseCase/RemoveItemsLocally' export * from './User/AccountEvent' export * from './User/AccountEventData' export * from './User/CredentialsChangeFunctionResponse' export * from './User/SignedInOrRegisteredEventPayload' export * from './User/SignedOutEventPayload' - +export * from './User/UserClientInterface' +export * from './User/UserClientInterface' +export * from './User/UserService' export * from './UserEvent/UserEventService' export * from './UserEvent/UserEventServiceEvent' +export * from './Vaults/ChangeVaultOptionsDTO' +export * from './Vaults/UseCase/ChangeVaultKeyOptions' +export * from './Vaults/UseCase/CreateVault' +export * from './Vaults/UseCase/DeleteVault' +export * from './Vaults/UseCase/GetVault' +export * from './Vaults/UseCase/MoveItemsToVault' +export * from './Vaults/UseCase/RemoveItemFromVault' +export * from './Vaults/UseCase/RotateVaultKey' +export * from './Vaults/VaultService' +export * from './Vaults/VaultServiceEvent' +export * from './Vaults/VaultServiceInterface' diff --git a/packages/sncrypto-common/src/Common/PureCryptoInterface.ts b/packages/sncrypto-common/src/Common/PureCryptoInterface.ts index e9d237501..dff4eafa7 100644 --- a/packages/sncrypto-common/src/Common/PureCryptoInterface.ts +++ b/packages/sncrypto-common/src/Common/PureCryptoInterface.ts @@ -136,8 +136,8 @@ export interface PureCryptoInterface { sodiumCryptoBoxEasyEncrypt( message: Utf8String, nonce: HexString, - senderSecretKey: HexString, recipientPublicKey: HexString, + senderSecretKey: HexString, ): Base64String sodiumCryptoBoxEasyDecrypt( ciphertext: Base64String, diff --git a/packages/sncrypto-web/src/crypto.ts b/packages/sncrypto-web/src/crypto.ts index 58751aae2..053587aca 100644 --- a/packages/sncrypto-web/src/crypto.ts +++ b/packages/sncrypto-web/src/crypto.ts @@ -350,8 +350,8 @@ export class SNWebCrypto implements PureCryptoInterface { public sodiumCryptoBoxEasyEncrypt( message: Utf8String, nonce: HexString, - senderSecretKey: HexString, recipientPublicKey: HexString, + senderSecretKey: HexString, ): Base64String { const result = sodium.crypto_box_easy( message, diff --git a/packages/sncrypto-web/test/crypto.test.js b/packages/sncrypto-web/test/crypto.test.js index d1a8263d5..ced5eb3f9 100644 --- a/packages/sncrypto-web/test/crypto.test.js +++ b/packages/sncrypto-web/test/crypto.test.js @@ -277,8 +277,8 @@ describe('crypto operations', async function () { const ciphertext = await webCrypto.sodiumCryptoBoxEasyEncrypt( plaintext, nonce, - senderKeyPair.privateKey, recipientKeyPair.publicKey, + senderKeyPair.privateKey, ) expect(ciphertext.length).to.equal(44) diff --git a/packages/snjs/lib/Application/Application.ts b/packages/snjs/lib/Application/Application.ts index ddc38b5a5..ab7a43e07 100644 --- a/packages/snjs/lib/Application/Application.ts +++ b/packages/snjs/lib/Application/Application.ts @@ -1,43 +1,53 @@ +import { SNMfaService } from './../Services/Mfa/MfaService' +import { KeyRecoveryService } from './../Services/KeyRecovery/KeyRecoveryService' +import { WebSocketsService } from './../Services/Api/WebsocketsService' +import { MigrationService } from './../Services/Migration/MigrationService' +import { LegacyApiService } from './../Services/Api/ApiService' +import { FeaturesService } from '@Lib/Services/Features/FeaturesService' +import { SNPreferencesService } from './../Services/Preferences/PreferencesService' +import { SNProtectionService } from './../Services/Protection/ProtectionService' +import { SessionManager } from './../Services/Session/SessionManager' +import { HttpService, HttpServiceInterface, UserRegistrationResponseBody } from '@standardnotes/api' +import { ApplicationIdentifier, compareVersions, ProtocolVersion, KeyParamsOrigination } from '@standardnotes/common' import { - AuthApiService, - AuthenticatorApiService, - AuthenticatorServer, - AuthServer, - HttpService, - HttpServiceInterface, - RevisionApiService, - RevisionServer, - SubscriptionApiService, - SubscriptionApiServiceInterface, - SubscriptionServer, - SubscriptionServerInterface, - UserApiService, - UserApiServiceInterface, - UserRegistrationResponseBody, - UserRequestServer, - UserRequestServerInterface, - UserServer, - UserServerInterface, - WebSocketApiService, - WebSocketApiServiceInterface, - WebSocketServer, - WebSocketServerInterface, -} from '@standardnotes/api' -import * as Common from '@standardnotes/common' -import * as ExternalServices from '@standardnotes/services' -import * as Models from '@standardnotes/models' -import * as Responses from '@standardnotes/responses' -import * as InternalServices from '../Services' -import * as Utils from '@standardnotes/utils' -import { UuidString, ApplicationEventPayload } from '../Types' -import { applicationEventForSyncEvent } from '@Lib/Application/Event' -import { + DeinitCallback, + SessionEvent, + SyncEvent, + ApplicationStage, + FeaturesEvent, + SyncMode, + SyncSource, + ApplicationStageChangedEventPayload, + StorageValueModes, + ChallengeObserver, + SyncOptions, + ImportDataReturnType, + ImportDataUseCase, + StoragePersistencePolicies, + HomeServerServiceInterface, + ApiServiceEvent, + IntegrityEvent, + DeviceInterface, + SubscriptionManagerInterface, + FeaturesClientInterface, + ItemManagerInterface, + SyncServiceInterface, + UserClientInterface, + MutatorClientInterface, + StatusServiceInterface, + AlertService, + StorageServiceInterface, + ChallengeServiceInterface, + AsymmetricMessageServiceInterface, + VaultServiceInterface, + ContactServiceInterface, + SharedVaultServiceInterface, + PreferenceServiceInterface, + InternalEventBusInterface, ApplicationEvent, ApplicationEventCallback, ChallengeValidation, ComponentManagerInterface, - DiagnosticInfo, - isDesktopDevice, ChallengeValue, StorageKey, ChallengeReason, @@ -47,10 +57,6 @@ import { ApplicationInterface, EncryptionService, EncryptionServiceEvent, - FilesBackupService, - FileService, - SubscriptionManagerInterface, - SubscriptionManager, ChallengePrompt, Challenge, ErrorAlertStrings, @@ -61,40 +67,58 @@ import { CredentialsChangeFunctionResponse, SessionStrings, AccountEvent, - AuthenticatorClientInterface, - AuthenticatorManager, - AuthClientInterface, - AuthManager, - RevisionClientInterface, - RevisionManager, - ApiServiceEvent, + PayloadManagerInterface, + HistoryServiceInterface, + InternalEventPublishStrategy, + EncryptionProviderInterface, } from '@standardnotes/services' import { - BackupServiceInterface, - DirectoryManagerInterface, - FileBackupsDevice, - FilesClientInterface, -} from '@standardnotes/files' -import { ComputePrivateUsername } from '@standardnotes/encryption' -import { useBoolean } from '@standardnotes/utils' -import { + PayloadEmitSource, + SNNote, + PrefKey, + PrefValue, + DecryptedItemMutator, BackupFile, DecryptedItemInterface, EncryptedItemInterface, Environment, ItemStream, Platform, + MutationType, } from '@standardnotes/models' -import { ClientDisplayableError, SessionListEntry } from '@standardnotes/responses' - -import { SnjsVersion } from './../Version' +import { + HttpResponse, + SessionListResponse, + User, + SignInResponse, + ClientDisplayableError, + SessionListEntry, +} from '@standardnotes/responses' +import { + SyncService, + ProtectionEvent, + SettingsService, + ActionsService, + ChallengeResponse, + ListedClientInterface, + DiskStorageService, +} from '../Services' +import { + nonSecureRandomIdentifier, + assertUnreachable, + removeFromArray, + isNullOrUndefined, + sleep, + UuidGenerator, + useBoolean, +} from '@standardnotes/utils' +import { UuidString, ApplicationEventPayload } from '../Types' +import { applicationEventForSyncEvent } from '@Lib/Application/Event' +import { BackupServiceInterface, FilesClientInterface } from '@standardnotes/files' +import { ComputePrivateUsername } from '@standardnotes/encryption' import { SNLog } from '../Log' -import { ChallengeResponse, ListedClientInterface } from '../Services' import { ApplicationConstructorOptions, FullyResolvedApplicationOptions } from './Options/ApplicationOptions' import { ApplicationOptionsDefaults } from './Options/Defaults' -import { LegacySession, MapperInterface, Session } from '@standardnotes/domain-core' -import { SessionStorageMapper } from '@Lib/Services/Mapping/SessionStorageMapper' -import { LegacySessionStorageMapper } from '@Lib/Services/Mapping/LegacySessionStorageMapper' import { SignInWithRecoveryCodes } from '@Lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes' import { UseCaseContainerInterface } from '@Lib/Domain/UseCase/UseCaseContainerInterface' import { GetRecoveryCodes } from '@Lib/Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes' @@ -106,6 +130,9 @@ import { GetRevision } from '@Lib/Domain/UseCase/GetRevision/GetRevision' import { DeleteRevision } from '@Lib/Domain/UseCase/DeleteRevision/DeleteRevision' import { GetAuthenticatorAuthenticationResponse } from '@Lib/Domain/UseCase/GetAuthenticatorAuthenticationResponse/GetAuthenticatorAuthenticationResponse' import { GetAuthenticatorAuthenticationOptions } from '@Lib/Domain/UseCase/GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions' +import { Dependencies } from './Dependencies/Dependencies' +import { TYPES } from './Dependencies/Types' +import { canBlockDeinit } from './Dependencies/isDeinitable' /** How often to automatically sync, in milliseconds */ const DEFAULT_AUTO_SYNC_INTERVAL = 30_000 @@ -122,90 +149,17 @@ type ApplicationObserver = { type ObserverRemover = () => void export class SNApplication implements ApplicationInterface, AppGroupManagedApplication, UseCaseContainerInterface { - onDeinit!: ExternalServices.DeinitCallback + onDeinit!: DeinitCallback /** * A runtime based identifier for each dynamic instantiation of the application instance. * This differs from the persistent application.identifier which persists in storage * across instantiations. */ - public readonly ephemeralIdentifier = Utils.nonSecureRandomIdentifier() - - private migrationService!: InternalServices.SNMigrationService - /** - * @deprecated will be fully replaced by @standardnotes/api::HttpService - */ - private deprecatedHttpService!: InternalServices.DeprecatedHttpService - private declare httpService: HttpServiceInterface - public payloadManager!: InternalServices.PayloadManager - public encryptionService!: EncryptionService - private diskStorageService!: InternalServices.DiskStorageService - private inMemoryStore!: ExternalServices.KeyValueStoreInterface - /** - * @deprecated will be fully replaced by @standardnotes/api services - */ - public apiService!: InternalServices.SNApiService - private declare userApiService: UserApiServiceInterface - private declare userServer: UserServerInterface - private declare userRequestServer: UserRequestServerInterface - private declare subscriptionApiService: SubscriptionApiServiceInterface - private declare subscriptionServer: SubscriptionServerInterface - private declare subscriptionManager: SubscriptionManagerInterface - private declare webSocketApiService: WebSocketApiServiceInterface - private declare webSocketServer: WebSocketServerInterface - - private sessionManager!: InternalServices.SNSessionManager - private syncService!: InternalServices.SNSyncService - public challengeService!: InternalServices.ChallengeService - public singletonManager!: InternalServices.SNSingletonManager - public componentManagerService!: InternalServices.SNComponentManager - public protectionService!: InternalServices.SNProtectionService - public actionsManager!: InternalServices.SNActionsService - public historyManager!: InternalServices.SNHistoryManager - private itemManager!: InternalServices.ItemManager - private keyRecoveryService!: InternalServices.SNKeyRecoveryService - private preferencesService!: InternalServices.SNPreferencesService - private featuresService!: InternalServices.SNFeaturesService - private userService!: UserService - private webSocketsService!: InternalServices.SNWebSocketsService - private settingsService!: InternalServices.SNSettingsService - private mfaService!: InternalServices.SNMfaService - private listedService!: InternalServices.ListedService - private fileService!: FileService - private mutatorService!: InternalServices.MutatorService - private integrityService!: ExternalServices.IntegrityService - private statusService!: ExternalServices.StatusService - private filesBackupService?: FilesBackupService - private vaultService!: ExternalServices.VaultServiceInterface - private contactService!: ExternalServices.ContactServiceInterface - private sharedVaultService!: ExternalServices.SharedVaultServiceInterface - private userEventService!: ExternalServices.UserEventService - private asymmetricMessageService!: ExternalServices.AsymmetricMessageService - private keySystemKeyManager!: ExternalServices.KeySystemKeyManager - - private declare sessionStorageMapper: MapperInterface> - private declare legacySessionStorageMapper: MapperInterface> - private declare authenticatorManager: AuthenticatorClientInterface - private declare authManager: AuthClientInterface - private declare revisionManager: RevisionClientInterface - private homeServerService?: ExternalServices.HomeServerService - - private declare _signInWithRecoveryCodes: SignInWithRecoveryCodes - private declare _getRecoveryCodes: GetRecoveryCodes - private declare _addAuthenticator: AddAuthenticator - private declare _listAuthenticators: ListAuthenticators - private declare _deleteAuthenticator: DeleteAuthenticator - private declare _getAuthenticatorAuthenticationOptions: GetAuthenticatorAuthenticationOptions - private declare _getAuthenticatorAuthenticationResponse: GetAuthenticatorAuthenticationResponse - private declare _listRevisions: ListRevisions - private declare _getRevision: GetRevision - private declare _deleteRevision: DeleteRevision - - public internalEventBus!: ExternalServices.InternalEventBusInterface + public readonly ephemeralIdentifier = nonSecureRandomIdentifier() private eventHandlers: ApplicationObserver[] = [] - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private services: ExternalServices.ApplicationServiceInterface[] = [] + private streamRemovers: ObserverRemover[] = [] private serviceObservers: ObserverRemover[] = [] private managedSubscribers: ObserverRemover[] = [] @@ -225,11 +179,12 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli public readonly environment: Environment public readonly platform: Platform - public deviceInterface: ExternalServices.DeviceInterface - public alertService: ExternalServices.AlertService - public readonly identifier: Common.ApplicationIdentifier + + public readonly identifier: ApplicationIdentifier public readonly options: FullyResolvedApplicationOptions + private dependencies: Dependencies + constructor(options: ApplicationConstructorOptions) { const allOptions: FullyResolvedApplicationOptions = { ...ApplicationOptionsDefaults, @@ -262,152 +217,141 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.environment = options.environment this.platform = options.platform - this.deviceInterface = options.deviceInterface - this.alertService = options.alertService + this.identifier = options.identifier this.options = Object.freeze(allOptions) - this.constructInternalEventBus() - - this.constructServices() + this.dependencies = new Dependencies(this.options) + this.registerServiceObservers() this.defineInternalEventHandlers() + this.createBackgroundDependencies() } - get subscriptions(): ExternalServices.SubscriptionManagerInterface { - return this.subscriptionManager - } + private registerServiceObservers() { + const encryptionService = this.dependencies.get(TYPES.EncryptionService) + this.serviceObservers.push( + encryptionService.addEventObserver(async (event) => { + if (event === EncryptionServiceEvent.RootKeyStatusChanged) { + await this.notifyEvent(ApplicationEvent.KeyStatusChanged) + } + }), + ) - get signInWithRecoveryCodes(): SignInWithRecoveryCodes { - return this._signInWithRecoveryCodes - } + this.dependencies.get(TYPES.DiskStorageService).provideEncryptionProvider(encryptionService) - get getRecoveryCodes(): GetRecoveryCodes { - return this._getRecoveryCodes - } + const apiService = this.dependencies.get(TYPES.LegacyApiService) + this.dependencies + .get(TYPES.HttpService) + .setCallbacks(apiService.processMetaObject.bind(apiService), apiService.setSession.bind(apiService)) - get addAuthenticator(): AddAuthenticator { - return this._addAuthenticator - } + this.serviceObservers.push( + this.dependencies.get(TYPES.SessionManager).addEventObserver(async (event) => { + switch (event) { + case SessionEvent.Restored: { + void (async () => { + await this.sync.sync({ sourceDescription: 'Session restored pre key creation' }) + if (encryptionService.needsNewRootKeyBasedItemsKey()) { + void encryptionService.createNewDefaultItemsKey().then(() => { + void this.sync.sync({ sourceDescription: 'Session restored post key creation' }) + }) + } + })() + break + } + case SessionEvent.Revoked: { + await this.handleRevokedSession() + break + } + case SessionEvent.UserKeyPairChanged: + break + default: { + assertUnreachable(event) + } + } + }), + ) - get listAuthenticators(): ListAuthenticators { - return this._listAuthenticators - } + const syncEventCallback = async (eventName: SyncEvent) => { + const appEvent = applicationEventForSyncEvent(eventName) + if (appEvent) { + await encryptionService.onSyncEvent(eventName) - get deleteAuthenticator(): DeleteAuthenticator { - return this._deleteAuthenticator - } + await this.notifyEvent(appEvent) - get getAuthenticatorAuthenticationOptions(): GetAuthenticatorAuthenticationOptions { - return this._getAuthenticatorAuthenticationOptions - } + if (appEvent === ApplicationEvent.CompletedFullSync) { + if (!this.handledFullSyncStage) { + this.handledFullSyncStage = true + await this.handleStage(ApplicationStage.FullSyncCompleted_13) + } + } + } + } + const syncService = this.dependencies.get(TYPES.SyncService) + const uninstall = syncService.addEventObserver(syncEventCallback) + this.serviceObservers.push(uninstall) - get getAuthenticatorAuthenticationResponse(): GetAuthenticatorAuthenticationResponse { - return this._getAuthenticatorAuthenticationResponse - } + const protectionService = this.dependencies.get(TYPES.ProtectionService) + this.serviceObservers.push( + protectionService.addEventObserver((event) => { + if (event === ProtectionEvent.UnprotectedSessionBegan) { + void this.notifyEvent(ApplicationEvent.UnprotectedSessionBegan) + } else if (event === ProtectionEvent.UnprotectedSessionExpired) { + void this.notifyEvent(ApplicationEvent.UnprotectedSessionExpired) + } + }), + ) - get listRevisions(): ListRevisions { - return this._listRevisions - } + const userService = this.dependencies.get(TYPES.UserService) + this.serviceObservers.push( + userService.addEventObserver(async (event, data) => { + switch (event) { + case AccountEvent.SignedInOrRegistered: { + void this.notifyEvent(ApplicationEvent.SignedIn) + break + } + case AccountEvent.SignedOut: { + await this.notifyEvent(ApplicationEvent.SignedOut) + await this.prepareForDeinit() + this.deinit(this.getDeinitMode(), data?.payload.source || DeinitSource.SignOut) + break + } + default: { + assertUnreachable(event) + } + } + }), + ) - get getRevision(): GetRevision { - return this._getRevision - } + const preferencesService = this.dependencies.get(TYPES.PreferencesService) + this.serviceObservers.push( + preferencesService.addEventObserver(() => { + void this.notifyEvent(ApplicationEvent.PreferencesChanged) + }), + ) - get deleteRevision(): DeleteRevision { - return this._deleteRevision - } - - public get files(): FilesClientInterface { - return this.fileService - } - - public get features(): ExternalServices.FeaturesClientInterface { - return this.featuresService - } - - public get items(): ExternalServices.ItemManagerInterface { - return this.itemManager - } - - public get protections(): ProtectionsClientInterface { - return this.protectionService - } - - public get sync(): InternalServices.SyncClientInterface { - return this.syncService - } - - public get user(): ExternalServices.UserClientInterface { - return this.userService - } - - public get settings(): InternalServices.SNSettingsService { - return this.settingsService - } - - public get mutator(): ExternalServices.MutatorClientInterface { - return this.mutatorService - } - - public get sessions(): SessionsClientInterface { - return this.sessionManager - } - - public get status(): ExternalServices.StatusServiceInterface { - return this.statusService - } - - public get fileBackups(): BackupServiceInterface | undefined { - return this.filesBackupService - } - - public get componentManager(): ComponentManagerInterface { - return this.componentManagerService - } - - public get listed(): ListedClientInterface { - return this.listedService - } - - public get alerts(): ExternalServices.AlertService { - return this.alertService - } - - public get storage(): ExternalServices.StorageServiceInterface { - return this.diskStorageService - } - - public get actions(): InternalServices.SNActionsService { - return this.actionsManager - } - - public get challenges(): ExternalServices.ChallengeServiceInterface { - return this.challengeService - } - - public get asymmetric(): ExternalServices.AsymmetricMessageServiceInterface { - return this.asymmetricMessageService - } - - get homeServer(): ExternalServices.HomeServerServiceInterface | undefined { - return this.homeServerService - } - - public get vaults(): ExternalServices.VaultServiceInterface { - return this.vaultService - } - - public get contacts(): ExternalServices.ContactServiceInterface { - return this.contactService - } - - public get sharedVaults(): ExternalServices.SharedVaultServiceInterface { - return this.sharedVaultService - } - - public get preferences(): ExternalServices.PreferenceServiceInterface { - return this.preferencesService + const featuresService = this.dependencies.get(TYPES.FeaturesService) + this.serviceObservers.push( + featuresService.addEventObserver((event) => { + switch (event) { + case FeaturesEvent.UserRolesChanged: { + void this.notifyEvent(ApplicationEvent.UserRolesChanged) + break + } + case FeaturesEvent.FeaturesAvailabilityChanged: { + void this.notifyEvent(ApplicationEvent.FeaturesAvailabilityChanged) + break + } + case FeaturesEvent.DidPurchaseSubscription: { + void this.notifyEvent(ApplicationEvent.DidPurchaseSubscription) + break + } + default: { + assertUnreachable(event) + } + } + }), + ) } public computePrivateUsername(username: string): Promise { @@ -427,31 +371,31 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli this.setLaunchCallback(callback) - const databaseResult = await this.deviceInterface.openDatabase(this.identifier).catch((error) => { + const databaseResult = await this.device.openDatabase(this.identifier).catch((error) => { void this.notifyEvent(ApplicationEvent.LocalDatabaseReadError, error) return undefined }) this.createdNewDatabase = useBoolean(databaseResult?.isNewDatabase, false) - await this.migrationService.initialize() + await this.migrations.initialize() await this.notifyEvent(ApplicationEvent.MigrationsLoaded) - await this.handleStage(ExternalServices.ApplicationStage.PreparingForLaunch_0) + await this.handleStage(ApplicationStage.PreparingForLaunch_0) - await this.diskStorageService.initializeFromDisk() + await this.storage.initializeFromDisk() await this.notifyEvent(ApplicationEvent.StorageReady) - await this.encryptionService.initialize() + await this.encryption.initialize() - await this.handleStage(ExternalServices.ApplicationStage.ReadyForLaunch_05) + await this.handleStage(ApplicationStage.ReadyForLaunch_05) this.started = true await this.notifyEvent(ApplicationEvent.Started) } private setLaunchCallback(callback: LaunchCallback) { - this.challengeService.sendChallenge = callback.receiveChallenge + this.challenges.sendChallenge = callback.receiveChallenge } /** @@ -470,61 +414,58 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli const launchChallenge = this.getLaunchChallenge() if (launchChallenge) { - const response = await this.challengeService.promptForChallengeResponse(launchChallenge) + const response = await this.challenges.promptForChallengeResponse(launchChallenge) if (!response) { throw Error('Launch challenge was cancelled.') } await this.handleLaunchChallengeResponse(response) } - if (this.diskStorageService.isStorageWrapped()) { + if (this.storage.isStorageWrapped()) { try { - await this.diskStorageService.decryptStorage() + await this.storage.decryptStorage() } catch (_error) { - void this.alertService.alert( - ErrorAlertStrings.StorageDecryptErrorBody, - ErrorAlertStrings.StorageDecryptErrorTitle, - ) + void this.alerts.alert(ErrorAlertStrings.StorageDecryptErrorBody, ErrorAlertStrings.StorageDecryptErrorTitle) } } - await this.handleStage(ExternalServices.ApplicationStage.StorageDecrypted_09) + await this.handleStage(ApplicationStage.StorageDecrypted_09) - const host = this.apiService.loadHost() + const host = this.legacyApi.loadHost() - this.httpService.setHost(host) + this.http.setHost(host) - this.webSocketsService.loadWebSocketUrl() + this.sockets.loadWebSocketUrl() - await this.sessionManager.initializeFromDisk() + await this.sessions.initializeFromDisk() - this.settingsService.initializeFromDisk() + this.settings.initializeFromDisk() - this.featuresService.initializeFromDisk() + this.features.initializeFromDisk() this.launched = true await this.notifyEvent(ApplicationEvent.Launched) - await this.handleStage(ExternalServices.ApplicationStage.Launched_10) + await this.handleStage(ApplicationStage.Launched_10) - await this.handleStage(ExternalServices.ApplicationStage.LoadingDatabase_11) + await this.handleStage(ApplicationStage.LoadingDatabase_11) if (this.createdNewDatabase) { - await this.syncService.onNewDatabaseCreated() + await this.sync.onNewDatabaseCreated() } /** * We don't want to await this, as we want to begin allowing the app to function * before local data has been loaded fully. */ - const loadPromise = this.syncService + const loadPromise = this.sync .loadDatabasePayloads() .then(async () => { if (this.dealloced) { throw 'Application has been destroyed.' } - await this.handleStage(ExternalServices.ApplicationStage.LoadedDatabase_12) + await this.handleStage(ApplicationStage.LoadedDatabase_12) this.beginAutoSyncTimer() - await this.syncService.sync({ - mode: ExternalServices.SyncMode.DownloadFirst, - source: ExternalServices.SyncSource.External, + await this.sync.sync({ + mode: SyncMode.DownloadFirst, + source: SyncSource.External, sourceDescription: 'Application Launch', }) }) @@ -546,7 +487,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } public getLaunchChallenge(): Challenge | undefined { - return this.protectionService.createLaunchChallenge() + return this.protections.createLaunchChallenge() } private async handleLaunchChallengeResponse(response: ChallengeResponse) { @@ -554,28 +495,27 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli let wrappingKey = response.artifacts?.wrappingKey if (!wrappingKey) { const value = response.getValueForType(ChallengeValidation.LocalPasscode) - wrappingKey = await this.encryptionService.computeWrappingKey(value.value as string) + wrappingKey = await this.encryption.computeWrappingKey(value.value as string) } - await this.encryptionService.unwrapRootKey(wrappingKey) + await this.encryption.unwrapRootKey(wrappingKey) } } private beginAutoSyncTimer() { this.autoSyncInterval = setInterval(() => { - this.syncService.log('Syncing from autosync') + this.sync.log('Syncing from autosync') void this.sync.sync({ sourceDescription: 'Auto Sync' }) }, DEFAULT_AUTO_SYNC_INTERVAL) } - private async handleStage(stage: ExternalServices.ApplicationStage) { - for (const service of this.services) { - await service.handleApplicationStage(stage) - } - - this.internalEventBus.publish({ - type: ApplicationEvent.ApplicationStageChanged, - payload: { stage } as ExternalServices.ApplicationStageChangedEventPayload, - }) + private async handleStage(stage: ApplicationStage) { + await this.events.publishSync( + { + type: ApplicationEvent.ApplicationStageChanged, + payload: { stage } as ApplicationStageChangedEventPayload, + }, + InternalEventPublishStrategy.SEQUENCE, + ) } /** @@ -585,12 +525,11 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli const observer = { callback, singleEvent } this.eventHandlers.push(observer) return () => { - Utils.removeFromArray(this.eventHandlers, observer) + removeFromArray(this.eventHandlers, observer) } } public addSingleEventObserver(event: ApplicationEvent, callback: ApplicationEventCallback): () => void { - // eslint-disable-next-line @typescript-eslint/require-await const filteredCallback = async (firedEvent: ApplicationEvent) => { if (firedEvent === event) { void callback(event) @@ -599,30 +538,6 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli return this.addEventObserver(filteredCallback, event) } - public async getDiagnostics(): Promise { - let result: DiagnosticInfo = { - application: { - snjsVersion: SnjsVersion, - appVersion: this.options.appVersion, - environment: this.options.environment, - platform: this.options.platform, - }, - } - - for (const service of this.services) { - const diagnostics = await service.getDiagnostics() - - if (diagnostics) { - result = { - ...result, - ...diagnostics, - } - } - } - - return result - } - private async notifyEvent(event: ApplicationEvent, data?: ApplicationEventPayload) { if (event === ApplicationEvent.Started) { this.onStart() @@ -636,30 +551,28 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } } - this.internalEventBus.publish({ + this.events.publish({ type: event, payload: data, }) - void this.migrationService.handleApplicationEvent(event) + void this.migrations.handleApplicationEvent(event) } /** * Whether the local database has completed loading local items. */ public isDatabaseLoaded(): boolean { - return this.syncService.isDatabaseLoaded() + return this.sync.isDatabaseLoaded() } - public getSessions(): Promise> { - return this.sessionManager.getSessionsList() + public getSessions(): Promise> { + return this.sessions.getSessionsList() } - public async revokeSession( - sessionId: UuidString, - ): Promise | undefined> { - if (await this.protectionService.authorizeSessionRevoking()) { - return this.sessionManager.revokeSession(sessionId) + public async revokeSession(sessionId: UuidString): Promise | undefined> { + if (await this.protections.authorizeSessionRevoking()) { + return this.sessions.revokeSession(sessionId) } return undefined } @@ -668,15 +581,15 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli * Revokes all sessions except the current one. */ public async revokeAllOtherSessions(): Promise { - return this.sessionManager.revokeAllOtherSessions() + return this.sessions.revokeAllOtherSessions() } public userCanManageSessions(): boolean { const userVersion = this.getUserVersion() - if (Utils.isNullOrUndefined(userVersion)) { + if (isNullOrUndefined(userVersion)) { return false } - return Common.compareVersions(userVersion, Common.ProtocolVersion.V004) >= 0 + return compareVersions(userVersion, ProtocolVersion.V004) >= 0 } /** @@ -688,19 +601,19 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli contentType: string | string[], stream: ItemStream, ): () => void { - const removeItemManagerObserver = this.itemManager.addObserver( + const removeItemManagerObserver = this.items.addObserver( contentType, ({ changed, inserted, removed, source }) => { stream({ changed, inserted, removed, source }) }, ) - const matches = this.itemManager.getItems(contentType) + const matches = this.items.getItems(contentType) stream({ inserted: matches, changed: [], removed: [], - source: Models.PayloadEmitSource.InitialObserverRegistrationPush, + source: PayloadEmitSource.InitialObserverRegistrationPush, }) this.streamRemovers.push(removeItemManagerObserver) @@ -708,7 +621,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli return () => { removeItemManagerObserver() - Utils.removeFromArray(this.streamRemovers, removeItemManagerObserver) + removeFromArray(this.streamRemovers, removeItemManagerObserver) } } @@ -716,45 +629,45 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli * Set the server's URL */ public async setHost(host: string): Promise { - this.httpService.setHost(host) + this.http.setHost(host) - await this.apiService.setHost(host) + await this.legacyApi.setHost(host) } public getHost(): string { - return this.apiService.getHost() + return this.legacyApi.getHost() } public async setCustomHost(host: string): Promise { await this.setHost(host) - this.webSocketsService.setWebSocketUrl(undefined) + this.sockets.setWebSocketUrl(undefined) } - public getUser(): Responses.User | undefined { + public getUser(): User | undefined { if (!this.launched) { throw Error('Attempting to access user before application unlocked') } - return this.sessionManager.getUser() + return this.sessions.getUser() } public getUserPasswordCreationDate(): Date | undefined { - return this.encryptionService.getPasswordCreatedDate() + return this.encryption.getPasswordCreatedDate() } public getProtocolEncryptionDisplayName(): Promise { - return this.encryptionService.getEncryptionDisplayName() + return this.encryption.getEncryptionDisplayName() } - public getUserVersion(): Common.ProtocolVersion | undefined { - return this.encryptionService.getUserVersion() + public getUserVersion(): ProtocolVersion | undefined { + return this.encryption.getUserVersion() } /** * Returns true if there is an upgrade available for the account or passcode */ public protocolUpgradeAvailable(): Promise { - return this.encryptionService.upgradeAvailable() + return this.encryption.upgradeAvailable() } /** @@ -771,15 +684,15 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli message: string } }> { - const result = await this.userService.performProtocolUpgrade() + const result = await this.user.performProtocolUpgrade() if (result.success) { if (this.hasAccount()) { - void this.alertService.alert(ProtocolUpgradeStrings.SuccessAccount) + void this.alerts.alert(ProtocolUpgradeStrings.SuccessAccount) } else { - void this.alertService.alert(ProtocolUpgradeStrings.SuccessPasscodeOnly) + void this.alerts.alert(ProtocolUpgradeStrings.SuccessPasscodeOnly) } } else if (result.error) { - void this.alertService.alert(ProtocolUpgradeStrings.Fail) + void this.alerts.alert(ProtocolUpgradeStrings.Fail) } return result } @@ -789,7 +702,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } public hasAccount(): boolean { - return this.encryptionService.hasAccount() + return this.encryption.hasAccount() } /** @@ -797,11 +710,11 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli * passcode, password, or biometrics. */ public hasProtectionSources(): boolean { - return this.protectionService.hasProtectionSources() + return this.protections.hasProtectionSources() } public hasUnprotectedAccessSession(): boolean { - return this.protectionService.hasUnprotectedAccessSession() + return this.protections.hasUnprotectedAccessSession() } /** @@ -809,82 +722,76 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli * challenge, a session will be started during which protections are disabled. */ public getProtectionSessionExpiryDate(): Date { - return this.protectionService.getSessionExpiryDate() + return this.protections.getSessionExpiryDate() } public clearProtectionSession(): Promise { - return this.protectionService.clearSession() + return this.protections.clearSession() } - public async authorizeProtectedActionForNotes( - notes: Models.SNNote[], - challengeReason: ChallengeReason, - ): Promise { - return await this.protectionService.authorizeProtectedActionForItems(notes, challengeReason) + public async authorizeProtectedActionForNotes(notes: SNNote[], challengeReason: ChallengeReason): Promise { + return await this.protections.authorizeProtectedActionForItems(notes, challengeReason) } /** * @returns whether note access has been granted or not */ - public authorizeNoteAccess(note: Models.SNNote): Promise { - return this.protectionService.authorizeItemAccess(note) + public authorizeNoteAccess(note: SNNote): Promise { + return this.protections.authorizeItemAccess(note) } public authorizeAutolockIntervalChange(): Promise { - return this.protectionService.authorizeAutolockIntervalChange() + return this.protections.authorizeAutolockIntervalChange() } public authorizeSearchingProtectedNotesText(): Promise { - return this.protectionService.authorizeSearchingProtectedNotesText() + return this.protections.authorizeSearchingProtectedNotesText() } public async createEncryptedBackupFileForAutomatedDesktopBackups(): Promise { - return this.encryptionService.createEncryptedBackupFile() + return this.encryption.createEncryptedBackupFile() } public async createEncryptedBackupFile(): Promise { - if (!(await this.protectionService.authorizeBackupCreation())) { + if (!(await this.protections.authorizeBackupCreation())) { return } - return this.encryptionService.createEncryptedBackupFile() + return this.encryption.createEncryptedBackupFile() } public async createDecryptedBackupFile(): Promise { - if (!(await this.protectionService.authorizeBackupCreation())) { + if (!(await this.protections.authorizeBackupCreation())) { return } - return this.encryptionService.createDecryptedBackupFile() + return this.encryption.createDecryptedBackupFile() } public isEphemeralSession(): boolean { - return this.diskStorageService.isEphemeralSession() + return this.storage.isEphemeralSession() } - public setValue(key: string, value: unknown, mode?: ExternalServices.StorageValueModes): void { - return this.diskStorageService.setValue(key, value, mode) + public setValue(key: string, value: unknown, mode?: StorageValueModes): void { + return this.storage.setValue(key, value, mode) } - public getValue(key: string, mode?: ExternalServices.StorageValueModes): T { - return this.diskStorageService.getValue(key, mode) + public getValue(key: string, mode?: StorageValueModes): T { + return this.storage.getValue(key, mode) } - public async removeValue(key: string, mode?: ExternalServices.StorageValueModes): Promise { - return this.diskStorageService.removeValue(key, mode) + public async removeValue(key: string, mode?: StorageValueModes): Promise { + return this.storage.removeValue(key, mode) } - public getPreference(key: K): Models.PrefValue[K] | undefined - public getPreference(key: K, defaultValue: Models.PrefValue[K]): Models.PrefValue[K] - public getPreference( - key: K, - defaultValue?: Models.PrefValue[K], - ): Models.PrefValue[K] | undefined { - return this.preferencesService.getValue(key, defaultValue) + public getPreference(key: K): PrefValue[K] | undefined + public getPreference(key: K, defaultValue: PrefValue[K]): PrefValue[K] + public getPreference(key: K, defaultValue?: PrefValue[K]): PrefValue[K] | undefined { + return this.preferences.getValue(key, defaultValue) } - public async setPreference(key: K, value: Models.PrefValue[K]): Promise { - return this.preferencesService.setValue(key, value) + public async setPreference(key: K, value: PrefValue[K]): Promise { + return this.preferences.setValue(key, value) } /** @@ -893,28 +800,29 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli * to finish tasks. 0 means no limit. */ private async prepareForDeinit(maxWait = 0): Promise { - const promise = Promise.all(this.services.map((service) => service.blockDeinit())) + const deps = this.dependencies.getAll().filter(canBlockDeinit) + const promise = Promise.all(deps.map((service) => service.blockDeinit())) if (maxWait === 0) { await promise } else { /** Await up to maxWait. If not resolved by then, return. */ - await Promise.race([promise, Utils.sleep(maxWait)]) + await Promise.race([promise, sleep(maxWait)]) } } - public addChallengeObserver(challenge: Challenge, observer: ExternalServices.ChallengeObserver): () => void { - return this.challengeService.addChallengeObserver(challenge, observer) + public addChallengeObserver(challenge: Challenge, observer: ChallengeObserver): () => void { + return this.challenges.addChallengeObserver(challenge, observer) } public submitValuesForChallenge(challenge: Challenge, values: ChallengeValue[]): Promise { - return this.challengeService.submitValuesForChallenge(challenge, values) + return this.challenges.submitValuesForChallenge(challenge, values) } public cancelChallenge(challenge: Challenge): void { - this.challengeService.cancelChallenge(challenge) + this.challenges.cancelChallenge(challenge) } - public setOnDeinit(onDeinit: ExternalServices.DeinitCallback): void { + public setOnDeinit(onDeinit: DeinitCallback): void { this.onDeinit = onDeinit } @@ -935,27 +843,19 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli uninstallSubscriber() } - for (const service of this.services) { - service.deinit() - } - - this.httpService.deinit() - ;(this.httpService as unknown) = undefined - this.options.crypto.deinit() ;(this.options as unknown) = undefined this.createdNewDatabase = false - this.services.length = 0 + this.serviceObservers.length = 0 this.managedSubscribers.length = 0 this.streamRemovers.length = 0 - this.clearInternalEventBus() - this.clearServices() - this.started = false + this.dependencies.deinit() + this.onDeinit?.(this, mode, source) ;(this.onDeinit as unknown) = undefined } @@ -970,7 +870,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli ephemeral = false, mergeLocal = true, ): Promise { - return this.userService.register(email, password, ephemeral, mergeLocal) + return this.user.register(email, password, ephemeral, mergeLocal) } /** @@ -984,17 +884,17 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli ephemeral = false, mergeLocal = true, awaitSync = false, - ): Promise> { - return this.userService.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync) + ): Promise> { + return this.user.signIn(email, password, strict, ephemeral, mergeLocal, awaitSync) } public async changeEmail( newEmail: string, currentPassword: string, passcode?: string, - origination = Common.KeyParamsOrigination.EmailChange, + origination = KeyParamsOrigination.EmailChange, ): Promise { - return this.userService.changeCredentials({ + return this.user.changeCredentials({ currentPassword, newEmail, passcode, @@ -1007,10 +907,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli currentPassword: string, newPassword: string, passcode?: string, - origination = Common.KeyParamsOrigination.PasswordChange, + origination = KeyParamsOrigination.PasswordChange, validateNewPasswordStrength = true, ): Promise { - return this.userService.changeCredentials({ + return this.user.changeCredentials({ currentPassword, newPassword, passcode, @@ -1019,50 +919,41 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli }) } - public async changeAndSaveItem( + public async changeAndSaveItem( itemToLookupUuidFor: DecryptedItemInterface, mutate: (mutator: M) => void, updateTimestamps = true, - emitSource?: Models.PayloadEmitSource, - syncOptions?: ExternalServices.SyncOptions, + emitSource?: PayloadEmitSource, + syncOptions?: SyncOptions, ): Promise { await this.mutator.changeItems( [itemToLookupUuidFor], mutate, - updateTimestamps ? Models.MutationType.UpdateUserTimestamps : Models.MutationType.NoUpdateUserTimestamps, + updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps, emitSource, ) - await this.syncService.sync(syncOptions) - return this.itemManager.findItem(itemToLookupUuidFor.uuid) + await this.sync.sync(syncOptions) + return this.items.findItem(itemToLookupUuidFor.uuid) } - public async changeAndSaveItems( + public async changeAndSaveItems( itemsToLookupUuidsFor: DecryptedItemInterface[], mutate: (mutator: M) => void, updateTimestamps = true, - emitSource?: Models.PayloadEmitSource, - syncOptions?: ExternalServices.SyncOptions, + emitSource?: PayloadEmitSource, + syncOptions?: SyncOptions, ): Promise { await this.mutator.changeItems( itemsToLookupUuidsFor, mutate, - updateTimestamps ? Models.MutationType.UpdateUserTimestamps : Models.MutationType.NoUpdateUserTimestamps, + updateTimestamps ? MutationType.UpdateUserTimestamps : MutationType.NoUpdateUserTimestamps, emitSource, ) - await this.syncService.sync(syncOptions) + await this.sync.sync(syncOptions) } - public async importData(data: BackupFile, awaitSync = false): Promise { - const usecase = new ExternalServices.ImportDataUseCase( - this.itemManager, - this.syncService, - this.protectionService, - this.encryptionService, - this.payloadManager, - this.challengeService, - this.historyManager, - ) - + public async importData(data: BackupFile, awaitSync = false): Promise { + const usecase = this.dependencies.get(TYPES.ImportDataUseCase) return usecase.execute(data, awaitSync) } @@ -1076,13 +967,13 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } this.revokingSession = true /** Keep a reference to the soon-to-be-cleared alertService */ - const alertService = this.alertService + const alertService = this.alerts await this.user.signOut(true) void alertService.alert(SessionStrings.CurrentSessionRevoked) } public async validateAccountPassword(password: string): Promise { - const { valid } = await this.encryptionService.validateAccountPassword(password) + const { valid } = await this.encryption.validateAccountPassword(password) return valid } @@ -1095,14 +986,14 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } public hasPasscode(): boolean { - return this.encryptionService.hasPasscode() + return this.encryption.hasPasscode() } async isLocked(): Promise { if (!this.started) { return Promise.resolve(true) } - const isPasscodeLocked = await this.challengeService.isPasscodeLocked() + const isPasscodeLocked = await this.challenges.isPasscodeLocked() return isPasscodeLocked || this.isBiometricsSoftLockEngaged } @@ -1121,7 +1012,7 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli false, ) - void this.challengeService.promptForChallengeResponse(challenge) + void this.challenges.promptForChallengeResponse(challenge) this.isBiometricsSoftLockEngaged = true void this.notifyEvent(ApplicationEvent.BiometricsSoftLockEngaged) @@ -1148,897 +1039,324 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli } public addPasscode(passcode: string): Promise { - return this.userService.addPasscode(passcode) + return this.user.addPasscode(passcode) } /** * @returns whether the passcode was successfuly removed */ public async removePasscode(): Promise { - return this.userService.removePasscode() + return this.user.removePasscode() } public async changePasscode( newPasscode: string, - origination = Common.KeyParamsOrigination.PasscodeChange, + origination = KeyParamsOrigination.PasscodeChange, ): Promise { - return this.userService.changePasscode(newPasscode, origination) + return this.user.changePasscode(newPasscode, origination) } public enableEphemeralPersistencePolicy(): Promise { - return this.diskStorageService.setPersistencePolicy(ExternalServices.StoragePersistencePolicies.Ephemeral) + return this.storage.setPersistencePolicy(StoragePersistencePolicies.Ephemeral) } public hasPendingMigrations(): Promise { - return this.migrationService.hasPendingMigrations() + return this.migrations.hasPendingMigrations() } public generateUuid(): string { - return Utils.UuidGenerator.GenerateUuid() + return UuidGenerator.GenerateUuid() } public presentKeyRecoveryWizard(): void { - return this.keyRecoveryService.presentKeyRecoveryWizard() + const service = this.dependencies.get(TYPES.KeyRecoveryService) + return service.presentKeyRecoveryWizard() } public canAttemptDecryptionOfItem(item: EncryptedItemInterface): ClientDisplayableError | true { - return this.keyRecoveryService.canAttemptDecryptionOfItem(item) - } - - /** - * Dynamically change the device interface, i.e when Desktop wants to override - * default web interface. - */ - public changeDeviceInterface(deviceInterface: ExternalServices.DeviceInterface): void { - this.deviceInterface = deviceInterface - - for (const service of this.services) { - if ('deviceInterface' in service) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ;(service as any)['deviceInterface'] = deviceInterface - } - } + const service = this.dependencies.get(TYPES.KeyRecoveryService) + return service.canAttemptDecryptionOfItem(item) } public async isMfaActivated(): Promise { - return this.mfaService.isMfaActivated() + return this.mfa.isMfaActivated() } public async generateMfaSecret(): Promise { - return this.mfaService.generateMfaSecret() + return this.mfa.generateMfaSecret() } public async getOtpToken(secret: string): Promise { - return this.mfaService.getOtpToken(secret) + return this.mfa.getOtpToken(secret) } public async enableMfa(secret: string, otpToken: string): Promise { - return this.mfaService.enableMfa(secret, otpToken) + return this.mfa.enableMfa(secret, otpToken) } public async disableMfa(): Promise { - if (await this.protectionService.authorizeMfaDisable()) { - return this.mfaService.disableMfa() + if (await this.protections.authorizeMfaDisable()) { + return this.mfa.disableMfa() } } public getNewSubscriptionToken(): Promise { - return this.apiService.getNewSubscriptionToken() + return this.legacyApi.getNewSubscriptionToken() } public isThirdPartyHostUsed(): boolean { - return this.apiService.isThirdPartyHostUsed() + return this.legacyApi.isThirdPartyHostUsed() } async isUsingHomeServer(): Promise { - if (!this.homeServerService) { + const homeServerService = this.dependencies.get(TYPES.HomeServerService) + + if (!homeServerService) { return false } - return this.getHost() === (await this.homeServerService.getHomeServerUrl()) + return this.getHost() === (await homeServerService.getHomeServerUrl()) } - private constructServices() { - this.createMappers() - this.createPayloadManager() - this.createItemManager() - this.createMutatorService() - - this.createDiskStorageManager() - this.createUserEventService() - - this.createInMemoryStorageManager() - - this.createKeySystemKeyManager() - this.createProtocolService() - - this.diskStorageService.provideEncryptionProvider(this.encryptionService) - this.createChallengeService() - this.createLegacyHttpManager() - this.createHttpServiceAndApiService() - this.createUserServer() - this.createUserRequestServer() - this.createUserApiService() - this.createSubscriptionServer() - this.createSubscriptionApiService() - this.createWebSocketServer() - this.createWebSocketApiService() - this.createWebSocketsService() - this.createSessionManager() - this.createSubscriptionManager() - this.createHistoryManager() - this.createSyncManager() - this.createProtectionService() - this.createUserService() - this.createKeyRecoveryService() - this.createSingletonManager() - this.createPreferencesService() - this.createSettingsService() - this.createFeaturesService() - this.createComponentManager() - this.createMfaService() - - this.createStatusService() - if (isDesktopDevice(this.deviceInterface)) { - this.createFilesBackupService(this.deviceInterface) - this.createHomeServerService(this.deviceInterface) - } - this.createMigrationService() - this.createFileService() - - this.createIntegrityService() - - this.createListedService() - this.createActionsManager() - this.createAuthenticatorManager() - this.createAuthManager() - this.createRevisionManager() - - this.createUseCases() - this.createContactService() - this.createVaultService() - this.createSharedVaultService() - this.createAsymmetricMessageService() - } - - private clearServices() { - ;(this.migrationService as unknown) = undefined - ;(this.alertService as unknown) = undefined - ;(this.deprecatedHttpService as unknown) = undefined - ;(this.httpService as unknown) = undefined - ;(this.payloadManager as unknown) = undefined - ;(this.encryptionService as unknown) = undefined - ;(this.diskStorageService as unknown) = undefined - ;(this.inMemoryStore as unknown) = undefined - ;(this.apiService as unknown) = undefined - ;(this.userApiService as unknown) = undefined - ;(this.userServer as unknown) = undefined - ;(this.userRequestServer as unknown) = undefined - ;(this.subscriptionApiService as unknown) = undefined - ;(this.subscriptionServer as unknown) = undefined - ;(this.subscriptionManager as unknown) = undefined - ;(this.webSocketApiService as unknown) = undefined - ;(this.webSocketServer as unknown) = undefined - ;(this.sessionManager as unknown) = undefined - ;(this.syncService as unknown) = undefined - ;(this.challengeService as unknown) = undefined - ;(this.singletonManager as unknown) = undefined - ;(this.componentManagerService as unknown) = undefined - ;(this.protectionService as unknown) = undefined - ;(this.actionsManager as unknown) = undefined - ;(this.historyManager as unknown) = undefined - ;(this.itemManager as unknown) = undefined - ;(this.keyRecoveryService as unknown) = undefined - ;(this.preferencesService as unknown) = undefined - ;(this.featuresService as unknown) = undefined - ;(this.userService as unknown) = undefined - ;(this.webSocketsService as unknown) = undefined - ;(this.settingsService as unknown) = undefined - ;(this.mfaService as unknown) = undefined - ;(this.listedService as unknown) = undefined - ;(this.fileService as unknown) = undefined - ;(this.integrityService as unknown) = undefined - ;(this.mutatorService as unknown) = undefined - ;(this.filesBackupService as unknown) = undefined - ;(this.statusService as unknown) = undefined - ;(this.sessionStorageMapper as unknown) = undefined - ;(this.legacySessionStorageMapper as unknown) = undefined - ;(this.authenticatorManager as unknown) = undefined - ;(this.authManager as unknown) = undefined - ;(this.revisionManager as unknown) = undefined - ;(this.homeServerService as unknown) = undefined - ;(this._signInWithRecoveryCodes as unknown) = undefined - ;(this._getRecoveryCodes as unknown) = undefined - ;(this._addAuthenticator as unknown) = undefined - ;(this._listAuthenticators as unknown) = undefined - ;(this._deleteAuthenticator as unknown) = undefined - ;(this._getAuthenticatorAuthenticationResponse as unknown) = undefined - ;(this._listRevisions as unknown) = undefined - ;(this._getRevision as unknown) = undefined - ;(this._deleteRevision as unknown) = undefined - ;(this.vaultService as unknown) = undefined - ;(this.contactService as unknown) = undefined - ;(this.sharedVaultService as unknown) = undefined - ;(this.userEventService as unknown) = undefined - ;(this.asymmetricMessageService as unknown) = undefined - ;(this.keySystemKeyManager as unknown) = undefined - - this.services = [] - } - - private constructInternalEventBus(): void { - this.internalEventBus = new ExternalServices.InternalEventBus() + private createBackgroundDependencies() { + this.dependencies.get(TYPES.UserEventService) + this.dependencies.get(TYPES.KeyRecoveryService) } private defineInternalEventHandlers(): void { - this.internalEventBus.addEventHandler(this.featuresService, ExternalServices.ApiServiceEvent.MetaReceived) - this.internalEventBus.addEventHandler(this.integrityService, ExternalServices.SyncEvent.SyncRequestsIntegrityCheck) - this.internalEventBus.addEventHandler(this.syncService, ExternalServices.IntegrityEvent.IntegrityCheckCompleted) - this.internalEventBus.addEventHandler(this.userService, AccountEvent.SignedInOrRegistered) - this.internalEventBus.addEventHandler(this.sessionManager, ApiServiceEvent.SessionRefreshed) - } + this.events.addEventHandler(this.dependencies.get(TYPES.FeaturesService), ApiServiceEvent.MetaReceived) + this.events.addEventHandler(this.dependencies.get(TYPES.IntegrityService), SyncEvent.SyncRequestsIntegrityCheck) + this.events.addEventHandler(this.dependencies.get(TYPES.SyncService), IntegrityEvent.IntegrityCheckCompleted) + this.events.addEventHandler(this.dependencies.get(TYPES.UserService), AccountEvent.SignedInOrRegistered) + this.events.addEventHandler(this.dependencies.get(TYPES.SessionManager), ApiServiceEvent.SessionRefreshed) - private clearInternalEventBus(): void { - this.internalEventBus.deinit() - ;(this.internalEventBus as unknown) = undefined - } + this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedSharedVaultInvites) + this.events.addEventHandler(this.dependencies.get(TYPES.SharedVaultService), SyncEvent.ReceivedRemoteSharedVaults) - private createUserEventService(): void { - this.userEventService = new ExternalServices.UserEventService(this.internalEventBus) - this.services.push(this.userEventService) - } - - private createAsymmetricMessageService() { - this.asymmetricMessageService = new ExternalServices.AsymmetricMessageService( - this.httpService, - this.encryptionService, - this.contacts, - this.itemManager, - this.mutator, - this.syncService, - this.internalEventBus, + this.events.addEventHandler( + this.dependencies.get(TYPES.AsymmetricMessageService), + SyncEvent.ReceivedAsymmetricMessages, ) - this.services.push(this.asymmetricMessageService) - } + this.events.addEventHandler(this.dependencies.get(TYPES.AsymmetricMessageService), SessionEvent.UserKeyPairChanged) - private createContactService(): void { - this.contactService = new ExternalServices.ContactService( - this.syncService, - this.itemManager, - this.mutator, - this.sessionManager, - this.options.crypto, - this.user, - this.encryptionService, - this.singletonManager, - this.internalEventBus, - ) - - this.services.push(this.contactService) - } - - private createSharedVaultService(): void { - this.sharedVaultService = new ExternalServices.SharedVaultService( - this.httpService, - this.syncService, - this.itemManager, - this.mutator, - this.encryptionService, - this.sessions, - this.contactService, - this.files, - this.vaults, - this.storage, - this.internalEventBus, - ) - this.services.push(this.sharedVaultService) - } - - private createVaultService(): void { - this.vaultService = new ExternalServices.VaultService( - this.syncService, - this.itemManager, - this.mutator, - this.encryptionService, - this.files, - this.alertService, - this.internalEventBus, - ) - - this.services.push(this.vaultService) - } - - private createListedService(): void { - this.listedService = new InternalServices.ListedService( - this.apiService, - this.itemManager, - this.settingsService, - this.deprecatedHttpService, - this.protectionService, - this.mutator, - this.sync, - this.internalEventBus, - ) - this.services.push(this.listedService) - } - - private createFileService() { - this.fileService = new FileService( - this.apiService, - this.mutator, - this.syncService, - this.encryptionService, - this.challengeService, - this.httpService, - this.alertService, - this.options.crypto, - this.internalEventBus, - this.fileBackups, - ) - - this.services.push(this.fileService) - } - - private createIntegrityService() { - this.integrityService = new ExternalServices.IntegrityService( - this.apiService, - this.apiService, - this.payloadManager, - this.internalEventBus, - ) - - this.services.push(this.integrityService) - } - - private createFeaturesService() { - this.featuresService = new InternalServices.SNFeaturesService( - this.diskStorageService, - this.itemManager, - this.mutator, - this.subscriptions, - this.apiService, - this.webSocketsService, - this.settingsService, - this.userService, - this.syncService, - this.alertService, - this.sessionManager, - this.options.crypto, - this.internalEventBus, - ) - this.serviceObservers.push( - this.featuresService.addEventObserver((event) => { - switch (event) { - case ExternalServices.FeaturesEvent.UserRolesChanged: { - void this.notifyEvent(ApplicationEvent.UserRolesChanged) - break - } - case ExternalServices.FeaturesEvent.FeaturesAvailabilityChanged: { - void this.notifyEvent(ApplicationEvent.FeaturesAvailabilityChanged) - break - } - case ExternalServices.FeaturesEvent.DidPurchaseSubscription: { - void this.notifyEvent(ApplicationEvent.DidPurchaseSubscription) - break - } - default: { - Utils.assertUnreachable(event) - } - } - }), - ) - this.services.push(this.featuresService) - } - - private createWebSocketsService() { - this.webSocketsService = new InternalServices.SNWebSocketsService( - this.diskStorageService, - this.options.webSocketUrl, - this.webSocketApiService, - this.internalEventBus, - ) - this.services.push(this.webSocketsService) - } - - private createMigrationService() { - this.migrationService = new InternalServices.SNMigrationService({ - encryptionService: this.encryptionService, - deviceInterface: this.deviceInterface, - storageService: this.diskStorageService, - sessionManager: this.sessionManager, - challengeService: this.challengeService, - itemManager: this.itemManager, - mutator: this.mutator, - singletonManager: this.singletonManager, - featuresService: this.featuresService, - environment: this.environment, - platform: this.platform, - identifier: this.identifier, - internalEventBus: this.internalEventBus, - legacySessionStorageMapper: this.legacySessionStorageMapper, - backups: this.fileBackups, - preferences: this.preferencesService, - }) - this.services.push(this.migrationService) - } - - private createUserService(): void { - this.userService = new UserService( - this.sessionManager, - this.syncService, - this.diskStorageService, - this.itemManager, - this.encryptionService, - this.alertService, - this.challengeService, - this.protectionService, - this.userApiService, - this.internalEventBus, - ) - this.serviceObservers.push( - this.userService.addEventObserver(async (event, data) => { - switch (event) { - case AccountEvent.SignedInOrRegistered: { - void this.notifyEvent(ApplicationEvent.SignedIn) - break - } - case AccountEvent.SignedOut: { - await this.notifyEvent(ApplicationEvent.SignedOut) - await this.prepareForDeinit() - this.deinit(this.getDeinitMode(), data?.payload.source || DeinitSource.SignOut) - break - } - default: { - Utils.assertUnreachable(event) - } - } - }), - ) - this.services.push(this.userService) - } - - private createUserApiService() { - this.userApiService = new UserApiService(this.userServer, this.userRequestServer) - } - - private createUserServer() { - this.userServer = new UserServer(this.httpService) - } - - private createUserRequestServer() { - this.userRequestServer = new UserRequestServer(this.httpService) - } - - private createSubscriptionApiService() { - this.subscriptionApiService = new SubscriptionApiService(this.subscriptionServer) - } - - private createSubscriptionServer() { - this.subscriptionServer = new SubscriptionServer(this.httpService) - } - - private createWebSocketApiService() { - this.webSocketApiService = new WebSocketApiService(this.webSocketServer) - } - - private createWebSocketServer() { - this.webSocketServer = new WebSocketServer(this.httpService) - } - - private createSubscriptionManager() { - this.subscriptionManager = new SubscriptionManager( - this.subscriptionApiService, - this.sessions, - this.storage, - this.internalEventBus, - ) - this.services.push(this.subscriptionManager) - } - - private createItemManager() { - this.itemManager = new InternalServices.ItemManager(this.payloadManager, this.internalEventBus) - this.services.push(this.itemManager) - } - - private createComponentManager() { - this.componentManagerService = new InternalServices.SNComponentManager( - this.itemManager, - this.mutator, - this.syncService, - this.featuresService, - this.preferencesService, - this.alertService, - this.environment, - this.platform, - this.deviceInterface, - this.internalEventBus, - ) - this.services.push(this.componentManagerService) - } - - private createLegacyHttpManager() { - this.deprecatedHttpService = new InternalServices.DeprecatedHttpService( - this.environment, - this.options.appVersion, - this.internalEventBus, - ) - this.services.push(this.deprecatedHttpService) - } - - private createHttpServiceAndApiService() { - this.httpService = new HttpService(this.environment, this.options.appVersion, SnjsVersion) - - this.apiService = new InternalServices.SNApiService( - this.httpService, - this.diskStorageService, - this.options.defaultHost, - this.inMemoryStore, - this.options.crypto, - this.sessionStorageMapper, - this.legacySessionStorageMapper, - this.internalEventBus, - ) - this.services.push(this.apiService) - - this.httpService.setCallbacks( - this.apiService.processMetaObject.bind(this.apiService), - this.apiService.setSession.bind(this.apiService), - ) - } - - private createMappers() { - this.sessionStorageMapper = new SessionStorageMapper() - this.legacySessionStorageMapper = new LegacySessionStorageMapper() - } - - private createPayloadManager() { - this.payloadManager = new InternalServices.PayloadManager(this.internalEventBus) - this.services.push(this.payloadManager) - } - - private createSingletonManager() { - this.singletonManager = new InternalServices.SNSingletonManager( - this.itemManager, - this.mutator, - this.payloadManager, - this.syncService, - this.internalEventBus, - ) - this.services.push(this.singletonManager) - } - - private createDiskStorageManager() { - this.diskStorageService = new InternalServices.DiskStorageService( - this.deviceInterface, - this.identifier, - this.internalEventBus, - ) - this.services.push(this.diskStorageService) - } - - private createHomeServerService(device: ExternalServices.DesktopDeviceInterface) { - this.homeServerService = new ExternalServices.HomeServerService(device, this.internalEventBus) - - this.services.push(this.homeServerService) - } - - private createInMemoryStorageManager() { - this.inMemoryStore = new ExternalServices.InMemoryStore() - } - - private createProtocolService() { - this.encryptionService = new EncryptionService( - this.itemManager, - this.mutator, - this.payloadManager, - this.deviceInterface, - this.diskStorageService, - this.keySystemKeyManager, - this.identifier, - this.options.crypto, - this.internalEventBus, - ) - this.serviceObservers.push( - this.encryptionService.addEventObserver(async (event) => { - if (event === EncryptionServiceEvent.RootKeyStatusChanged) { - await this.notifyEvent(ApplicationEvent.KeyStatusChanged) - } - }), - ) - this.services.push(this.encryptionService) - } - - private createKeySystemKeyManager() { - this.keySystemKeyManager = new ExternalServices.KeySystemKeyManager( - this.itemManager, - this.mutator, - this.storage, - this.internalEventBus, - ) - - this.services.push(this.keySystemKeyManager) - } - - private createKeyRecoveryService() { - this.keyRecoveryService = new InternalServices.SNKeyRecoveryService( - this.itemManager, - this.payloadManager, - this.apiService, - this.encryptionService, - this.challengeService, - this.alertService, - this.diskStorageService, - this.syncService, - this.userService, - this.internalEventBus, - ) - this.services.push(this.keyRecoveryService) - } - - private createSessionManager() { - this.sessionManager = new InternalServices.SNSessionManager( - this.diskStorageService, - this.apiService, - this.userApiService, - this.alertService, - this.encryptionService, - this.challengeService, - this.webSocketsService, - this.httpService, - this.sessionStorageMapper, - this.legacySessionStorageMapper, - this.identifier, - this.internalEventBus, - ) - this.serviceObservers.push( - this.sessionManager.addEventObserver(async (event) => { - switch (event) { - case ExternalServices.SessionEvent.Restored: { - void (async () => { - await this.sync.sync({ sourceDescription: 'Session restored pre key creation' }) - if (this.encryptionService.needsNewRootKeyBasedItemsKey()) { - void this.encryptionService.createNewDefaultItemsKey().then(() => { - void this.sync.sync({ sourceDescription: 'Session restored post key creation' }) - }) - } - })() - break - } - case ExternalServices.SessionEvent.Revoked: { - await this.handleRevokedSession() - break - } - case ExternalServices.SessionEvent.UserKeyPairChanged: - break - default: { - Utils.assertUnreachable(event) - } - } - }), - ) - this.services.push(this.sessionManager) - } - - private createSyncManager() { - this.syncService = new InternalServices.SNSyncService( - this.itemManager, - this.sessionManager, - this.encryptionService, - this.diskStorageService, - this.payloadManager, - this.apiService, - this.historyManager, - this.deviceInterface, - this.identifier, - { - loadBatchSize: this.options.loadBatchSize, - sleepBetweenBatches: this.options.sleepBetweenBatches, - }, - this.internalEventBus, - ) - const syncEventCallback = async (eventName: ExternalServices.SyncEvent) => { - const appEvent = applicationEventForSyncEvent(eventName) - if (appEvent) { - await this.encryptionService.onSyncEvent(eventName) - - await this.notifyEvent(appEvent) - - if (appEvent === ApplicationEvent.CompletedFullSync) { - if (!this.handledFullSyncStage) { - this.handledFullSyncStage = true - await this.handleStage(ExternalServices.ApplicationStage.FullSyncCompleted_13) - } - } - } + if (this.dependencies.get(TYPES.FilesBackupService)) { + this.events.addEventHandler( + this.dependencies.get(TYPES.FilesBackupService), + ApplicationEvent.ApplicationStageChanged, + ) + } + if (this.dependencies.get(TYPES.HomeServerService)) { + this.events.addEventHandler( + this.dependencies.get(TYPES.HomeServerService), + ApplicationEvent.ApplicationStageChanged, + ) } - const uninstall = this.syncService.addEventObserver(syncEventCallback) - this.serviceObservers.push(uninstall) - this.services.push(this.syncService) - } - private createChallengeService() { - this.challengeService = new InternalServices.ChallengeService( - this.diskStorageService, - this.encryptionService, - this.internalEventBus, + this.events.addEventHandler( + this.dependencies.get(TYPES.SelfContactManager), + ApplicationEvent.ApplicationStageChanged, ) - this.services.push(this.challengeService) - } - - private createProtectionService() { - this.protectionService = new InternalServices.SNProtectionService( - this.encryptionService, - this.mutator, - this.challengeService, - this.diskStorageService, - this.internalEventBus, + this.events.addEventHandler( + this.dependencies.get(TYPES.KeySystemKeyManager), + ApplicationEvent.ApplicationStageChanged, ) - this.serviceObservers.push( - this.protectionService.addEventObserver((event) => { - if (event === InternalServices.ProtectionEvent.UnprotectedSessionBegan) { - void this.notifyEvent(ApplicationEvent.UnprotectedSessionBegan) - } else if (event === InternalServices.ProtectionEvent.UnprotectedSessionExpired) { - void this.notifyEvent(ApplicationEvent.UnprotectedSessionExpired) - } - }), + this.events.addEventHandler( + this.dependencies.get(TYPES.SubscriptionManager), + ApplicationEvent.ApplicationStageChanged, ) - this.services.push(this.protectionService) - } - - private createHistoryManager() { - this.historyManager = new InternalServices.SNHistoryManager( - this.itemManager, - this.diskStorageService, - this.deviceInterface, - this.internalEventBus, + this.events.addEventHandler(this.dependencies.get(TYPES.FeaturesService), ApplicationEvent.ApplicationStageChanged) + this.events.addEventHandler( + this.dependencies.get(TYPES.KeyRecoveryService), + ApplicationEvent.ApplicationStageChanged, ) - this.services.push(this.historyManager) - } - - private createActionsManager() { - this.actionsManager = new InternalServices.SNActionsService( - this.itemManager, - this.alertService, - this.deviceInterface, - this.deprecatedHttpService, - this.payloadManager, - this.encryptionService, - this.syncService, - this.challengeService, - this.listedService, - this.internalEventBus, + this.events.addEventHandler(this.dependencies.get(TYPES.MigrationService), ApplicationEvent.ApplicationStageChanged) + this.events.addEventHandler( + this.dependencies.get(TYPES.PreferencesService), + ApplicationEvent.ApplicationStageChanged, ) - this.services.push(this.actionsManager) - } - - private createPreferencesService() { - this.preferencesService = new InternalServices.SNPreferencesService( - this.singletonManager, - this.itemManager, - this.mutator, - this.syncService, - this.internalEventBus, + this.events.addEventHandler( + this.dependencies.get(TYPES.ProtectionService), + ApplicationEvent.ApplicationStageChanged, ) - this.serviceObservers.push( - this.preferencesService.addEventObserver(() => { - void this.notifyEvent(ApplicationEvent.PreferencesChanged) - }), - ) - this.services.push(this.preferencesService) - } - - private createSettingsService() { - this.settingsService = new InternalServices.SNSettingsService( - this.sessionManager, - this.apiService, - this.internalEventBus, - ) - this.services.push(this.settingsService) - } - - private createMfaService() { - this.mfaService = new InternalServices.SNMfaService( - this.settingsService, - this.options.crypto, - this.featuresService, - this.internalEventBus, - ) - this.services.push(this.mfaService) - } - - private createMutatorService() { - this.mutatorService = new InternalServices.MutatorService( - this.itemManager, - this.payloadManager, - this.alertService, - this.internalEventBus, - ) - this.services.push(this.mutatorService) - } - - private createFilesBackupService(device: ExternalServices.DesktopDeviceInterface): void { - this.filesBackupService = new FilesBackupService( - this.itemManager, - this.apiService, - this.encryptionService, - device as FileBackupsDevice, - this.statusService, - this.options.crypto, - this.storage, - this.sessions, - this.payloadManager, - this.historyManager, - device as DirectoryManagerInterface, - this.internalEventBus, - ) - this.services.push(this.filesBackupService) - } - - private createStatusService(): void { - this.statusService = new ExternalServices.StatusService(this.internalEventBus) - this.services.push(this.statusService) - } - - private createAuthenticatorManager() { - const authenticatorServer = new AuthenticatorServer(this.httpService) - - const authenticatorApiService = new AuthenticatorApiService(authenticatorServer) - - this.authenticatorManager = new AuthenticatorManager( - authenticatorApiService, - this.preferencesService, - this.internalEventBus, + this.events.addEventHandler( + this.dependencies.get(TYPES.DiskStorageService), + ApplicationEvent.ApplicationStageChanged, ) } - private createAuthManager() { - const authServer = new AuthServer(this.httpService) - - const authApiService = new AuthApiService(authServer) - - this.authManager = new AuthManager(authApiService, this.internalEventBus) + get device(): DeviceInterface { + return this.dependencies.get(TYPES.DeviceInterface) } - private createRevisionManager() { - const revisionServer = new RevisionServer(this.httpService) - - const revisionApiService = new RevisionApiService(revisionServer) - - this.revisionManager = new RevisionManager(revisionApiService, this.internalEventBus) + get subscriptions(): SubscriptionManagerInterface { + return this.dependencies.get(TYPES.SubscriptionManager) } - private createUseCases() { - this._signInWithRecoveryCodes = new SignInWithRecoveryCodes( - this.authManager, - this.encryptionService, - this.inMemoryStore, - this.options.crypto, - this.sessionManager, - this.internalEventBus, - ) + get signInWithRecoveryCodes(): SignInWithRecoveryCodes { + return this.dependencies.get(TYPES.SignInWithRecoveryCodes) + } - this._getRecoveryCodes = new GetRecoveryCodes(this.authManager, this.settingsService) + get getRecoveryCodes(): GetRecoveryCodes { + return this.dependencies.get(TYPES.GetRecoveryCodes) + } - this._addAuthenticator = new AddAuthenticator( - this.authenticatorManager, - this.options.u2fAuthenticatorRegistrationPromptFunction, - ) + get addAuthenticator(): AddAuthenticator { + return this.dependencies.get(TYPES.AddAuthenticator) + } - this._listAuthenticators = new ListAuthenticators(this.authenticatorManager) + get listAuthenticators(): ListAuthenticators { + return this.dependencies.get(TYPES.ListAuthenticators) + } - this._deleteAuthenticator = new DeleteAuthenticator(this.authenticatorManager) + get deleteAuthenticator(): DeleteAuthenticator { + return this.dependencies.get(TYPES.DeleteAuthenticator) + } - this._getAuthenticatorAuthenticationOptions = new GetAuthenticatorAuthenticationOptions(this.authenticatorManager) + get getAuthenticatorAuthenticationOptions(): GetAuthenticatorAuthenticationOptions { + return this.dependencies.get(TYPES.GetAuthenticatorAuthenticationOptions) + } - this._getAuthenticatorAuthenticationResponse = new GetAuthenticatorAuthenticationResponse( - this._getAuthenticatorAuthenticationOptions, - this.options.u2fAuthenticatorVerificationPromptFunction, - ) + get getAuthenticatorAuthenticationResponse(): GetAuthenticatorAuthenticationResponse { + return this.dependencies.get(TYPES.GetAuthenticatorAuthenticationResponse) + } - this._listRevisions = new ListRevisions(this.revisionManager) + get listRevisions(): ListRevisions { + return this.dependencies.get(TYPES.ListRevisions) + } - this._getRevision = new GetRevision(this.revisionManager, this.encryptionService) + get getRevision(): GetRevision { + return this.dependencies.get(TYPES.GetRevision) + } - this._deleteRevision = new DeleteRevision(this.revisionManager) + get deleteRevision(): DeleteRevision { + return this.dependencies.get(TYPES.DeleteRevision) + } + + public get files(): FilesClientInterface { + return this.dependencies.get(TYPES.FileService) + } + + public get features(): FeaturesClientInterface { + return this.dependencies.get(TYPES.FeaturesService) + } + + public get items(): ItemManagerInterface { + return this.dependencies.get(TYPES.ItemManager) + } + + public get payloads(): PayloadManagerInterface { + return this.dependencies.get(TYPES.PayloadManager) + } + + public get protections(): ProtectionsClientInterface { + return this.dependencies.get(TYPES.ProtectionService) + } + + public get sync(): SyncServiceInterface { + return this.dependencies.get(TYPES.SyncService) + } + + public get user(): UserClientInterface { + return this.dependencies.get(TYPES.UserService) + } + + public get settings(): SettingsService { + return this.dependencies.get(TYPES.SettingsService) + } + + public get mutator(): MutatorClientInterface { + return this.dependencies.get(TYPES.MutatorService) + } + + public get sessions(): SessionsClientInterface { + return this.dependencies.get(TYPES.SessionManager) + } + + public get status(): StatusServiceInterface { + return this.dependencies.get(TYPES.StatusService) + } + + public get fileBackups(): BackupServiceInterface | undefined { + return this.dependencies.get(TYPES.FilesBackupService) + } + + public get componentManager(): ComponentManagerInterface { + return this.dependencies.get(TYPES.ComponentManager) + } + + public get listed(): ListedClientInterface { + return this.dependencies.get(TYPES.ListedService) + } + + public get alerts(): AlertService { + return this.dependencies.get(TYPES.AlertService) + } + + public get storage(): StorageServiceInterface { + return this.dependencies.get(TYPES.DiskStorageService) + } + + public get actions(): ActionsService { + return this.dependencies.get(TYPES.ActionsService) + } + + public get challenges(): ChallengeServiceInterface { + return this.dependencies.get(TYPES.ChallengeService) + } + + public get asymmetric(): AsymmetricMessageServiceInterface { + return this.dependencies.get(TYPES.AsymmetricMessageService) + } + + get homeServer(): HomeServerServiceInterface | undefined { + return this.dependencies.get(TYPES.HomeServerService) + } + + public get vaults(): VaultServiceInterface { + return this.dependencies.get(TYPES.VaultService) + } + + public get contacts(): ContactServiceInterface { + return this.dependencies.get(TYPES.ContactService) + } + + public get sharedVaults(): SharedVaultServiceInterface { + return this.dependencies.get(TYPES.SharedVaultService) + } + + public get preferences(): PreferenceServiceInterface { + return this.dependencies.get(TYPES.PreferencesService) + } + + public get history(): HistoryServiceInterface { + return this.dependencies.get(TYPES.HistoryManager) + } + + private get migrations(): MigrationService { + return this.dependencies.get(TYPES.MigrationService) + } + + public get encryption(): EncryptionProviderInterface { + return this.dependencies.get(TYPES.EncryptionService) + } + + private get legacyApi(): LegacyApiService { + return this.dependencies.get(TYPES.LegacyApiService) + } + + private get http(): HttpServiceInterface { + return this.dependencies.get(TYPES.HttpService) + } + + private get sockets(): WebSocketsService { + return this.dependencies.get(TYPES.WebSocketsService) + } + + public get events(): InternalEventBusInterface { + return this.dependencies.get(TYPES.InternalEventBus) + } + + private get mfa(): SNMfaService { + return this.dependencies.get(TYPES.MfaService) } } diff --git a/packages/snjs/lib/Application/Dependencies/Dependencies.ts b/packages/snjs/lib/Application/Dependencies/Dependencies.ts new file mode 100644 index 000000000..c3d0a76b3 --- /dev/null +++ b/packages/snjs/lib/Application/Dependencies/Dependencies.ts @@ -0,0 +1,1211 @@ +import { ActionsService } from './../../Services/Actions/ActionsService' +import { DeleteRevision } from '../../Domain/UseCase/DeleteRevision/DeleteRevision' +import { GetRevision } from '../../Domain/UseCase/GetRevision/GetRevision' +import { ListRevisions } from '../../Domain/UseCase/ListRevisions/ListRevisions' +import { GetAuthenticatorAuthenticationResponse } from '../../Domain/UseCase/GetAuthenticatorAuthenticationResponse/GetAuthenticatorAuthenticationResponse' +import { GetAuthenticatorAuthenticationOptions } from '../../Domain/UseCase/GetAuthenticatorAuthenticationOptions/GetAuthenticatorAuthenticationOptions' +import { DeleteAuthenticator } from '../../Domain/UseCase/DeleteAuthenticator/DeleteAuthenticator' +import { ListAuthenticators } from '../../Domain/UseCase/ListAuthenticators/ListAuthenticators' +import { AddAuthenticator } from '../../Domain/UseCase/AddAuthenticator/AddAuthenticator' +import { GetRecoveryCodes } from '../../Domain/UseCase/GetRecoveryCodes/GetRecoveryCodes' +import { SignInWithRecoveryCodes } from '../../Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes' +import { ListedService } from '../../Services/Listed/ListedService' +import { MigrationService } from '../../Services/Migration/MigrationService' +import { SNMfaService } from '../../Services/Mfa/MfaService' +import { SNComponentManager } from '../../Services/ComponentManager/ComponentManager' +import { FeaturesService } from '@Lib/Services/Features/FeaturesService' +import { SettingsService } from '../../Services/Settings/SNSettingsService' +import { SNPreferencesService } from '../../Services/Preferences/PreferencesService' +import { SingletonManager } from '../../Services/Singleton/SingletonManager' +import { KeyRecoveryService } from '../../Services/KeyRecovery/KeyRecoveryService' +import { SNProtectionService } from '../../Services/Protection/ProtectionService' +import { SyncService } from '../../Services/Sync/SyncService' +import { HistoryManager } from '../../Services/History/HistoryManager' +import { SessionManager } from '../../Services/Session/SessionManager' +import { WebSocketsService } from '../../Services/Api/WebsocketsService' +import { LegacyApiService } from '../../Services/Api/ApiService' +import { SnjsVersion } from '../../Version' +import { DeprecatedHttpService } from '../../Services/Api/DeprecatedHttpService' +import { ChallengeService } from '../../Services/Challenge/ChallengeService' +import { DiskStorageService } from '../../Services/Storage/DiskStorageService' +import { MutatorService } from '../../Services/Mutator/MutatorService' +import { + AuthManager, + AuthenticatorManager, + ContactService, + CreateOrEditContact, + EditContact, + EncryptionService, + FileService, + FilesBackupService, + FindContact, + GetAllContacts, + GetVault, + HomeServerService, + ImportDataUseCase, + InMemoryStore, + IntegrityService, + InternalEventBus, + KeySystemKeyManager, + RemoveItemsLocally, + RevisionManager, + SelfContactManager, + StatusService, + SubscriptionManager, + UserEventService, + UserService, + ValidateItemSigner, + isDesktopDevice, + ChangeVaultKeyOptions, + MoveItemsToVault, + CreateVault, + RemoveItemFromVault, + DeleteVault, + RotateVaultKey, + VaultService, + SharedVaultService, + CreateSharedVault, + HandleKeyPairChange, + ReuploadAllInvites, + ReuploadInvite, + ResendAllMessages, + NotifyVaultUsersOfKeyRotation, + SendVaultDataChangedMessage, + GetTrustedPayload, + GetUntrustedPayload, + GetVaultContacts, + AcceptVaultInvite, + InviteToVault, + LeaveVault, + DeleteThirdPartyVault, + ShareContactWithVault, + ConvertToSharedVault, + DeleteSharedVault, + RemoveVaultMember, + GetVaultUsers, + AsymmetricMessageService, + ReplaceContactData, + DecryptOwnMessage, + SendVaultInvite, + EncryptMessage, + DecryptMessage, + ResendMessage, + SendMessage, + ProcessAcceptedVaultInvite, + HandleRootKeyChangedMessage, + SendOwnContactChangeMessage, + GetOutboundMessages, + GetInboundMessages, + SendVaultKeyChangedMessage, + CreateNewDefaultItemsKey, + CreateNewItemsKeyWithRollback, + FindDefaultItemsKey, + DecryptErroredTypeAPayloads, + DecryptTypeAPayload, + DecryptTypeAPayloadWithKeyLookup, + EncryptTypeAPayload, + EncryptTypeAPayloadWithKeyLookup, + RootKeyManager, + ItemsEncryptionService, + DecryptBackupFile, +} from '@standardnotes/services' +import { ItemManager } from '../../Services/Items/ItemManager' +import { PayloadManager } from '../../Services/Payloads/PayloadManager' +import { LegacySessionStorageMapper } from '@Lib/Services/Mapping/LegacySessionStorageMapper' +import { SessionStorageMapper } from '@Lib/Services/Mapping/SessionStorageMapper' +import { + AsymmetricMessageServer, + AuthApiService, + AuthServer, + AuthenticatorApiService, + AuthenticatorServer, + HttpService, + RevisionApiService, + RevisionServer, + SharedVaultInvitesServer, + SharedVaultServer, + SharedVaultUsersServer, + SubscriptionApiService, + SubscriptionServer, + UserApiService, + UserServer, + WebSocketApiService, + WebSocketServer, +} from '@standardnotes/api' +import { FullyResolvedApplicationOptions } from '../Options/ApplicationOptions' +import { TYPES } from './Types' +import { isDeinitable } from './isDeinitable' +import { isNotUndefined } from '@standardnotes/utils' +import { EncryptionOperators } from '@standardnotes/encryption' + +export class Dependencies { + private factory = new Map unknown>() + private dependencies = new Map() + + constructor(private options: FullyResolvedApplicationOptions) { + this.dependencies.set(TYPES.DeviceInterface, options.deviceInterface) + this.dependencies.set(TYPES.AlertService, options.alertService) + this.dependencies.set(TYPES.Crypto, options.crypto) + + this.registerServiceMakers() + this.registerUseCaseMakers() + } + + public deinit() { + this.factory.clear() + + const deps = this.getAll() + for (const dep of deps) { + if (isDeinitable(dep)) { + dep.deinit() + } + } + + this.dependencies.clear() + } + + public getAll(): unknown[] { + return Array.from(this.dependencies.values()).filter(isNotUndefined) + } + + public get(sym: symbol): T { + const dep = this.dependencies.get(sym) + if (dep) { + return dep as T + } + + const maker = this.factory.get(sym) + if (!maker) { + throw new Error(`No dependency maker found for ${sym.toString()}`) + } + + const instance = maker() + if (!instance) { + /** Could be optional */ + return undefined as T + } + + this.dependencies.set(sym, instance) + + return instance as T + } + + private registerUseCaseMakers() { + this.factory.set(TYPES.ImportDataUseCase, () => { + return new ImportDataUseCase( + this.get(TYPES.ItemManager), + this.get(TYPES.SyncService), + this.get(TYPES.ProtectionService), + this.get(TYPES.EncryptionService), + this.get(TYPES.PayloadManager), + this.get(TYPES.ChallengeService), + this.get(TYPES.HistoryManager), + this.get(TYPES.DecryptBackupFile), + ) + }) + + this.factory.set(TYPES.DecryptBackupFile, () => { + return new DecryptBackupFile(this.get(TYPES.EncryptionService)) + }) + + this.factory.set(TYPES.RemoveItemsLocally, () => { + return new RemoveItemsLocally(this.get(TYPES.ItemManager), this.get(TYPES.DiskStorageService)) + }) + + this.factory.set(TYPES.FindContact, () => { + return new FindContact(this.get(TYPES.ItemManager)) + }) + + this.factory.set(TYPES.EditContact, () => { + return new EditContact(this.get(TYPES.MutatorService), this.get(TYPES.SyncService)) + }) + + this.factory.set(TYPES.GetAllContacts, () => { + return new GetAllContacts(this.get(TYPES.ItemManager)) + }) + + this.factory.set(TYPES.ValidateItemSigner, () => { + return new ValidateItemSigner(this.get(TYPES.FindContact)) + }) + + this.factory.set(TYPES.CreateOrEditContact, () => { + return new CreateOrEditContact( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.FindContact), + this.get(TYPES.EditContact), + ) + }) + + this.factory.set(TYPES.GetVault, () => { + return new GetVault(this.get(TYPES.ItemManager)) + }) + + this.factory.set(TYPES.ChangeVaultKeyOptions, () => { + return new ChangeVaultKeyOptions( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.EncryptionService), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.GetVault), + ) + }) + + this.factory.set(TYPES.MoveItemsToVault, () => { + return new MoveItemsToVault( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.FileService), + ) + }) + + this.factory.set(TYPES.CreateVault, () => { + return new CreateVault( + this.get(TYPES.MutatorService), + this.get(TYPES.EncryptionService), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.SyncService), + ) + }) + + this.factory.set(TYPES.RemoveItemFromVault, () => { + return new RemoveItemFromVault( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.FileService), + ) + }) + + this.factory.set(TYPES.DeleteVault, () => { + return new DeleteVault( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.KeySystemKeyManager), + ) + }) + + this.factory.set(TYPES.RotateVaultKey, () => { + return new RotateVaultKey( + this.get(TYPES.MutatorService), + this.get(TYPES.EncryptionService), + this.get(TYPES.KeySystemKeyManager), + ) + }) + + this.factory.set(TYPES.ReuploadInvite, () => { + return new ReuploadInvite( + this.get(TYPES.DecryptOwnMessage), + this.get(TYPES.SendVaultInvite), + this.get(TYPES.EncryptMessage), + ) + }) + + this.factory.set(TYPES.ReuploadAllInvites, () => { + return new ReuploadAllInvites( + this.get(TYPES.ReuploadInvite), + this.get(TYPES.FindContact), + this.get(TYPES.SharedVaultInvitesServer), + ) + }) + + this.factory.set(TYPES.ResendAllMessages, () => { + return new ResendAllMessages( + this.get(TYPES.ResendMessage), + this.get(TYPES.AsymmetricMessageServer), + this.get(TYPES.FindContact), + ) + }) + + this.factory.set(TYPES.CreateSharedVault, () => { + return new CreateSharedVault( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.SharedVaultServer), + this.get(TYPES.CreateVault), + this.get(TYPES.MoveItemsToVault), + ) + }) + + this.factory.set(TYPES.HandleKeyPairChange, () => { + return new HandleKeyPairChange(this.get(TYPES.ReuploadAllInvites), this.get(TYPES.ResendAllMessages)) + }) + + this.factory.set(TYPES.NotifyVaultUsersOfKeyRotation, () => { + return new NotifyVaultUsersOfKeyRotation( + this.get(TYPES.FindContact), + this.get(TYPES.SendVaultKeyChangedMessage), + this.get(TYPES.InviteToVault), + this.get(TYPES.SharedVaultInvitesServer), + this.get(TYPES.GetVaultContacts), + this.get(TYPES.DecryptOwnMessage), + ) + }) + + this.factory.set(TYPES.SendVaultKeyChangedMessage, () => { + return new SendVaultKeyChangedMessage( + this.get(TYPES.EncryptMessage), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.FindContact), + this.get(TYPES.SendMessage), + this.get(TYPES.GetVaultUsers), + ) + }) + + this.factory.set(TYPES.SendVaultDataChangedMessage, () => { + return new SendVaultDataChangedMessage( + this.get(TYPES.EncryptMessage), + this.get(TYPES.FindContact), + this.get(TYPES.GetVaultUsers), + this.get(TYPES.SendMessage), + ) + }) + + this.factory.set(TYPES.ReplaceContactData, () => { + return new ReplaceContactData( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.FindContact), + ) + }) + + this.factory.set(TYPES.GetTrustedPayload, () => { + return new GetTrustedPayload(this.get(TYPES.DecryptMessage)) + }) + + this.factory.set(TYPES.GetUntrustedPayload, () => { + return new GetUntrustedPayload(this.get(TYPES.DecryptMessage)) + }) + + this.factory.set(TYPES.GetVaultContacts, () => { + return new GetVaultContacts(this.get(TYPES.FindContact), this.get(TYPES.GetVaultUsers)) + }) + + this.factory.set(TYPES.AcceptVaultInvite, () => { + return new AcceptVaultInvite(this.get(TYPES.SharedVaultInvitesServer), this.get(TYPES.ProcessAcceptedVaultInvite)) + }) + + this.factory.set(TYPES.InviteToVault, () => { + return new InviteToVault( + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.EncryptMessage), + this.get(TYPES.SendVaultInvite), + this.get(TYPES.ShareContactWithVault), + ) + }) + + this.factory.set(TYPES.SendVaultInvite, () => { + return new SendVaultInvite(this.get(TYPES.SharedVaultInvitesServer)) + }) + + this.factory.set(TYPES.DeleteThirdPartyVault, () => { + return new DeleteThirdPartyVault( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.SyncService), + this.get(TYPES.RemoveItemsLocally), + ) + }) + + this.factory.set(TYPES.LeaveVault, () => { + return new LeaveVault( + this.get(TYPES.SharedVaultUsersServer), + this.get(TYPES.ItemManager), + this.get(TYPES.DeleteThirdPartyVault), + ) + }) + + this.factory.set(TYPES.ShareContactWithVault, () => { + return new ShareContactWithVault( + this.get(TYPES.FindContact), + this.get(TYPES.EncryptMessage), + this.get(TYPES.SendMessage), + this.get(TYPES.GetVaultUsers), + ) + }) + + this.factory.set(TYPES.ConvertToSharedVault, () => { + return new ConvertToSharedVault( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.SharedVaultServer), + this.get(TYPES.MoveItemsToVault), + ) + }) + + this.factory.set(TYPES.DeleteSharedVault, () => { + return new DeleteSharedVault( + this.get(TYPES.SharedVaultServer), + this.get(TYPES.SyncService), + this.get(TYPES.DeleteVault), + ) + }) + + this.factory.set(TYPES.RemoveVaultMember, () => { + return new RemoveVaultMember(this.get(TYPES.SharedVaultUsersServer)) + }) + + this.factory.set(TYPES.GetVaultUsers, () => { + return new GetVaultUsers(this.get(TYPES.SharedVaultUsersServer)) + }) + + this.factory.set(TYPES.DecryptOwnMessage, () => { + return new DecryptOwnMessage(this.get(TYPES.EncryptionOperators)) + }) + + this.factory.set(TYPES.EncryptMessage, () => { + return new EncryptMessage(this.get(TYPES.EncryptionOperators)) + }) + + this.factory.set(TYPES.DecryptMessage, () => { + return new DecryptMessage(this.get(TYPES.EncryptionOperators)) + }) + + this.factory.set(TYPES.ResendMessage, () => { + return new ResendMessage( + this.get(TYPES.DecryptOwnMessage), + this.get(TYPES.SendMessage), + this.get(TYPES.EncryptMessage), + ) + }) + + this.factory.set(TYPES.SendMessage, () => { + return new SendMessage(this.get(TYPES.AsymmetricMessageServer)) + }) + + this.factory.set(TYPES.ProcessAcceptedVaultInvite, () => { + return new ProcessAcceptedVaultInvite( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.CreateOrEditContact), + ) + }) + + this.factory.set(TYPES.HandleRootKeyChangedMessage, () => { + return new HandleRootKeyChangedMessage( + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.EncryptionService), + this.get(TYPES.GetVault), + ) + }) + + this.factory.set(TYPES.SendOwnContactChangeMessage, () => { + return new SendOwnContactChangeMessage(this.get(TYPES.EncryptMessage), this.get(TYPES.SendMessage)) + }) + + this.factory.set(TYPES.GetOutboundMessages, () => { + return new GetOutboundMessages(this.get(TYPES.AsymmetricMessageServer)) + }) + + this.factory.set(TYPES.GetInboundMessages, () => { + return new GetInboundMessages(this.get(TYPES.AsymmetricMessageServer)) + }) + + this.factory.set(TYPES.CreateNewDefaultItemsKey, () => { + return new CreateNewDefaultItemsKey( + this.get(TYPES.MutatorService), + this.get(TYPES.ItemManager), + this.get(TYPES.EncryptionOperators), + this.get(TYPES.RootKeyManager), + ) + }) + + this.factory.set(TYPES.CreateNewItemsKeyWithRollback, () => { + return new CreateNewItemsKeyWithRollback( + this.get(TYPES.MutatorService), + this.get(TYPES.ItemManager), + this.get(TYPES.CreateNewDefaultItemsKey), + this.get(TYPES.RemoveItemsLocally), + this.get(TYPES.FindDefaultItemsKey), + ) + }) + + this.factory.set(TYPES.FindDefaultItemsKey, () => { + return new FindDefaultItemsKey() + }) + + this.factory.set(TYPES.DecryptErroredTypeAPayloads, () => { + return new DecryptErroredTypeAPayloads( + this.get(TYPES.PayloadManager), + this.get(TYPES.EncryptionOperators), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.RootKeyManager), + ) + }) + + this.factory.set(TYPES.DecryptTypeAPayload, () => { + return new DecryptTypeAPayload(this.get(TYPES.EncryptionOperators)) + }) + + this.factory.set(TYPES.DecryptTypeAPayloadWithKeyLookup, () => { + return new DecryptTypeAPayloadWithKeyLookup( + this.get(TYPES.EncryptionOperators), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.RootKeyManager), + ) + }) + + this.factory.set(TYPES.EncryptTypeAPayload, () => { + return new EncryptTypeAPayload(this.get(TYPES.EncryptionOperators)) + }) + + this.factory.set(TYPES.EncryptTypeAPayloadWithKeyLookup, () => { + return new EncryptTypeAPayloadWithKeyLookup( + this.get(TYPES.EncryptionOperators), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.RootKeyManager), + ) + }) + } + + private registerServiceMakers() { + this.factory.set(TYPES.UserServer, () => { + return new UserServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.RootKeyManager, () => { + return new RootKeyManager( + this.get(TYPES.DeviceInterface), + this.get(TYPES.DiskStorageService), + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.EncryptionOperators), + this.options.identifier, + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.ItemsEncryptionService, () => { + return new ItemsEncryptionService( + this.get(TYPES.ItemManager), + this.get(TYPES.PayloadManager), + this.get(TYPES.DiskStorageService), + this.get(TYPES.EncryptionOperators), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.FindDefaultItemsKey), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.EncryptionOperators, () => { + return new EncryptionOperators(this.get(TYPES.Crypto)) + }) + + this.factory.set(TYPES.SharedVaultInvitesServer, () => { + return new SharedVaultInvitesServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.SharedVaultServer, () => { + return new SharedVaultServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.AsymmetricMessageServer, () => { + return new AsymmetricMessageServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.SharedVaultUsersServer, () => { + return new SharedVaultUsersServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.AsymmetricMessageService, () => { + return new AsymmetricMessageService( + this.get(TYPES.AsymmetricMessageServer), + this.get(TYPES.EncryptionService), + this.get(TYPES.MutatorService), + this.get(TYPES.CreateOrEditContact), + this.get(TYPES.FindContact), + this.get(TYPES.GetAllContacts), + this.get(TYPES.ReplaceContactData), + this.get(TYPES.GetTrustedPayload), + this.get(TYPES.GetVault), + this.get(TYPES.HandleRootKeyChangedMessage), + this.get(TYPES.SendOwnContactChangeMessage), + this.get(TYPES.GetOutboundMessages), + this.get(TYPES.GetInboundMessages), + this.get(TYPES.GetUntrustedPayload), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SharedVaultService, () => { + return new SharedVaultService( + this.get(TYPES.SyncService), + this.get(TYPES.ItemManager), + this.get(TYPES.EncryptionService), + this.get(TYPES.SessionManager), + this.get(TYPES.VaultService), + this.get(TYPES.SharedVaultInvitesServer), + this.get(TYPES.GetVault), + this.get(TYPES.CreateSharedVault), + this.get(TYPES.HandleKeyPairChange), + this.get(TYPES.NotifyVaultUsersOfKeyRotation), + this.get(TYPES.SendVaultDataChangedMessage), + this.get(TYPES.GetTrustedPayload), + this.get(TYPES.GetUntrustedPayload), + this.get(TYPES.FindContact), + this.get(TYPES.GetAllContacts), + this.get(TYPES.GetVaultContacts), + this.get(TYPES.AcceptVaultInvite), + this.get(TYPES.InviteToVault), + this.get(TYPES.LeaveVault), + this.get(TYPES.DeleteThirdPartyVault), + this.get(TYPES.ShareContactWithVault), + this.get(TYPES.ConvertToSharedVault), + this.get(TYPES.DeleteSharedVault), + this.get(TYPES.RemoveVaultMember), + this.get(TYPES.GetVaultUsers), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.VaultService, () => { + return new VaultService( + this.get(TYPES.SyncService), + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.EncryptionService), + this.get(TYPES.KeySystemKeyManager), + this.get(TYPES.AlertService), + this.get(TYPES.GetVault), + this.get(TYPES.ChangeVaultKeyOptions), + this.get(TYPES.MoveItemsToVault), + this.get(TYPES.CreateVault), + this.get(TYPES.RemoveItemFromVault), + this.get(TYPES.DeleteVault), + this.get(TYPES.RotateVaultKey), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SelfContactManager, () => { + return new SelfContactManager( + this.get(TYPES.SyncService), + this.get(TYPES.ItemManager), + this.get(TYPES.SessionManager), + this.get(TYPES.SingletonManager), + this.get(TYPES.CreateOrEditContact), + ) + }) + + this.factory.set(TYPES.ContactService, () => { + return new ContactService( + this.get(TYPES.SyncService), + this.get(TYPES.MutatorService), + this.get(TYPES.SessionManager), + this.get(TYPES.Crypto), + this.get(TYPES.UserService), + this.get(TYPES.SelfContactManager), + this.get(TYPES.EncryptionService), + this.get(TYPES.FindContact), + this.get(TYPES.GetAllContacts), + this.get(TYPES.CreateOrEditContact), + this.get(TYPES.EditContact), + this.get(TYPES.ValidateItemSigner), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SignInWithRecoveryCodes, () => { + return new SignInWithRecoveryCodes( + this.get(TYPES.AuthManager), + this.get(TYPES.EncryptionService), + this.get(TYPES.InMemoryStore), + this.get(TYPES.Crypto), + this.get(TYPES.SessionManager), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.GetRecoveryCodes, () => { + return new GetRecoveryCodes(this.get(TYPES.AuthManager), this.get(TYPES.SettingsService)) + }) + + this.factory.set(TYPES.AddAuthenticator, () => { + return new AddAuthenticator( + this.get(TYPES.AuthenticatorManager), + this.options.u2fAuthenticatorRegistrationPromptFunction, + ) + }) + + this.factory.set(TYPES.ListAuthenticators, () => { + return new ListAuthenticators(this.get(TYPES.AuthenticatorManager)) + }) + + this.factory.set(TYPES.DeleteAuthenticator, () => { + return new DeleteAuthenticator(this.get(TYPES.AuthenticatorManager)) + }) + + this.factory.set(TYPES.GetAuthenticatorAuthenticationOptions, () => { + return new GetAuthenticatorAuthenticationOptions(this.get(TYPES.AuthenticatorManager)) + }) + + this.factory.set(TYPES.GetAuthenticatorAuthenticationResponse, () => { + return new GetAuthenticatorAuthenticationResponse( + this.get(TYPES.GetAuthenticatorAuthenticationOptions), + this.options.u2fAuthenticatorVerificationPromptFunction, + ) + }) + + this.factory.set(TYPES.ListRevisions, () => { + return new ListRevisions(this.get(TYPES.RevisionManager)) + }) + + this.factory.set(TYPES.GetRevision, () => { + return new GetRevision(this.get(TYPES.RevisionManager), this.get(TYPES.EncryptionService)) + }) + + this.factory.set(TYPES.DeleteRevision, () => { + return new DeleteRevision(this.get(TYPES.RevisionManager)) + }) + + this.factory.set(TYPES.RevisionServer, () => { + return new RevisionServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.RevisionApiService, () => { + return new RevisionApiService(this.get(TYPES.RevisionServer)) + }) + + this.factory.set(TYPES.RevisionManager, () => { + return new RevisionManager(this.get(TYPES.RevisionApiService), this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.AuthServer, () => { + return new AuthServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.AuthApiService, () => { + return new AuthApiService(this.get(TYPES.AuthServer)) + }) + + this.factory.set(TYPES.AuthManager, () => { + return new AuthManager(this.get(TYPES.AuthApiService), this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.AuthenticatorServer, () => { + return new AuthenticatorServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.AuthenticatorApiService, () => { + return new AuthenticatorApiService(this.get(TYPES.AuthenticatorServer)) + }) + + this.factory.set(TYPES.AuthenticatorManager, () => { + return new AuthenticatorManager( + this.get(TYPES.AuthenticatorApiService), + this.get(TYPES.PreferencesService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.ActionsService, () => { + return new ActionsService( + this.get(TYPES.ItemManager), + this.get(TYPES.AlertService), + this.get(TYPES.DeviceInterface), + this.get(TYPES.DeprecatedHttpService), + this.get(TYPES.EncryptionService), + this.get(TYPES.ChallengeService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.ListedService, () => { + return new ListedService( + this.get(TYPES.LegacyApiService), + this.get(TYPES.ItemManager), + this.get(TYPES.SettingsService), + this.get(TYPES.DeprecatedHttpService), + this.get(TYPES.ProtectionService), + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.IntegrityService, () => { + return new IntegrityService( + this.get(TYPES.LegacyApiService), + this.get(TYPES.LegacyApiService), + this.get(TYPES.PayloadManager), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.FileService, () => { + return new FileService( + this.get(TYPES.LegacyApiService), + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.EncryptionService), + this.get(TYPES.ChallengeService), + this.get(TYPES.HttpService), + this.get(TYPES.AlertService), + this.get(TYPES.Crypto), + this.get(TYPES.InternalEventBus), + this.get(TYPES.FilesBackupService), + ) + }) + + this.factory.set(TYPES.MigrationService, () => { + return new MigrationService({ + encryptionService: this.get(TYPES.EncryptionService), + deviceInterface: this.get(TYPES.DeviceInterface), + storageService: this.get(TYPES.DiskStorageService), + sessionManager: this.get(TYPES.SessionManager), + challengeService: this.get(TYPES.ChallengeService), + itemManager: this.get(TYPES.ItemManager), + mutator: this.get(TYPES.MutatorService), + singletonManager: this.get(TYPES.SingletonManager), + featuresService: this.get(TYPES.FeaturesService), + environment: this.options.environment, + platform: this.options.platform, + identifier: this.options.identifier, + internalEventBus: this.get(TYPES.InternalEventBus), + legacySessionStorageMapper: this.get(TYPES.LegacySessionStorageMapper), + backups: this.get(TYPES.FilesBackupService), + preferences: this.get(TYPES.PreferencesService), + }) + }) + + this.factory.set(TYPES.HomeServerService, () => { + if (!isDesktopDevice(this.get(TYPES.DeviceInterface))) { + return undefined + } + + return new HomeServerService(this.get(TYPES.DeviceInterface), this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.FilesBackupService, () => { + if (!isDesktopDevice(this.get(TYPES.DeviceInterface))) { + return undefined + } + + return new FilesBackupService( + this.get(TYPES.ItemManager), + this.get(TYPES.LegacyApiService), + this.get(TYPES.EncryptionService), + this.get(TYPES.DeviceInterface), + this.get(TYPES.StatusService), + this.get(TYPES.Crypto), + this.get(TYPES.DiskStorageService), + this.get(TYPES.SessionManager), + this.get(TYPES.PayloadManager), + this.get(TYPES.HistoryManager), + this.get(TYPES.DeviceInterface), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.StatusService, () => { + return new StatusService(this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.MfaService, () => { + return new SNMfaService( + this.get(TYPES.SettingsService), + this.get(TYPES.Crypto), + this.get(TYPES.FeaturesService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.ComponentManager, () => { + return new SNComponentManager( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.FeaturesService), + this.get(TYPES.PreferencesService), + this.get(TYPES.AlertService), + this.options.environment, + this.options.platform, + this.get(TYPES.DeviceInterface), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.FeaturesService, () => { + return new FeaturesService( + this.get(TYPES.DiskStorageService), + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.SubscriptionManager), + this.get(TYPES.LegacyApiService), + this.get(TYPES.WebSocketsService), + this.get(TYPES.SettingsService), + this.get(TYPES.UserService), + this.get(TYPES.SyncService), + this.get(TYPES.AlertService), + this.get(TYPES.SessionManager), + this.get(TYPES.Crypto), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SettingsService, () => { + return new SettingsService( + this.get(TYPES.SessionManager), + this.get(TYPES.LegacyApiService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.PreferencesService, () => { + return new SNPreferencesService( + this.get(TYPES.SingletonManager), + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.SyncService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SingletonManager, () => { + return new SingletonManager( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.PayloadManager), + this.get(TYPES.SyncService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.KeyRecoveryService, () => { + return new KeyRecoveryService( + this.get(TYPES.ItemManager), + this.get(TYPES.PayloadManager), + this.get(TYPES.LegacyApiService), + this.get(TYPES.EncryptionService), + this.get(TYPES.ChallengeService), + this.get(TYPES.AlertService), + this.get(TYPES.DiskStorageService), + this.get(TYPES.SyncService), + this.get(TYPES.UserService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.UserService, () => { + return new UserService( + this.get(TYPES.SessionManager), + this.get(TYPES.SyncService), + this.get(TYPES.DiskStorageService), + this.get(TYPES.ItemManager), + this.get(TYPES.EncryptionService), + this.get(TYPES.AlertService), + this.get(TYPES.ChallengeService), + this.get(TYPES.ProtectionService), + this.get(TYPES.UserApiService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.ProtectionService, () => { + return new SNProtectionService( + this.get(TYPES.EncryptionService), + this.get(TYPES.MutatorService), + this.get(TYPES.ChallengeService), + this.get(TYPES.DiskStorageService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SyncService, () => { + return new SyncService( + this.get(TYPES.ItemManager), + this.get(TYPES.SessionManager), + this.get(TYPES.EncryptionService), + this.get(TYPES.DiskStorageService), + this.get(TYPES.PayloadManager), + this.get(TYPES.LegacyApiService), + this.get(TYPES.HistoryManager), + this.get(TYPES.DeviceInterface), + this.options.identifier, + { + loadBatchSize: this.options.loadBatchSize, + sleepBetweenBatches: this.options.sleepBetweenBatches, + }, + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.HistoryManager, () => { + return new HistoryManager( + this.get(TYPES.ItemManager), + this.get(TYPES.DiskStorageService), + this.get(TYPES.DeviceInterface), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SubscriptionManager, () => { + return new SubscriptionManager( + this.get(TYPES.SubscriptionApiService), + this.get(TYPES.SessionManager), + this.get(TYPES.DiskStorageService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SessionManager, () => { + return new SessionManager( + this.get(TYPES.DiskStorageService), + this.get(TYPES.LegacyApiService), + this.get(TYPES.UserApiService), + this.get(TYPES.AlertService), + this.get(TYPES.EncryptionService), + this.get(TYPES.Crypto), + this.get(TYPES.ChallengeService), + this.get(TYPES.WebSocketsService), + this.get(TYPES.HttpService), + this.get(TYPES.SessionStorageMapper), + this.get(TYPES.LegacySessionStorageMapper), + this.options.identifier, + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.WebSocketsService, () => { + return new WebSocketsService( + this.get(TYPES.DiskStorageService), + this.options.webSocketUrl, + this.get(TYPES.WebSocketApiService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.WebSocketApiService, () => { + return new WebSocketApiService(this.get(TYPES.WebSocketServer)) + }) + + this.factory.set(TYPES.WebSocketServer, () => { + return new WebSocketServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.SubscriptionApiService, () => { + return new SubscriptionApiService(this.get(TYPES.SubscriptionServer)) + }) + + this.factory.set(TYPES.UserApiService, () => { + return new UserApiService(this.get(TYPES.UserServer), this.get(TYPES.UserRequestServer)) + }) + + this.factory.set(TYPES.SubscriptionServer, () => { + return new SubscriptionServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.UserRequestServer, () => { + return new UserServer(this.get(TYPES.HttpService)) + }) + + this.factory.set(TYPES.InternalEventBus, () => { + return new InternalEventBus() + }) + + this.factory.set(TYPES.PayloadManager, () => { + return new PayloadManager(this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.ItemManager, () => { + return new ItemManager(this.get(TYPES.PayloadManager), this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.MutatorService, () => { + return new MutatorService( + this.get(TYPES.ItemManager), + this.get(TYPES.PayloadManager), + this.get(TYPES.AlertService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.DiskStorageService, () => { + return new DiskStorageService( + this.get(TYPES.DeviceInterface), + this.options.identifier, + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.UserEventService, () => { + return new UserEventService(this.get(TYPES.InternalEventBus)) + }) + + this.factory.set(TYPES.InMemoryStore, () => { + return new InMemoryStore() + }) + + this.factory.set(TYPES.KeySystemKeyManager, () => { + return new KeySystemKeyManager( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.DiskStorageService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.ChallengeService, () => { + return new ChallengeService( + this.get(TYPES.DiskStorageService), + this.get(TYPES.EncryptionService), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.EncryptionService, () => { + return new EncryptionService( + this.get(TYPES.ItemManager), + this.get(TYPES.MutatorService), + this.get(TYPES.PayloadManager), + this.get(TYPES.EncryptionOperators), + this.get(TYPES.ItemsEncryptionService), + this.get(TYPES.RootKeyManager), + this.get(TYPES.Crypto), + this.get(TYPES.CreateNewItemsKeyWithRollback), + this.get(TYPES.FindDefaultItemsKey), + this.get(TYPES.DecryptErroredTypeAPayloads), + this.get(TYPES.EncryptTypeAPayloadWithKeyLookup), + this.get(TYPES.EncryptTypeAPayload), + this.get(TYPES.DecryptTypeAPayload), + this.get(TYPES.DecryptTypeAPayloadWithKeyLookup), + this.get(TYPES.CreateNewDefaultItemsKey), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.DeprecatedHttpService, () => { + return new DeprecatedHttpService( + this.options.environment, + this.options.appVersion, + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.HttpService, () => { + return new HttpService(this.options.environment, this.options.appVersion, SnjsVersion) + }) + + this.factory.set(TYPES.LegacyApiService, () => { + return new LegacyApiService( + this.get(TYPES.HttpService), + this.get(TYPES.DiskStorageService), + this.options.defaultHost, + this.get(TYPES.InMemoryStore), + this.get(TYPES.Crypto), + this.get(TYPES.SessionStorageMapper), + this.get(TYPES.LegacySessionStorageMapper), + this.get(TYPES.InternalEventBus), + ) + }) + + this.factory.set(TYPES.SessionStorageMapper, () => { + return new SessionStorageMapper() + }) + + this.factory.set(TYPES.LegacySessionStorageMapper, () => { + return new LegacySessionStorageMapper() + }) + } +} diff --git a/packages/snjs/lib/Application/Dependencies/Types.ts b/packages/snjs/lib/Application/Dependencies/Types.ts new file mode 100644 index 000000000..60ea1015a --- /dev/null +++ b/packages/snjs/lib/Application/Dependencies/Types.ts @@ -0,0 +1,147 @@ +export const TYPES = { + // System + DeviceInterface: Symbol.for('DeviceInterface'), + AlertService: Symbol.for('AlertService'), + Crypto: Symbol.for('Crypto'), + + // Services + InternalEventBus: Symbol.for('InternalEventBus'), + PayloadManager: Symbol.for('PayloadManager'), + ItemManager: Symbol.for('ItemManager'), + MutatorService: Symbol.for('MutatorService'), + DiskStorageService: Symbol.for('DiskStorageService'), + UserEventService: Symbol.for('UserEventService'), + InMemoryStore: Symbol.for('InMemoryStore'), + KeySystemKeyManager: Symbol.for('KeySystemKeyManager'), + EncryptionService: Symbol.for('EncryptionService'), + ChallengeService: Symbol.for('ChallengeService'), + DeprecatedHttpService: Symbol.for('DeprecatedHttpService'), + HttpService: Symbol.for('HttpService'), + LegacyApiService: Symbol.for('LegacyApiService'), + UserServer: Symbol.for('UserServer'), + UserRequestServer: Symbol.for('UserRequestServer'), + UserApiService: Symbol.for('UserApiService'), + SubscriptionServer: Symbol.for('SubscriptionServer'), + SubscriptionApiService: Symbol.for('SubscriptionApiService'), + WebSocketServer: Symbol.for('WebSocketServer'), + WebSocketApiService: Symbol.for('WebSocketApiService'), + WebSocketsService: Symbol.for('WebSocketsService'), + SessionManager: Symbol.for('SessionManager'), + SubscriptionManager: Symbol.for('SubscriptionManager'), + HistoryManager: Symbol.for('HistoryManager'), + SyncService: Symbol.for('SyncService'), + ProtectionService: Symbol.for('ProtectionService'), + UserService: Symbol.for('UserService'), + KeyRecoveryService: Symbol.for('KeyRecoveryService'), + SingletonManager: Symbol.for('SingletonManager'), + PreferencesService: Symbol.for('PreferencesService'), + SettingsService: Symbol.for('SettingsService'), + FeaturesService: Symbol.for('FeaturesService'), + ComponentManager: Symbol.for('ComponentManager'), + MfaService: Symbol.for('MfaService'), + StatusService: Symbol.for('StatusService'), + MigrationService: Symbol.for('MigrationService'), + FileService: Symbol.for('FileService'), + IntegrityService: Symbol.for('IntegrityService'), + ListedService: Symbol.for('ListedService'), + ActionsService: Symbol.for('ActionsService'), + AuthenticatorApiService: Symbol.for('AuthenticatorApiService'), + AuthenticatorManager: Symbol.for('AuthenticatorManager'), + AuthApiService: Symbol.for('AuthApiService'), + AuthManager: Symbol.for('AuthManager'), + RevisionApiService: Symbol.for('RevisionApiService'), + RevisionManager: Symbol.for('RevisionManager'), + ContactService: Symbol.for('ContactService'), + VaultService: Symbol.for('VaultService'), + SharedVaultService: Symbol.for('SharedVaultService'), + AsymmetricMessageService: Symbol.for('AsymmetricMessageService'), + SelfContactManager: Symbol.for('SelfContactManager'), + EncryptionOperators: Symbol.for('EncryptionOperators'), + RootKeyManager: Symbol.for('RootKeyManager'), + ItemsEncryptionService: Symbol.for('ItemsEncryptionService'), + + // Servers + RevisionServer: Symbol.for('RevisionServer'), + AuthenticatorServer: Symbol.for('AuthenticatorServer'), + AuthServer: Symbol.for('AuthServer'), + SharedVaultInvitesServer: Symbol.for('SharedVaultInvitesServer'), + SharedVaultServer: Symbol.for('SharedVaultServer'), + SharedVaultUsersServer: Symbol.for('SharedVaultUsersServer'), + AsymmetricMessageServer: Symbol.for('AsymmetricMessageServer'), + + // Desktop Services + FilesBackupService: Symbol.for('FilesBackupService'), + HomeServerService: Symbol.for('HomeServerService'), + + // Usecases + SignInWithRecoveryCodes: Symbol.for('SignInWithRecoveryCodes'), + GetRecoveryCodes: Symbol.for('GetRecoveryCodes'), + AddAuthenticator: Symbol.for('AddAuthenticator'), + ListAuthenticators: Symbol.for('ListAuthenticators'), + DeleteAuthenticator: Symbol.for('DeleteAuthenticator'), + GetAuthenticatorAuthenticationOptions: Symbol.for('GetAuthenticatorAuthenticationOptions'), + GetAuthenticatorAuthenticationResponse: Symbol.for('GetAuthenticatorAuthenticationResponse'), + ListRevisions: Symbol.for('ListRevisions'), + GetRevision: Symbol.for('GetRevision'), + DeleteRevision: Symbol.for('DeleteRevision'), + ImportDataUseCase: Symbol.for('ImportDataUseCase'), + RemoveItemsLocally: Symbol.for('RemoveItemsLocally'), + FindContact: Symbol.for('FindContact'), + GetAllContacts: Symbol.for('GetAllContacts'), + CreateOrEditContact: Symbol.for('CreateOrEditContact'), + EditContact: Symbol.for('EditContact'), + ValidateItemSigner: Symbol.for('ValidateItemSigner'), + GetVault: Symbol.for('GetVault'), + ChangeVaultKeyOptions: Symbol.for('ChangeVaultKeyOptions'), + MoveItemsToVault: Symbol.for('MoveItemsToVault'), + CreateVault: Symbol.for('CreateVault'), + RemoveItemFromVault: Symbol.for('RemoveItemFromVault'), + DeleteVault: Symbol.for('DeleteVault'), + RotateVaultKey: Symbol.for('RotateVaultKey'), + CreateSharedVault: Symbol.for('CreateSharedVault'), + HandleKeyPairChange: Symbol.for('HandleKeyPairChange'), + NotifyVaultUsersOfKeyRotation: Symbol.for('NotifyVaultUsersOfKeyRotation'), + SendVaultDataChangedMessage: Symbol.for('SendVaultDataChangedMessage'), + SendVaultKeyChangedMessage: Symbol.for('SendVaultKeyChangedMessage'), + GetTrustedPayload: Symbol.for('GetTrustedPayload'), + GetUntrustedPayload: Symbol.for('GetUntrustedPayload'), + GetVaultContacts: Symbol.for('GetVaultContacts'), + AcceptVaultInvite: Symbol.for('AcceptVaultInvite'), + InviteToVault: Symbol.for('InviteToVault'), + LeaveVault: Symbol.for('LeaveVault'), + DeleteThirdPartyVault: Symbol.for('DeleteThirdPartyVault'), + ShareContactWithVault: Symbol.for('ShareContactWithVault'), + ConvertToSharedVault: Symbol.for('ConvertToSharedVault'), + DeleteSharedVault: Symbol.for('DeleteSharedVault'), + RemoveVaultMember: Symbol.for('RemoveVaultMember'), + GetVaultUsers: Symbol.for('GetSharedVaultUsers'), + ResendAllMessages: Symbol.for('ResendAllMessages'), + ReuploadAllInvites: Symbol.for('ReuploadAllInvites'), + ReuploadInvite: Symbol.for('ReuploadInvite'), + GetInboundMessages: Symbol.for('GetInboundMessages'), + GetOutboundMessages: Symbol.for('GetOutboundMessages'), + HandleRootKeyChangedMessage: Symbol.for('HandleRootKeyChangedMessage'), + ProcessAcceptedVaultInvite: Symbol.for('ProcessAcceptedVaultInvite'), + ResendMessage: Symbol.for('ResendMessage'), + SendMessage: Symbol.for('SendMessage'), + SendOwnContactChangeMessage: Symbol.for('SendOwnContactChangeMessage'), + DecryptMessage: Symbol.for('DecryptMessage'), + DecryptOwnMessage: Symbol.for('DecryptOwnMessage'), + EncryptMessage: Symbol.for('EncryptMessage'), + GetMessageAdditionalData: Symbol.for('GetMessageAdditionalData'), + SendVaultInvite: Symbol.for('SendVaultInvite'), + ReplaceContactData: Symbol.for('ReplaceContactData'), + CreateNewDefaultItemsKey: Symbol.for('CreateNewDefaultItemsKey'), + CreateNewItemsKeyWithRollback: Symbol.for('CreateNewItemsKeyWithRollback'), + FindDefaultItemsKey: Symbol.for('FindDefaultItemsKey'), + DecryptErroredTypeAPayloads: Symbol.for('DecryptErroredTypeAPayloads'), + DecryptTypeAPayload: Symbol.for('DecryptTypeAPayload'), + DecryptTypeAPayloadWithKeyLookup: Symbol.for('DecryptTypeAPayloadWithKeyLookup'), + EncryptTypeAPayload: Symbol.for('EncryptTypeAPayload'), + EncryptTypeAPayloadWithKeyLookup: Symbol.for('EncryptTypeAPayloadWithKeyLookup'), + DecryptBackupFile: Symbol.for('DecryptBackupFile'), + + // Mappers + SessionStorageMapper: Symbol.for('SessionStorageMapper'), + LegacySessionStorageMapper: Symbol.for('LegacySessionStorageMapper'), +} diff --git a/packages/snjs/lib/Application/Dependencies/isDeinitable.ts b/packages/snjs/lib/Application/Dependencies/isDeinitable.ts new file mode 100644 index 000000000..ae66990dc --- /dev/null +++ b/packages/snjs/lib/Application/Dependencies/isDeinitable.ts @@ -0,0 +1,14 @@ +export function isDeinitable(service: unknown): service is { deinit(): void } { + if (!service) { + throw new Error('Service is undefined') + } + return typeof (service as { deinit(): void }).deinit === 'function' +} + +export function canBlockDeinit(service: unknown): service is { blockDeinit(): Promise } { + if (!service) { + throw new Error('Service is undefined') + } + + return typeof (service as { blockDeinit(): Promise }).blockDeinit === 'function' +} diff --git a/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.spec.ts b/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.spec.ts index 524d0b042..d43d74da2 100644 --- a/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.spec.ts +++ b/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.spec.ts @@ -1,6 +1,5 @@ import { EncryptedPayloadInterface, HistoryEntry } from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' -import { RevisionClientInterface } from '@standardnotes/services' +import { EncryptionProviderInterface, RevisionClientInterface } from '@standardnotes/services' jest.mock('@standardnotes/models', () => { const original = jest.requireActual('@standardnotes/models') @@ -32,11 +31,13 @@ describe('GetRevision', () => { enc_item_key: 'foobar', auth_hash: 'foobar', created_at: '2021-01-01T00:00:00.000Z', - updated_at: '2021-01-01T00:00:00.000Z' + updated_at: '2021-01-01T00:00:00.000Z', } as jest.Mocked) encryptionService = {} as jest.Mocked - encryptionService.getEmbeddedPayloadAuthenticatedData = jest.fn().mockReturnValue({ u: '00000000-0000-0000-0000-000000000000' }) + encryptionService.getEmbeddedPayloadAuthenticatedData = jest + .fn() + .mockReturnValue({ u: '00000000-0000-0000-0000-000000000000' }) const encryptedPayload = { content: 'foobar', } as jest.Mocked diff --git a/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.ts b/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.ts index c076be65f..18fee0deb 100644 --- a/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.ts +++ b/packages/snjs/lib/Domain/UseCase/GetRevision/GetRevision.ts @@ -1,5 +1,5 @@ import { ServerItemResponse } from '@standardnotes/responses' -import { RevisionClientInterface } from '@standardnotes/services' +import { EncryptionProviderInterface, RevisionClientInterface } from '@standardnotes/services' import { Result, UseCaseInterface, Uuid } from '@standardnotes/domain-core' import { EncryptedPayload, @@ -9,7 +9,6 @@ import { NoteContent, PayloadTimestampDefaults, } from '@standardnotes/models' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { GetRevisionDTO } from './GetRevisionDTO' diff --git a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts index 495d47ece..e73b3834d 100644 --- a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts +++ b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.spec.ts @@ -1,10 +1,10 @@ import { AuthClientInterface, + EncryptionProviderInterface, InternalEventBusInterface, KeyValueStoreInterface, SessionsClientInterface, } from '@standardnotes/services' -import { EncryptionProviderInterface } from '@standardnotes/encryption' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { AnyKeyParamsContent } from '@standardnotes/common' import { DecryptedPayloadInterface, RootKeyContent, RootKeyInterface } from '@standardnotes/models' @@ -20,14 +20,8 @@ describe('SignInWithRecoveryCodes', () => { let sessionManager: SessionsClientInterface let internalEventBus: InternalEventBusInterface - const createUseCase = () => new SignInWithRecoveryCodes( - authManager, - encryptionService, - inMemoryStore, - crypto, - sessionManager, - internalEventBus, - ) + const createUseCase = () => + new SignInWithRecoveryCodes(authManager, encryptionService, inMemoryStore, crypto, sessionManager, internalEventBus) beforeEach(() => { authManager = {} as jest.Mocked @@ -54,12 +48,7 @@ describe('SignInWithRecoveryCodes', () => { encryptionService.hasAccount = jest.fn() encryptionService.computeRootKey = jest.fn().mockReturnValue(rootKey) encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(true) - encryptionService.supportedVersions = jest.fn().mockReturnValue([ - '001', - '002', - '003', - '004', - ]) + encryptionService.supportedVersions = jest.fn().mockReturnValue(['001', '002', '003', '004']) encryptionService.isVersionNewerThanLibraryVersion = jest.fn() inMemoryStore = {} as jest.Mocked> @@ -82,7 +71,11 @@ describe('SignInWithRecoveryCodes', () => { encryptionService.hasAccount = jest.fn().mockReturnValue(true) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(true) expect(result.getError()).toEqual('Tried to sign in when an account already exists.') @@ -92,7 +85,11 @@ describe('SignInWithRecoveryCodes', () => { authManager.recoveryKeyParams = jest.fn().mockReturnValue(false) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(true) expect(result.getError()).toEqual('Could not retrieve recovery key params') @@ -102,10 +99,16 @@ describe('SignInWithRecoveryCodes', () => { encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(true) - expect(result.getError()).toEqual('Your account was created on a platform with higher security capabilities than this browser supports. If we attempted to generate your login keys here, it would take hours. Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in.') + expect(result.getError()).toEqual( + 'Your account was created on a platform with higher security capabilities than this browser supports. If we attempted to generate your login keys here, it would take hours. Please use a browser with more up to date security capabilities, like Google Chrome or Firefox, to log in.', + ) }) it('should fail if key params has unsupported version', async () => { @@ -123,10 +126,16 @@ describe('SignInWithRecoveryCodes', () => { encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(true) - expect(result.getError()).toEqual('This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.') + expect(result.getError()).toEqual( + 'This version of the application does not support your newer account type. Please upgrade to the latest version of Standard Notes to sign in.', + ) }) it('should fail if key params has expired version', async () => { @@ -144,17 +153,27 @@ describe('SignInWithRecoveryCodes', () => { encryptionService.platformSupportsKeyDerivation = jest.fn().mockReturnValue(false) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(true) - expect(result.getError()).toEqual('The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.com/help/security for more information.') + expect(result.getError()).toEqual( + 'The protocol version associated with your account is outdated and no longer supported by this application. Please visit standardnotes.com/help/security for more information.', + ) }) it('should fail if the sign in with recovery code fails', async () => { authManager.signInWithRecoveryCodes = jest.fn().mockReturnValue(false) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(true) expect(result.getError()).toEqual('Could not sign in with recovery code') @@ -168,11 +187,15 @@ describe('SignInWithRecoveryCodes', () => { uuid: '1-2-3', email: 'test@test.te', protocolVersion: '004', - } + }, }) const useCase = createUseCase() - const result = await useCase.execute({ recoveryCodes: 'recovery-codes', password: 'foobar', username: 'test@test.te' }) + const result = await useCase.execute({ + recoveryCodes: 'recovery-codes', + password: 'foobar', + username: 'test@test.te', + }) expect(result.isFailed()).toBe(false) }) diff --git a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts index 6bf973736..3478380bc 100644 --- a/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts +++ b/packages/snjs/lib/Domain/UseCase/SignInWithRecoveryCodes/SignInWithRecoveryCodes.ts @@ -4,6 +4,7 @@ import { AccountEvent, AuthClientInterface, EXPIRED_PROTOCOL_VERSION, + EncryptionProviderInterface, InternalEventBusInterface, InternalEventPublishStrategy, KeyValueStoreInterface, @@ -12,7 +13,7 @@ import { UNSUPPORTED_KEY_DERIVATION, UNSUPPORTED_PROTOCOL_VERSION, } from '@standardnotes/services' -import { CreateAnyKeyParams, EncryptionProviderInterface, SNRootKey } from '@standardnotes/encryption' +import { CreateAnyKeyParams, SNRootKey } from '@standardnotes/encryption' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SignInWithRecoveryCodesDTO } from './SignInWithRecoveryCodesDTO' diff --git a/packages/snjs/lib/Migrations/MigrationServices.ts b/packages/snjs/lib/Migrations/MigrationServices.ts index 5dd6e4dff..bc08c8feb 100644 --- a/packages/snjs/lib/Migrations/MigrationServices.ts +++ b/packages/snjs/lib/Migrations/MigrationServices.ts @@ -7,10 +7,10 @@ import { MutatorClientInterface, PreferenceServiceInterface, } from '@standardnotes/services' -import { SNSessionManager } from '../Services/Session/SessionManager' +import { SessionManager } from '../Services/Session/SessionManager' import { ApplicationIdentifier } from '@standardnotes/common' import { ItemManager } from '@Lib/Services/Items/ItemManager' -import { ChallengeService, SNSingletonManager, SNFeaturesService, DiskStorageService } from '@Lib/Services' +import { ChallengeService, SingletonManager, FeaturesService, DiskStorageService } from '@Lib/Services' import { LegacySession, MapperInterface } from '@standardnotes/domain-core' export type MigrationServices = { @@ -18,12 +18,12 @@ export type MigrationServices = { deviceInterface: DeviceInterface storageService: DiskStorageService challengeService: ChallengeService - sessionManager: SNSessionManager + sessionManager: SessionManager backups?: BackupServiceInterface itemManager: ItemManager mutator: MutatorClientInterface - singletonManager: SNSingletonManager - featuresService: SNFeaturesService + singletonManager: SingletonManager + featuresService: FeaturesService preferences: PreferenceServiceInterface environment: Environment platform: Platform diff --git a/packages/snjs/lib/Services/Actions/ActionsService.ts b/packages/snjs/lib/Services/Actions/ActionsService.ts index 22d26d684..2722e1c8f 100644 --- a/packages/snjs/lib/Services/Actions/ActionsService.ts +++ b/packages/snjs/lib/Services/Actions/ActionsService.ts @@ -1,7 +1,6 @@ import { removeFromArray } from '@standardnotes/utils' import { SNRootKey } from '@standardnotes/encryption' import { ChallengeService } from '../Challenge' -import { ListedService } from '../Listed/ListedService' import { ActionResponse, DeprecatedHttpResponse } from '@standardnotes/responses' import { ItemManager } from '@Lib/Services/Items/ItemManager' import { @@ -21,8 +20,6 @@ import { TransferPayload, ItemContent, } from '@standardnotes/models' -import { SNSyncService } from '../Sync/SyncService' -import { PayloadManager } from '../Payloads/PayloadManager' import { DeprecatedHttpService } from '../Api/DeprecatedHttpService' import { AbstractService, @@ -53,20 +50,17 @@ type PayloadRequestHandler = (uuid: string) => TransferPayload | undefined * `post`: sends an item's data to a remote service. This is used for example by Listed * to allow publishing a note to a user's blog. */ -export class SNActionsService extends AbstractService { +export class ActionsService extends AbstractService { private previousPasswords: string[] = [] private payloadRequestHandlers: PayloadRequestHandler[] = [] constructor( private itemManager: ItemManager, private alertService: AlertService, - public deviceInterface: DeviceInterface, + private device: DeviceInterface, private httpService: DeprecatedHttpService, - private payloadManager: PayloadManager, private encryptionService: EncryptionService, - private syncService: SNSyncService, private challengeService: ChallengeService, - private listedService: ListedService, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) @@ -76,13 +70,10 @@ export class SNActionsService extends AbstractService { public override deinit(): void { ;(this.itemManager as unknown) = undefined ;(this.alertService as unknown) = undefined - ;(this.deviceInterface as unknown) = undefined + ;(this.device as unknown) = undefined ;(this.httpService as unknown) = undefined - ;(this.payloadManager as unknown) = undefined - ;(this.listedService as unknown) = undefined ;(this.challengeService as unknown) = undefined ;(this.encryptionService as unknown) = undefined - ;(this.syncService as unknown) = undefined this.payloadRequestHandlers.length = 0 this.previousPasswords.length = 0 super.deinit() @@ -323,7 +314,7 @@ export class SNActionsService extends AbstractService { } private handleShowAction(action: Action) { - void this.deviceInterface.openUrl(action.url) + void this.device.openUrl(action.url) return {} as ActionResponse } diff --git a/packages/snjs/lib/Services/Api/ApiService.ts b/packages/snjs/lib/Services/Api/ApiService.ts index 80b36916d..877e94d08 100644 --- a/packages/snjs/lib/Services/Api/ApiService.ts +++ b/packages/snjs/lib/Services/Api/ApiService.ts @@ -1,7 +1,7 @@ import { joinPaths } from '@standardnotes/utils' import { AbstractService, - ApiServiceInterface, + LegacyApiServiceInterface, InternalEventBusInterface, IntegrityApiInterface, ItemsServerInterface, @@ -86,10 +86,10 @@ const V0_API_VERSION = '20200115' type InvalidSessionObserver = (revoked: boolean) => void -export class SNApiService +export class LegacyApiService extends AbstractService implements - ApiServiceInterface, + LegacyApiServiceInterface, FilesApiInterface, IntegrityApiInterface, ItemsServerInterface, diff --git a/packages/snjs/lib/Services/Api/WebsocketsService.spec.ts b/packages/snjs/lib/Services/Api/WebsocketsService.spec.ts index e55cf6eb6..82cf0458b 100644 --- a/packages/snjs/lib/Services/Api/WebsocketsService.spec.ts +++ b/packages/snjs/lib/Services/Api/WebsocketsService.spec.ts @@ -2,7 +2,7 @@ import { InternalEventBusInterface } from '@standardnotes/services' import { WebSocketApiServiceInterface } from '@standardnotes/api' import { StorageKey, DiskStorageService } from '@Lib/index' -import { SNWebSocketsService } from './WebsocketsService' +import { WebSocketsService } from './WebsocketsService' describe('webSocketsService', () => { const webSocketUrl = '' @@ -12,7 +12,7 @@ describe('webSocketsService', () => { let internalEventBus: InternalEventBusInterface const createService = () => { - return new SNWebSocketsService(storageService, webSocketUrl, webSocketApiService, internalEventBus) + return new WebSocketsService(storageService, webSocketUrl, webSocketApiService, internalEventBus) } beforeEach(() => { diff --git a/packages/snjs/lib/Services/Api/WebsocketsService.ts b/packages/snjs/lib/Services/Api/WebsocketsService.ts index 79aab31e7..625b2909c 100644 --- a/packages/snjs/lib/Services/Api/WebsocketsService.ts +++ b/packages/snjs/lib/Services/Api/WebsocketsService.ts @@ -9,7 +9,7 @@ import { import { WebSocketApiServiceInterface } from '@standardnotes/api' import { WebSocketsServiceEvent } from './WebSocketsServiceEvent' -export class SNWebSocketsService extends AbstractService { +export class WebSocketsService extends AbstractService { private webSocket?: WebSocket constructor( diff --git a/packages/snjs/lib/Services/Challenge/ChallengeService.ts b/packages/snjs/lib/Services/Challenge/ChallengeService.ts index c932ff752..eb2828712 100644 --- a/packages/snjs/lib/Services/Challenge/ChallengeService.ts +++ b/packages/snjs/lib/Services/Challenge/ChallengeService.ts @@ -158,7 +158,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic return { wrappingKey } } - public isPasscodeLocked() { + public isPasscodeLocked(): Promise { return this.encryptionService.isPasscodeLocked() } @@ -260,7 +260,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic delete this.challengeOperations[challenge.id] } - public cancelChallenge(challenge: Challenge) { + public cancelChallenge(challenge: Challenge): void { const operation = this.challengeOperations[challenge.id] operation.cancel() @@ -274,7 +274,7 @@ export class ChallengeService extends AbstractService implements ChallengeServic this.deleteChallengeOperation(operation) } - public async submitValuesForChallenge(challenge: Challenge, values: ChallengeValue[]) { + public async submitValuesForChallenge(challenge: Challenge, values: ChallengeValue[]): Promise { if (values.length === 0) { throw Error('Attempting to submit 0 values for challenge') } diff --git a/packages/snjs/lib/Services/ComponentManager/ComponentManager.spec.ts b/packages/snjs/lib/Services/ComponentManager/ComponentManager.spec.ts index 1d8322929..5f71b07b1 100644 --- a/packages/snjs/lib/Services/ComponentManager/ComponentManager.spec.ts +++ b/packages/snjs/lib/Services/ComponentManager/ComponentManager.spec.ts @@ -10,14 +10,14 @@ import { PreferenceServiceInterface, } from '@standardnotes/services' import { ItemManager } from '@Lib/Services/Items/ItemManager' -import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService' +import { FeaturesService } from '@Lib/Services/Features/FeaturesService' import { SNComponentManager } from './ComponentManager' -import { SNSyncService } from '../Sync/SyncService' +import { SyncService } from '../Sync/SyncService' describe('featuresService', () => { let items: ItemManagerInterface let mutator: MutatorClientInterface - let features: SNFeaturesService + let features: FeaturesService let alerts: AlertService let sync: SyncServiceInterface let prefs: PreferenceServiceInterface @@ -47,7 +47,7 @@ describe('featuresService', () => { attachEvent: jest.fn(), } as unknown as Window & typeof globalThis - sync = {} as jest.Mocked + sync = {} as jest.Mocked sync.sync = jest.fn() items = {} as jest.Mocked @@ -61,7 +61,7 @@ describe('featuresService', () => { mutator.changeItem = jest.fn() mutator.changeFeatureRepo = jest.fn() - features = {} as jest.Mocked + features = {} as jest.Mocked prefs = {} as jest.Mocked prefs.addEventObserver = jest.fn() diff --git a/packages/snjs/lib/Services/ComponentManager/ComponentManager.ts b/packages/snjs/lib/Services/ComponentManager/ComponentManager.ts index 402f116ee..67e17731d 100644 --- a/packages/snjs/lib/Services/ComponentManager/ComponentManager.ts +++ b/packages/snjs/lib/Services/ComponentManager/ComponentManager.ts @@ -1,4 +1,4 @@ -import { SNFeaturesService } from '@Lib/Services/Features/FeaturesService' +import { FeaturesService } from '@Lib/Services/Features/FeaturesService' import { ContentType } from '@standardnotes/domain-core' import { ActionObserver, @@ -94,7 +94,7 @@ export class SNComponentManager private items: ItemManagerInterface, private mutator: MutatorClientInterface, private sync: SyncServiceInterface, - private features: SNFeaturesService, + private features: FeaturesService, private preferences: PreferenceServiceInterface, protected alerts: AlertService, private environment: Environment, diff --git a/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts b/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts index 139d88b09..098eb8c71 100644 --- a/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts +++ b/packages/snjs/lib/Services/ComponentManager/ComponentViewer.ts @@ -11,7 +11,7 @@ import { ItemManagerInterface, SyncServiceInterface, } from '@standardnotes/services' -import { SNFeaturesService } from '@Lib/Services' +import { FeaturesService } from '@Lib/Services' import { ActionObserver, ComponentEventObserver, @@ -100,7 +100,7 @@ export class ComponentViewer implements ComponentViewerInterface { sync: SyncServiceInterface alerts: AlertService preferences: PreferenceServiceInterface - features: SNFeaturesService + features: FeaturesService }, private options: { item: ComponentViewerItem diff --git a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts index cf5c02543..dfc579672 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.spec.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.spec.ts @@ -1,15 +1,15 @@ import { ItemInterface, SNFeatureRepo } from '@standardnotes/models' -import { SNSyncService } from '../Sync/SyncService' +import { SyncService } from '../Sync/SyncService' import { SettingName } from '@standardnotes/settings' -import { SNFeaturesService } from '@Lib/Services/Features' +import { FeaturesService } from '@Lib/Services/Features' import { RoleName, ContentType } from '@standardnotes/domain-core' import { FeatureIdentifier, GetFeatures } from '@standardnotes/features' -import { SNWebSocketsService } from '../Api/WebsocketsService' -import { SNSettingsService } from '../Settings' +import { WebSocketsService } from '../Api/WebsocketsService' +import { SettingsService } from '../Settings' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' import { AlertService, - ApiServiceInterface, + LegacyApiServiceInterface, FeaturesEvent, FeatureStatus, InternalEventBusInterface, @@ -23,7 +23,7 @@ import { UserClientInterface, UserService, } from '@standardnotes/services' -import { SNApiService, SNSessionManager } from '../Api' +import { LegacyApiService, SessionManager } from '../Api' import { ItemManager } from '../Items' import { DiskStorageService } from '../Storage/DiskStorageService' import { SettingsClientInterface } from '../Settings/SettingsClientInterface' @@ -33,8 +33,8 @@ describe('FeaturesService', () => { let itemManager: ItemManagerInterface let mutator: MutatorClientInterface let subscriptions: SubscriptionManagerInterface - let apiService: ApiServiceInterface - let webSocketsService: SNWebSocketsService + let apiService: LegacyApiServiceInterface + let webSocketsService: WebSocketsService let settingsService: SettingsClientInterface let userService: UserClientInterface let syncService: SyncServiceInterface @@ -46,7 +46,7 @@ describe('FeaturesService', () => { let internalEventBus: InternalEventBusInterface const createService = () => { - return new SNFeaturesService( + return new FeaturesService( storageService, itemManager, mutator, @@ -72,7 +72,7 @@ describe('FeaturesService', () => { storageService.setValue = jest.fn() storageService.getValue = jest.fn() - apiService = {} as jest.Mocked + apiService = {} as jest.Mocked apiService.addEventObserver = jest.fn() apiService.isThirdPartyHostUsed = jest.fn().mockReturnValue(false) @@ -92,23 +92,23 @@ describe('FeaturesService', () => { subscriptions.getOnlineSubscription = jest.fn() subscriptions.addEventObserver = jest.fn() - webSocketsService = {} as jest.Mocked + webSocketsService = {} as jest.Mocked webSocketsService.addEventObserver = jest.fn() - settingsService = {} as jest.Mocked + settingsService = {} as jest.Mocked settingsService.updateSetting = jest.fn() userService = {} as jest.Mocked userService.addEventObserver = jest.fn() - syncService = {} as jest.Mocked + syncService = {} as jest.Mocked syncService.sync = jest.fn() alertService = {} as jest.Mocked alertService.confirm = jest.fn().mockReturnValue(true) alertService.alert = jest.fn() - sessionManager = {} as jest.Mocked + sessionManager = {} as jest.Mocked sessionManager.isSignedIntoFirstPartyServer = jest.fn() sessionManager.getUser = jest.fn() diff --git a/packages/snjs/lib/Services/Features/FeaturesService.ts b/packages/snjs/lib/Services/Features/FeaturesService.ts index b02e40054..1261357eb 100644 --- a/packages/snjs/lib/Services/Features/FeaturesService.ts +++ b/packages/snjs/lib/Services/Features/FeaturesService.ts @@ -4,7 +4,7 @@ import { ClientDisplayableError } from '@standardnotes/responses' import { RoleName, ContentType } from '@standardnotes/domain-core' import { PROD_OFFLINE_FEATURES_URL } from '../../Hosts' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' -import { SNWebSocketsService } from '../Api/WebsocketsService' +import { WebSocketsService } from '../Api/WebsocketsService' import { WebSocketsServiceEvent } from '../Api/WebSocketsServiceEvent' import { TRUSTED_CUSTOM_EXTENSIONS_HOSTS, TRUSTED_FEATURE_HOSTS } from '@Lib/Hosts' import { UserRolesChangedEvent } from '@standardnotes/domain-events' @@ -39,7 +39,7 @@ import { StorageKey, MutatorClientInterface, StorageServiceInterface, - ApiServiceInterface, + LegacyApiServiceInterface, ItemManagerInterface, SyncServiceInterface, SessionsClientInterface, @@ -47,6 +47,8 @@ import { SubscriptionManagerInterface, AccountEvent, SubscriptionManagerEvent, + ApplicationEvent, + ApplicationStageChangedEventPayload, } from '@standardnotes/services' import { DownloadRemoteThirdPartyFeatureUseCase } from './UseCase/DownloadRemoteThirdPartyFeature' @@ -56,7 +58,7 @@ import { SettingsClientInterface } from '../Settings/SettingsClientInterface' type GetOfflineSubscriptionDetailsResponse = OfflineSubscriptionEntitlements | ClientDisplayableError -export class SNFeaturesService +export class FeaturesService extends AbstractService implements FeaturesClientInterface, InternalEventHandlerInterface { @@ -71,8 +73,8 @@ export class SNFeaturesService private items: ItemManagerInterface, private mutator: MutatorClientInterface, private subscriptions: SubscriptionManagerInterface, - private api: ApiServiceInterface, - sockets: SNWebSocketsService, + private api: LegacyApiServiceInterface, + sockets: WebSocketsService, private settings: SettingsClientInterface, private user: UserClientInterface, private sync: SyncServiceInterface, @@ -152,20 +154,19 @@ export class SNFeaturesService const { userRoles } = event.payload as MetaReceivedData void this.updateOnlineRolesWithNewValues(userRoles.map((role) => role.name)) } - } - override async handleApplicationStage(stage: ApplicationStage): Promise { - if (stage === ApplicationStage.FullSyncCompleted_13) { - if (!this.hasFirstPartyOnlineSubscription()) { - const offlineRepo = this.getOfflineRepo() + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.FullSyncCompleted_13) { + if (!this.hasFirstPartyOnlineSubscription()) { + const offlineRepo = this.getOfflineRepo() - if (offlineRepo) { - void this.downloadOfflineRoles(offlineRepo) + if (offlineRepo) { + void this.downloadOfflineRoles(offlineRepo) + } } } } - - return super.handleApplicationStage(stage) } public enableExperimentalFeature(identifier: FeatureIdentifier): void { diff --git a/packages/snjs/lib/Services/Features/UseCase/DownloadRemoteThirdPartyFeature.ts b/packages/snjs/lib/Services/Features/UseCase/DownloadRemoteThirdPartyFeature.ts index cfad78adb..cf2470e63 100644 --- a/packages/snjs/lib/Services/Features/UseCase/DownloadRemoteThirdPartyFeature.ts +++ b/packages/snjs/lib/Services/Features/UseCase/DownloadRemoteThirdPartyFeature.ts @@ -9,13 +9,17 @@ import { import { AlertService, API_MESSAGE_FAILED_DOWNLOADING_EXTENSION, - ApiServiceInterface, + LegacyApiServiceInterface, ItemManagerInterface, } from '@standardnotes/services' import { isString } from '@standardnotes/utils' export class DownloadRemoteThirdPartyFeatureUseCase { - constructor(private api: ApiServiceInterface, private items: ItemManagerInterface, private alerts: AlertService) {} + constructor( + private api: LegacyApiServiceInterface, + private items: ItemManagerInterface, + private alerts: AlertService, + ) {} async execute(url: string): Promise { const response = await this.api.downloadFeatureUrl(url) diff --git a/packages/snjs/lib/Services/History/HistoryManager.ts b/packages/snjs/lib/Services/History/HistoryManager.ts index f46356665..248a87402 100644 --- a/packages/snjs/lib/Services/History/HistoryManager.ts +++ b/packages/snjs/lib/Services/History/HistoryManager.ts @@ -30,7 +30,7 @@ const LargeEntryDeltaThreshold = 25 * 2. Remote server history. Entries are automatically added by the server and must be * retrieved per item via an API call. */ -export class SNHistoryManager extends AbstractService implements HistoryServiceInterface { +export class HistoryManager extends AbstractService implements HistoryServiceInterface { private removeChangeObserver: () => void /** diff --git a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts index f3f2874f4..9a30e6f29 100644 --- a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts +++ b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryOperation.ts @@ -1,9 +1,13 @@ import { ItemsKeyInterface } from '@standardnotes/models' import { dateSorted } from '@standardnotes/utils' -import { SNRootKeyParams, EncryptionProviderInterface } from '@standardnotes/encryption' +import { SNRootKeyParams } from '@standardnotes/encryption' import { DecryptionQueueItem, KeyRecoveryOperationResult } from './Types' import { serverKeyParamsAreSafe } from './Utils' -import { ChallengeServiceInterface, DecryptItemsKeyByPromptingUser } from '@standardnotes/services' +import { + ChallengeServiceInterface, + DecryptItemsKeyByPromptingUser, + EncryptionProviderInterface, +} from '@standardnotes/services' import { ItemManager } from '../Items' import { ContentType } from '@standardnotes/domain-core' diff --git a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts index 3e3a979eb..220e536c4 100644 --- a/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts +++ b/packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts @@ -11,11 +11,11 @@ import { getIncrementedDirtyIndex, ContentTypeUsesRootKeyEncryption, } from '@standardnotes/models' -import { SNSyncService } from '../Sync/SyncService' +import { SyncService } from '../Sync/SyncService' import { DiskStorageService } from '../Storage/DiskStorageService' import { PayloadManager } from '../Payloads/PayloadManager' import { ChallengeService } from '../Challenge' -import { SNApiService } from '@Lib/Services/Api/ApiService' +import { LegacyApiService } from '@Lib/Services/Api/ApiService' import { ItemManager } from '../Items/ItemManager' import { removeFromArray, Uuids } from '@standardnotes/utils' import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses' @@ -33,6 +33,10 @@ import { EncryptionService, Challenge, UserService, + InternalEventHandlerInterface, + InternalEventInterface, + ApplicationEvent, + ApplicationStageChangedEventPayload, } from '@standardnotes/services' import { UndecryptableItemsStorage, @@ -79,7 +83,10 @@ import { ContentType } from '@standardnotes/domain-core' * but our current copy is not, we will ignore the incoming value until we can properly * decrypt it. */ -export class SNKeyRecoveryService extends AbstractService { +export class KeyRecoveryService + extends AbstractService + implements InternalEventHandlerInterface +{ private removeItemObserver: () => void private decryptionQueue: DecryptionQueueItem[] = [] private isProcessingQueue = false @@ -87,12 +94,12 @@ export class SNKeyRecoveryService extends AbstractService { - void super.handleApplicationStage(stage) - if (stage === ApplicationStage.LoadedDatabase_12) { - void this.processPersistedUndecryptables() + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.LoadedDatabase_12) { + void this.processPersistedUndecryptables() + } } } @@ -383,8 +391,8 @@ export class SNKeyRecoveryService extends AbstractService p.dirty)) { - await this.syncService.sync() + await this.sync.sync() } await this.notifyEvent(KeyRecoveryEvent.KeysRecovered, decryptedMatching) diff --git a/packages/snjs/lib/Services/Listed/ListedService.ts b/packages/snjs/lib/Services/Listed/ListedService.ts index 7f7a1391a..bbc998874 100644 --- a/packages/snjs/lib/Services/Listed/ListedService.ts +++ b/packages/snjs/lib/Services/Listed/ListedService.ts @@ -1,27 +1,31 @@ -import { SyncClientInterface } from './../Sync/SyncClientInterface' import { isString, lastElement, sleep } from '@standardnotes/utils' import { UuidString } from '@Lib/Types/UuidString' import { ItemManager } from '@Lib/Services/Items/ItemManager' import { DeprecatedHttpService } from '../Api/DeprecatedHttpService' import { SettingName } from '@standardnotes/settings' -import { SNSettingsService } from '../Settings/SNSettingsService' +import { SettingsService } from '../Settings/SNSettingsService' import { ListedClientInterface } from './ListedClientInterface' -import { SNApiService } from '../Api/ApiService' +import { LegacyApiService } from '../Api/ApiService' import { isErrorResponse, ListedAccount, ListedAccountInfo, ListedAccountInfoResponse } from '@standardnotes/responses' import { NoteMutator, SNActionsExtension, SNNote } from '@standardnotes/models' -import { AbstractService, InternalEventBusInterface, MutatorClientInterface } from '@standardnotes/services' +import { + AbstractService, + InternalEventBusInterface, + MutatorClientInterface, + SyncServiceInterface, +} from '@standardnotes/services' import { SNProtectionService } from '../Protection' import { ContentType } from '@standardnotes/domain-core' export class ListedService extends AbstractService implements ListedClientInterface { constructor( - private apiService: SNApiService, + private apiService: LegacyApiService, private itemManager: ItemManager, - private settingsService: SNSettingsService, + private settingsService: SettingsService, private httpSerivce: DeprecatedHttpService, private protectionService: SNProtectionService, private mutator: MutatorClientInterface, - private sync: SyncClientInterface, + private sync: SyncServiceInterface, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) diff --git a/packages/snjs/lib/Services/Mfa/MfaService.ts b/packages/snjs/lib/Services/Mfa/MfaService.ts index b5f87c65e..aded3da96 100644 --- a/packages/snjs/lib/Services/Mfa/MfaService.ts +++ b/packages/snjs/lib/Services/Mfa/MfaService.ts @@ -1,15 +1,15 @@ import { SettingName } from '@standardnotes/settings' -import { SNSettingsService } from '../Settings' +import { SettingsService } from '../Settings' import { PureCryptoInterface } from '@standardnotes/sncrypto-common' -import { SNFeaturesService } from '../Features/FeaturesService' +import { FeaturesService } from '../Features/FeaturesService' import { AbstractService, InternalEventBusInterface, SignInStrings } from '@standardnotes/services' export class SNMfaService extends AbstractService { constructor( - private settingsService: SNSettingsService, + private settingsService: SettingsService, private crypto: PureCryptoInterface, - private featuresService: SNFeaturesService, + private featuresService: FeaturesService, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) diff --git a/packages/snjs/lib/Services/Migration/MigrationService.ts b/packages/snjs/lib/Services/Migration/MigrationService.ts index e413e9469..8ed6f5428 100644 --- a/packages/snjs/lib/Services/Migration/MigrationService.ts +++ b/packages/snjs/lib/Services/Migration/MigrationService.ts @@ -10,6 +10,9 @@ import { ApplicationStage, AbstractService, DiagnosticInfo, + InternalEventHandlerInterface, + InternalEventInterface, + ApplicationStageChangedEventPayload, } from '@standardnotes/services' import { SnjsVersion, isRightVersionGreaterThanLeft } from '../../Version' import { SNLog } from '@Lib/Log' @@ -23,7 +26,7 @@ import { MigrationClasses } from '@Lib/Migrations/Versions' * first launches, and also other steps after the application is unlocked, or after the * first sync completes. Migrations live under /migrations and inherit from the base Migration class. */ -export class SNMigrationService extends AbstractService { +export class MigrationService extends AbstractService implements InternalEventHandlerInterface { private activeMigrations?: Migration[] private baseMigration!: BaseMigration @@ -44,7 +47,7 @@ export class SNMigrationService extends AbstractService { public async initialize(): Promise { await this.runBaseMigrationPreRun() - const requiredMigrations = SNMigrationService.getRequiredMigrations(await this.getStoredSnjsVersion()) + const requiredMigrations = MigrationService.getRequiredMigrations(await this.getStoredSnjsVersion()) this.activeMigrations = this.instantiateMigrationClasses(requiredMigrations) @@ -70,13 +73,11 @@ export class SNMigrationService extends AbstractService { await this.baseMigration.preRun() } - /** - * Application instances will call this function directly when they arrive - * at a certain migratory state. - */ - public override async handleApplicationStage(stage: ApplicationStage): Promise { - await super.handleApplicationStage(stage) - await this.handleStage(stage) + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + await this.handleStage(stage) + } } /** @@ -89,7 +90,7 @@ export class SNMigrationService extends AbstractService { } public async hasPendingMigrations(): Promise { - const requiredMigrations = SNMigrationService.getRequiredMigrations(await this.getStoredSnjsVersion()) + const requiredMigrations = MigrationService.getRequiredMigrations(await this.getStoredSnjsVersion()) return requiredMigrations.length > 0 || (await this.baseMigration.needsKeychainRepair()) } diff --git a/packages/snjs/lib/Services/Preferences/PreferencesService.ts b/packages/snjs/lib/Services/Preferences/PreferencesService.ts index 6f9c60805..ea5b811d4 100644 --- a/packages/snjs/lib/Services/Preferences/PreferencesService.ts +++ b/packages/snjs/lib/Services/Preferences/PreferencesService.ts @@ -1,7 +1,7 @@ import { SNUserPrefs, PrefKey, PrefValue, UserPrefsMutator, ItemContent, FillItemContent } from '@standardnotes/models' import { ItemManager } from '../Items/ItemManager' -import { SNSingletonManager } from '../Singleton/SingletonManager' -import { SNSyncService } from '../Sync/SyncService' +import { SingletonManager } from '../Singleton/SingletonManager' +import { SyncService } from '../Sync/SyncService' import { AbstractService, InternalEventBusInterface, @@ -10,12 +10,16 @@ import { PreferenceServiceInterface, PreferencesServiceEvent, MutatorClientInterface, + InternalEventHandlerInterface, + InternalEventInterface, + ApplicationEvent, + ApplicationStageChangedEventPayload, } from '@standardnotes/services' import { ContentType } from '@standardnotes/domain-core' export class SNPreferencesService extends AbstractService - implements PreferenceServiceInterface + implements PreferenceServiceInterface, InternalEventHandlerInterface { private shouldReload = true private reloading = false @@ -24,10 +28,10 @@ export class SNPreferencesService private removeSyncObserver?: () => void constructor( - private singletons: SNSingletonManager, + private singletons: SingletonManager, items: ItemManager, private mutator: MutatorClientInterface, - private sync: SNSyncService, + private sync: SyncService, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) @@ -52,18 +56,19 @@ export class SNPreferencesService super.deinit() } - public override async handleApplicationStage(stage: ApplicationStage): Promise { - await super.handleApplicationStage(stage) + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.LoadedDatabase_12) { + /** Try to read preferences singleton from storage */ + this.preferences = this.singletons.findSingleton( + ContentType.TYPES.UserPrefs, + SNUserPrefs.singletonPredicate, + ) - if (stage === ApplicationStage.LoadedDatabase_12) { - /** Try to read preferences singleton from storage */ - this.preferences = this.singletons.findSingleton( - ContentType.TYPES.UserPrefs, - SNUserPrefs.singletonPredicate, - ) - - if (this.preferences) { - void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged) + if (this.preferences) { + void this.notifyEvent(PreferencesServiceEvent.PreferencesChanged) + } } } } diff --git a/packages/snjs/lib/Services/Protection/ProtectionService.ts b/packages/snjs/lib/Services/Protection/ProtectionService.ts index d2e863ea3..8c5dca8ab 100644 --- a/packages/snjs/lib/Services/Protection/ProtectionService.ts +++ b/packages/snjs/lib/Services/Protection/ProtectionService.ts @@ -25,6 +25,10 @@ import { TimingDisplayOption, ProtectionsClientInterface, MutatorClientInterface, + InternalEventHandlerInterface, + InternalEventInterface, + ApplicationEvent, + ApplicationStageChangedEventPayload, } from '@standardnotes/services' import { ContentType } from '@standardnotes/domain-core' @@ -70,7 +74,10 @@ export const ProtectionSessionDurations = [ * like viewing a protected note, as well as managing how long that * authentication should be valid for. */ -export class SNProtectionService extends AbstractService implements ProtectionsClientInterface { +export class SNProtectionService + extends AbstractService + implements ProtectionsClientInterface, InternalEventHandlerInterface +{ private sessionExpiryTimeout = -1 private mobilePasscodeTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit private mobileBiometricsTiming: MobileUnlockTiming | undefined = MobileUnlockTiming.OnQuit @@ -93,13 +100,15 @@ export class SNProtectionService extends AbstractService implem super.deinit() } - override handleApplicationStage(stage: ApplicationStage): Promise { - if (stage === ApplicationStage.LoadedDatabase_12) { - this.updateSessionExpiryTimer(this.getSessionExpiryDate()) - this.mobilePasscodeTiming = this.getMobilePasscodeTiming() - this.mobileBiometricsTiming = this.getMobileBiometricsTiming() + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.LoadedDatabase_12) { + this.updateSessionExpiryTimer(this.getSessionExpiryDate()) + this.mobilePasscodeTiming = this.getMobilePasscodeTiming() + this.mobileBiometricsTiming = this.getMobileBiometricsTiming() + } } - return Promise.resolve() } public hasProtectionSources(): boolean { diff --git a/packages/snjs/lib/Services/Session/SessionManager.ts b/packages/snjs/lib/Services/Session/SessionManager.ts index 630ac1be9..795f2d564 100644 --- a/packages/snjs/lib/Services/Session/SessionManager.ts +++ b/packages/snjs/lib/Services/Session/SessionManager.ts @@ -30,7 +30,7 @@ import { InternalFeatureService, InternalFeature, } from '@standardnotes/services' -import { Base64String, PkcKeyPair } from '@standardnotes/sncrypto-common' +import { Base64String, PkcKeyPair, PureCryptoInterface } from '@standardnotes/sncrypto-common' import { SessionBody, ErrorTag, @@ -52,9 +52,9 @@ import * as Common from '@standardnotes/common' import { RawStorageValue } from './Sessions/Types' import { ShareToken } from './ShareToken' -import { SNApiService } from '../Api/ApiService' +import { LegacyApiService } from '../Api/ApiService' import { DiskStorageService } from '../Storage/DiskStorageService' -import { SNWebSocketsService } from '../Api/WebsocketsService' +import { WebSocketsService } from '../Api/WebsocketsService' import { Strings } from '@Lib/Strings' import { UuidString } from '@Lib/Types/UuidString' import { ChallengeResponse, ChallengeService } from '../Challenge' @@ -78,7 +78,7 @@ const cleanedEmailString = (email: string) => { * server credentials, such as the session token. It also exposes methods for registering * for a new account, signing into an existing one, or changing an account password. */ -export class SNSessionManager +export class SessionManager extends AbstractService implements SessionsClientInterface, InternalEventHandlerInterface { @@ -87,13 +87,14 @@ export class SNSessionManager private session?: Session | LegacySession constructor( - private diskStorageService: DiskStorageService, - private apiService: SNApiService, + private storage: DiskStorageService, + private apiService: LegacyApiService, private userApiService: UserApiServiceInterface, private alertService: AlertService, private encryptionService: EncryptionService, + private crypto: PureCryptoInterface, private challengeService: ChallengeService, - private webSocketsService: SNWebSocketsService, + private webSocketsService: WebSocketsService, private httpService: HttpServiceInterface, private sessionStorageMapper: MapperInterface>, private legacySessionStorageMapper: MapperInterface>, @@ -118,7 +119,7 @@ export class SNSessionManager override deinit(): void { ;(this.encryptionService as unknown) = undefined - ;(this.diskStorageService as unknown) = undefined + ;(this.storage as unknown) = undefined ;(this.apiService as unknown) = undefined ;(this.alertService as unknown) = undefined ;(this.challengeService as unknown) = undefined @@ -141,17 +142,17 @@ export class SNSessionManager this.apiService.setUser(user) } - async initializeFromDisk() { - this.memoizeUser(this.diskStorageService.getValue(StorageKey.User)) + async initializeFromDisk(): Promise { + this.memoizeUser(this.storage.getValue(StorageKey.User)) if (!this.user) { - const legacyUuidLookup = this.diskStorageService.getValue(StorageKey.LegacyUuid) + const legacyUuidLookup = this.storage.getValue(StorageKey.LegacyUuid) if (legacyUuidLookup) { this.memoizeUser({ uuid: legacyUuidLookup, email: legacyUuidLookup }) } } - const rawSession = this.diskStorageService.getValue(StorageKey.Session) + const rawSession = this.storage.getValue(StorageKey.Session) if (rawSession) { try { const session = @@ -286,7 +287,7 @@ export class SNSessionManager email, password, false, - this.diskStorageService.isEphemeralSession(), + this.storage.isEphemeralSession(), currentKeyParams?.version, ) if (isErrorResponse(response)) { @@ -630,10 +631,17 @@ export class SNSessionManager if (!isErrorResponse(rawResponse)) { if (InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) { const eventData: UserKeyPairChangedEventData = { - oldKeyPair, - oldSigningKeyPair, - newKeyPair: parameters.newRootKey.encryptionKeyPair, - newSigningKeyPair: parameters.newRootKey.signingKeyPair, + previous: + oldKeyPair && oldSigningKeyPair + ? { + encryption: oldKeyPair, + signing: oldSigningKeyPair, + } + : undefined, + current: { + encryption: parameters.newRootKey.encryptionKeyPair, + signing: parameters.newRootKey.signingKeyPair, + }, } void this.notifyEvent(SessionEvent.UserKeyPairChanged, eventData) @@ -692,7 +700,7 @@ export class SNSessionManager } private decodeDemoShareToken(token: Base64String): ShareToken { - const jsonString = this.encryptionService.crypto.base64Decode(token) + const jsonString = this.crypto.base64Decode(token) return JSON.parse(jsonString) } @@ -712,7 +720,7 @@ export class SNSessionManager await this.encryptionService.setRootKey(rootKey, wrappingKey) this.memoizeUser(user) - this.diskStorageService.setValue(StorageKey.User, user) + this.storage.setValue(StorageKey.User, user) void this.apiService.setHost(host) this.httpService.setHost(host) diff --git a/packages/snjs/lib/Services/Settings/SNSettingsService.ts b/packages/snjs/lib/Services/Settings/SNSettingsService.ts index 37a8bebf9..9a22963cf 100644 --- a/packages/snjs/lib/Services/Settings/SNSettingsService.ts +++ b/packages/snjs/lib/Services/Settings/SNSettingsService.ts @@ -1,11 +1,11 @@ -import { SNApiService } from '../Api/ApiService' +import { LegacyApiService } from '../Api/ApiService' import { SettingsGateway } from './SettingsGateway' -import { SNSessionManager } from '../Session/SessionManager' +import { SessionManager } from '../Session/SessionManager' import { EmailBackupFrequency, SettingName } from '@standardnotes/settings' import { AbstractService, InternalEventBusInterface } from '@standardnotes/services' import { SettingsClientInterface } from './SettingsClientInterface' -export class SNSettingsService extends AbstractService implements SettingsClientInterface { +export class SettingsService extends AbstractService implements SettingsClientInterface { private provider!: SettingsGateway private frequencyOptionsLabels = { [EmailBackupFrequency.Disabled]: 'No email backups', @@ -14,8 +14,8 @@ export class SNSettingsService extends AbstractService implements SettingsClient } constructor( - private readonly sessionManager: SNSessionManager, - private readonly apiService: SNApiService, + private readonly sessionManager: SessionManager, + private readonly apiService: LegacyApiService, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) diff --git a/packages/snjs/lib/Services/Singleton/SingletonManager.ts b/packages/snjs/lib/Services/Singleton/SingletonManager.ts index b4fac7ca3..8c6aa1f04 100644 --- a/packages/snjs/lib/Services/Singleton/SingletonManager.ts +++ b/packages/snjs/lib/Services/Singleton/SingletonManager.ts @@ -12,7 +12,7 @@ import { Predicate, } from '@standardnotes/models' import { arrayByRemovingFromIndex, extendArray, UuidGenerator } from '@standardnotes/utils' -import { SNSyncService } from '../Sync/SyncService' +import { SyncService } from '../Sync/SyncService' import { AbstractService, InternalEventBusInterface, @@ -33,7 +33,7 @@ import { ContentType } from '@standardnotes/domain-core' * 2. Items can override isSingleton, singletonPredicate, and strategyWhenConflictingWithItem (optional) * to automatically gain singleton resolution. */ -export class SNSingletonManager extends AbstractService implements SingletonManagerInterface { +export class SingletonManager extends AbstractService implements SingletonManagerInterface { private resolveQueue: DecryptedItemInterface[] = [] private removeItemObserver!: () => void @@ -43,7 +43,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana private itemManager: ItemManager, private mutator: MutatorClientInterface, private payloadManager: PayloadManager, - private syncService: SNSyncService, + private sync: SyncService, protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) @@ -51,7 +51,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana } public override deinit(): void { - ;(this.syncService as unknown) = undefined + ;(this.sync as unknown) = undefined ;(this.mutator as unknown) = undefined ;(this.itemManager as unknown) = undefined ;(this.payloadManager as unknown) = undefined @@ -93,7 +93,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana } }) - this.removeSyncObserver = this.syncService.addEventObserver(async (eventName) => { + this.removeSyncObserver = this.sync.addEventObserver(async (eventName) => { if ( eventName === SyncEvent.DownloadFirstSyncCompleted || eventName === SyncEvent.SyncCompletedWithAllItemsUploaded @@ -142,7 +142,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana * of a download-first request. */ if (handled.length > 0 && eventSource === SyncEvent.SyncCompletedWithAllItemsUploaded) { - await this.syncService?.sync({ sourceDescription: 'Resolve singletons for items' }) + await this.sync?.sync({ sourceDescription: 'Resolve singletons for items' }) } } @@ -182,7 +182,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana } /** Item not found, safe to create after full sync has completed */ - if (!this.syncService.getLastSyncDate()) { + if (!this.sync.getLastSyncDate()) { /** * Add a temporary observer in case of long-running sync request, where * the item we're looking for ends up resolving early or in the middle. @@ -199,7 +199,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana } }) - await this.syncService.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' }) + await this.sync.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' }) removeObserver() @@ -233,7 +233,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana const item = await this.mutator.emitItemFromPayload(dirtyPayload, PayloadEmitSource.LocalInserted) - void this.syncService.sync({ sourceDescription: 'After find or create singleton' }) + void this.sync.sync({ sourceDescription: 'After find or create singleton' }) return item as T } @@ -248,7 +248,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana } /** Item not found, safe to create after full sync has completed */ - if (!this.syncService.getLastSyncDate()) { + if (!this.sync.getLastSyncDate()) { /** * Add a temporary observer in case of long-running sync request, where * the item we're looking for ends up resolving early or in the middle. @@ -265,7 +265,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana } }) - await this.syncService.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' }) + await this.sync.sync({ sourceDescription: 'Find or create singleton, before any sync has completed' }) removeObserver() @@ -292,7 +292,7 @@ export class SNSingletonManager extends AbstractService implements SingletonMana const item = await this.mutator.emitItemFromPayload(dirtyPayload, PayloadEmitSource.LocalInserted) - void this.syncService.sync({ sourceDescription: 'After find or create singleton' }) + void this.sync.sync({ sourceDescription: 'After find or create singleton' }) return item as T } diff --git a/packages/snjs/lib/Services/Storage/DiskStorageService.ts b/packages/snjs/lib/Services/Storage/DiskStorageService.ts index f2a8762e1..a5d74b2c3 100644 --- a/packages/snjs/lib/Services/Storage/DiskStorageService.ts +++ b/packages/snjs/lib/Services/Storage/DiskStorageService.ts @@ -1,8 +1,33 @@ import { Copy, extendArray, UuidGenerator, Uuids } from '@standardnotes/utils' import { SNLog } from '../../Log' -import { isErrorDecryptingParameters, SNRootKey } from '@standardnotes/encryption' -import * as Encryption from '@standardnotes/encryption' -import * as Services from '@standardnotes/services' +import { + KeyedDecryptionSplit, + KeyedEncryptionSplit, + SplitPayloadsByEncryptionType, + CreateEncryptionSplitWithKeyLookup, + isErrorDecryptingParameters, + SNRootKey, +} from '@standardnotes/encryption' +import { + AbstractService, + StorageServiceInterface, + InternalEventHandlerInterface, + StoragePersistencePolicies, + StorageValuesObject, + DeviceInterface, + InternalEventBusInterface, + InternalEventInterface, + ApplicationEvent, + ApplicationStageChangedEventPayload, + ApplicationStage, + ValueModesKeys, + StorageValueModes, + namespacedKey, + RawStorageKey, + WrappedStorageValue, + ValuesObjectRecord, + EncryptionProviderInterface, +} from '@standardnotes/services' import { CreateDecryptedLocalStorageContextPayload, CreateDeletedLocalStorageContextPayload, @@ -31,25 +56,28 @@ import { ContentType } from '@standardnotes/domain-core' * decrypt the persisted key/values, and also a method to determine whether a particular * key can decrypt wrapped storage. */ -export class DiskStorageService extends Services.AbstractService implements Services.StorageServiceInterface { - private encryptionProvider!: Encryption.EncryptionProviderInterface +export class DiskStorageService + extends AbstractService + implements StorageServiceInterface, InternalEventHandlerInterface +{ + private encryptionProvider!: EncryptionProviderInterface private storagePersistable = false - private persistencePolicy!: Services.StoragePersistencePolicies + private persistencePolicy!: StoragePersistencePolicies private needsPersist = false - private currentPersistPromise?: Promise + private currentPersistPromise?: Promise - private values!: Services.StorageValuesObject + private values!: StorageValuesObject constructor( - private deviceInterface: Services.DeviceInterface, + private deviceInterface: DeviceInterface, private identifier: string, - protected override internalEventBus: Services.InternalEventBusInterface, + protected override internalEventBus: InternalEventBusInterface, ) { super(internalEventBus) - void this.setPersistencePolicy(Services.StoragePersistencePolicies.Default) + void this.setPersistencePolicy(StoragePersistencePolicies.Default) } - public provideEncryptionProvider(provider: Encryption.EncryptionProviderInterface): void { + public provideEncryptionProvider(provider: EncryptionProviderInterface): void { this.encryptionProvider = provider } @@ -60,21 +88,22 @@ export class DiskStorageService extends Services.AbstractService implements Serv super.deinit() } - override async handleApplicationStage(stage: Services.ApplicationStage) { - await super.handleApplicationStage(stage) - - if (stage === Services.ApplicationStage.Launched_10) { - this.storagePersistable = true - if (this.needsPersist) { - void this.persistValuesToDisk() + async handleEvent(event: InternalEventInterface): Promise { + if (event.type === ApplicationEvent.ApplicationStageChanged) { + const stage = (event.payload as ApplicationStageChangedEventPayload).stage + if (stage === ApplicationStage.Launched_10) { + this.storagePersistable = true + if (this.needsPersist) { + void this.persistValuesToDisk() + } } } } - public async setPersistencePolicy(persistencePolicy: Services.StoragePersistencePolicies) { + public async setPersistencePolicy(persistencePolicy: StoragePersistencePolicies) { this.persistencePolicy = persistencePolicy - if (this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral) { + if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) { await this.deviceInterface.clearNamespacedKeychainValue(this.identifier) await this.deviceInterface.removeAllDatabaseEntries(this.identifier) await this.deviceInterface.removeRawStorageValuesForIdentifier(this.identifier) @@ -82,42 +111,42 @@ export class DiskStorageService extends Services.AbstractService implements Serv } } - public isEphemeralSession() { - return this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral + public isEphemeralSession(): boolean { + return this.persistencePolicy === StoragePersistencePolicies.Ephemeral } - public async initializeFromDisk() { + public async initializeFromDisk(): Promise { const value = await this.deviceInterface.getRawStorageValue(this.getPersistenceKey()) const values = value ? JSON.parse(value as string) : undefined await this.setInitialValues(values) } - private async setInitialValues(values?: Services.StorageValuesObject) { + private async setInitialValues(values?: StorageValuesObject) { const sureValues = values || this.defaultValuesObject() - if (!sureValues[Services.ValueModesKeys.Unwrapped]) { - sureValues[Services.ValueModesKeys.Unwrapped] = {} + if (!sureValues[ValueModesKeys.Unwrapped]) { + sureValues[ValueModesKeys.Unwrapped] = {} } this.values = sureValues if (!this.isStorageWrapped()) { - this.values[Services.ValueModesKeys.Unwrapped] = { - ...(this.values[Services.ValueModesKeys.Wrapped].content as object), - ...this.values[Services.ValueModesKeys.Unwrapped], + this.values[ValueModesKeys.Unwrapped] = { + ...(this.values[ValueModesKeys.Wrapped].content as object), + ...this.values[ValueModesKeys.Unwrapped], } } } public isStorageWrapped(): boolean { - const wrappedValue = this.values[Services.ValueModesKeys.Wrapped] + const wrappedValue = this.values[ValueModesKeys.Wrapped] return wrappedValue != undefined && isEncryptedLocalStoragePayload(wrappedValue) } public async canDecryptWithKey(key: SNRootKey): Promise { - const wrappedValue = this.values[Services.ValueModesKeys.Wrapped] + const wrappedValue = this.values[ValueModesKeys.Wrapped] if (!isEncryptedLocalStoragePayload(wrappedValue)) { throw Error('Attempting to decrypt non decrypted storage value') @@ -143,7 +172,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv content_type: ContentType.TYPES.EncryptedStorage, }) - const split: Encryption.KeyedDecryptionSplit = key + const split: KeyedDecryptionSplit = key ? { usesRootKey: { items: [payload], @@ -161,8 +190,8 @@ export class DiskStorageService extends Services.AbstractService implements Serv return decryptedPayload } - public async decryptStorage() { - const wrappedValue = this.values[Services.ValueModesKeys.Wrapped] + public async decryptStorage(): Promise { + const wrappedValue = this.values[ValueModesKeys.Wrapped] if (!isEncryptedLocalStoragePayload(wrappedValue)) { throw Error('Attempting to decrypt already decrypted storage') @@ -174,7 +203,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv throw SNLog.error(Error('Unable to decrypt storage.')) } - this.values[Services.ValueModesKeys.Unwrapped] = Copy(decryptedPayload.content) + this.values[ValueModesKeys.Unwrapped] = Copy(decryptedPayload.content) } /** @todo This function should be debounced. */ @@ -184,7 +213,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv return } - if (this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral) { + if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) { return } @@ -195,18 +224,18 @@ export class DiskStorageService extends Services.AbstractService implements Serv const values = await this.immediatelyPersistValuesToDisk() /** Save the persisted value so we have access to it in memory (for unit tests afawk) */ - this.values[Services.ValueModesKeys.Wrapped] = values[Services.ValueModesKeys.Wrapped] + this.values[ValueModesKeys.Wrapped] = values[ValueModesKeys.Wrapped] } public async awaitPersist(): Promise { await this.currentPersistPromise } - private async immediatelyPersistValuesToDisk(): Promise { + private async immediatelyPersistValuesToDisk(): Promise { this.currentPersistPromise = this.executeCriticalFunction(async () => { const values = await this.generatePersistableValues() - const persistencePolicySuddenlyChanged = this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral + const persistencePolicySuddenlyChanged = this.persistencePolicy === StoragePersistencePolicies.Ephemeral if (persistencePolicySuddenlyChanged) { return values } @@ -224,10 +253,10 @@ export class DiskStorageService extends Services.AbstractService implements Serv * either as a plain object, or an encrypted item. */ private async generatePersistableValues() { - const rawContent = >Copy(this.values) + const rawContent = >Copy(this.values) - const valuesToWrap = rawContent[Services.ValueModesKeys.Unwrapped] - rawContent[Services.ValueModesKeys.Unwrapped] = undefined + const valuesToWrap = rawContent[ValueModesKeys.Unwrapped] + rawContent[ValueModesKeys.Unwrapped] = undefined const payload = new DecryptedPayload({ uuid: UuidGenerator.GenerateUuid(), @@ -237,7 +266,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv }) if (this.encryptionProvider.hasRootKeyEncryptionSource()) { - const split: Encryption.KeyedEncryptionSplit = { + const split: KeyedEncryptionSplit = { usesRootKeyWithKeyLookup: { items: [payload], }, @@ -245,31 +274,27 @@ export class DiskStorageService extends Services.AbstractService implements Serv const encryptedPayload = await this.encryptionProvider.encryptSplitSingle(split) - rawContent[Services.ValueModesKeys.Wrapped] = CreateEncryptedLocalStorageContextPayload(encryptedPayload) + rawContent[ValueModesKeys.Wrapped] = CreateEncryptedLocalStorageContextPayload(encryptedPayload) } else { - rawContent[Services.ValueModesKeys.Wrapped] = CreateDecryptedLocalStorageContextPayload(payload) + rawContent[ValueModesKeys.Wrapped] = CreateDecryptedLocalStorageContextPayload(payload) } - return rawContent as Services.StorageValuesObject + return rawContent as StorageValuesObject } - public setValue(key: string, value: T, mode = Services.StorageValueModes.Default): void { + public setValue(key: string, value: T, mode = StorageValueModes.Default): void { this.setValueWithNoPersist(key, value, mode) void this.persistValuesToDisk() } - public async setValueAndAwaitPersist( - key: string, - value: unknown, - mode = Services.StorageValueModes.Default, - ): Promise { + public async setValueAndAwaitPersist(key: string, value: unknown, mode = StorageValueModes.Default): Promise { this.setValueWithNoPersist(key, value, mode) await this.persistValuesToDisk() } - private setValueWithNoPersist(key: string, value: unknown, mode = Services.StorageValueModes.Default): void { + private setValueWithNoPersist(key: string, value: unknown, mode = StorageValueModes.Default): void { if (!this.values) { throw Error(`Attempting to set storage key ${key} before loading local storage.`) } @@ -279,7 +304,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv domainStorage[key] = value } - public getValue(key: string, mode = Services.StorageValueModes.Default, defaultValue?: T): T { + public getValue(key: string, mode = StorageValueModes.Default, defaultValue?: T): T { if (!this.values) { throw Error(`Attempting to get storage key ${key} before loading local storage.`) } @@ -293,7 +318,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv return value != undefined ? (value as T) : (defaultValue as T) } - public getAllKeys(mode = Services.StorageValueModes.Default): string[] { + public getAllKeys(mode = StorageValueModes.Default): string[] { if (!this.values) { throw Error('Attempting to get all keys before loading local storage.') } @@ -301,7 +326,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv return Object.keys(this.values[this.domainKeyForMode(mode)]) } - public async removeValue(key: string, mode = Services.StorageValueModes.Default): Promise { + public async removeValue(key: string, mode = StorageValueModes.Default): Promise { if (!this.values) { throw Error(`Attempting to remove storage key ${key} before loading local storage.`) } @@ -318,34 +343,34 @@ export class DiskStorageService extends Services.AbstractService implements Serv * Default persistence key. Platforms can override as needed. */ private getPersistenceKey() { - return Services.namespacedKey(this.identifier, Services.RawStorageKey.StorageObject) + return namespacedKey(this.identifier, RawStorageKey.StorageObject) } private defaultValuesObject( - wrapped?: Services.WrappedStorageValue, - unwrapped?: Services.ValuesObjectRecord, - nonwrapped?: Services.ValuesObjectRecord, + wrapped?: WrappedStorageValue, + unwrapped?: ValuesObjectRecord, + nonwrapped?: ValuesObjectRecord, ) { return DiskStorageService.DefaultValuesObject(wrapped, unwrapped, nonwrapped) } public static DefaultValuesObject( - wrapped: Services.WrappedStorageValue = {} as Services.WrappedStorageValue, - unwrapped: Services.ValuesObjectRecord = {}, - nonwrapped: Services.ValuesObjectRecord = {}, + wrapped: WrappedStorageValue = {} as WrappedStorageValue, + unwrapped: ValuesObjectRecord = {}, + nonwrapped: ValuesObjectRecord = {}, ) { return { - [Services.ValueModesKeys.Wrapped]: wrapped, - [Services.ValueModesKeys.Unwrapped]: unwrapped, - [Services.ValueModesKeys.Nonwrapped]: nonwrapped, - } as Services.StorageValuesObject + [ValueModesKeys.Wrapped]: wrapped, + [ValueModesKeys.Unwrapped]: unwrapped, + [ValueModesKeys.Nonwrapped]: nonwrapped, + } as StorageValuesObject } - private domainKeyForMode(mode: Services.StorageValueModes) { - if (mode === Services.StorageValueModes.Default) { - return Services.ValueModesKeys.Unwrapped - } else if (mode === Services.StorageValueModes.Nonwrapped) { - return Services.ValueModesKeys.Nonwrapped + private domainKeyForMode(mode: StorageValueModes) { + if (mode === StorageValueModes.Default) { + return ValueModesKeys.Unwrapped + } else if (mode === StorageValueModes.Nonwrapped) { + return ValueModesKeys.Nonwrapped } else { throw Error('Invalid mode') } @@ -368,7 +393,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv } public async savePayloads(payloads: FullyFormedPayloadInterface[]): Promise { - if (this.persistencePolicy === Services.StoragePersistencePolicies.Ephemeral) { + if (this.persistencePolicy === StoragePersistencePolicies.Ephemeral) { return } @@ -380,7 +405,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv const unencryptable: DecryptedPayloadInterface[] = [] const { rootKeyEncryption, keySystemRootKeyEncryption, itemsKeyEncryption } = - Encryption.SplitPayloadsByEncryptionType(decrypted) + SplitPayloadsByEncryptionType(decrypted) if (itemsKeyEncryption) { extendArray(encryptable, itemsKeyEncryption) @@ -402,9 +427,9 @@ export class DiskStorageService extends Services.AbstractService implements Serv await this.deletePayloads(discardable) } - const encryptableSplit = Encryption.SplitPayloadsByEncryptionType(encryptable) + const encryptableSplit = SplitPayloadsByEncryptionType(encryptable) - const keyLookupSplit = Encryption.CreateEncryptionSplitWithKeyLookup(encryptableSplit) + const keyLookupSplit = CreateEncryptionSplitWithKeyLookup(encryptableSplit) const encryptedResults = await this.encryptionProvider.encryptSplit(keyLookupSplit) @@ -449,9 +474,7 @@ export class DiskStorageService extends Services.AbstractService implements Serv await this.clearValues() await this.clearAllPayloads() - await this.deviceInterface.removeRawStorageValue( - Services.namespacedKey(this.identifier, Services.RawStorageKey.SnjsVersion), - ) + await this.deviceInterface.removeRawStorageValue(namespacedKey(this.identifier, RawStorageKey.SnjsVersion)) await this.deviceInterface.removeRawStorageValue(this.getPersistenceKey()) }) diff --git a/packages/snjs/lib/Services/Sync/Account/Operation.ts b/packages/snjs/lib/Services/Sync/Account/Operation.ts index b62f76fe1..61656cf97 100644 --- a/packages/snjs/lib/Services/Sync/Account/Operation.ts +++ b/packages/snjs/lib/Services/Sync/Account/Operation.ts @@ -2,7 +2,7 @@ import { ServerSyncPushContextualPayload } from '@standardnotes/models' import { arrayByDifference, nonSecureRandomIdentifier, subtractFromArray } from '@standardnotes/utils' import { ServerSyncResponse } from '@Lib/Services/Sync/Account/Response' import { ResponseSignalReceiver, SyncSignal } from '@Lib/Services/Sync/Signals' -import { SNApiService } from '../../Api/ApiService' +import { LegacyApiService } from '../../Api/ApiService' export const SyncUpDownLimit = 150 @@ -23,7 +23,7 @@ export class AccountSyncOperation { constructor( public readonly payloads: ServerSyncPushContextualPayload[], private receiver: ResponseSignalReceiver, - private apiService: SNApiService, + private apiService: LegacyApiService, public readonly options: { syncToken?: string paginationToken?: string diff --git a/packages/snjs/lib/Services/Sync/SyncClientInterface.ts b/packages/snjs/lib/Services/Sync/SyncClientInterface.ts deleted file mode 100644 index 775b3cfed..000000000 --- a/packages/snjs/lib/Services/Sync/SyncClientInterface.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { SyncOpStatus } from './SyncOpStatus' -import { AbstractService, SyncEvent, SyncOptions } from '@standardnotes/services' - -export interface SyncClientInterface extends AbstractService { - setLaunchPriorityUuids(launchPriorityUuids: string[]): void - sync(options?: Partial): Promise - isOutOfSync(): boolean - getLastSyncDate(): Date | undefined - getSyncStatus(): SyncOpStatus - lockSyncing(): void - unlockSyncing(): void - - completedOnlineDownloadFirstSync: boolean -} diff --git a/packages/snjs/lib/Services/Sync/SyncService.ts b/packages/snjs/lib/Services/Sync/SyncService.ts index 2381fd2ac..895efdf37 100644 --- a/packages/snjs/lib/Services/Sync/SyncService.ts +++ b/packages/snjs/lib/Services/Sync/SyncService.ts @@ -13,14 +13,12 @@ import { import { ItemManager } from '@Lib/Services/Items/ItemManager' import { OfflineSyncOperation } from '@Lib/Services/Sync/Offline/Operation' import { PayloadManager } from '../Payloads/PayloadManager' -import { SNApiService } from '../Api/ApiService' -import { SNHistoryManager } from '../History/HistoryManager' +import { LegacyApiService } from '../Api/ApiService' +import { HistoryManager } from '../History/HistoryManager' import { SNLog } from '@Lib/Log' -import { SNSessionManager } from '../Session/SessionManager' +import { SessionManager } from '../Session/SessionManager' import { DiskStorageService } from '../Storage/DiskStorageService' -import { SyncClientInterface } from './SyncClientInterface' import { SyncPromise } from './Types' -import { SyncOpStatus } from '@Lib/Services/Sync/SyncOpStatus' import { ServerSyncResponse } from '@Lib/Services/Sync/Account/Response' import { ServerSyncResponseResolver } from '@Lib/Services/Sync/Account/ResponseResolver' import { SyncSignal, SyncStats } from '@Lib/Services/Sync/Signals' @@ -84,6 +82,7 @@ import { SyncEventReceivedRemoteSharedVaultsData, SyncEventReceivedUserEventsData, SyncEventReceivedAsymmetricMessagesData, + SyncOpStatus, } from '@standardnotes/services' import { OfflineSyncResponse } from './Offline/Response' import { @@ -121,9 +120,9 @@ const ContentTypeLocalLoadPriorty = [ * After each sync request, any changes made or retrieved are also persisted locally. * The sync service largely does not perform any task unless it is called upon. */ -export class SNSyncService +export class SyncService extends AbstractService - implements SyncServiceInterface, InternalEventHandlerInterface, SyncClientInterface + implements SyncServiceInterface, InternalEventHandlerInterface { private dirtyIndexAtLastPresyncSave?: number private lastSyncDate?: Date @@ -152,12 +151,12 @@ export class SNSyncService constructor( private itemManager: ItemManager, - private sessionManager: SNSessionManager, + private sessionManager: SessionManager, private encryptionService: EncryptionService, private storageService: DiskStorageService, private payloadManager: PayloadManager, - private apiService: SNApiService, - private historyService: SNHistoryManager, + private apiService: LegacyApiService, + private historyService: HistoryManager, private device: DeviceInterface, private identifier: string, private readonly options: ApplicationSyncOptions, @@ -968,25 +967,25 @@ export class SNSyncService const historyMap = this.historyService.getHistoryMapCopy() - if (response.userEvents) { + if (response.userEvents && response.userEvents.length > 0) { await this.notifyEventSync(SyncEvent.ReceivedUserEvents, response.userEvents as SyncEventReceivedUserEventsData) } - if (response.asymmetricMessages) { + if (response.asymmetricMessages && response.asymmetricMessages.length > 0) { await this.notifyEventSync( SyncEvent.ReceivedAsymmetricMessages, response.asymmetricMessages as SyncEventReceivedAsymmetricMessagesData, ) } - if (response.vaults) { + if (response.vaults && response.vaults.length > 0) { await this.notifyEventSync( SyncEvent.ReceivedRemoteSharedVaults, response.vaults as SyncEventReceivedRemoteSharedVaultsData, ) } - if (response.vaultInvites) { + if (response.vaultInvites && response.vaultInvites.length > 0) { await this.notifyEventSync( SyncEvent.ReceivedSharedVaultInvites, response.vaultInvites as SyncEventReceivedSharedVaultInvitesData, diff --git a/packages/snjs/lib/Services/Sync/index.ts b/packages/snjs/lib/Services/Sync/index.ts index b60c1a013..2e75c0374 100644 --- a/packages/snjs/lib/Services/Sync/index.ts +++ b/packages/snjs/lib/Services/Sync/index.ts @@ -1,7 +1,5 @@ export * from './SyncService' export * from './Types' -export * from './SyncOpStatus' -export * from './SyncClientInterface' export * from './Account/Operation' export * from './Account/ResponseResolver' export * from './Offline/Operation' diff --git a/packages/snjs/lib/index.ts b/packages/snjs/lib/index.ts index ca3479706..32d61d28c 100644 --- a/packages/snjs/lib/index.ts +++ b/packages/snjs/lib/index.ts @@ -1,4 +1,5 @@ export * from './Application' +export * from './Application/Dependencies/Types' export * from './ApplicationGroup' export * from './Client' export * from './Domain' diff --git a/packages/snjs/mocha/001.test.js b/packages/snjs/mocha/001.test.js index 1db694631..2d8214eb0 100644 --- a/packages/snjs/mocha/001.test.js +++ b/packages/snjs/mocha/001.test.js @@ -31,7 +31,7 @@ describe('001 protocol operations', () => { }) it('cost minimum', () => { - expect(application.encryptionService.costMinimumForVersion('001')).to.equal(3000) + expect(application.encryption.costMinimumForVersion('001')).to.equal(3000) }) it('generates valid keys for registration', async () => { @@ -46,7 +46,7 @@ describe('001 protocol operations', () => { it('generates valid keys from existing params and decrypts', async () => { const password = 'password' - const keyParams = await application.encryptionService.createKeyParams({ + const keyParams = await application.encryption.createKeyParams({ pw_func: 'pbkdf2', pw_alg: 'sha512', pw_key_size: 512, @@ -67,7 +67,7 @@ describe('001 protocol operations', () => { 'sVuHmG0XAp1PRDE8r8XqFXijjP8Pqdwal9YFRrXK4hKLt1yyq8MwQU+1Z95Tz/b7ajYdidwFE0iDwd8Iu8281VtJsQ4yhh2tJiAzBy6newyHfhA5nH93yZ3iXRJaG87bgNQE9lsXzTV/OHAvqMuQtw/QVSWI3Qy1Pyu1Tn72q7FPKKhRRkzEEZ+Ax0BA1fHg', uuid: '54001a6f-7c22-4b34-8316-fadf9b1fc255', }) - const decrypted = await application.encryptionService.decryptSplitSingle({ + const decrypted = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [payload], key: key, diff --git a/packages/snjs/mocha/002.test.js b/packages/snjs/mocha/002.test.js index dba890df0..21d9779fd 100644 --- a/packages/snjs/mocha/002.test.js +++ b/packages/snjs/mocha/002.test.js @@ -30,7 +30,7 @@ describe('002 protocol operations', () => { }) it('cost minimum', () => { - expect(application.encryptionService.costMinimumForVersion('002')).to.equal(3000) + expect(application.encryption.costMinimumForVersion('002')).to.equal(3000) }) it('generates valid keys for registration', async () => { @@ -46,7 +46,7 @@ describe('002 protocol operations', () => { it('generates valid keys from existing params and decrypts', async () => { const password = 'password' - const keyParams = await application.encryptionService.createKeyParams({ + const keyParams = await application.encryption.createKeyParams({ pw_salt: '8d381ef44cdeab1489194f87066b747b46053a833ee24956e846e7b40440f5f4', pw_cost: 101000, version: '002', @@ -64,7 +64,7 @@ describe('002 protocol operations', () => { '002:24a8e8f7728bbe06605d8209d87ad338d3d15ef81154bb64d3967c77daa01333:959b042a-3892-461e-8c50-477c10c7c40a:f1d294388742dca34f6f266a01483a4e:VdlEDyjhZ35GbJDg8ruSZv3Tp6WtMME3T5LLvcBYLHIMhrMi0RlPK83lK6F0aEaZvY82pZ0ntU+XpAX7JMSEdKdPXsACML7WeFrqKb3z2qHnA7NxgnIC0yVT/Z2mRrvlY3NNrUPGwJbfRcvfS7FVyw87MemT9CSubMZRviXvXETx82t7rsgjV/AIwOOeWhFi', uuid: '959b042a-3892-461e-8c50-477c10c7c40a', }) - const decrypted = await application.encryptionService.decryptSplitSingle({ + const decrypted = await application.encryption.decryptSplitSingle({ usesRootKey: { items: [payload], key: key, diff --git a/packages/snjs/mocha/003.test.js b/packages/snjs/mocha/003.test.js index d07aca6cd..25f5cb850 100644 --- a/packages/snjs/mocha/003.test.js +++ b/packages/snjs/mocha/003.test.js @@ -39,7 +39,7 @@ describe('003 protocol operations', () => { it('cost minimum should throw', () => { expect(() => { - sharedApplication.encryptionService.costMinimumForVersion('003') + sharedApplication.encryption.costMinimumForVersion('003') }).to.throw('Cost minimums only apply to versions <= 002') }) @@ -59,7 +59,7 @@ describe('003 protocol operations', () => { it('computes proper keys for sign in', async () => { const identifier = 'foo@bar.com' const password = 'very_secure' - const keyParams = sharedApplication.encryptionService.createKeyParams({ + const keyParams = sharedApplication.encryption.createKeyParams({ pw_nonce: 'baaec0131d677cf993381367eb082fe377cefe70118c1699cb9b38f0bc850e7b', identifier: identifier, version: '003', @@ -73,7 +73,7 @@ describe('003 protocol operations', () => { it('can decrypt item generated with web version 3.3.6', async () => { const identifier = 'demo@standardnotes.org' const password = 'password' - const keyParams = sharedApplication.encryptionService.createKeyParams({ + const keyParams = sharedApplication.encryption.createKeyParams({ pw_nonce: '31107837b44d86179140b7c602a55d694243e2e9ced0c4c914ac21ad90215055', identifier: identifier, version: '003', diff --git a/packages/snjs/mocha/004.test.js b/packages/snjs/mocha/004.test.js index 390a5fc81..839fc765b 100644 --- a/packages/snjs/mocha/004.test.js +++ b/packages/snjs/mocha/004.test.js @@ -25,16 +25,12 @@ describe('004 protocol operations', function () { it('cost minimum should throw', function () { expect(function () { - application.encryptionService.costMinimumForVersion('004') + application.encryption.costMinimumForVersion('004') }).to.throw('Cost minimums only apply to versions <= 002') }) it('generates valid keys for registration', async function () { - const key = await application.encryptionService.createRootKey( - _identifier, - _password, - KeyParamsOrigination.Registration, - ) + const key = await application.encryption.createRootKey(_identifier, _password, KeyParamsOrigination.Registration) expect(key.masterKey).to.be.ok @@ -51,7 +47,7 @@ describe('004 protocol operations', function () { it('computes proper keys for sign in', async function () { const identifier = 'foo@bar.com' const password = 'very_secure' - const keyParams = application.encryptionService.createKeyParams({ + const keyParams = application.encryption.createKeyParams({ pw_nonce: 'baaec0131d677cf993381367eb082fe377cefe70118c1699cb9b38f0bc850e7b', identifier: identifier, version: '004', @@ -64,7 +60,7 @@ describe('004 protocol operations', function () { it('generates random key', async function () { const length = 96 - const key = await application.encryptionService.crypto.generateRandomKey(length) + const key = await application.encryption.crypto.generateRandomKey(length) expect(key.length).to.equal(length / 4) }) @@ -78,7 +74,7 @@ describe('004 protocol operations', function () { }), }) - const operator = application.encryptionService.operators.operatorForVersion(ProtocolVersion.V004) + const operator = application.dependencies.get(TYPES.EncryptionOperators).operatorForVersion(ProtocolVersion.V004) const encrypted = await operator.generateEncryptedParameters(payload, rootKey) const decrypted = await operator.generateDecryptedParameters(encrypted, rootKey) @@ -97,7 +93,7 @@ describe('004 protocol operations', function () { }), }) - const operator = application.encryptionService.operators.operatorForVersion(ProtocolVersion.V004) + const operator = application.dependencies.get(TYPES.EncryptionOperators).operatorForVersion(ProtocolVersion.V004) const encrypted = await operator.generateEncryptedParameters(payload, rootKey) const decrypted = await operator.generateDecryptedParameters( @@ -112,7 +108,7 @@ describe('004 protocol operations', function () { }) it('generates existing keys for key params', async function () { - const key = await application.encryptionService.computeRootKey(_password, rootKeyParams) + const key = await application.encryption.computeRootKey(_password, rootKeyParams) expect(key.compare(rootKey)).to.be.true }) diff --git a/packages/snjs/mocha/TestRegistry/BaseTests.js b/packages/snjs/mocha/TestRegistry/BaseTests.js index da672bf47..9d1bb9d22 100644 --- a/packages/snjs/mocha/TestRegistry/BaseTests.js +++ b/packages/snjs/mocha/TestRegistry/BaseTests.js @@ -55,4 +55,4 @@ export const BaseTests = [ 'session.test.js', 'subscriptions.test.js', 'recovery.test.js', -]; +] diff --git a/packages/snjs/mocha/TestRegistry/VaultTests.js b/packages/snjs/mocha/TestRegistry/VaultTests.js index 0f5a5034a..af3496be2 100644 --- a/packages/snjs/mocha/TestRegistry/VaultTests.js +++ b/packages/snjs/mocha/TestRegistry/VaultTests.js @@ -5,6 +5,8 @@ export const VaultTests = [ 'vaults/contacts.test.js', 'vaults/crypto.test.js', 'vaults/asymmetric-messages.test.js', + 'vaults/keypair-change.test.js', + 'vaults/signatures.test.js', 'vaults/shared_vaults.test.js', 'vaults/invites.test.js', 'vaults/items.test.js', diff --git a/packages/snjs/mocha/actions.test.js b/packages/snjs/mocha/actions.test.js index 11d8348ec..574bacad3 100644 --- a/packages/snjs/mocha/actions.test.js +++ b/packages/snjs/mocha/actions.test.js @@ -14,8 +14,8 @@ describe('actions service', () => { localStorage.clear() this.application = await Factory.createInitAppWithFakeCrypto() - this.itemManager = this.application.itemManager - this.actionsManager = this.application.actionsManager + this.itemManager = this.application.items + this.actionsManager = this.application.actions this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() @@ -25,7 +25,7 @@ describe('actions service', () => { password: this.password, }) - const rootKey = await this.application.encryptionService.createRootKey( + const rootKey = await this.application.encryption.createRootKey( this.email, this.password, KeyParamsOrigination.Registration, @@ -117,7 +117,7 @@ describe('actions service', () => { }) const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -170,7 +170,10 @@ describe('actions service', () => { }) // Extension item - const extensionItem = await this.application.mutator.createItem(ContentType.TYPES.ActionsExtension, this.actionsExtension) + const extensionItem = await this.application.mutator.createItem( + ContentType.TYPES.ActionsExtension, + this.actionsExtension, + ) this.extensionItemUuid = extensionItem.uuid }) @@ -308,8 +311,8 @@ describe('actions service', () => { }) beforeEach(async function () { - this.actionsManager.deviceInterface.openUrl = (url) => url - this.deviceInterfaceOpenUrl = sandbox.spy(this.actionsManager.deviceInterface, 'openUrl') + this.actionsManager.device.openUrl = (url) => url + this.deviceInterfaceOpenUrl = sandbox.spy(this.actionsManager.device, 'openUrl') }) this.afterEach(async function () { @@ -359,14 +362,14 @@ describe('actions service', () => { const response = await this.actionsManager.runAction(this.encryptedPostAction, this.noteItem) expect(response.items[0].enc_item_key).to.satisfy((string) => { - return string.startsWith(this.application.encryptionService.getLatestVersion()) + return string.startsWith(this.application.encryption.getLatestVersion()) }) expect(response.items[0].uuid).to.eq(this.noteItem.uuid) expect(response.items[0].auth_hash).to.not.be.ok expect(response.items[0].content_type).to.be.ok expect(response.items[0].created_at).to.be.ok expect(response.items[0].content).to.satisfy((string) => { - return string.startsWith(this.application.encryptionService.getLatestVersion()) + return string.startsWith(this.application.encryption.getLatestVersion()) }) }) diff --git a/packages/snjs/mocha/application.test.js b/packages/snjs/mocha/application.test.js index 5dc740f42..88e3e18f3 100644 --- a/packages/snjs/mocha/application.test.js +++ b/packages/snjs/mocha/application.test.js @@ -24,12 +24,12 @@ describe('application instances', () => { const context2 = await Factory.createAppContext({ identifier: 'app2' }) await Promise.all([context1.launch(), context2.launch()]) - expect(context1.application.payloadManager).to.equal(context1.application.payloadManager) - expect(context1.application.payloadManager).to.not.equal(context2.application.payloadManager) + expect(context1.application.payloads).to.equal(context1.application.payloads) + expect(context1.application.payloads).to.not.equal(context2.application.payloads) await Factory.createMappedNote(context1.application) - expect(context1.application.itemManager.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1) - expect(context2.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) + expect(context1.application.items.items.length).length.to.equal(BaseItemCounts.DefaultItems + 1) + expect(context2.application.items.items.length).to.equal(BaseItemCounts.DefaultItems) await context1.deinit() await context2.deinit() @@ -40,16 +40,16 @@ describe('application instances', () => { const app2 = await Factory.createAndInitializeApplication('app2') await Factory.createMappedNote(app1) - await app1.syncService.sync(syncOptions) + await app1.sync.sync(syncOptions) - expect((await app1.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1) - expect((await app2.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems) + expect((await app1.storage.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1) + expect((await app2.storage.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems) await Factory.createMappedNote(app2) - await app2.syncService.sync(syncOptions) + await app2.sync.sync(syncOptions) - expect((await app1.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1) - expect((await app2.diskStorageService.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1) + expect((await app1.storage.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1) + expect((await app2.storage.getAllRawPayloads()).length).length.to.equal(BaseItemCounts.DefaultItems + 1) await Factory.safeDeinit(app1) await Factory.safeDeinit(app2) }) @@ -58,7 +58,7 @@ describe('application instances', () => { /** This test will always succeed but should be observed for console exceptions */ const app = await Factory.createAndInitializeApplication('app') /** Don't await */ - app.diskStorageService.persistValuesToDisk() + app.storage.persistValuesToDisk() await app.prepareForDeinit() await Factory.safeDeinit(app) }) @@ -101,13 +101,13 @@ describe('application instances', () => { const app = await Factory.createAndInitializeApplication('app') /** Don't await */ const MaximumWaitTime = 0.5 - app.diskStorageService.executeCriticalFunction(async () => { + app.storage.executeCriticalFunction(async () => { /** If we sleep less than the maximum, locking should occur safely. * If we sleep more than the maximum, locking should occur with exception on * app deinit. */ await Factory.sleep(MaximumWaitTime - 0.05) /** Access any deviceInterface function */ - app.diskStorageService.deviceInterface.getAllDatabaseEntries(app.identifier) + app.device.getAllDatabaseEntries(app.identifier) }) await app.lock() }) diff --git a/packages/snjs/mocha/auth-fringe-cases.test.js b/packages/snjs/mocha/auth-fringe-cases.test.js index 582a1812f..4de8801d2 100644 --- a/packages/snjs/mocha/auth-fringe-cases.test.js +++ b/packages/snjs/mocha/auth-fringe-cases.test.js @@ -48,7 +48,7 @@ describe('auth fringe cases', () => { console.warn("Expecting errors 'Unable to find operator for version undefined'") const restartedApplication = await Factory.restartApplication(context.application) - const refreshedNote = restartedApplication.payloadManager.findOne(note.uuid) + const refreshedNote = restartedApplication.payloads.findOne(note.uuid) expect(refreshedNote.errorDecrypting).to.equal(true) await Factory.safeDeinit(restartedApplication) @@ -67,9 +67,9 @@ describe('auth fringe cases', () => { ) await restartedApplication.signIn(context.email, context.password, undefined, undefined, undefined, awaitSync) - const refreshedNote = restartedApplication.itemManager.findItem(note.uuid) + const refreshedNote = restartedApplication.items.findItem(note.uuid) expect(isDecryptedItem(refreshedNote)).to.equal(true) - expect(restartedApplication.itemManager.getDisplayableNotes().length).to.equal(1) + expect(restartedApplication.items.getDisplayableNotes().length).to.equal(1) await Factory.safeDeinit(restartedApplication) }).timeout(10000) }) @@ -97,15 +97,13 @@ describe('auth fringe cases', () => { /** Sign in and merge local data */ await newApplication.signIn(context.email, context.password, undefined, undefined, true, true) - expect(newApplication.itemManager.getDisplayableNotes().length).to.equal(2) + expect(newApplication.items.getDisplayableNotes().length).to.equal(2) - expect( - newApplication.itemManager.getDisplayableNotes().find((n) => n.uuid === firstVersionOfNote.uuid).text, - ).to.equal(staleText) + expect(newApplication.items.getDisplayableNotes().find((n) => n.uuid === firstVersionOfNote.uuid).text).to.equal( + staleText, + ) - const conflictedCopy = newApplication.itemManager - .getDisplayableNotes() - .find((n) => n.uuid !== firstVersionOfNote.uuid) + const conflictedCopy = newApplication.items.getDisplayableNotes().find((n) => n.uuid !== firstVersionOfNote.uuid) expect(conflictedCopy.text).to.equal(serverText) expect(conflictedCopy.duplicate_of).to.equal(firstVersionOfNote.uuid) await Factory.safeDeinit(newApplication) diff --git a/packages/snjs/mocha/auth.test.js b/packages/snjs/mocha/auth.test.js index 86f5b0540..61847252e 100644 --- a/packages/snjs/mocha/auth.test.js +++ b/packages/snjs/mocha/auth.test.js @@ -31,7 +31,7 @@ describe('basic auth', function () { it('successfully register new account', async function () { const response = await this.application.register(this.email, this.password) expect(response).to.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok }) it('fails register new account with short password', async function () { @@ -49,18 +49,18 @@ describe('basic auth', function () { 'For your security, please choose a longer password or, ideally, a passphrase, and try again.', ) - expect(await this.application.encryptionService.getRootKey()).to.not.be.ok + expect(await this.application.encryption.getRootKey()).to.not.be.ok }) it('successfully signs out of account', async function () { await this.application.register(this.email, this.password) - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok this.application = await Factory.signOutApplicationAndReturnNew(this.application) - expect(await this.application.encryptionService.getRootKey()).to.not.be.ok - expect(this.application.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyNone) + expect(await this.application.encryption.getRootKey()).to.not.be.ok + expect(this.application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyNone) }) it('successfully signs in to registered account', async function () { @@ -69,7 +69,7 @@ describe('basic auth', function () { const response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) expect(response).to.be.ok expect(response.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok }).timeout(20000) it('cannot sign while already signed in', async function () { @@ -79,7 +79,7 @@ describe('basic auth', function () { const response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) expect(response).to.be.ok expect(response.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok let error try { @@ -99,7 +99,7 @@ describe('basic auth', function () { error = e } expect(error).to.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok }).timeout(20000) it('cannot perform two sign-ins at the same time', async function () { @@ -111,7 +111,7 @@ describe('basic auth', function () { const response = await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) expect(response).to.be.ok expect(response.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok })(), (async () => { /** Make sure the first function runs first */ @@ -134,7 +134,7 @@ describe('basic auth', function () { const response = await this.application.register(this.email, this.password) expect(response).to.be.ok expect(response.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok })(), (async () => { /** Make sure the first function runs first */ @@ -185,7 +185,7 @@ describe('basic auth', function () { */ await this.application.register(uppercase, this.password) - const response = await this.application.sessionManager.retrieveKeyParams(lowercase) + const response = await this.application.sessions.retrieveKeyParams(lowercase) const keyParams = response.keyParams expect(keyParams.identifier).to.equal(lowercase) expect(keyParams.identifier).to.not.equal(uppercase) @@ -204,7 +204,7 @@ describe('basic auth', function () { const response = await this.application.signIn(uppercase, this.password, undefined, undefined, undefined, true) expect(response).to.be.ok expect(response.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok }).timeout(20000) it('can sign into account regardless of whitespace', async function () { @@ -220,7 +220,7 @@ describe('basic auth', function () { const response = await this.application.signIn(withspace, this.password, undefined, undefined, undefined, true) expect(response).to.be.ok expect(response.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok }).timeout(20000) it('fails login with wrong password', async function () { @@ -229,7 +229,7 @@ describe('basic auth', function () { const response = await this.application.signIn(this.email, 'wrongpassword', undefined, undefined, undefined, true) expect(response).to.be.ok expect(response.data.error).to.be.ok - expect(await this.application.encryptionService.getRootKey()).to.not.be.ok + expect(await this.application.encryption.getRootKey()).to.not.be.ok }).timeout(20000) it('fails to change to short password', async function () { @@ -255,7 +255,7 @@ describe('basic auth', function () { let outOfSync = true let didCompletePostDownloadFirstSync = false let didCompleteDownloadFirstSync = false - this.application.syncService.addEventObserver((eventName) => { + this.application.sync.addEventObserver((eventName) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { didCompleteDownloadFirstSync = true } @@ -265,7 +265,7 @@ describe('basic auth', function () { if (!didCompletePostDownloadFirstSync && eventName === SyncEvent.PaginatedSyncRequestCompleted) { didCompletePostDownloadFirstSync = true /** Should be in sync */ - outOfSync = this.application.syncService.isOutOfSync() + outOfSync = this.application.sync.isOutOfSync() } }) @@ -289,9 +289,9 @@ describe('basic auth', function () { this.expectedItemCount += noteCount - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) const newPassword = 'newpassword' const response = await this.application.changePassword(this.password, newPassword) @@ -299,18 +299,18 @@ describe('basic auth', function () { /** New items key */ this.expectedItemCount++ - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(response.error).to.not.be.ok - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) - expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(this.application.payloads.invalidPayloads.length).to.equal(0) - await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() - await this.application.syncService.sync(syncOptions) + await this.application.sync.markAllItemsAsNeedingSyncAndPersist() + await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) - const note = this.application.itemManager.getDisplayableNotes()[0] + const note = this.application.items.getDisplayableNotes()[0] /** * Create conflict for a note. First modify the item without saving so that @@ -339,10 +339,10 @@ describe('basic auth', function () { expect(signinResponse).to.be.ok expect(signinResponse.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) - expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(this.application.payloads.invalidPayloads.length).to.equal(0) } it('successfully changes password', changePassword).timeout(40000) @@ -383,7 +383,7 @@ describe('basic auth', function () { const noteCount = 10 await Factory.createManyMappedNotes(this.application, noteCount) this.expectedItemCount += noteCount - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) const numTimesToChangePw = 3 let newPassword = Factory.randomString() @@ -398,16 +398,16 @@ describe('basic auth', function () { currentPassword = newPassword newPassword = Factory.randomString() - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) - expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) + expect(this.application.payloads.invalidPayloads.length).to.equal(0) - await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() - await this.application.syncService.sync(syncOptions) + await this.application.sync.markAllItemsAsNeedingSyncAndPersist() + await this.application.sync.sync(syncOptions) this.application = await this.context.signout() - expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) - expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) + expect(this.application.items.items.length).to.equal(BaseItemCounts.DefaultItems) + expect(this.application.payloads.invalidPayloads.length).to.equal(0) /** Should login with new password */ const signinResponse = await this.application.signIn( @@ -421,7 +421,7 @@ describe('basic auth', function () { expect(signinResponse).to.be.ok expect(signinResponse.data.error).to.not.be.ok - expect(await this.application.encryptionService.getRootKey()).to.be.ok + expect(await this.application.encryption.getRootKey()).to.be.ok } }).timeout(80000) @@ -432,7 +432,7 @@ describe('basic auth', function () { password: this.password, }) this.application = await Factory.signOutApplicationAndReturnNew(this.application) - const performSignIn = sinon.spy(this.application.sessionManager, 'performSignIn') + const performSignIn = sinon.spy(this.application.sessions, 'performSignIn') await this.application.signIn(this.email, 'wrong password', undefined, undefined, undefined, true) expect(performSignIn.callCount).to.equal(1) }) @@ -441,8 +441,8 @@ describe('basic auth', function () { /** Should delete the new items key locally without marking it as deleted so that it doesn't sync */ await this.context.register() - const originalImpl = this.application.encryptionService.getSureDefaultItemsKey - this.application.encryptionService.getSureDefaultItemsKey = () => { + const originalImpl = this.application.encryption.getSureDefaultItemsKey + this.application.encryption.getSureDefaultItemsKey = () => { return { neverSynced: true, } @@ -454,7 +454,7 @@ describe('basic auth', function () { await this.context.changePassword('new-password') - this.application.encryptionService.getSureDefaultItemsKey = originalImpl + this.application.encryption.getSureDefaultItemsKey = originalImpl expect(mutatorSpy.callCount).to.equal(0) expect(removeItemsSpy.callCount).to.equal(1) @@ -518,8 +518,8 @@ describe('basic auth', function () { const _response = await this.application.deleteAccount() - sinon.spy(snApp.challengeService, 'sendChallenge') - const spyCall = snApp.challengeService.sendChallenge.getCall(0) + sinon.spy(snApp.challenges, 'sendChallenge') + const spyCall = snApp.challenges.sendChallenge.getCall(0) const challenge = spyCall.firstArg expect(challenge.prompts).to.have.lengthOf(2) expect(challenge.prompts[0].validation).to.equal(ChallengeValidation.AccountPassword) diff --git a/packages/snjs/mocha/backups.test.js b/packages/snjs/mocha/backups.test.js index 45e804bb1..7b11bca52 100644 --- a/packages/snjs/mocha/backups.test.js +++ b/packages/snjs/mocha/backups.test.js @@ -27,10 +27,10 @@ describe('backups', function () { it('backup file should have a version number', async function () { let data = await this.application.createDecryptedBackupFile() - expect(data.version).to.equal(this.application.encryptionService.getLatestVersion()) + expect(data.version).to.equal(this.application.encryption.getLatestVersion()) await this.application.addPasscode('passcode') data = await this.application.createEncryptedBackupFileForAutomatedDesktopBackups() - expect(data.version).to.equal(this.application.encryptionService.getLatestVersion()) + expect(data.version).to.equal(this.application.encryption.getLatestVersion()) }) it('no passcode + no account backup file should have correct number of items', async function () { @@ -143,7 +143,7 @@ describe('backups', function () { const note = await Factory.createSyncedNote(this.application) - const encrypted = await this.application.encryptionService.encryptSplitSingle({ + const encrypted = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -153,9 +153,9 @@ describe('backups', function () { errorDecrypting: true, }) - await this.application.payloadManager.emitPayload(errored) + await this.application.payloads.emitPayload(errored) - const erroredItem = this.application.itemManager.findAnyItem(errored.uuid) + const erroredItem = this.application.items.findAnyItem(errored.uuid) expect(erroredItem.errorDecrypting).to.equal(true) diff --git a/packages/snjs/mocha/device_auth.test.js b/packages/snjs/mocha/device_auth.test.js index e21cd7fd5..982ddb90b 100644 --- a/packages/snjs/mocha/device_auth.test.js +++ b/packages/snjs/mocha/device_auth.test.js @@ -18,11 +18,11 @@ describe('device authentication', function () { const application = await Factory.createAndInitializeApplication(namespace) const passcode = 'foobar' const wrongPasscode = 'barfoo' - expect(await application.protectionService.createLaunchChallenge()).to.not.be.ok + expect(await application.protections.createLaunchChallenge()).to.not.be.ok await application.addPasscode(passcode) expect(await application.hasPasscode()).to.equal(true) - expect(await application.protectionService.createLaunchChallenge()).to.be.ok - expect(application.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) + expect(await application.protections.createLaunchChallenge()).to.be.ok + expect(application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) await Factory.safeDeinit(application) /** Recreate application and initialize */ @@ -49,10 +49,10 @@ describe('device authentication', function () { tmpApplication.submitValuesForChallenge(challenge, initialValues) } await tmpApplication.prepareForLaunch({ receiveChallenge }) - expect(await tmpApplication.encryptionService.getRootKey()).to.not.be.ok + expect(await tmpApplication.encryption.getRootKey()).to.not.be.ok await tmpApplication.launch(true) - expect(await tmpApplication.encryptionService.getRootKey()).to.be.ok - expect(tmpApplication.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) + expect(await tmpApplication.encryption.getRootKey()).to.be.ok + expect(tmpApplication.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) await Factory.safeDeinit(tmpApplication) }).timeout(10000) @@ -64,8 +64,8 @@ describe('device authentication', function () { await application.addPasscode(passcode) await application.protections.enableBiometrics() expect(await application.hasPasscode()).to.equal(true) - expect((await application.protectionService.createLaunchChallenge()).prompts.length).to.equal(2) - expect(application.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) + expect((await application.protections.createLaunchChallenge()).prompts.length).to.equal(2) + expect(application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) await Factory.safeDeinit(application) /** Recreate application and initialize */ @@ -98,11 +98,11 @@ describe('device authentication', function () { } await tmpApplication.prepareForLaunch({ receiveChallenge }) - expect(await tmpApplication.encryptionService.getRootKey()).to.not.be.ok - expect((await tmpApplication.protectionService.createLaunchChallenge()).prompts.length).to.equal(2) + expect(await tmpApplication.encryption.getRootKey()).to.not.be.ok + expect((await tmpApplication.protections.createLaunchChallenge()).prompts.length).to.equal(2) await tmpApplication.launch(true) - expect(await tmpApplication.encryptionService.getRootKey()).to.be.ok - expect(tmpApplication.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) + expect(await tmpApplication.encryption.getRootKey()).to.be.ok + expect(tmpApplication.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.WrapperOnly) await Factory.safeDeinit(tmpApplication) }).timeout(Factory.TwentySecondTimeout) @@ -118,12 +118,12 @@ describe('device authentication', function () { }) const sampleStorageKey = 'foo' const sampleStorageValue = 'bar' - await application.diskStorageService.setValue(sampleStorageKey, sampleStorageValue) - expect(application.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyOnly) + await application.storage.setValue(sampleStorageKey, sampleStorageValue) + expect(application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyOnly) const passcode = 'foobar' Factory.handlePasswordChallenges(application, password) await application.addPasscode(passcode) - expect(application.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyPlusWrapper) + expect(application.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyPlusWrapper) expect(await application.hasPasscode()).to.equal(true) await Factory.safeDeinit(application) @@ -154,11 +154,11 @@ describe('device authentication', function () { await tmpApplication.prepareForLaunch({ receiveChallenge: receiveChallenge, }) - expect(await tmpApplication.encryptionService.getRootKey()).to.not.be.ok + expect(await tmpApplication.encryption.getRootKey()).to.not.be.ok await tmpApplication.launch(true) - expect(await tmpApplication.diskStorageService.getValue(sampleStorageKey)).to.equal(sampleStorageValue) - expect(await tmpApplication.encryptionService.getRootKey()).to.be.ok - expect(tmpApplication.encryptionService.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyPlusWrapper) + expect(await tmpApplication.storage.getValue(sampleStorageKey)).to.equal(sampleStorageValue) + expect(await tmpApplication.encryption.getRootKey()).to.be.ok + expect(tmpApplication.encryption.rootKeyManager.getKeyMode()).to.equal(KeyMode.RootKeyPlusWrapper) await Factory.safeDeinit(tmpApplication) }).timeout(Factory.TwentySecondTimeout) }) diff --git a/packages/snjs/mocha/features.test.js b/packages/snjs/mocha/features.test.js index 77616d701..3954d5868 100644 --- a/packages/snjs/mocha/features.test.js +++ b/packages/snjs/mocha/features.test.js @@ -32,8 +32,8 @@ describe('features', () => { describe('new user roles received on api response meta', () => { it('should save roles and features', async () => { - expect(application.featuresService.onlineRoles).to.have.lengthOf(1) - expect(application.featuresService.onlineRoles[0]).to.equal('CORE_USER') + expect(application.features.onlineRoles).to.have.lengthOf(1) + expect(application.features.onlineRoles[0]).to.equal('CORE_USER') const storedRoles = await application.getValue(StorageKey.UserRoles) @@ -44,7 +44,7 @@ describe('features', () => { describe('extension repo items observer', () => { it('should migrate to user setting when extension repo is added', async () => { - sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => { + sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => { return false }) @@ -57,7 +57,7 @@ describe('features', () => { const extensionKey = UuidGenerator.GenerateUuid().split('-').join('') const promise = new Promise((resolve) => { - sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve) + sinon.stub(application.features, 'migrateFeatureRepoToUserSetting').callsFake(resolve) }) await application.mutator.createItem( @@ -71,14 +71,14 @@ describe('features', () => { }) it('signing into account with ext repo should migrate it', async () => { - sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => { + sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => { return false }) /** Attach an ExtensionRepo object to an account, but prevent it from being migrated. * Then sign out, sign back in, and ensure the item is migrated. */ /** Prevent migration from running */ sinon - .stub(application.featuresService, 'migrateFeatureRepoToUserSetting') + .stub(application.features, 'migrateFeatureRepoToUserSetting') // eslint-disable-next-line @typescript-eslint/no-empty-function .callsFake(() => {}) const extensionKey = UuidGenerator.GenerateUuid().split('-').join('') @@ -93,11 +93,11 @@ describe('features', () => { application = await Factory.signOutApplicationAndReturnNew(application) sinon.restore() - sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => { + sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => { return false }) const promise = new Promise((resolve) => { - sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve) + sinon.stub(application.features, 'migrateFeatureRepoToUserSetting').callsFake(resolve) }) await Factory.loginToApplication({ application, @@ -109,7 +109,7 @@ describe('features', () => { it('having an ext repo with no account, then signing into account, should migrate it', async () => { application = await Factory.signOutApplicationAndReturnNew(application) - sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => { + sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => { return false }) const extensionKey = UuidGenerator.GenerateUuid().split('-').join('') @@ -123,7 +123,7 @@ describe('features', () => { await application.sync.sync() const promise = new Promise((resolve) => { - sinon.stub(application.featuresService, 'migrateFeatureRepoToUserSetting').callsFake(resolve) + sinon.stub(application.features, 'migrateFeatureRepoToUserSetting').callsFake(resolve) }) await Factory.loginToApplication({ application, @@ -134,7 +134,7 @@ describe('features', () => { }) it.skip('migrated ext repo should have property indicating it was migrated', async () => { - sinon.stub(application.apiService, 'isThirdPartyHostUsed').callsFake(() => { + sinon.stub(application.legacyApi, 'isThirdPartyHostUsed').callsFake(() => { return false }) expect(await application.settings.getDoesSensitiveSettingExist(SettingName.ExtensionKey)).to.equal(false) @@ -171,7 +171,7 @@ describe('features', () => { ) await application.sync.sync() - const repo = application.featuresService.getOfflineRepo() + const repo = application.features.getOfflineRepo() expect(repo.migratedToOfflineEntitlements).to.equal(true) expect(repo.offlineFeaturesUrl).to.equal('https://api.standardnotes.com/v1/offline/features') expect(repo.offlineKey).to.equal(extensionKey) diff --git a/packages/snjs/mocha/files.test.js b/packages/snjs/mocha/files.test.js index aaadf91f7..8b02d9b37 100644 --- a/packages/snjs/mocha/files.test.js +++ b/packages/snjs/mocha/files.test.js @@ -12,7 +12,6 @@ describe('files', function () { let context let fileService let itemManager - let subscriptionId = 1001 beforeEach(function () { localStorage.clear() @@ -28,8 +27,8 @@ describe('files', function () { await context.launch() application = context.application - fileService = context.application.fileService - itemManager = context.application.itemManager + fileService = context.application.files + itemManager = context.application.items await Factory.registerUserToApplication({ application: context.application, @@ -51,7 +50,7 @@ describe('files', function () { await setup({ fakeCrypto: true, subscription: true }) const remoteIdentifier = Utils.generateUuid() - const token = await application.apiService.createUserFileValetToken(remoteIdentifier, ValetTokenOperation.Write) + const token = await application.legacyApi.createUserFileValetToken(remoteIdentifier, ValetTokenOperation.Write) expect(token.length).to.be.above(0) }) @@ -60,7 +59,7 @@ describe('files', function () { await setup({ fakeCrypto: true, subscription: false }) const remoteIdentifier = Utils.generateUuid() - const tokenOrError = await application.apiService.createUserFileValetToken( + const tokenOrError = await application.legacyApi.createUserFileValetToken( remoteIdentifier, ValetTokenOperation.Write, ) @@ -79,7 +78,7 @@ describe('files', function () { }) const remoteIdentifier = Utils.generateUuid() - const tokenOrError = await application.apiService.createUserFileValetToken( + const tokenOrError = await application.legacyApi.createUserFileValetToken( remoteIdentifier, ValetTokenOperation.Write, ) @@ -90,19 +89,19 @@ describe('files', function () { it('creating two upload sessions successively should succeed', async function () { await setup({ fakeCrypto: true, subscription: true }) - const firstToken = await application.apiService.createUserFileValetToken( + const firstToken = await application.legacyApi.createUserFileValetToken( Utils.generateUuid(), ValetTokenOperation.Write, ) - const firstSession = await application.apiService.startUploadSession(firstToken, 'user') + const firstSession = await application.legacyApi.startUploadSession(firstToken, 'user') expect(firstSession.uploadId).to.be.ok - const secondToken = await application.apiService.createUserFileValetToken( + const secondToken = await application.legacyApi.createUserFileValetToken( Utils.generateUuid(), ValetTokenOperation.Write, ) - const secondSession = await application.apiService.startUploadSession(secondToken, 'user') + const secondSession = await application.legacyApi.startUploadSession(secondToken, 'user') expect(secondSession.uploadId).to.be.ok }) diff --git a/packages/snjs/mocha/history.test.js b/packages/snjs/mocha/history.test.js index 99767d61e..715796074 100644 --- a/packages/snjs/mocha/history.test.js +++ b/packages/snjs/mocha/history.test.js @@ -24,10 +24,10 @@ describe('history manager', () => { describe('session', function () { beforeEach(async function () { this.application = await Factory.createInitAppWithFakeCrypto() - this.historyManager = this.application.historyManager - this.payloadManager = this.application.payloadManager + this.history = this.application.dependencies.get(TYPES.HistoryManager) + this.payloadManager = this.application.payloads /** Automatically optimize after every revision by setting this to 0 */ - this.historyManager.itemRevisionThreshold = 0 + this.history.itemRevisionThreshold = 0 }) afterEach(async function () { @@ -52,11 +52,11 @@ describe('history manager', () => { it('create basic history entries 1', async function () { const item = await Factory.createSyncedNote(this.application) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(0) + expect(this.history.sessionHistoryForItem(item).length).to.equal(0) /** Sync with same contents, should not create new entry */ await Factory.markDirtyAndSyncItem(this.application, item) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(0) + expect(this.history.sessionHistoryForItem(item).length).to.equal(0) /** Sync with different contents, should create new entry */ await this.application.changeAndSaveItem( @@ -68,7 +68,7 @@ describe('history manager', () => { undefined, syncOptions, ) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(1) + expect(this.history.sessionHistoryForItem(item).length).to.equal(1) }) it('first change should create revision with previous value', async function () { @@ -78,7 +78,7 @@ describe('history manager', () => { /** Simulate loading new application session */ const context = await Factory.createAppContext({ identifier }) await context.launch() - expect(context.application.historyManager.sessionHistoryForItem(item).length).to.equal(0) + expect(context.history.sessionHistoryForItem(item).length).to.equal(0) await context.application.changeAndSaveItem( item, (mutator) => { @@ -88,7 +88,7 @@ describe('history manager', () => { undefined, syncOptions, ) - const entries = context.application.historyManager.sessionHistoryForItem(item) + const entries = context.history.sessionHistoryForItem(item) expect(entries.length).to.equal(1) expect(entries[0].payload.content.title).to.equal(item.content.title) await context.deinit() @@ -101,7 +101,7 @@ describe('history manager', () => { references: [], }) await context.application.mutator.insertItem(item) - expect(context.application.historyManager.sessionHistoryForItem(item).length).to.equal(0) + expect(context.history.sessionHistoryForItem(item).length).to.equal(0) await context.application.changeAndSaveItem( item, @@ -112,7 +112,7 @@ describe('history manager', () => { undefined, syncOptions, ) - expect(context.application.historyManager.sessionHistoryForItem(item).length).to.equal(0) + expect(context.history.sessionHistoryForItem(item).length).to.equal(0) await context.deinit() }) @@ -123,14 +123,14 @@ describe('history manager', () => { * won't here because it's the first change, which we want to keep. */ await setTextAndSync(this.application, item, item.content.text + '1') - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(1) + expect(this.history.sessionHistoryForItem(item).length).to.equal(1) /** * Changing it by one character should keep this entry, * since it's now the last (and will keep the first) */ item = await setTextAndSync(this.application, item, item.content.text + '2') - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(2) + expect(this.history.sessionHistoryForItem(item).length).to.equal(2) /** * Change it over the largeCharacterChange threshold. It should keep this * revision, but now remove the previous revision, since it's no longer @@ -141,29 +141,29 @@ describe('history manager', () => { item, item.content.text + Factory.randomString(largeCharacterChange + 1), ) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(2) + expect(this.history.sessionHistoryForItem(item).length).to.equal(2) item = await setTextAndSync( this.application, item, item.content.text + Factory.randomString(largeCharacterChange + 1), ) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(2) + expect(this.history.sessionHistoryForItem(item).length).to.equal(2) /** Delete over threshold text. */ item = await setTextAndSync( this.application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1), ) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(3) + expect(this.history.sessionHistoryForItem(item).length).to.equal(3) /** * Delete just 1 character. It should now retain the previous revision, as well as the * one previous to that. */ item = await setTextAndSync(this.application, item, deleteCharsFromString(item.content.text, 1)) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(4) + expect(this.history.sessionHistoryForItem(item).length).to.equal(4) item = await setTextAndSync(this.application, item, deleteCharsFromString(item.content.text, 1)) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(5) + expect(this.history.sessionHistoryForItem(item).length).to.equal(5) }) it('should keep the entry right before a large deletion, regardless of its delta', async function () { @@ -174,25 +174,25 @@ describe('history manager', () => { ) let item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) await this.application.mutator.setItemDirty(item) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) /** It should keep the first and last by default */ item = await setTextAndSync(this.application, item, item.content.text) item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(2) + expect(this.history.sessionHistoryForItem(item).length).to.equal(2) item = await setTextAndSync( this.application, item, deleteCharsFromString(item.content.text, largeCharacterChange + 1), ) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(2) + expect(this.history.sessionHistoryForItem(item).length).to.equal(2) item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(3) + expect(this.history.sessionHistoryForItem(item).length).to.equal(3) item = await setTextAndSync( this.application, item, item.content.text + Factory.randomString(largeCharacterChange + 1), ) - expect(this.historyManager.sessionHistoryForItem(item).length).to.equal(4) + expect(this.history.sessionHistoryForItem(item).length).to.equal(4) }) it('entries should be ordered from newest to oldest', async function () { @@ -205,7 +205,7 @@ describe('history manager', () => { let item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) await this.application.mutator.setItemDirty(item) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) item = await setTextAndSync(this.application, item, item.content.text + Factory.randomString(1)) @@ -224,10 +224,10 @@ describe('history manager', () => { ) /** First entry should be the latest revision. */ - const latestRevision = this.historyManager.sessionHistoryForItem(item)[0] + const latestRevision = this.history.sessionHistoryForItem(item)[0] /** Last entry should be the initial revision. */ const initialRevision = - this.historyManager.sessionHistoryForItem(item)[this.historyManager.sessionHistoryForItem(item).length - 1] + this.history.sessionHistoryForItem(item)[this.history.sessionHistoryForItem(item).length - 1] expect(latestRevision).to.not.equal(initialRevision) @@ -252,7 +252,7 @@ describe('history manager', () => { undefined, syncOptions, ) - const historyItem = this.historyManager.sessionHistoryForItem(item)[0] + const historyItem = this.history.sessionHistoryForItem(item)[0] expect(historyItem.previewTitle()).to.equal(historyItem.payload.created_at.toLocaleString()) }) }) @@ -262,8 +262,8 @@ describe('history manager', () => { beforeEach(async function () { this.application = await Factory.createInitAppWithFakeCrypto() - this.historyManager = this.application.historyManager - this.payloadManager = this.application.payloadManager + this.history = this.application.dependencies.get(TYPES.HistoryManager) + this.payloadManager = this.application.payloads this.email = UuidGenerator.GenerateUuid() this.password = UuidGenerator.GenerateUuid() await Factory.registerUserToApplication({ @@ -280,10 +280,10 @@ describe('history manager', () => { it('response from server should be failed if not signed in', async function () { await this.application.user.signOut() this.application = await Factory.createInitAppWithFakeCrypto() - this.historyManager = this.application.historyManager - this.payloadManager = this.application.payloadManager + this.history = this.application.dependencies.get(TYPES.HistoryManager) + this.payloadManager = this.application.payloads const item = await Factory.createSyncedNote(this.application) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) const itemHistoryOrError = await this.application.listRevisions.execute({ itemUuid: item.uuid }) expect(itemHistoryOrError.isFailed()).to.equal(true) @@ -355,7 +355,7 @@ describe('history manager', () => { expect(payloadFromServer.uuid).to.eq(item.payload.uuid) expect(payloadFromServer.content).to.eql(item.payload.content) - item = this.application.itemManager.findItem(item.uuid) + item = this.application.items.findItem(item.uuid) expect(payloadFromServer.content).to.not.eql(item.payload.content) }) diff --git a/packages/snjs/mocha/key_recovery_service.test.js b/packages/snjs/mocha/key_recovery_service.test.js index 1694af8c2..750e69625 100644 --- a/packages/snjs/mocha/key_recovery_service.test.js +++ b/packages/snjs/mocha/key_recovery_service.test.js @@ -37,21 +37,21 @@ describe('key recovery service', function () { await context.register() - const randomRootKey = await application.encryptionService.createRootKey( + const randomRootKey = await application.encryption.createRootKey( unassociatedIdentifier, unassociatedPassword, KeyParamsOrigination.Registration, ) - const randomItemsKey = await application.encryptionService.operators.defaultOperator().createItemsKey() + const randomItemsKey = await context.operators.defaultOperator().createItemsKey() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKey: { items: [randomItemsKey.payload], key: randomRootKey, }, }) - const errored = await application.encryptionService.decryptSplitSingle({ + const errored = await application.encryption.decryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [encrypted], }, @@ -59,13 +59,13 @@ describe('key recovery service', function () { expect(errored.errorDecrypting).to.equal(true) - await application.payloadManager.emitPayload(errored, PayloadEmitSource.LocalInserted) + await application.payloads.emitPayload(errored, PayloadEmitSource.LocalInserted) await context.resolveWhenKeyRecovered(errored.uuid) expect(application.items.findItem(errored.uuid).errorDecrypting).to.not.be.ok - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() }) @@ -89,37 +89,37 @@ describe('key recovery service', function () { }) await context.register() - const randomRootKey = await application.encryptionService.createRootKey( + const randomRootKey = await application.encryption.createRootKey( unassociatedIdentifier, unassociatedPassword, KeyParamsOrigination.Registration, ) - const randomItemsKey = await application.encryptionService.operators.defaultOperator().createItemsKey() + const randomItemsKey = await context.operators.defaultOperator().createItemsKey() - await application.payloadManager.emitPayload( + await application.payloads.emitPayload( randomItemsKey.payload.copy({ dirty: true, dirtyIndex: getIncrementedDirtyIndex() }), PayloadEmitSource.LocalInserted, ) await context.sync() - const originalSyncTime = application.payloadManager.findOne(randomItemsKey.uuid).lastSyncEnd.getTime() + const originalSyncTime = application.payloads.findOne(randomItemsKey.uuid).lastSyncEnd.getTime() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKey: { items: [randomItemsKey.payload], key: randomRootKey, }, }) - const errored = await application.encryptionService.decryptSplitSingle({ + const errored = await application.encryption.decryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [encrypted], }, }) - await application.payloadManager.emitPayload(errored, PayloadEmitSource.LocalInserted) + await application.payloads.emitPayload(errored, PayloadEmitSource.LocalInserted) const recoveryPromise = context.resolveWhenKeyRecovered(errored.uuid) @@ -127,7 +127,7 @@ describe('key recovery service', function () { await recoveryPromise - expect(application.payloadManager.findOne(errored.uuid).lastSyncEnd.getTime()).to.be.above(originalSyncTime) + expect(application.payloads.findOne(errored.uuid).lastSyncEnd.getTime()).to.be.above(originalSyncTime) await context.deinit() }) @@ -214,15 +214,15 @@ describe('key recovery service', function () { }) /** Create items key associated with a random root key */ - const randomRootKey = await application.encryptionService.createRootKey( + const randomRootKey = await application.encryption.createRootKey( unassociatedIdentifier, unassociatedPassword, KeyParamsOrigination.Registration, ) - const randomItemsKey = await application.encryptionService.operators.defaultOperator().createItemsKey() - const randomItemsKey2 = await application.encryptionService.operators.defaultOperator().createItemsKey() + const randomItemsKey = await context.operators.defaultOperator().createItemsKey() + const randomItemsKey2 = await context.operators.defaultOperator().createItemsKey() - const encrypted = await application.encryptionService.encryptSplit({ + const encrypted = await application.encryption.encryptSplit({ usesRootKey: { items: [randomItemsKey.payload, randomItemsKey2.payload], key: randomRootKey, @@ -230,13 +230,13 @@ describe('key recovery service', function () { }) /** Attempt decryption and insert into rotation in errored state */ - const decrypted = await application.encryptionService.decryptSplit({ + const decrypted = await application.encryption.decryptSplit({ usesRootKeyWithKeyLookup: { items: encrypted, }, }) - await application.payloadManager.emitPayloads(decrypted, PayloadEmitSource.LocalInserted) + await application.payloads.emitPayloads(decrypted, PayloadEmitSource.LocalInserted) /** Wait and allow recovery wizard to complete */ await Factory.sleep(1.5) @@ -247,7 +247,7 @@ describe('key recovery service', function () { expect(totalPromptCount).to.equal(1) - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() }) @@ -289,19 +289,19 @@ describe('key recovery service', function () { /** Same previously errored key should now no longer be errored, */ expect(contextA.application.items.getAnyItems(ContentType.TYPES.ItemsKey).length).to.equal(2) - for (const key of contextA.application.itemManager.getDisplayableItemsKeys()) { + for (const key of contextA.application.items.getDisplayableItemsKeys()) { expect(key.errorDecrypting).to.not.be.ok } - const aKey = await contextA.application.encryptionService.getRootKey() - const bKey = await contextB.application.encryptionService.getRootKey() + const aKey = await contextA.application.encryption.getRootKey() + const bKey = await contextB.application.encryption.getRootKey() expect(aKey.compare(bKey)).to.equal(true) expect(contextA.application.items.findItem(note.uuid).errorDecrypting).to.not.be.ok expect(contextB.application.items.findItem(note.uuid).errorDecrypting).to.not.be.ok - expect(contextA.application.syncService.isOutOfSync()).to.equal(false) - expect(contextB.application.syncService.isOutOfSync()).to.equal(false) + expect(contextA.application.sync.isOutOfSync()).to.equal(false) + expect(contextB.application.sync.isOutOfSync()).to.equal(false) await contextA.deinit() await contextB.deinit() @@ -341,7 +341,7 @@ describe('key recovery service', function () { /** We expect the item in appA to be errored at this point, but we do not want it to recover */ await appA.sync.sync() - expect(appA.payloadManager.findOne(note.uuid).waitingForKey).to.equal(true) + expect(appA.payloads.findOne(note.uuid).waitingForKey).to.equal(true) console.warn('Expecting exceptions below as we destroy app during key recovery') await Factory.safeDeinit(appA) @@ -351,8 +351,8 @@ describe('key recovery service', function () { await recreatedAppA.prepareForLaunch({ receiveChallenge: () => {} }) await recreatedAppA.launch(true) - expect(recreatedAppA.payloadManager.findOne(note.uuid).errorDecrypting).to.equal(true) - expect(recreatedAppA.payloadManager.findOne(note.uuid).waitingForKey).to.equal(true) + expect(recreatedAppA.payloads.findOne(note.uuid).errorDecrypting).to.equal(true) + expect(recreatedAppA.payloads.findOne(note.uuid).waitingForKey).to.equal(true) await Factory.safeDeinit(recreatedAppA) }) @@ -379,7 +379,7 @@ describe('key recovery service', function () { await application.launch(true) await context.register() - const correctRootKey = await application.encryptionService.getRootKey() + const correctRootKey = await application.encryption.getRootKey() /** * 1. Change our root key locally so that its keys params doesn't match the server's @@ -390,19 +390,19 @@ describe('key recovery service', function () { const unassociatedIdentifier = 'foorand' /** Create items key associated with a random root key */ - const randomRootKey = await application.encryptionService.createRootKey( + const randomRootKey = await application.encryption.createRootKey( unassociatedIdentifier, unassociatedPassword, KeyParamsOrigination.Registration, ) - const signInFunction = sinon.spy(application.keyRecoveryService, 'performServerSignIn') + const signInFunction = sinon.spy(context.keyRecovery, 'performServerSignIn') - await application.encryptionService.setRootKey(randomRootKey) + await application.encryption.setRootKey(randomRootKey) - const correctItemsKey = await application.encryptionService.operators.defaultOperator().createItemsKey() + const correctItemsKey = await context.operators.defaultOperator().createItemsKey() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKey: { items: [correctItemsKey.payload], key: randomRootKey, @@ -414,7 +414,7 @@ describe('key recovery service', function () { context.resolveWhenKeyRecovered(correctItemsKey.uuid), ]) - await application.payloadManager.emitPayload( + await application.payloads.emitPayload( encrypted.copy({ errorDecrypting: true, dirty: true, @@ -428,14 +428,14 @@ describe('key recovery service', function () { expect(signInFunction.callCount).to.equal(1) - const clientRootKey = await application.encryptionService.getRootKey() + const clientRootKey = await application.encryption.getRootKey() expect(clientRootKey.compare(correctRootKey)).to.equal(true) const decryptedKey = application.items.findItem(correctItemsKey.uuid) expect(decryptedKey).to.be.ok expect(decryptedKey.content.itemsKey).to.equal(correctItemsKey.content.itemsKey) - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() }) @@ -448,8 +448,8 @@ describe('key recovery service', function () { await context.register() /** Create and emit errored encrypted items key payload */ - const itemsKey = await application.encryptionService.getSureDefaultItemsKey() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const itemsKey = await application.encryption.getSureDefaultItemsKey() + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [itemsKey.payload], }, @@ -474,7 +474,7 @@ describe('key recovery service', function () { /** The timestamp of our current key should be updated however so we do not enter out of sync state */ expect(currentItemsKey.serverUpdatedAt.getTime()).to.equal(newUpdated.getTime()) - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() }) @@ -485,8 +485,8 @@ describe('key recovery service', function () { await context.launch() await context.register() - const itemsKey = await application.encryptionService.getSureDefaultItemsKey() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const itemsKey = await application.encryption.getSureDefaultItemsKey() + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [itemsKey.payload], }, @@ -498,7 +498,7 @@ describe('key recovery service', function () { updated_at: newUpdated, }) - await application.payloadManager.emitDeltaEmit({ + await application.payloads.emitDeltaEmit({ emits: [], ignored: [errored], source: PayloadEmitSource.RemoteRetrieved, @@ -511,7 +511,7 @@ describe('key recovery service', function () { expect(latestItemsKey.errorDecrypting).to.not.be.ok expect(latestItemsKey.itemsKey).to.equal(itemsKey.itemsKey) expect(latestItemsKey.serverUpdatedAt.getTime()).to.equal(newUpdated.getTime()) - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() }) @@ -524,8 +524,8 @@ describe('key recovery service', function () { await context.register() /** Create and emit errored encrypted items key payload */ - const itemsKey = await application.encryptionService.getSureDefaultItemsKey() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const itemsKey = await application.encryption.getSureDefaultItemsKey() + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [itemsKey.payload], }, @@ -533,7 +533,7 @@ describe('key recovery service', function () { context.disableKeyRecovery() - await application.payloadManager.emitDeltaEmit({ + await application.payloads.emitDeltaEmit({ emits: [], ignored: [ encrypted.copy({ @@ -548,7 +548,7 @@ describe('key recovery service', function () { await Factory.sleep(0.1) - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() @@ -589,17 +589,17 @@ describe('key recovery service', function () { }) /** Create items key associated with a random root key */ - const randomRootKey = await application.encryptionService.createRootKey( + const randomRootKey = await application.encryption.createRootKey( unassociatedIdentifier, unassociatedPassword, KeyParamsOrigination.Registration, ProtocolVersion.V003, ) - const randomItemsKey = await application.encryptionService.operators + const randomItemsKey = await context.operators .operatorForVersion(ProtocolVersion.V003) .createItemsKey() - const encrypted = await application.encryptionService.encryptSplitSingle({ + const encrypted = await application.encryption.encryptSplitSingle({ usesRootKey: { items: [randomItemsKey.payload], key: randomRootKey, @@ -607,7 +607,7 @@ describe('key recovery service', function () { }) /** Attempt decryption and insert into rotation in errored state */ - const decrypted = await application.encryptionService.decryptSplitSingle({ + const decrypted = await application.encryption.decryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [encrypted], }, @@ -616,7 +616,7 @@ describe('key recovery service', function () { expect(decrypted.errorDecrypting).to.equal(true) /** Insert into rotation */ - await application.payloadManager.emitPayload(decrypted, PayloadEmitSource.LocalInserted) + await application.payloads.emitPayload(decrypted, PayloadEmitSource.LocalInserted) /** Wait and allow recovery wizard to complete */ await Factory.sleep(0.3) @@ -624,7 +624,7 @@ describe('key recovery service', function () { /** Should be decrypted now */ expect(application.items.findItem(encrypted.uuid).errorDecrypting).to.not.be.ok - expect(application.syncService.isOutOfSync()).to.equal(false) + expect(application.sync.isOutOfSync()).to.equal(false) await context.deinit() }) @@ -653,9 +653,9 @@ describe('key recovery service', function () { contextA.password = newPassword await appB.sync.sync() - const newDefaultKey = appB.encryptionService.getSureDefaultItemsKey() + const newDefaultKey = appB.encryption.getSureDefaultItemsKey() - const encrypted = await appB.encryptionService.encryptSplitSingle({ + const encrypted = await appB.encryption.encryptSplitSingle({ usesRootKeyWithKeyLookup: { items: [newDefaultKey.payload], }, @@ -663,28 +663,26 @@ describe('key recovery service', function () { /** Insert foreign items key into appA, which shouldn't be able to decrypt it yet */ const appA = contextA.application - await appA.payloadManager.emitPayload( + await appA.payloads.emitPayload( encrypted.copy({ errorDecrypting: true, }), PayloadEmitSource.LocalInserted, ) - await Factory.awaitFunctionInvokation(appA.keyRecoveryService, 'handleDecryptionOfAllKeysMatchingCorrectRootKey') + await Factory.awaitFunctionInvokation(contextA.keyRecovery, 'handleDecryptionOfAllKeysMatchingCorrectRootKey') /** Stored version of items key should use new root key */ - const stored = (await appA.deviceInterface.getAllDatabaseEntries(appA.identifier)).find( + const stored = (await appA.device.getAllDatabaseEntries(appA.identifier)).find( (payload) => payload.uuid === newDefaultKey.uuid, ) - const storedParams = await appA.encryptionService.getKeyEmbeddedKeyParamsFromItemsKey(new EncryptedPayload(stored)) + const storedParams = await appA.encryption.getKeyEmbeddedKeyParamsFromItemsKey(new EncryptedPayload(stored)) - const correctStored = (await appB.deviceInterface.getAllDatabaseEntries(appB.identifier)).find( + const correctStored = (await appB.device.getAllDatabaseEntries(appB.identifier)).find( (payload) => payload.uuid === newDefaultKey.uuid, ) - const correctParams = await appB.encryptionService.getKeyEmbeddedKeyParamsFromItemsKey( - new EncryptedPayload(correctStored), - ) + const correctParams = await appB.encryption.getKeyEmbeddedKeyParamsFromItemsKey(new EncryptedPayload(correctStored)) expect(storedParams).to.eql(correctParams) diff --git a/packages/snjs/mocha/keys.test.js b/packages/snjs/mocha/keys.test.js index add04b2e5..5a24141c6 100644 --- a/packages/snjs/mocha/keys.test.js +++ b/packages/snjs/mocha/keys.test.js @@ -29,7 +29,7 @@ describe('keys', function () { }) it('should not have root key by default', async function () { - expect(await this.application.encryptionService.getRootKey()).to.not.be.ok + expect(await this.application.encryption.getRootKey()).to.not.be.ok }) it('validates content types requiring root encryption', function () { @@ -43,7 +43,7 @@ describe('keys', function () { /** Items key available by default */ const payload = Factory.createNotePayload() const processedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -54,44 +54,44 @@ describe('keys', function () { it('has root key and one items key after registering user', async function () { await Factory.registerUserToApplication({ application: this.application }) - expect(this.application.encryptionService.getRootKey()).to.be.ok - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.encryption.getRootKey()).to.be.ok + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) }) it('changing root key with passcode should re-wrap root key', async function () { const email = 'foo' const password = 'bar' - const key = await this.application.encryptionService.createRootKey(email, password, KeyParamsOrigination.Registration) - await this.application.encryptionService.setRootKey(key) + const key = await this.application.encryption.createRootKey(email, password, KeyParamsOrigination.Registration) + await this.application.encryption.setRootKey(key) Factory.handlePasswordChallenges(this.application, password) await this.application.addPasscode(password) /** We should be able to decrypt wrapped root key with passcode */ - const wrappingKeyParams = await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams() - const wrappingKey = await this.application.encryptionService.computeRootKey(password, wrappingKeyParams) - await this.application.encryptionService.unwrapRootKey(wrappingKey).catch((error) => { + const wrappingKeyParams = await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams() + const wrappingKey = await this.application.encryption.computeRootKey(password, wrappingKeyParams) + await this.application.encryption.unwrapRootKey(wrappingKey).catch((error) => { expect(error).to.not.be.ok }) const newPassword = 'bar' - const newKey = await this.application.encryptionService.createRootKey( + const newKey = await this.application.encryption.createRootKey( email, newPassword, KeyParamsOrigination.Registration, ) - await this.application.encryptionService.setRootKey(newKey, wrappingKey) - await this.application.encryptionService.unwrapRootKey(wrappingKey).catch((error) => { + await this.application.encryption.setRootKey(newKey, wrappingKey) + await this.application.encryption.unwrapRootKey(wrappingKey).catch((error) => { expect(error).to.not.be.ok }) }) it('items key should be encrypted with root key', async function () { await Factory.registerUserToApplication({ application: this.application }) - const itemsKey = await this.application.encryptionService.getSureDefaultItemsKey() - const rootKey = await this.application.encryptionService.getRootKey() + const itemsKey = await this.application.encryption.getSureDefaultItemsKey() + const rootKey = await this.application.encryption.getRootKey() /** Encrypt items key */ - const encryptedPayload = await this.application.encryptionService.encryptSplitSingle({ + const encryptedPayload = await this.application.encryption.encryptSplitSingle({ usesRootKey: { items: [itemsKey.payloadRepresentation()], key: rootKey, @@ -102,7 +102,7 @@ describe('keys', function () { expect(encryptedPayload.items_key_id).to.not.be.ok /** Attempt to decrypt with root key. Should succeed. */ - const decryptedPayload = await this.application.encryptionService.decryptSplitSingle({ + const decryptedPayload = await this.application.encryption.decryptSplitSingle({ usesRootKey: { items: [encryptedPayload], key: rootKey, @@ -114,7 +114,7 @@ describe('keys', function () { }) it('should create random items key if no account and no passcode', async function () { - const itemsKeys = this.application.itemManager.getDisplayableItemsKeys() + const itemsKeys = this.application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) const notePayload = Factory.createNotePayload() @@ -122,55 +122,55 @@ describe('keys', function () { dirty: true, dirtyIndex: getIncrementedDirtyIndex(), }) - await this.application.payloadManager.emitPayload(dirtied, PayloadEmitSource.LocalChanged) + await this.application.payloads.emitPayload(dirtied, PayloadEmitSource.LocalChanged) await this.application.sync.sync() - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const rawNotePayload = rawPayloads.find((r) => r.content_type === ContentType.TYPES.Note) expect(typeof rawNotePayload.content).to.equal('string') }) it('should keep offline created items key upon registration', async function () { - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) - const originalItemsKey = this.application.itemManager.getDisplayableItemsKeys()[0] + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + const originalItemsKey = this.application.items.getDisplayableItemsKeys()[0] await this.application.register(this.email, this.password) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) - const newestItemsKey = this.application.itemManager.getDisplayableItemsKeys()[0] + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + const newestItemsKey = this.application.items.getDisplayableItemsKeys()[0] expect(newestItemsKey.uuid).to.equal(originalItemsKey.uuid) }) it('should use items key for encryption of note', async function () { const notePayload = Factory.createNotePayload() - const keyToUse = await this.application.encryptionService.itemsEncryption.keyToUseForItemEncryption(notePayload) + const keyToUse = await this.application.encryption.itemsEncryption.keyToUseForItemEncryption(notePayload) expect(keyToUse.content_type).to.equal(ContentType.TYPES.ItemsKey) }) it('encrypting an item should associate an items key to it', async function () { const note = Factory.createNotePayload() - const encryptedPayload = await this.application.encryptionService.encryptSplitSingle({ + const encryptedPayload = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note], }, }) - const itemsKey = this.application.encryptionService.itemsKeyForEncryptedPayload(encryptedPayload) + const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) expect(itemsKey).to.be.ok }) it('decrypt encrypted item with associated key', async function () { const note = Factory.createNotePayload() const title = note.content.title - const encryptedPayload = await this.application.encryptionService.encryptSplitSingle({ + const encryptedPayload = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note], }, }) - const itemsKey = this.application.encryptionService.itemsKeyForEncryptedPayload(encryptedPayload) + const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) expect(itemsKey).to.be.ok - const decryptedPayload = await this.application.encryptionService.decryptSplitSingle({ + const decryptedPayload = await this.application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [encryptedPayload], }, @@ -182,17 +182,17 @@ describe('keys', function () { it('decrypts items waiting for keys', async function () { const notePayload = Factory.createNotePayload() const title = notePayload.content.title - const encryptedPayload = await this.application.encryptionService.encryptSplitSingle({ + const encryptedPayload = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [notePayload], }, }) - const itemsKey = this.application.encryptionService.itemsKeyForEncryptedPayload(encryptedPayload) + const itemsKey = this.application.encryption.itemsKeyForEncryptedPayload(encryptedPayload) - await this.application.itemManager.removeItemLocally(itemsKey) + await this.application.items.removeItemLocally(itemsKey) - const erroredPayload = await this.application.encryptionService.decryptSplitSingle({ + const erroredPayload = await this.application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [encryptedPayload], }, @@ -200,7 +200,7 @@ describe('keys', function () { await this.application.mutator.emitItemsFromPayloads([erroredPayload], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.findAnyItem(notePayload.uuid) + const note = this.application.items.findAnyItem(notePayload.uuid) expect(note.errorDecrypting).to.equal(true) expect(note.waitingForKey).to.equal(true) @@ -213,7 +213,7 @@ describe('keys', function () { */ await Factory.sleep(0.2) - const updatedNote = this.application.itemManager.findItem(note.uuid) + const updatedNote = this.application.items.findItem(note.uuid) expect(updatedNote.errorDecrypting).to.not.be.ok expect(updatedNote.waitingForKey).to.not.be.ok @@ -223,7 +223,7 @@ describe('keys', function () { it('attempting to emit errored items key for which there exists a non errored master copy should ignore it', async function () { await Factory.registerUserToApplication({ application: this.application }) - const itemsKey = await this.application.encryptionService.getSureDefaultItemsKey() + const itemsKey = await this.application.encryption.getSureDefaultItemsKey() expect(itemsKey.errorDecrypting).to.not.be.ok @@ -239,9 +239,9 @@ describe('keys', function () { }, }) - await this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) + await this.application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) - const refreshedKey = this.application.payloadManager.findOne(itemsKey.uuid) + const refreshedKey = this.application.payloads.findOne(itemsKey.uuid) expect(refreshedKey.errorDecrypting).to.not.be.ok expect(refreshedKey.content.itemsKey).to.be.ok @@ -250,19 +250,19 @@ describe('keys', function () { it('generating export params with logged in account should produce encrypted payload', async function () { await Factory.registerUserToApplication({ application: this.application }) const payload = Factory.createNotePayload() - const encryptedPayload = await this.application.encryptionService.encryptSplitSingle({ + const encryptedPayload = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, }) expect(typeof encryptedPayload.content).to.equal('string') - expect(encryptedPayload.content.substring(0, 3)).to.equal(this.application.encryptionService.getLatestVersion()) + expect(encryptedPayload.content.substring(0, 3)).to.equal(this.application.encryption.getLatestVersion()) }) it('When setting passcode, should encrypt items keys', async function () { await this.application.addPasscode('foo') - const itemsKey = this.application.itemManager.getDisplayableItemsKeys()[0] - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const itemsKey = this.application.items.getDisplayableItemsKeys()[0] + const rawPayloads = await this.application.storage.getAllRawPayloads() const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) expect(itemsKeyPayload.enc_item_key).to.be.ok @@ -270,13 +270,13 @@ describe('keys', function () { it('items key encrypted payload should contain root key params', async function () { await this.application.addPasscode('foo') - const itemsKey = this.application.itemManager.getDisplayableItemsKeys()[0] - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const itemsKey = this.application.items.getDisplayableItemsKeys()[0] + const rawPayloads = await this.application.storage.getAllRawPayloads() const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === itemsKey.uuid) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) const authenticatedData = this.context.encryption.getEmbeddedPayloadAuthenticatedData(itemsKeyPayload) - const rootKeyParams = await this.application.encryptionService.getRootKeyParams() + const rootKeyParams = await this.application.encryption.getRootKeyParams() expect(authenticatedData.kp).to.be.ok expect(authenticatedData.kp).to.eql(rootKeyParams.getPortableValue()) @@ -286,8 +286,8 @@ describe('keys', function () { it('correctly validates local passcode', async function () { const passcode = 'foo' await this.application.addPasscode('foo') - expect((await this.application.encryptionService.validatePasscode('wrong')).valid).to.equal(false) - expect((await this.application.encryptionService.validatePasscode(passcode)).valid).to.equal(true) + expect((await this.application.encryption.validatePasscode('wrong')).valid).to.equal(false) + expect((await this.application.encryption.validatePasscode(passcode)).valid).to.equal(true) }) it('signing into 003 account should delete latest offline items key and create 003 items key', async function () { @@ -296,8 +296,8 @@ describe('keys', function () { * Upon signing into an 003 account, the application should delete any neverSynced items keys, * and create a new default items key that is the default for a given protocol version. */ - const defaultItemsKey = await this.application.encryptionService.getSureDefaultItemsKey() - const latestVersion = this.application.encryptionService.getLatestVersion() + const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() + const latestVersion = this.application.encryption.getLatestVersion() expect(defaultItemsKey.keyVersion).to.equal(latestVersion) /** Register with 003 version */ @@ -308,11 +308,11 @@ describe('keys', function () { version: ProtocolVersion.V003, }) - const itemsKeys = this.application.itemManager.getDisplayableItemsKeys() + const itemsKeys = this.application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) const newestItemsKey = itemsKeys[0] expect(newestItemsKey.keyVersion).to.equal(ProtocolVersion.V003) - const rootKey = await this.application.encryptionService.getRootKey() + const rootKey = await this.application.encryption.getRootKey() expect(newestItemsKey.itemsKey).to.equal(rootKey.masterKey) expect(newestItemsKey.dataAuthenticationKey).to.equal(rootKey.dataAuthenticationKey) }) @@ -326,33 +326,33 @@ describe('keys', function () { version: ProtocolVersion.V003, }) - expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) - expect(this.application.itemManager.getDisplayableItemsKeys()[0].dirty).to.equal(false) + expect(this.application.payloads.invalidPayloads.length).to.equal(0) + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.items.getDisplayableItemsKeys()[0].dirty).to.equal(false) /** Sign out and back in */ this.application = await Factory.signOutApplicationAndReturnNew(this.application) await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(10) - expect(this.application.payloadManager.invalidPayloads.length).to.equal(0) + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(10) + expect(this.application.payloads.invalidPayloads.length).to.equal(0) }) it('When root key changes, all items keys must be re-encrypted', async function () { const passcode = 'foo' await this.application.addPasscode(passcode) await Factory.createSyncedNote(this.application) - const itemsKeys = this.application.itemManager.getDisplayableItemsKeys() + const itemsKeys = this.application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) const originalItemsKey = itemsKeys[0] - const originalRootKey = await this.application.encryptionService.getRootKey() + const originalRootKey = await this.application.encryption.getRootKey() /** Expect that we can decrypt raw payload with current root key */ - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const itemsKeyRawPayload = rawPayloads.find((p) => p.uuid === originalItemsKey.uuid) const itemsKeyPayload = new EncryptedPayload(itemsKeyRawPayload) - const decrypted = await this.application.encryptionService.decryptSplitSingle({ + const decrypted = await this.application.encryption.decryptSplitSingle({ usesRootKey: { items: [itemsKeyPayload], key: originalRootKey, @@ -366,7 +366,7 @@ describe('keys', function () { Factory.handlePasswordChallenges(this.application, passcode) await this.application.changePasscode('bar') - const newRootKey = await this.application.encryptionService.getRootKey() + const newRootKey = await this.application.encryption.getRootKey() expect(newRootKey).to.not.equal(originalRootKey) expect(newRootKey.masterKey).to.not.equal(originalRootKey.masterKey) @@ -374,12 +374,12 @@ describe('keys', function () { * Expect that originalRootKey can no longer decrypt originalItemsKey * as items key has been re-encrypted with new root key */ - const rawPayloads2 = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads2 = await this.application.storage.getAllRawPayloads() const itemsKeyRawPayload2 = rawPayloads2.find((p) => p.uuid === originalItemsKey.uuid) expect(itemsKeyRawPayload2.content).to.not.equal(itemsKeyRawPayload.content) const itemsKeyPayload2 = new EncryptedPayload(itemsKeyRawPayload2) - const decrypted2 = await this.application.encryptionService.decryptSplitSingle({ + const decrypted2 = await this.application.encryption.decryptSplitSingle({ usesRootKey: { items: [itemsKeyPayload2], key: originalRootKey, @@ -388,7 +388,7 @@ describe('keys', function () { expect(decrypted2.errorDecrypting).to.equal(true) /** Should be able to decrypt with new root key */ - const decrypted3 = await this.application.encryptionService.decryptSplitSingle({ + const decrypted3 = await this.application.encryption.decryptSplitSingle({ usesRootKey: { items: [itemsKeyPayload2], key: newRootKey, @@ -403,19 +403,19 @@ describe('keys', function () { email: this.email, password: this.password, }) - const itemsKeys = this.application.itemManager.getDisplayableItemsKeys() + const itemsKeys = this.application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) - const defaultItemsKey = await this.application.encryptionService.getSureDefaultItemsKey() + const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() const result = await this.application.changePassword(this.password, 'foobarfoo') expect(result.error).to.not.be.ok - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(2) - const newDefaultItemsKey = await this.application.encryptionService.getSureDefaultItemsKey() + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(2) + const newDefaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() expect(newDefaultItemsKey.uuid).to.not.equal(defaultItemsKey.uuid) const note = await Factory.createSyncedNote(this.application) - const payload = await this.application.encryptionService.encryptSplitSingle({ + const payload = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -430,20 +430,20 @@ describe('keys', function () { email: email, password: password, }) - const itemsKeys = application.itemManager.getDisplayableItemsKeys() + const itemsKeys = application.items.getDisplayableItemsKeys() expect(itemsKeys.length).to.equal(1) - const defaultItemsKey = application.encryptionService.getSureDefaultItemsKey() + const defaultItemsKey = application.encryption.getSureDefaultItemsKey() const newEmail = UuidGenerator.GenerateUuid() const result = await application.changeEmail(newEmail, password) expect(result.error).to.not.be.ok - expect(application.itemManager.getDisplayableItemsKeys().length).to.equal(2) - const newDefaultItemsKey = application.encryptionService.getSureDefaultItemsKey() + expect(application.items.getDisplayableItemsKeys().length).to.equal(2) + const newDefaultItemsKey = application.encryption.getSureDefaultItemsKey() expect(newDefaultItemsKey.uuid).to.not.equal(defaultItemsKey.uuid) const note = await Factory.createSyncedNote(application) - const payload = await application.encryptionService.encryptSplitSingle({ + const payload = await application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -480,34 +480,28 @@ describe('keys', function () { it('loading the keychain root key should also load its key params', async function () { await Factory.registerUserToApplication({ application: this.application }) - const rootKey = await this.application.encryptionService.rootKeyManager.getRootKeyFromKeychain() + const rootKey = await this.application.encryption.rootKeyManager.getRootKeyFromKeychain() expect(rootKey.keyParams).to.be.ok }) it('key params should be persisted separately and not as part of root key', async function () { await Factory.registerUserToApplication({ application: this.application }) - const rawKey = await this.application.deviceInterface.getNamespacedKeychainValue(this.application.identifier) + const rawKey = await this.application.device.getNamespacedKeychainValue(this.application.identifier) expect(rawKey.keyParams).to.not.be.ok - const rawKeyParams = await this.application.diskStorageService.getValue( - StorageKey.RootKeyParams, - StorageValueModes.Nonwrapped, - ) + const rawKeyParams = await this.application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) expect(rawKeyParams).to.be.ok }) it('persisted key params should exactly equal in memory rootKey.keyParams', async function () { await Factory.registerUserToApplication({ application: this.application }) - const rootKey = await this.application.encryptionService.getRootKey() - const rawKeyParams = await this.application.diskStorageService.getValue( - StorageKey.RootKeyParams, - StorageValueModes.Nonwrapped, - ) + const rootKey = await this.application.encryption.getRootKey() + const rawKeyParams = await this.application.storage.getValue(StorageKey.RootKeyParams, StorageValueModes.Nonwrapped) expect(rootKey.keyParams.content).to.eql(rawKeyParams) }) it('key params should have expected values', async function () { await Factory.registerUserToApplication({ application: this.application }) - const keyParamsObject = await this.application.encryptionService.getRootKeyParams() + const keyParamsObject = await this.application.encryption.getRootKeyParams() const keyParams = keyParamsObject.content expect(keyParams.identifier).to.be.ok expect(keyParams.pw_nonce).to.be.ok @@ -533,7 +527,7 @@ describe('keys', function () { email, password, }) - const keyParamsObject = await this.application.encryptionService.getRootKeyParams() + const keyParamsObject = await this.application.encryption.getRootKeyParams() const keyParams = keyParamsObject.content expect(keyParams.created).to.be.ok @@ -551,7 +545,7 @@ describe('keys', function () { password: this.password, version: ProtocolVersion.V003, }) - const keyParamsObject = await this.application.encryptionService.getRootKeyParams() + const keyParamsObject = await this.application.encryption.getRootKeyParams() const keyParams = keyParamsObject.content expect(keyParams.created).to.be.ok @@ -566,12 +560,12 @@ describe('keys', function () { password: this.password, version: ProtocolVersion.V003, }) - expect(await this.application.encryptionService.getEncryptionDisplayName()).to.equal('AES-256') + expect(await this.application.encryption.getEncryptionDisplayName()).to.equal('AES-256') this.application = await Factory.signOutApplicationAndReturnNew(this.application) /** Register with 004 account */ await this.application.register(this.email + 'new', this.password) - expect(await this.application.encryptionService.getEncryptionDisplayName()).to.equal('XChaCha20-Poly1305') + expect(await this.application.encryption.getEncryptionDisplayName()).to.equal('XChaCha20-Poly1305') }) it('when launching app with no keychain but data, should present account recovery challenge', async function () { @@ -587,7 +581,7 @@ describe('keys', function () { password: this.password, }) /** Simulate empty keychain */ - await this.application.deviceInterface.clearRawKeychainValue() + await this.application.device.clearRawKeychainValue() const recreatedApp = await Factory.createApplicationWithFakeCrypto(id) let totalChallenges = 0 @@ -599,7 +593,7 @@ describe('keys', function () { await recreatedApp.prepareForLaunch({ receiveChallenge }) await recreatedApp.launch(true) - expect(recreatedApp.encryptionService.getRootKey()).to.be.ok + expect(recreatedApp.encryption.getRootKey()).to.be.ok expect(totalChallenges).to.equal(expectedChallenges) await Factory.safeDeinit(recreatedApp) }) @@ -630,7 +624,7 @@ describe('keys', function () { const newPassword = Utils.generateUuid() - await contextA.application.userService.changeCredentials({ + await contextA.application.user.changeCredentials({ currentPassword: password, newPassword: newPassword, origination: KeyParamsOrigination.PasswordChange, @@ -639,8 +633,8 @@ describe('keys', function () { await contextB.syncWithIntegrityCheck() await contextA.syncWithIntegrityCheck() - const clientAUndecryptables = contextA.application.keyRecoveryService.getUndecryptables() - const clientBUndecryptables = contextB.application.keyRecoveryService.getUndecryptables() + const clientAUndecryptables = contextA.keyRecovery.getUndecryptables() + const clientBUndecryptables = contextB.keyRecovery.getUndecryptables() expect(Object.keys(clientBUndecryptables).length).to.equal(1) expect(Object.keys(clientAUndecryptables).length).to.equal(0) @@ -684,13 +678,13 @@ describe('keys', function () { /** Change password through session manager directly instead of application, * as not to create any items key (to simulate 003 client behavior) */ - const currentRootKey = await oldClient.encryptionService.computeRootKey( + const currentRootKey = await oldClient.encryption.computeRootKey( this.password, - await oldClient.encryptionService.getRootKeyParams(), + await oldClient.encryption.getRootKeyParams(), ) - const operator = oldClient.encryptionService.operators.operatorForVersion(ProtocolVersion.V003) + const operator = this.context.operators.operatorForVersion(ProtocolVersion.V003) const newRootKey = await operator.createRootKey(this.email, this.password) - Object.defineProperty(oldClient.apiService, 'apiVersion', { + Object.defineProperty(oldClient.legacyApi, 'apiVersion', { get: function () { return '20190520' }, @@ -701,7 +695,7 @@ describe('keys', function () { */ await newClient.signIn(this.email, this.password) - await oldClient.sessionManager.changeCredentials({ + await oldClient.sessions.changeCredentials({ currentServerPassword: currentRootKey.serverPassword, newRootKey, }) @@ -711,7 +705,7 @@ describe('keys', function () { await Factory.sleep(1) /** Expect a new items key to be created based on the new root key */ - expect(newClient.itemManager.getDisplayableItemsKeys().length).to.equal(2) + expect(newClient.items.getDisplayableItemsKeys().length).to.equal(2) await Factory.safeDeinit(newClient) await Factory.safeDeinit(oldClient) @@ -734,11 +728,11 @@ describe('keys', function () { /** Change password through session manager directly instead of application, * as not to create any items key (to simulate 003 client behavior) */ - const currentRootKey = await this.application.encryptionService.computeRootKey( + const currentRootKey = await this.application.encryption.computeRootKey( this.password, - await this.application.encryptionService.getRootKeyParams(), + await this.application.encryption.getRootKeyParams(), ) - const operator = this.application.encryptionService.operators.operatorForVersion(ProtocolVersion.V003) + const operator = this.context.operators.operatorForVersion(ProtocolVersion.V003) const newRootKeyTemplate = await operator.createRootKey(this.email, this.password) const newRootKey = CreateNewRootKey({ ...newRootKeyTemplate.content, @@ -748,7 +742,7 @@ describe('keys', function () { }, }) - Object.defineProperty(this.application.apiService, 'apiVersion', { + Object.defineProperty(this.application.legacyApi, 'apiVersion', { get: function () { return '20190520' }, @@ -757,25 +751,25 @@ describe('keys', function () { /** Renew session to prevent timeouts */ this.application = await Factory.signOutAndBackIn(this.application, this.email, this.password) - await this.application.sessionManager.changeCredentials({ + await this.application.sessions.changeCredentials({ currentServerPassword: currentRootKey.serverPassword, newRootKey, }) - await this.application.encryptionService.reencryptApplicableItemsAfterUserRootKeyChange() + await this.application.encryption.reencryptApplicableItemsAfterUserRootKeyChange() /** Note: this may result in a deadlock if features_service syncs and results in an error */ await this.application.sync.sync({ awaitAll: true }) /** Relaunch application and expect new items key to be created */ const identifier = this.application.identifier /** Set to pre 2.0.15 version so migration runs */ - await this.application.deviceInterface.setRawStorageValue(`${identifier}-snjs_version`, '2.0.14') + await this.application.device.setRawStorageValue(`${identifier}-snjs_version`, '2.0.14') await Factory.safeDeinit(this.application) const refreshedApp = await Factory.createApplicationWithFakeCrypto(identifier) await Factory.initializeApplication(refreshedApp) /** Expect a new items key to be created based on the new root key */ - expect(refreshedApp.itemManager.getDisplayableItemsKeys().length).to.equal(2) + expect(refreshedApp.items.getDisplayableItemsKeys().length).to.equal(2) await Factory.safeDeinit(refreshedApp) }) }) @@ -793,8 +787,8 @@ describe('keys', function () { await this.context.sync() await promise - await this.application.itemManager.removeAllItemsFromMemory() - expect(this.application.encryptionService.getSureDefaultItemsKey()).to.not.be.ok + await this.application.items.removeAllItemsFromMemory() + expect(this.application.encryption.getSureDefaultItemsKey()).to.not.be.ok const protocol003 = new SNProtocolOperator003(new SNWebCrypto()) const key = await protocol003.createItemsKey() @@ -810,19 +804,19 @@ describe('keys', function () { }), ) - const defaultKey = this.application.encryptionService.getSureDefaultItemsKey() + const defaultKey = this.application.encryption.getSureDefaultItemsKey() expect(defaultKey.keyVersion).to.equal(ProtocolVersion.V003) expect(defaultKey.uuid).to.equal(key.uuid) await Factory.registerUserToApplication({ application: this.application }) const notePayload = Factory.createNotePayload() - expect(await this.application.encryptionService.itemsEncryption.keyToUseForItemEncryption(notePayload)).to.be.ok + expect(await this.application.encryption.itemsEncryption.keyToUseForItemEncryption(notePayload)).to.be.ok }) it('having unsynced items keys should resync them upon download first sync completion', async function () { await Factory.registerUserToApplication({ application: this.application }) - const itemsKey = this.application.itemManager.getDisplayableItemsKeys()[0] + const itemsKey = this.application.items.getDisplayableItemsKeys()[0] await this.application.mutator.emitItemFromPayload( itemsKey.payload.copy({ dirty: false, @@ -830,7 +824,7 @@ describe('keys', function () { deleted: false, }), ) - await this.application.syncService.sync({ + await this.application.sync.sync({ mode: SyncMode.DownloadFirst, }) const updatedKey = this.application.items.findItem(itemsKey.uuid) @@ -840,7 +834,7 @@ describe('keys', function () { it('having key while offline then signing into account with key should only have 1 default items key', async function () { const otherClient = await Factory.createInitAppWithFakeCrypto() /** Invert order of keys */ - otherClient.itemManager.itemsKeyDisplayController.setDisplayOptions({ sortBy: 'dsc' }) + otherClient.items.itemsKeyDisplayController.setDisplayOptions({ sortBy: 'dsc' }) /** On client A, create account and note */ await Factory.registerUserToApplication({ application: this.application, @@ -856,12 +850,12 @@ describe('keys', function () { email: this.email, password: this.password, }) - const defaultKeys = otherClient.encryptionService.itemsEncryption.getItemsKeys().filter((key) => { + const defaultKeys = otherClient.encryption.itemsEncryption.getItemsKeys().filter((key) => { return key.isDefault }) expect(defaultKeys.length).to.equal(1) - const rawPayloads = await otherClient.diskStorageService.getAllRawPayloads() + const rawPayloads = await otherClient.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(notePayload.items_key_id).to.equal(itemsKey.uuid) diff --git a/packages/snjs/mocha/lib/AppContext.js b/packages/snjs/mocha/lib/AppContext.js index 231f5d783..4127f0446 100644 --- a/packages/snjs/mocha/lib/AppContext.js +++ b/packages/snjs/mocha/lib/AppContext.js @@ -26,17 +26,16 @@ export class AppContext { } enableLogging() { - const syncService = this.application.syncService - const payloadManager = this.application.payloadManager + const payloadManager = this.application.payloads - syncService.getServiceName = () => { + this.application.sync.getServiceName = () => { return `${this.identifier}—SyncService` } payloadManager.getServiceName = () => { return `${this.identifier}-PayloadManager` } - syncService.loggingEnabled = true + this.application.sync.loggingEnabled = true payloadManager.loggingEnabled = true } @@ -51,7 +50,7 @@ export class AppContext { } get vaults() { - return this.application.vaultService + return this.application.vaults } get sessions() { @@ -67,31 +66,51 @@ export class AppContext { } get payloads() { - return this.application.payloadManager + return this.application.payloads } get encryption() { - return this.application.encryptionService + return this.application.encryption + } + + get keyRecovery() { + return this.application.dependencies.get(TYPES.KeyRecoveryService) + } + + get singletons() { + return this.application.dependencies.get(TYPES.SingletonManager) + } + + get history() { + return this.application.dependencies.get(TYPES.HistoryManager) + } + + get subscriptions() { + return this.application.dependencies.get(TYPES.SubscriptionManager) } get contacts() { - return this.application.contactService + return this.application.contacts } get sharedVaults() { - return this.application.sharedVaultService + return this.application.sharedVaults } get files() { - return this.application.fileService + return this.application.files } get keys() { - return this.application.keySystemKeyManager + return this.application.dependencies.get(TYPES.KeySystemKeyManager) + } + + get operators() { + return this.application.dependencies.get(TYPES.EncryptionOperators) } get asymmetric() { - return this.application.asymmetricMessageService + return this.application.asymmetric } get publicKey() { @@ -115,20 +134,20 @@ export class AppContext { } disableIntegrityAutoHeal() { - this.application.syncService.emitOutOfSyncRemotePayloads = () => { + this.application.sync.emitOutOfSyncRemotePayloads = () => { console.warn('Integrity self-healing is disabled for this test') } } disableKeyRecovery() { - this.application.keyRecoveryService.beginKeyRecovery = () => { + this.keyRecovery.beginKeyRecovery = () => { console.warn('Key recovery is disabled for this test') } } handleChallenge = (challenge) => { if (this.ignoringChallenges) { - this.application.challengeService.cancelChallenge(challenge) + this.application.challenges.cancelChallenge(challenge) return } @@ -178,15 +197,12 @@ export class AppContext { }, }) - return this.application.syncService.handleSuccessServerResponse( - { payloadsSavedOrSaving: [], options: {} }, - response, - ) + return this.application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) } resolveWhenKeyRecovered(uuid) { return new Promise((resolve) => { - this.application.keyRecoveryService.addEventObserver((_eventName, keys) => { + this.keyRecovery.addEventObserver((_eventName, keys) => { if (Uuids(keys).includes(uuid)) { resolve() } @@ -196,7 +212,7 @@ export class AppContext { resolveWhenSharedVaultUserKeysResolved() { return new Promise((resolve) => { - this.application.vaultService.collaboration.addEventObserver((eventName) => { + this.application.vaults.collaboration.addEventObserver((eventName) => { if (eventName === SharedVaultServiceEvent.SharedVaultStatusChanged) { resolve() } @@ -206,7 +222,7 @@ export class AppContext { async awaitSignInEvent() { return new Promise((resolve) => { - this.application.userService.addEventObserver((eventName) => { + this.application.user.addEventObserver((eventName) => { if (eventName === AccountEvent.SignedInOrRegistered) { resolve() } @@ -235,7 +251,7 @@ export class AppContext { awaitNextSucessfulSync() { return new Promise((resolve) => { - const removeObserver = this.application.syncService.addEventObserver((event) => { + const removeObserver = this.application.sync.addEventObserver((event) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploadedAndDownloaded) { removeObserver() resolve() @@ -246,7 +262,7 @@ export class AppContext { awaitNextSyncEvent(eventName) { return new Promise((resolve) => { - const removeObserver = this.application.syncService.addEventObserver((event, data) => { + const removeObserver = this.application.sync.addEventObserver((event, data) => { if (event === eventName) { removeObserver() resolve(data) @@ -257,7 +273,7 @@ export class AppContext { awaitNextSyncSharedVaultFromScratchEvent() { return new Promise((resolve) => { - const removeObserver = this.application.syncService.addEventObserver((event, data) => { + const removeObserver = this.application.sync.addEventObserver((event, data) => { if (event === SyncEvent.PaginatedSyncRequestCompleted && data?.options?.sharedVaultUuids) { removeObserver() resolve(data) @@ -268,7 +284,7 @@ export class AppContext { resolveWithUploadedPayloads() { return new Promise((resolve) => { - this.application.syncService.addEventObserver((event, data) => { + this.application.sync.addEventObserver((event, data) => { if (event === SyncEvent.PaginatedSyncRequestCompleted) { resolve(data.uploadedPayloads) } @@ -278,7 +294,7 @@ export class AppContext { resolveWithConflicts() { return new Promise((resolve) => { - this.application.syncService.addEventObserver((event, response) => { + this.application.sync.addEventObserver((event, response) => { if (event === SyncEvent.PaginatedSyncRequestCompleted) { resolve(response.rawConflictObjects) } @@ -288,7 +304,7 @@ export class AppContext { resolveWhenSavedSyncPayloadsIncludesItemUuid(uuid) { return new Promise((resolve) => { - this.application.syncService.addEventObserver((event, response) => { + this.application.sync.addEventObserver((event, response) => { if (event === SyncEvent.PaginatedSyncRequestCompleted) { const savedPayload = response.savedPayloads.find((payload) => payload.uuid === uuid) if (savedPayload) { @@ -301,7 +317,7 @@ export class AppContext { resolveWhenSavedSyncPayloadsIncludesItemThatIsDuplicatedOf(uuid) { return new Promise((resolve) => { - this.application.syncService.addEventObserver((event, response) => { + this.application.sync.addEventObserver((event, response) => { if (event === SyncEvent.PaginatedSyncRequestCompleted) { const savedPayload = response.savedPayloads.find((payload) => payload.duplicate_of === uuid) if (savedPayload) { @@ -354,7 +370,7 @@ export class AppContext { resolveWhenUserMessagesProcessingCompletes() { return new Promise((resolve) => { - const objectToSpy = this.application.userEventService + const objectToSpy = this.application.dependencies.get(TYPES.UserEventService) sinon.stub(objectToSpy, 'handleReceivedUserEvents').callsFake(async (params) => { objectToSpy.handleReceivedUserEvents.restore() const result = await objectToSpy.handleReceivedUserEvents(params) @@ -364,12 +380,36 @@ export class AppContext { }) } + resolveWhenAllInboundAsymmetricMessagesAreDeleted() { + return new Promise((resolve) => { + const objectToSpy = this.application.dependencies.get(TYPES.AsymmetricMessageServer) + sinon.stub(objectToSpy, 'deleteAllInboundMessages').callsFake(async (params) => { + objectToSpy.deleteAllInboundMessages.restore() + const result = await objectToSpy.deleteAllInboundMessages(params) + resolve() + return result + }) + }) + } + + resolveWhenAllInboundSharedVaultInvitesAreDeleted() { + return new Promise((resolve) => { + const objectToSpy = this.application.sharedVaults.invitesServer + sinon.stub(objectToSpy, 'deleteAllInboundInvites').callsFake(async (params) => { + objectToSpy.deleteAllInboundInvites.restore() + const result = await objectToSpy.deleteAllInboundInvites(params) + resolve() + return result + }) + }) + } + resolveWhenSharedVaultServiceSendsContactShareMessage() { return new Promise((resolve) => { const objectToSpy = this.sharedVaults - sinon.stub(objectToSpy, 'shareContactWithUserAdministeredSharedVaults').callsFake(async (contact) => { - objectToSpy.shareContactWithUserAdministeredSharedVaults.restore() - const result = await objectToSpy.shareContactWithUserAdministeredSharedVaults(contact) + sinon.stub(objectToSpy, 'shareContactWithVaults').callsFake(async (contact) => { + objectToSpy.shareContactWithVaults.restore() + const result = await objectToSpy.shareContactWithVaults(contact) resolve() return result }) @@ -405,14 +445,14 @@ export class AppContext { } awaitUserPrefsSingletonCreation() { - const preferences = this.application.preferencesService.preferences + const preferences = this.application.preferences.preferences if (preferences) { return } let didCompleteRelevantSync = false return new Promise((resolve) => { - this.application.syncService.addEventObserver((eventName, data) => { + this.application.sync.addEventObserver((eventName, data) => { if (!didCompleteRelevantSync) { if (data?.savedPayloads) { const matching = data.savedPayloads.find((p) => { @@ -430,7 +470,7 @@ export class AppContext { awaitUserPrefsSingletonResolution() { return new Promise((resolve) => { - this.application.preferencesService.addEventObserver((eventName) => { + this.application.preferences.addEventObserver((eventName) => { if (eventName === PreferencesServiceEvent.PreferencesChanged) { resolve() } @@ -473,7 +513,7 @@ export class AppContext { } findPayload(uuid) { - return this.application.payloadManager.findPayload(uuid) + return this.application.payloads.findPayload(uuid) } get itemsKeys() { @@ -491,15 +531,15 @@ export class AppContext { } disableKeyRecoveryServerSignIn() { - this.application.keyRecoveryService.performServerSignIn = () => { - console.warn('application.keyRecoveryService.performServerSignIn has been stubbed with an empty implementation') + this.keyRecovery.performServerSignIn = () => { + console.warn('application.keyRecovery.performServerSignIn has been stubbed with an empty implementation') } } preventKeyRecoveryOfKeys(ids) { - const originalImpl = this.application.keyRecoveryService.handleUndecryptableItemsKeys + const originalImpl = this.keyRecovery.handleUndecryptableItemsKeys - this.application.keyRecoveryService.handleUndecryptableItemsKeys = function (keys) { + this.keyRecovery.handleUndecryptableItemsKeys = function (keys) { const filtered = keys.filter((k) => !ids.includes(k.uuid)) originalImpl.apply(this, [filtered]) @@ -520,18 +560,18 @@ export class AppContext { const payload = createNotePayload(title, text) const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) await this.application.mutator.setItemDirty(item) - await this.application.syncService.sync(MaximumSyncOptions) + await this.application.sync.sync(MaximumSyncOptions) const note = this.application.items.findItem(payload.uuid) return note } lockSyncing() { - this.application.syncService.lockSyncing() + this.application.sync.lockSyncing() } unlockSyncing() { - this.application.syncService.unlockSyncing() + this.application.sync.unlockSyncing() } async deleteItemAndSync(item) { @@ -624,7 +664,9 @@ export class AppContext { await Utils.sleep(2) } catch (error) { - console.warn(`Mock events service not available. You are probalby running a test suite for home server: ${error.message}`) + console.warn( + `Mock events service not available. You are probalby running a test suite for home server: ${error.message}`, + ) } try { @@ -632,7 +674,9 @@ export class AppContext { await Utils.sleep(1) } catch (error) { - console.warn(`Home server not available. You are probalby running a test suite for self hosted setup: ${error.message}`) + console.warn( + `Home server not available. You are probalby running a test suite for self hosted setup: ${error.message}`, + ) } } } diff --git a/packages/snjs/mocha/lib/Collaboration.js b/packages/snjs/mocha/lib/Collaboration.js index c3328e0d4..8163e19c7 100644 --- a/packages/snjs/mocha/lib/Collaboration.js +++ b/packages/snjs/mocha/lib/Collaboration.js @@ -15,7 +15,7 @@ export const createTrustedContactForUserOfContext = async ( contextAddingNewContact, contextImportingContactInfoFrom, ) => { - const contact = await contextAddingNewContact.application.contactService.createOrEditTrustedContact({ + const contact = await contextAddingNewContact.contacts.createOrEditTrustedContact({ name: 'John Doe', publicKey: contextImportingContactInfoFrom.publicKey, signingPublicKey: contextImportingContactInfoFrom.signingPublicKey, @@ -27,6 +27,10 @@ export const createTrustedContactForUserOfContext = async ( export const acceptAllInvites = async (context) => { const inviteRecords = context.sharedVaults.getCachedPendingInviteRecords() + if (inviteRecords.length === 0) { + throw new Error('No pending invites to accept') + } + for (const record of inviteRecords) { await context.sharedVaults.acceptPendingSharedVaultInvite(record) } @@ -72,7 +76,7 @@ export const createSharedVaultWithUnacceptedButTrustedInvite = async ( const contact = await createTrustedContactForUserOfContext(context, contactContext) await createTrustedContactForUserOfContext(contactContext, context) - const invite = await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions) + const invite = (await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue() await contactContext.sync() return { sharedVault, contact, contactContext, deinitContactContext, invite } @@ -87,7 +91,7 @@ export const createSharedVaultWithUnacceptedAndUntrustedInvite = async ( const { contactContext, deinitContactContext } = await createContactContext() const contact = await createTrustedContactForUserOfContext(context, contactContext) - const invite = await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions) + const invite = (await context.sharedVaults.inviteContactToSharedVault(sharedVault, contact, permissions)).getValue() await contactContext.sync() return { sharedVault, contact, contactContext, deinitContactContext, invite } diff --git a/packages/snjs/mocha/lib/Utils.js b/packages/snjs/mocha/lib/Utils.js index 5c6bcb830..a39b01225 100644 --- a/packages/snjs/mocha/lib/Utils.js +++ b/packages/snjs/mocha/lib/Utils.js @@ -8,10 +8,10 @@ export async function safeDeinit(application) { return } - await application.diskStorageService.awaitPersist() + await application.storage.awaitPersist() /** Limit waiting to 1s */ - await Promise.race([sleep(1), application.syncService?.awaitCurrentSyncs()]) + await Promise.race([sleep(1), application.sync?.awaitCurrentSyncs()]) await application.prepareForDeinit() diff --git a/packages/snjs/mocha/lib/factory.js b/packages/snjs/mocha/lib/factory.js index 87e5f9311..7f3fa9bdc 100644 --- a/packages/snjs/mocha/lib/factory.js +++ b/packages/snjs/mocha/lib/factory.js @@ -58,7 +58,7 @@ export async function createAppContext({ identifier, crypto, email, password, ho } export function disableIntegrityAutoHeal(application) { - application.syncService.emitOutOfSyncRemotePayloads = () => { + application.sync.emitOutOfSyncRemotePayloads = () => { console.warn('Integrity self-healing is disabled for this test') } } @@ -112,12 +112,12 @@ export function registerUserToApplication({ application, email, password, epheme } export async function setOldVersionPasscode({ application, passcode, version }) { - const identifier = await application.encryptionService.crypto.generateUUID() - const operator = application.encryptionService.operators.operatorForVersion(version) + const identifier = await application.encryption.crypto.generateUUID() + const operator = application.dependencies.get(TYPES.EncryptionOperators).operatorForVersion(version) const key = await operator.createRootKey(identifier, passcode, KeyParamsOrigination.PasscodeCreate) - await application.encryptionService.setNewRootKeyWrapper(key) - await application.userService.rewriteItemsKeys() - await application.syncService.sync(syncOptions) + await application.encryption.setNewRootKeyWrapper(key) + await application.user.rewriteItemsKeys() + await application.sync.sync(syncOptions) } /** @@ -127,25 +127,26 @@ export async function setOldVersionPasscode({ application, passcode, version }) export async function registerOldUser({ application, email, password, version }) { if (!email) email = Utils.generateUuid() if (!password) password = Utils.generateUuid() - const operator = application.encryptionService.operators.operatorForVersion(version) + const operator = application.dependencies.get(TYPES.EncryptionOperators).operatorForVersion(version) const accountKey = await operator.createRootKey(email, password, KeyParamsOrigination.Registration) - const response = await application.userApiService.register({ + const response = await application.dependencies.get(TYPES.UserApiService).register({ email: email, serverPassword: accountKey.serverPassword, keyParams: accountKey.keyParams, }) + /** Mark all existing items as dirty. */ - await application.mutator.changeItems(application.itemManager.items, (m) => { + await application.mutator.changeItems(application.items.items, (m) => { m.dirty = true }) - await application.sessionManager.handleSuccessAuthResponse(response, accountKey) + await application.sessions.handleSuccessAuthResponse(response, accountKey) application.notifyEvent(ApplicationEvent.SignedIn) - await application.syncService.sync({ + await application.sync.sync({ mode: SyncMode.DownloadFirst, ...syncOptions, }) - await application.encryptionService.decryptErroredPayloads() + await application.encryption.decryptErroredPayloads() } export function createStorageItemPayload(contentType) { @@ -182,13 +183,13 @@ export async function createSyncedNote(application, title, text) { const payload = createNotePayload(title, text) const item = await application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) await application.mutator.setItemDirty(item) - await application.syncService.sync(syncOptions) + await application.sync.sync(syncOptions) const note = application.items.findItem(payload.uuid) return note } export async function getStoragePayloadsOfType(application, type) { - const rawPayloads = await application.diskStorageService.getAllRawPayloads() + const rawPayloads = await application.storage.getAllRawPayloads() return rawPayloads .filter((rp) => rp.content_type === type) .map((rp) => { @@ -263,7 +264,7 @@ export async function restartApplication(application) { } export async function storagePayloadCount(application) { - const payloads = await application.diskStorageService.getAllRawPayloads() + const payloads = await application.storage.getAllRawPayloads() return payloads.length } @@ -397,28 +398,28 @@ export async function insertItemWithOverride(application, contentType, content, errorDecrypting, }) - await application.payloadManager.emitPayload(encrypted) + await application.payloads.emitPayload(encrypted) } else { const decrypted = new DecryptedPayload({ ...item.payload.ejected(), }) - await application.payloadManager.emitPayload(decrypted) + await application.payloads.emitPayload(decrypted) } - return application.itemManager.findAnyItem(item.uuid) + return application.items.findAnyItem(item.uuid) } export async function alternateUuidForItem(application, uuid) { - const item = application.itemManager.findItem(uuid) + const item = application.items.findItem(uuid) const payload = new DecryptedPayload(item) - const results = await PayloadsByAlternatingUuid(payload, application.payloadManager.getMasterCollection()) - await application.payloadManager.emitPayloads(results, PayloadEmitSource.LocalChanged) - await application.syncService.persistPayloads(results) - return application.itemManager.findItem(results[0].uuid) + const results = await PayloadsByAlternatingUuid(payload, application.payloads.getMasterCollection()) + await application.payloads.emitPayloads(results, PayloadEmitSource.LocalChanged) + await application.sync.persistPayloads(results) + return application.items.findItem(results[0].uuid) } export async function markDirtyAndSyncItem(application, itemToLookupUuidFor) { - const item = application.itemManager.findItem(itemToLookupUuidFor.uuid) + const item = application.items.findItem(itemToLookupUuidFor.uuid) if (!item) { throw Error('Attempting to save non-inserted item') } @@ -433,11 +434,11 @@ export async function changePayloadTimeStampAndSync(application, payload, timest await application.sync.sync(syncOptions) - return application.itemManager.findAnyItem(payload.uuid) + return application.items.findAnyItem(payload.uuid) } export async function changePayloadTimeStamp(application, payload, timestamp, contentOverride) { - payload = application.payloadManager.collection.find(payload.uuid) + payload = application.payloads.collection.find(payload.uuid) const changedPayload = new DecryptedPayload({ ...payload, dirty: true, @@ -451,11 +452,11 @@ export async function changePayloadTimeStamp(application, payload, timestamp, co await application.mutator.emitItemFromPayload(changedPayload) - return application.itemManager.findAnyItem(payload.uuid) + return application.items.findAnyItem(payload.uuid) } export async function changePayloadUpdatedAt(application, payload, updatedAt) { - const latestPayload = application.payloadManager.collection.find(payload.uuid) + const latestPayload = application.payloads.collection.find(payload.uuid) const changedPayload = new DecryptedPayload({ ...latestPayload.ejected(), @@ -468,7 +469,7 @@ export async function changePayloadUpdatedAt(application, payload, updatedAt) { } export async function changePayloadTimeStampDeleteAndSync(application, payload, timestamp, syncOptions) { - payload = application.payloadManager.collection.find(payload.uuid) + payload = application.payloads.collection.find(payload.uuid) const changedPayload = new DeletedPayload({ ...payload, content: undefined, @@ -478,6 +479,6 @@ export async function changePayloadTimeStampDeleteAndSync(application, payload, updated_at_timestamp: timestamp, }) - await application.payloadManager.emitPayload(changedPayload) + await application.payloads.emitPayload(changedPayload) await application.sync.sync(syncOptions) } diff --git a/packages/snjs/mocha/lib/fake_web_crypto.js b/packages/snjs/mocha/lib/fake_web_crypto.js index c898b693b..d910fdaaa 100644 --- a/packages/snjs/mocha/lib/fake_web_crypto.js +++ b/packages/snjs/mocha/lib/fake_web_crypto.js @@ -139,8 +139,8 @@ export default class FakeWebCrypto { const data = { message, nonce, - senderSecretKey, recipientPublicKey, + senderSecretKey, } return btoa(JSON.stringify(data)) } diff --git a/packages/snjs/mocha/mfa_service.test.js b/packages/snjs/mocha/mfa_service.test.js index 76c809212..cb585202a 100644 --- a/packages/snjs/mocha/mfa_service.test.js +++ b/packages/snjs/mocha/mfa_service.test.js @@ -54,11 +54,11 @@ describe('mfa service', () => { const secret = await snApp.generateMfaSecret() const token = await snApp.getOtpToken(secret) - sinon.spy(snApp.challengeService, 'sendChallenge') + sinon.spy(snApp.challenges, 'sendChallenge') await snApp.enableMfa(secret, token) await snApp.disableMfa() - const spyCall = snApp.challengeService.sendChallenge.getCall(0) + const spyCall = snApp.challenges.sendChallenge.getCall(0) const challenge = spyCall.firstArg expect(challenge.prompts).to.have.lengthOf(2) expect(challenge.prompts[0].validation).to.equal(ChallengeValidation.AccountPassword) diff --git a/packages/snjs/mocha/migrations/migration.test.js b/packages/snjs/mocha/migrations/migration.test.js index 5afc60eb8..517dc8565 100644 --- a/packages/snjs/mocha/migrations/migration.test.js +++ b/packages/snjs/mocha/migrations/migration.test.js @@ -15,39 +15,39 @@ describe('migrations', () => { it('version number is stored as string', async function () { const application = await Factory.createInitAppWithFakeCrypto() - const version = await application.migrationService.getStoredSnjsVersion() + const version = await application.migrations.getStoredSnjsVersion() expect(typeof version).to.equal('string') await Factory.safeDeinit(application) }) it('should return correct required migrations if stored version is 1.0.0', async function () { - expect((await SNMigrationService.getRequiredMigrations('1.0.0')).length).to.equal(allMigrationsLength) + expect((await MigrationService.getRequiredMigrations('1.0.0')).length).to.equal(allMigrationsLength) }) it('should return correct required migrations if stored version is 2.0.0', async function () { - expect((await SNMigrationService.getRequiredMigrations('2.0.0')).length).to.equal(allMigrationsLength) + expect((await MigrationService.getRequiredMigrations('2.0.0')).length).to.equal(allMigrationsLength) }) it('should return 0 required migrations if stored version is futuristic', async function () { - expect((await SNMigrationService.getRequiredMigrations('100.0.1')).length).to.equal(0) + expect((await MigrationService.getRequiredMigrations('100.0.1')).length).to.equal(0) }) it('after running base migration with no present storage values, should set version to current', async function () { const application = await Factory.createAppWithRandNamespace() - await application.migrationService.runBaseMigrationPreRun() - expect(await application.migrationService.getStoredSnjsVersion()).to.equal(SnjsVersion) + await application.migrations.runBaseMigrationPreRun() + expect(await application.migrations.getStoredSnjsVersion()).to.equal(SnjsVersion) await Factory.safeDeinit(application) }) it('after running all migrations from a 2.0.0 installation, should set stored version to current', async function () { const application = await Factory.createAppWithRandNamespace() /** Set up 2.0.0 structure with tell-tale storage key */ - await application.deviceInterface.setRawStorageValue('last_migration_timestamp', JSON.stringify(['anything'])) + await application.device.setRawStorageValue('last_migration_timestamp', JSON.stringify(['anything'])) await application.prepareForLaunch({ receiveChallenge: () => {}, }) await application.launch(true) - expect(await application.migrationService.getStoredSnjsVersion()).to.equal(SnjsVersion) + expect(await application.migrations.getStoredSnjsVersion()).to.equal(SnjsVersion) await Factory.safeDeinit(application) }) @@ -72,18 +72,18 @@ describe('migrations', () => { await application.sync.sync() expect(application.items.getItems('SF|MFA').length).to.equal(1) - expect( - (await application.diskStorageService.getAllRawPayloads()).filter((p) => p.content_type === 'SF|MFA').length, - ).to.equal(1) + expect((await application.storage.getAllRawPayloads()).filter((p) => p.content_type === 'SF|MFA').length).to.equal( + 1, + ) /** Run migration */ - const migration = new Migration2_20_0(application.migrationService.services) + const migration = new Migration2_20_0(application.migrations.services) await migration.handleStage(ApplicationStage.LoadedDatabase_12) expect(application.items.getItems('SF|MFA').length).to.equal(0) - expect( - (await application.diskStorageService.getAllRawPayloads()).filter((p) => p.content_type === 'SF|MFA').length, - ).to.equal(0) + expect((await application.storage.getAllRawPayloads()).filter((p) => p.content_type === 'SF|MFA').length).to.equal( + 0, + ) await Factory.safeDeinit(application) }) @@ -113,7 +113,7 @@ describe('migrations', () => { expect(application.items.getItems(ContentType.TYPES.Theme).length).to.equal(1) /** Run migration */ - const migration = new Migration2_42_0(application.migrationService.services) + const migration = new Migration2_42_0(application.migrations.services) await migration.handleStage(ApplicationStage.FullSyncCompleted_13) await application.sync.sync() @@ -156,7 +156,7 @@ describe('migrations', () => { expect(application.items.getItems(ContentType.TYPES.Component).length).to.equal(1) /** Run migration */ - const migration = new Migration2_202_1(application.migrationService.services) + const migration = new Migration2_202_1(application.migrations.services) await migration.handleStage(ApplicationStage.FullSyncCompleted_13) await application.sync.sync() @@ -181,7 +181,7 @@ describe('migrations', () => { expect(application.items.getItems(ContentType.TYPES.Component).length).to.equal(1) /** Run migration */ - const migration = new Migration2_202_1(application.migrationService.services) + const migration = new Migration2_202_1(application.migrations.services) await migration.handleStage(ApplicationStage.FullSyncCompleted_13) await application.sync.sync() diff --git a/packages/snjs/mocha/migrations/tags-to-folders.test.js b/packages/snjs/mocha/migrations/tags-to-folders.test.js index d617cc89e..989a2be42 100644 --- a/packages/snjs/mocha/migrations/tags-to-folders.test.js +++ b/packages/snjs/mocha/migrations/tags-to-folders.test.js @@ -171,7 +171,7 @@ const makeTags = async (application, titles) => { const extractTagHierarchy = (application) => { const result = {} - const roots = application.itemManager.getRootTags() + const roots = application.items.getRootTags() const constructHierarchy = (currentTag, result) => { result[currentTag.title] = { _uuid: currentTag.uuid } diff --git a/packages/snjs/mocha/model_tests/appmodels.test.js b/packages/snjs/mocha/model_tests/appmodels.test.js index 9171d1d18..366eeab97 100644 --- a/packages/snjs/mocha/model_tests/appmodels.test.js +++ b/packages/snjs/mocha/model_tests/appmodels.test.js @@ -30,8 +30,8 @@ describe('app models', () => { await Factory.safeDeinit(this.application) }) - it('payloadManager should be defined', () => { - expect(sharedApplication.payloadManager).to.be.ok + it('payloads should be defined', () => { + expect(sharedApplication.payloads).to.be.ok }) it('item should be defined', () => { @@ -75,14 +75,14 @@ describe('app models', () => { await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) await this.application.mutator.emitItemsFromPayloads([params2], PayloadEmitSource.LocalChanged) - const item1 = this.application.itemManager.findItem(params1.uuid) - const item2 = this.application.itemManager.findItem(params2.uuid) + const item1 = this.application.items.findItem(params1.uuid) + const item2 = this.application.items.findItem(params2.uuid) expect(item1.content.references.length).to.equal(1) expect(item2.content.references.length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item1).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) }) it('mapping an item twice shouldnt cause problems', async function () { @@ -103,7 +103,7 @@ describe('app models', () => { item = items[0] expect(item.content.foo).to.equal('bar') - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) }) it('mapping item twice should preserve references', async function () { @@ -117,7 +117,7 @@ describe('app models', () => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) }) - const refreshedItem = this.application.itemManager.findItem(item1.uuid) + const refreshedItem = this.application.items.findItem(item1.uuid) expect(refreshedItem.content.references.length).to.equal(1) }) @@ -132,8 +132,8 @@ describe('app models', () => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) }) - const refreshedItem1 = this.application.itemManager.findItem(item1.uuid) - const refreshedItem2 = this.application.itemManager.findItem(item2.uuid) + const refreshedItem1 = this.application.items.findItem(item1.uuid) + const refreshedItem2 = this.application.items.findItem(item2.uuid) expect(refreshedItem1.content.references.length).to.equal(1) expect(refreshedItem2.content.references.length).to.equal(1) @@ -147,8 +147,8 @@ describe('app models', () => { }) await this.application.mutator.emitItemsFromPayloads([damagedPayload], PayloadEmitSource.LocalChanged) - const refreshedItem1_2 = this.application.itemManager.findItem(item1.uuid) - const refreshedItem2_2 = this.application.itemManager.findItem(item2.uuid) + const refreshedItem1_2 = this.application.items.findItem(item1.uuid) + const refreshedItem2_2 = this.application.items.findItem(item2.uuid) expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem2_2.content.references.length).to.equal(1) @@ -164,14 +164,14 @@ describe('app models', () => { mutator.e2ePendingRefactor_addItemAsRelationship(item1) }) - const refreshedItem1 = this.application.itemManager.findItem(item1.uuid) - const refreshedItem2 = this.application.itemManager.findItem(item2.uuid) + const refreshedItem1 = this.application.items.findItem(item1.uuid) + const refreshedItem2 = this.application.items.findItem(item2.uuid) expect(refreshedItem1.content.references.length).to.equal(1) expect(refreshedItem2.content.references.length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(item1)).to.include(refreshedItem2) - expect(this.application.itemManager.itemsReferencingItem(item2)).to.include(refreshedItem1) + expect(this.application.items.itemsReferencingItem(item1)).to.include(refreshedItem2) + expect(this.application.items.itemsReferencingItem(item2)).to.include(refreshedItem1) await this.application.mutator.changeItem(item1, (mutator) => { mutator.removeItemAsRelationship(item2) @@ -180,14 +180,14 @@ describe('app models', () => { mutator.removeItemAsRelationship(item1) }) - const refreshedItem1_2 = this.application.itemManager.findItem(item1.uuid) - const refreshedItem2_2 = this.application.itemManager.findItem(item2.uuid) + const refreshedItem1_2 = this.application.items.findItem(item1.uuid) + const refreshedItem2_2 = this.application.items.findItem(item2.uuid) expect(refreshedItem1_2.content.references.length).to.equal(0) expect(refreshedItem2_2.content.references.length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item1).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0) }) it('properly duplicates item with no relationships', async function () { @@ -213,10 +213,10 @@ describe('app models', () => { expect(duplicate.uuid).to.not.equal(item1.uuid) expect(duplicate.content.references.length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(item1).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(2) + expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(2) - const refreshedItem1_2 = this.application.itemManager.findItem(item1.uuid) + const refreshedItem1_2 = this.application.items.findItem(item1.uuid) expect(refreshedItem1_2.isItemContentEqualWith(duplicate)).to.equal(true) expect(refreshedItem1_2.created_at.toISOString()).to.equal(duplicate.created_at.toISOString()) expect(refreshedItem1_2.content_type).to.equal(duplicate.content_type) @@ -240,8 +240,8 @@ describe('app models', () => { PayloadEmitSource.LocalChanged, ) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item1).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(item1).length).to.equal(0) expect(refreshedItem1_2.content.references.length).to.equal(0) }) @@ -254,18 +254,18 @@ describe('app models', () => { }) expect(refreshedItem1.content.references.length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) const alternatedItem = await Factory.alternateUuidForItem(this.application, item1.uuid) - const refreshedItem1_2 = this.application.itemManager.findItem(item1.uuid) + const refreshedItem1_2 = this.application.items.findItem(item1.uuid) expect(refreshedItem1_2).to.not.be.ok - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2) + expect(this.application.items.getDisplayableNotes().length).to.equal(2) expect(alternatedItem.content.references.length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(alternatedItem.uuid).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(alternatedItem.uuid).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) expect(alternatedItem.isReferencingItem(item2)).to.equal(true) expect(alternatedItem.dirty).to.equal(true) @@ -279,10 +279,10 @@ describe('app models', () => { it('alterating itemskey uuid should update errored items encrypted with that key', async function () { const item1 = await Factory.createMappedNote(this.application) - const itemsKey = this.application.itemManager.getDisplayableItemsKeys()[0] + const itemsKey = this.application.items.getDisplayableItemsKeys()[0] /** Encrypt item1 and emit as errored so it persists with items_key_id */ - const encrypted = await this.application.encryptionService.encryptSplitSingle({ + const encrypted = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [item1.payload], }, @@ -292,17 +292,17 @@ describe('app models', () => { waitingForKey: true, }) - await this.application.payloadManager.emitPayload(errored) + await this.application.payloads.emitPayload(errored) - expect(this.application.payloadManager.findOne(item1.uuid).errorDecrypting).to.equal(true) - expect(this.application.payloadManager.findOne(item1.uuid).items_key_id).to.equal(itemsKey.uuid) + expect(this.application.payloads.findOne(item1.uuid).errorDecrypting).to.equal(true) + expect(this.application.payloads.findOne(item1.uuid).items_key_id).to.equal(itemsKey.uuid) - sinon.stub(this.application.encryptionService.itemsEncryption, 'decryptErroredItemPayloads').callsFake(() => { + sinon.stub(this.application.encryption.itemsEncryption, 'decryptErroredItemPayloads').callsFake(() => { // prevent auto decryption }) const alternatedKey = await Factory.alternateUuidForItem(this.application, itemsKey.uuid) - const updatedPayload = this.application.payloadManager.findOne(item1.uuid) + const updatedPayload = this.application.payloads.findOne(item1.uuid) expect(updatedPayload.items_key_id).to.equal(alternatedKey.uuid) }) @@ -316,22 +316,22 @@ describe('app models', () => { mutator.e2ePendingRefactor_addItemAsRelationship(item2) }) - expect(this.application.itemManager.itemsReferencingItem(item2).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(item2).length).to.equal(1) const alternatedItem1 = await Factory.alternateUuidForItem(this.application, item1.uuid) const alternatedItem2 = await Factory.alternateUuidForItem(this.application, item2.uuid) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(item1.uuid).to.not.equal(alternatedItem1.uuid) expect(item2.uuid).to.not.equal(alternatedItem2.uuid) - const refreshedAltItem1 = this.application.itemManager.findItem(alternatedItem1.uuid) + const refreshedAltItem1 = this.application.items.findItem(alternatedItem1.uuid) expect(refreshedAltItem1.content.references.length).to.equal(1) expect(refreshedAltItem1.content.references[0].uuid).to.equal(alternatedItem2.uuid) expect(alternatedItem2.content.references.length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(alternatedItem2).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(alternatedItem2).length).to.equal(1) expect(refreshedAltItem1.isReferencingItem(alternatedItem2)).to.equal(true) expect(alternatedItem2.isReferencingItem(refreshedAltItem1)).to.equal(false) @@ -350,12 +350,12 @@ describe('app models', () => { const noteCopy = await this.application.mutator.duplicateItem(note) expect(note.uuid).to.not.equal(noteCopy.uuid) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2) - expect(this.application.itemManager.getDisplayableTags().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(2) + expect(this.application.items.getDisplayableTags().length).to.equal(1) expect(note.content.references.length).to.equal(0) expect(noteCopy.content.references.length).to.equal(0) - const refreshedTag_2 = this.application.itemManager.findItem(tag.uuid) + const refreshedTag_2 = this.application.items.findItem(tag.uuid) expect(refreshedTag_2.content.references.length).to.equal(2) }) diff --git a/packages/snjs/mocha/model_tests/importing.test.js b/packages/snjs/mocha/model_tests/importing.test.js index 08ca86cfb..71d2c0c5c 100644 --- a/packages/snjs/mocha/model_tests/importing.test.js +++ b/packages/snjs/mocha/model_tests/importing.test.js @@ -73,14 +73,14 @@ describe('importing', function () { await application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) expectedItemCount += 2 - const note = application.itemManager.getItems([ContentType.TYPES.Note])[0] - const tag = application.itemManager.getItems([ContentType.TYPES.Tag])[0] + const note = application.items.getItems([ContentType.TYPES.Note])[0] + const tag = application.items.getItems([ContentType.TYPES.Tag])[0] expect(tag.content.references.length).to.equal(1) expect(tag.noteCount).to.equal(1) expect(note.content.references.length).to.equal(0) - expect(application.itemManager.itemsReferencingItem(note).length).to.equal(1) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) await application.importData( { @@ -89,13 +89,13 @@ describe('importing', function () { true, ) - expect(application.itemManager.items.length).to.equal(expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) expect(tag.content.references.length).to.equal(1) expect(tag.noteCount).to.equal(1) expect(note.content.references.length).to.equal(0) - expect(application.itemManager.itemsReferencingItem(note).length).to.equal(1) + expect(application.items.itemsReferencingItem(note).length).to.equal(1) }) it('importing same note many times should create only one duplicate', async function () { @@ -121,8 +121,8 @@ describe('importing', function () { true, ) expectedItemCount++ - expect(application.itemManager.getDisplayableNotes().length).to.equal(2) - const imported = application.itemManager.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) + expect(application.items.getDisplayableNotes().length).to.equal(2) + const imported = application.items.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) expect(imported.content.title).to.equal(mutatedNote.content.title) }) @@ -144,8 +144,8 @@ describe('importing', function () { }, true, ) - expect(application.itemManager.getDisplayableTags().length).to.equal(1) - expect(application.itemManager.findItem(tagPayload.uuid).content.references.length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) + expect(application.items.findItem(tagPayload.uuid).content.references.length).to.equal(1) }) it('importing data with differing content should create duplicates', async function () { @@ -155,8 +155,8 @@ describe('importing', function () { const tagPayload = pair[1] await application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) expectedItemCount += 2 - const note = application.itemManager.getDisplayableNotes()[0] - const tag = application.itemManager.getDisplayableTags()[0] + const note = application.items.getDisplayableNotes()[0] + const tag = application.items.getDisplayableTags()[0] const mutatedNote = new DecryptedPayload({ ...notePayload, content: { @@ -178,27 +178,27 @@ describe('importing', function () { true, ) expectedItemCount += 2 - expect(application.itemManager.items.length).to.equal(expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) - const newNote = application.itemManager.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) - const newTag = application.itemManager.getDisplayableTags().find((t) => t.uuid !== tagPayload.uuid) + const newNote = application.items.getDisplayableNotes().find((n) => n.uuid !== notePayload.uuid) + const newTag = application.items.getDisplayableTags().find((t) => t.uuid !== tagPayload.uuid) expect(newNote.uuid).to.not.equal(note.uuid) expect(newTag.uuid).to.not.equal(tag.uuid) - const refreshedTag = application.itemManager.findItem(tag.uuid) + const refreshedTag = application.items.findItem(tag.uuid) expect(refreshedTag.content.references.length).to.equal(2) expect(refreshedTag.noteCount).to.equal(2) - const refreshedNote = application.itemManager.findItem(note.uuid) + const refreshedNote = application.items.findItem(note.uuid) expect(refreshedNote.content.references.length).to.equal(0) - expect(application.itemManager.itemsReferencingItem(refreshedNote).length).to.equal(2) + expect(application.items.itemsReferencingItem(refreshedNote).length).to.equal(2) expect(newTag.content.references.length).to.equal(1) expect(newTag.noteCount).to.equal(1) expect(newNote.content.references.length).to.equal(0) - expect(application.itemManager.itemsReferencingItem(newNote).length).to.equal(1) + expect(application.items.itemsReferencingItem(newNote).length).to.equal(1) }) it('when importing items, imported values should not be used to determine if changed', async function () { @@ -249,9 +249,9 @@ describe('importing', function () { expectedItemCount += 1 /** We expect now that the total item count is 3, not 4. */ - expect(application.itemManager.items.length).to.equal(expectedItemCount) + expect(application.items.items.length).to.equal(expectedItemCount) - const refreshedTag = application.itemManager.findItem(tag.uuid) + const refreshedTag = application.items.findItem(tag.uuid) /** References from both items have merged. */ expect(refreshedTag.content.references.length).to.equal(2) }) @@ -286,10 +286,10 @@ describe('importing', function () { true, ) - expect(application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.findItem(note.uuid).deleted).to.not.be.ok - expect(application.itemManager.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) expect(application.items.findItem(tag.uuid).deleted).to.not.be.ok }) @@ -320,8 +320,8 @@ describe('importing', function () { true, ) - expect(application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(application.itemManager.getDisplayableNotes()[0].uuid).to.not.equal(note.uuid) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes()[0].uuid).to.not.equal(note.uuid) }) it('should maintain consistency between storage and PayloadManager after an import with conflicts', async function () { @@ -350,8 +350,8 @@ describe('importing', function () { true, ) - const storedPayloads = await application.diskStorageService.getAllRawPayloads() - expect(application.itemManager.items.length).to.equal(storedPayloads.length) + const storedPayloads = await application.storage.getAllRawPayloads() + expect(application.items.items.length).to.equal(storedPayloads.length) const notes = storedPayloads.filter((p) => p.content_type === ContentType.TYPES.Note) const itemsKeys = storedPayloads.filter((p) => p.content_type === ContentType.TYPES.ItemsKey) expect(notes.length).to.equal(1) @@ -383,10 +383,10 @@ describe('importing', function () { await application.importData(backupData, true) - expect(application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) expect(application.items.findItem(note.uuid).deleted).to.not.be.ok - expect(application.itemManager.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) expect(application.items.findItem(tag.uuid).deleted).to.not.be.ok }) @@ -465,10 +465,10 @@ describe('importing', function () { expect(result.affectedItems.length).to.be.eq(backupData.items.length) expect(result.errorCount).to.be.eq(0) - const decryptedNote = application.itemManager.findItem(noteItem.uuid) + const decryptedNote = application.items.findItem(noteItem.uuid) expect(decryptedNote.title).to.be.eq('Encrypted note') expect(decryptedNote.text).to.be.eq('On protocol version 003.') - expect(application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) }) it('should import data from 003 encrypted payload using server generated backup with 004 key params', async function () { @@ -546,10 +546,10 @@ describe('importing', function () { expect(result.affectedItems.length).to.be.eq(backupData.items.length) expect(result.errorCount).to.be.eq(0) - const decryptedNote = application.itemManager.findItem(noteItem.uuid) + const decryptedNote = application.items.findItem(noteItem.uuid) expect(decryptedNote.title).to.be.eq('Encrypted note') expect(decryptedNote.text).to.be.eq('On protocol version 004.') - expect(application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) }) it('should return correct errorCount', async function () { @@ -624,7 +624,7 @@ describe('importing', function () { expect(result.affectedItems.length).to.be.eq(0) expect(result.errorCount).to.be.eq(backupData.items.length) - expect(application.itemManager.getDisplayableNotes().length).to.equal(0) + expect(application.items.getDisplayableNotes().length).to.equal(0) }) it('should not import data from 004 encrypted payload if an invalid password is provided', async function () { @@ -655,7 +655,7 @@ describe('importing', function () { expect(result).to.not.be.undefined expect(result.affectedItems.length).to.be.eq(0) expect(result.errorCount).to.be.eq(backupData.items.length) - expect(application.itemManager.getDisplayableNotes().length).to.equal(0) + expect(application.items.getDisplayableNotes().length).to.equal(0) }) it('should not import encrypted data with no keyParams or auth_params', async function () { @@ -710,7 +710,7 @@ describe('importing', function () { expect(result.affectedItems.length).to.equal(BaseItemCounts.BackupFileRootKeyEncryptedItems) expect(result.errorCount).to.be.eq(backupData.items.length - BaseItemCounts.BackupFileRootKeyEncryptedItems) - expect(application.itemManager.getDisplayableNotes().length).to.equal(0) + expect(application.items.getDisplayableNotes().length).to.equal(0) }) it('importing data with no items key should use the root key generated by the file password', async function () { @@ -726,7 +726,7 @@ describe('importing', function () { const identifier = 'standardnotes' const application = await Factory.createApplicationWithRealCrypto(identifier) /** Create legacy migrations value so that base migration detects old app */ - await application.deviceInterface.setRawStorageValue( + await application.device.setRawStorageValue( 'keychain', JSON.stringify({ [identifier]: { @@ -736,7 +736,7 @@ describe('importing', function () { }, }), ) - await application.deviceInterface.setRawStorageValue( + await application.device.setRawStorageValue( 'descriptors', JSON.stringify({ [identifier]: { @@ -746,8 +746,8 @@ describe('importing', function () { }, }), ) - await application.deviceInterface.setRawStorageValue('standardnotes-snjs_version', '2.0.11') - await application.deviceInterface.saveDatabaseEntry( + await application.device.setRawStorageValue('standardnotes-snjs_version', '2.0.11') + await application.device.saveDatabaseEntry( { content: '003:9f2c7527eb8b2a1f8bfb3ea6b885403b6886bce2640843ebd57a6c479cbf7597:58e3322b-269a-4be3-a658-b035dffcd70f:9140b23a0fa989e224e292049f133154:SESTNOgIGf2+ZqmJdFnGU4EMgQkhKOzpZNoSzx76SJaImsayzctAgbUmJ+UU2gSQAHADS3+Z5w11bXvZgIrStTsWriwvYkNyyKmUPadKHNSBwOk4WeBZpWsA9gtI5zgI04Q5pvb8hS+kNW2j1DjM4YWqd0JQxMOeOrMIrxr/6Awn5TzYE+9wCbXZdYHyvRQcp9ui/G02ZJ67IA86vNEdjTTBAAWipWqTqKH9VDZbSQ2W/IOKfIquB373SFDKZb1S1NmBFvcoG2G7w//fAl/+ehYiL6UdiNH5MhXCDAOTQRFNfOh57HFDWVnz1VIp8X+VAPy6d9zzQH+8aws1JxHq/7BOhXrFE8UCueV6kERt9njgQxKJzd9AH32ShSiUB9X/sPi0fUXbS178xAZMJrNx3w==:eyJwd19ub25jZSI6IjRjYjEwM2FhODljZmY0NTYzYTkxMWQzZjM5NjU4M2NlZmM2ODMzYzY2Zjg4MGZiZWUwNmJkYTk0YzMxZjg2OGIiLCJwd19jb3N0IjoxMTAwMDAsImlkZW50aWZpZXIiOiJub3YyMzIyQGJpdGFyLmlvIiwidmVyc2lvbiI6IjAwMyIsIm9yaWdpbmF0aW9uIjoicmVnaXN0cmF0aW9uIn0=', @@ -763,7 +763,7 @@ describe('importing', function () { /** * Note that this storage contains "sync.standardnotes.org" as the API Host param. */ - await application.deviceInterface.setRawStorageValue( + await application.device.setRawStorageValue( 'standardnotes-storage', JSON.stringify({ wrapped: { @@ -874,13 +874,13 @@ describe('importing', function () { await application.importData(backupData, true) - expect(application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(application.itemManager.getDisplayableTags().length).to.equal(1) + expect(application.items.getDisplayableNotes().length).to.equal(1) + expect(application.items.getDisplayableTags().length).to.equal(1) - const importedNote = application.itemManager.getDisplayableNotes()[0] - const importedTag = application.itemManager.getDisplayableTags()[0] - expect(application.itemManager.referencesForItem(importedTag).length).to.equal(1) - expect(application.itemManager.itemsReferencingItem(importedNote).length).to.equal(1) + const importedNote = application.items.getDisplayableNotes()[0] + const importedTag = application.items.getDisplayableTags()[0] + expect(application.items.referencesForItem(importedTag).length).to.equal(1) + expect(application.items.itemsReferencingItem(importedNote).length).to.equal(1) }) it('should decrypt backup file which contains a vaulted note without a synced key system root key', async () => { diff --git a/packages/snjs/mocha/model_tests/items.test.js b/packages/snjs/mocha/model_tests/items.test.js index 8d7ac26ce..81eeb723b 100644 --- a/packages/snjs/mocha/model_tests/items.test.js +++ b/packages/snjs/mocha/model_tests/items.test.js @@ -23,11 +23,11 @@ describe('items', () => { it('setting an item as dirty should update its client updated at', async function () { const params = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) - const item = this.application.itemManager.items[0] + const item = this.application.items.items[0] const prevDate = item.userModifiedDate.getTime() await Factory.sleep(0.1) await this.application.mutator.setItemDirty(item, true) - const refreshedItem = this.application.itemManager.findItem(item.uuid) + const refreshedItem = this.application.items.findItem(item.uuid) const newDate = refreshedItem.userModifiedDate.getTime() expect(prevDate).to.not.equal(newDate) }) @@ -35,7 +35,7 @@ describe('items', () => { it('setting an item as dirty with option to skip client updated at', async function () { const params = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) - const item = this.application.itemManager.items[0] + const item = this.application.items.items[0] const prevDate = item.userModifiedDate.getTime() await Factory.sleep(0.1) await this.application.mutator.setItemDirty(item) @@ -47,7 +47,7 @@ describe('items', () => { const params = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([params], PayloadEmitSource.LocalChanged) - const item = this.application.itemManager.items[0] + const item = this.application.items.items[0] expect(item.pinned).to.not.be.ok const refreshedItem = await this.application.changeAndSaveItem( @@ -71,8 +71,8 @@ describe('items', () => { const params2 = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged) - let item1 = this.application.itemManager.getDisplayableNotes()[0] - let item2 = this.application.itemManager.getDisplayableNotes()[1] + let item1 = this.application.items.getDisplayableNotes()[0] + let item2 = this.application.items.getDisplayableNotes()[1] expect(item1.isItemContentEqualWith(item2)).to.equal(true) @@ -176,8 +176,8 @@ describe('items', () => { const params2 = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([params1, params2], PayloadEmitSource.LocalChanged) - let item1 = this.application.itemManager.getDisplayableNotes()[0] - const item2 = this.application.itemManager.getDisplayableNotes()[1] + let item1 = this.application.items.getDisplayableNotes()[0] + const item2 = this.application.items.getDisplayableNotes()[1] item1 = await this.application.changeAndSaveItem( item1, diff --git a/packages/snjs/mocha/model_tests/mapping.test.js b/packages/snjs/mocha/model_tests/mapping.test.js index f42ad5f65..f1707bf50 100644 --- a/packages/snjs/mocha/model_tests/mapping.test.js +++ b/packages/snjs/mocha/model_tests/mapping.test.js @@ -22,7 +22,7 @@ describe('model manager mapping', () => { const payload = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) this.expectedItemCount++ - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('mapping nonexistent deleted item doesnt create it', async function () { @@ -31,8 +31,8 @@ describe('model manager mapping', () => { dirty: false, deleted: true, }) - await this.application.payloadManager.emitPayload(payload, PayloadEmitSource.LocalChanged) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + await this.application.payloads.emitPayload(payload, PayloadEmitSource.LocalChanged) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('mapping and deleting nonexistent item creates and deletes it', async function () { @@ -41,7 +41,7 @@ describe('model manager mapping', () => { this.expectedItemCount++ - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) const changedParams = new DeletedPayload({ ...payload, @@ -53,7 +53,7 @@ describe('model manager mapping', () => { await this.application.mutator.emitItemsFromPayloads([changedParams], PayloadEmitSource.LocalChanged) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('mapping deleted but dirty item should not delete it', async function () { @@ -63,13 +63,13 @@ describe('model manager mapping', () => { this.expectedItemCount++ - await this.application.payloadManager.emitPayload(new DeleteItemMutator(item).getDeletedResult()) + await this.application.payloads.emitPayload(new DeleteItemMutator(item).getDeletedResult()) - const payload2 = new DeletedPayload(this.application.payloadManager.findOne(payload.uuid).ejected()) + const payload2 = new DeletedPayload(this.application.payloads.findOne(payload.uuid).ejected()) - await this.application.payloadManager.emitPayloads([payload2], PayloadEmitSource.LocalChanged) + await this.application.payloads.emitPayloads([payload2], PayloadEmitSource.LocalChanged) - expect(this.application.payloadManager.collection.all().length).to.equal(this.expectedItemCount) + expect(this.application.payloads.collection.all().length).to.equal(this.expectedItemCount) }) it('mapping existing item updates its properties', async function () { @@ -85,7 +85,7 @@ describe('model manager mapping', () => { }, }) await this.application.mutator.emitItemsFromPayloads([mutated], PayloadEmitSource.LocalChanged) - const item = this.application.itemManager.getDisplayableNotes()[0] + const item = this.application.items.getDisplayableNotes()[0] expect(item.content.title).to.equal(newTitle) }) @@ -93,9 +93,9 @@ describe('model manager mapping', () => { it('setting an item dirty should retrieve it in dirty items', async function () { const payload = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getDisplayableNotes()[0] + const note = this.application.items.getDisplayableNotes()[0] await this.application.mutator.setItemDirty(note) - const dirtyItems = this.application.itemManager.getDirtyItems() + const dirtyItems = this.application.items.getDirtyItems() expect(Uuids(dirtyItems).includes(note.uuid)) }) @@ -107,18 +107,18 @@ describe('model manager mapping', () => { payloads.push(Factory.createNotePayload()) } await this.application.mutator.emitItemsFromPayloads(payloads, PayloadEmitSource.LocalChanged) - await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() + await this.application.sync.markAllItemsAsNeedingSyncAndPersist() - const dirtyItems = this.application.itemManager.getDirtyItems() + const dirtyItems = this.application.items.getDirtyItems() expect(dirtyItems.length).to.equal(this.expectedItemCount) }) it('sync observers should be notified of changes', async function () { const payload = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([payload], PayloadEmitSource.LocalChanged) - const item = this.application.itemManager.items[0] + const item = this.application.items.items[0] return new Promise((resolve) => { - this.application.itemManager.addObserver(ContentType.TYPES.Any, ({ changed }) => { + this.application.items.addObserver(ContentType.TYPES.Any, ({ changed }) => { expect(changed[0].uuid === item.uuid) resolve() }) diff --git a/packages/snjs/mocha/model_tests/notes_tags.test.js b/packages/snjs/mocha/model_tests/notes_tags.test.js index eb06acd84..a86504fea 100644 --- a/packages/snjs/mocha/model_tests/notes_tags.test.js +++ b/packages/snjs/mocha/model_tests/notes_tags.test.js @@ -26,7 +26,7 @@ describe('notes and tags', () => { it('uses proper class for note', async function () { const payload = Factory.createNotePayload() await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] + const note = this.application.items.getItems([ContentType.TYPES.Note])[0] expect(note.constructor === SNNote).to.equal(true) }) @@ -74,11 +74,11 @@ describe('notes and tags', () => { }) await this.application.mutator.emitItemsFromPayloads([mutatedNote, mutatedTag], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] - const tag = this.application.itemManager.getItems([ContentType.TYPES.Tag])[0] + const note = this.application.items.getItems([ContentType.TYPES.Note])[0] + const tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] expect(note.content.references.length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(tag).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(tag).length).to.equal(1) }) it('creates relationship between note and tag', async function () { @@ -90,8 +90,8 @@ describe('notes and tags', () => { expect(tagPayload.content.references.length).to.equal(1) await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - let note = this.application.itemManager.getDisplayableNotes()[0] - let tag = this.application.itemManager.getDisplayableTags()[0] + let note = this.application.items.getDisplayableNotes()[0] + let tag = this.application.items.getDisplayableTags()[0] expect(note.dirty).to.not.be.ok expect(tag.dirty).to.not.be.ok @@ -102,26 +102,26 @@ describe('notes and tags', () => { expect(note.isReferencingItem(tag)).to.equal(false) expect(tag.isReferencingItem(note)).to.equal(true) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) expect(note.payload.references.length).to.equal(0) expect(tag.noteCount).to.equal(1) await this.application.mutator.setItemToBeDeleted(note) - tag = this.application.itemManager.getDisplayableTags()[0] + tag = this.application.items.getDisplayableTags()[0] - const deletedNotePayload = this.application.payloadManager.findOne(note.uuid) + const deletedNotePayload = this.application.payloads.findOne(note.uuid) expect(deletedNotePayload.dirty).to.be.true expect(tag.dirty).to.be.true - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) expect(tag.content.references.length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(0) expect(tag.noteCount).to.equal(0) - tag = this.application.itemManager.getDisplayableTags()[0] - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(0) + tag = this.application.items.getDisplayableTags()[0] + expect(this.application.items.getDisplayableNotes().length).to.equal(0) expect(tag.dirty).to.be.false }) @@ -131,13 +131,13 @@ describe('notes and tags', () => { const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) - let note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.itemManager.getItems([ContentType.TYPES.Tag])[0] + let note = this.application.items.getItems([ContentType.TYPES.Note])[0] + let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] expect(note.content.references.length).to.equal(0) expect(tag.content.references.length).to.equal(1) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) const mutatedTag = new DecryptedPayload({ ...tagPayload, @@ -149,11 +149,11 @@ describe('notes and tags', () => { }) await this.application.mutator.emitItemsFromPayloads([mutatedTag], PayloadEmitSource.LocalChanged) - note = this.application.itemManager.findItem(note.uuid) - tag = this.application.itemManager.findItem(tag.uuid) + note = this.application.items.findItem(note.uuid) + tag = this.application.items.findItem(tag.uuid) expect(tag.content.references.length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(0) expect(tag.noteCount).to.equal(0) // expect to be false @@ -178,8 +178,8 @@ describe('notes and tags', () => { const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.itemManager.getItems([ContentType.TYPES.Tag])[0] + const note = this.application.items.getItems([ContentType.TYPES.Note])[0] + let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] expect(note.content.references.length).to.equal(0) expect(tag.content.references.length).to.equal(1) @@ -194,21 +194,21 @@ describe('notes and tags', () => { syncOptions, ) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(0) expect(tag.noteCount).to.equal(0) }) it('properly handles tag duplication', async function () { const pair = createRelatedNoteTagPairPayload() await this.application.mutator.emitItemsFromPayloads(pair, PayloadEmitSource.LocalChanged) - let note = this.application.itemManager.getDisplayableNotes()[0] - let tag = this.application.itemManager.getDisplayableTags()[0] + let note = this.application.items.getDisplayableNotes()[0] + let tag = this.application.items.getDisplayableTags()[0] const duplicateTag = await this.application.mutator.duplicateItem(tag, true) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) - note = this.application.itemManager.findItem(note.uuid) - tag = this.application.itemManager.findItem(tag.uuid) + note = this.application.items.findItem(note.uuid) + tag = this.application.items.findItem(tag.uuid) expect(tag.uuid).to.not.equal(duplicateTag.uuid) expect(tag.content.references.length).to.equal(1) @@ -216,7 +216,7 @@ describe('notes and tags', () => { expect(duplicateTag.content.references.length).to.equal(1) expect(duplicateTag.noteCount).to.equal(1) - const noteTags = this.application.itemManager.itemsReferencingItem(note) + const noteTags = this.application.items.itemsReferencingItem(note) expect(noteTags.length).to.equal(2) const noteTag1 = noteTags[0] @@ -233,12 +233,12 @@ describe('notes and tags', () => { const notePayload = pair[0] const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] + const note = this.application.items.getItems([ContentType.TYPES.Note])[0] const duplicateNote = await this.application.mutator.duplicateItem(note, true) expect(note.uuid).to.not.equal(duplicateNote.uuid) - expect(this.application.itemManager.itemsReferencingItem(duplicateNote).length).to.equal( - this.application.itemManager.itemsReferencingItem(note).length, + expect(this.application.items.itemsReferencingItem(duplicateNote).length).to.equal( + this.application.items.itemsReferencingItem(note).length, ) }) @@ -247,24 +247,24 @@ describe('notes and tags', () => { const notePayload = pair[0] const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.itemManager.getItems([ContentType.TYPES.Tag])[0] + const note = this.application.items.getItems([ContentType.TYPES.Note])[0] + let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] expect(tag.content.references.length).to.equal(1) expect(tag.noteCount).to.equal(1) expect(note.content.references.length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) await this.application.mutator.setItemToBeDeleted(tag) - tag = this.application.itemManager.findItem(tag.uuid) + tag = this.application.items.findItem(tag.uuid) expect(tag).to.not.be.ok }) it('modifying item content should not modify payload content', async function () { const notePayload = Factory.createNotePayload() await this.application.mutator.emitItemsFromPayloads([notePayload], PayloadEmitSource.LocalChanged) - let note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] + let note = this.application.items.getItems([ContentType.TYPES.Note])[0] note = await this.application.changeAndSaveItem( note, (mutator) => { @@ -286,14 +286,14 @@ describe('notes and tags', () => { const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - let note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] - let tag = this.application.itemManager.getItems([ContentType.TYPES.Tag])[0] + let note = this.application.items.getItems([ContentType.TYPES.Note])[0] + let tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) await this.application.mutator.setItemToBeDeleted(tag) - note = this.application.itemManager.findItem(note.uuid) - this.application.itemManager.findItem(tag.uuid) + note = this.application.items.findItem(note.uuid) + this.application.items.findItem(tag.uuid) expect(note.dirty).to.not.be.ok }) @@ -320,7 +320,7 @@ describe('notes and tags', () => { title: 'Foo', }) await this.application.mutator.insertItem(note) - note = this.application.itemManager.findItem(note.uuid) + note = this.application.items.findItem(note.uuid) expect(note.content.title).to.equal('Foo') }) @@ -385,13 +385,13 @@ describe('notes and tags', () => { ) }), ) - const pinnedNote = this.application.itemManager.getDisplayableNotes().find((note) => note.title === 'B') + const pinnedNote = this.application.items.getDisplayableNotes().find((note) => note.title === 'B') await this.application.mutator.changeItem(pinnedNote, (mutator) => { mutator.pinned = true }) const tag = await this.application.mutator.findOrCreateTag('A') await this.application.mutator.changeItem(tag, (mutator) => { - for (const note of this.application.itemManager.getDisplayableNotes()) { + for (const note of this.application.items.getDisplayableNotes()) { mutator.e2ePendingRefactor_addItemAsRelationship(note) } }) diff --git a/packages/snjs/mocha/model_tests/performance.test.js b/packages/snjs/mocha/model_tests/performance.test.js index 8493aeee5..ab6e6391c 100644 --- a/packages/snjs/mocha/model_tests/performance.test.js +++ b/packages/snjs/mocha/model_tests/performance.test.js @@ -65,8 +65,8 @@ describe('mapping performance', () => { const expectedRunTime = 3 // seconds expect(seconds).to.be.at.most(expectedRunTime) - for (const note of application.itemManager.getItems(ContentType.TYPES.Note)) { - expect(application.itemManager.itemsReferencingItem(note).length).to.be.above(0) + for (const note of application.items.getItems(ContentType.TYPES.Note)) { + expect(application.items.itemsReferencingItem(note).length).to.be.above(0) } await Factory.safeDeinit(application) }).timeout(20000) @@ -131,9 +131,9 @@ describe('mapping performance', () => { const MAX_RUN_TIME = 15.0 // seconds expect(seconds).to.be.at.most(MAX_RUN_TIME) - application.itemManager.getItems(ContentType.TYPES.Tag)[0] - for (const note of application.itemManager.getItems(ContentType.TYPES.Note)) { - expect(application.itemManager.itemsReferencingItem(note).length).to.equal(1) + application.items.getItems(ContentType.TYPES.Tag)[0] + for (const note of application.items.getItems(ContentType.TYPES.Note)) { + expect(application.items.itemsReferencingItem(note).length).to.equal(1) } await Factory.safeDeinit(application) }).timeout(20000) diff --git a/packages/snjs/mocha/mutator_service.test.js b/packages/snjs/mocha/mutator_service.test.js index be87f0bb5..dc93ecab9 100644 --- a/packages/snjs/mocha/mutator_service.test.js +++ b/packages/snjs/mocha/mutator_service.test.js @@ -155,7 +155,7 @@ describe('mutator service', function () { const sandbox = sinon.createSandbox() beforeEach(async function () { - this.emitPayloads = sandbox.spy(application.items.payloadManager, 'emitPayloads') + this.emitPayloads = sandbox.spy(application.payloads, 'emitPayloads') }) afterEach(async function () { diff --git a/packages/snjs/mocha/payload_encryption.test.js b/packages/snjs/mocha/payload_encryption.test.js index fd47d3483..dc24b387d 100644 --- a/packages/snjs/mocha/payload_encryption.test.js +++ b/packages/snjs/mocha/payload_encryption.test.js @@ -40,7 +40,7 @@ describe('payload encryption', function () { lastSyncBegan: new Date(), }) - const encryptedPayload = await this.application.encryptionService.encryptSplitSingle({ + const encryptedPayload = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [notePayload], }, @@ -114,7 +114,7 @@ describe('payload encryption', function () { it('returns valid encrypted params for syncing', async function () { const payload = Factory.createNotePayload() const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -126,7 +126,7 @@ describe('payload encryption', function () { expect(encryptedPayload.content_type).to.be.ok expect(encryptedPayload.created_at).to.be.ok expect(encryptedPayload.content).to.satisfy((string) => { - return string.startsWith(this.application.encryptionService.getLatestVersion()) + return string.startsWith(this.application.encryption.getLatestVersion()) }) }).timeout(5000) @@ -134,7 +134,7 @@ describe('payload encryption', function () { const payload = Factory.createNotePayload() const encryptedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -150,14 +150,14 @@ describe('payload encryption', function () { expect(encryptedPayload.deleted).to.not.be.ok expect(encryptedPayload.errorDecrypting).to.not.be.ok expect(encryptedPayload.content).to.satisfy((string) => { - return string.startsWith(this.application.encryptionService.getLatestVersion()) + return string.startsWith(this.application.encryption.getLatestVersion()) }) }) it('omits deleted for export file', async function () { const payload = Factory.createNotePayload() const encryptedPayload = CreateEncryptedBackupFileContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -170,7 +170,7 @@ describe('payload encryption', function () { expect(encryptedPayload.created_at).to.be.ok expect(encryptedPayload.deleted).to.not.be.ok expect(encryptedPayload.content).to.satisfy((string) => { - return string.startsWith(this.application.encryptionService.getLatestVersion()) + return string.startsWith(this.application.encryption.getLatestVersion()) }) }) diff --git a/packages/snjs/mocha/preferences.test.js b/packages/snjs/mocha/preferences.test.js index a952a0dba..b298ee7a0 100644 --- a/packages/snjs/mocha/preferences.test.js +++ b/packages/snjs/mocha/preferences.test.js @@ -106,7 +106,7 @@ describe('preferences', function () { Factory.initializeApplication(this.application) await willSyncPromise - expect(this.application.preferencesService.preferences).to.exist + expect(this.application.preferences.preferences).to.exist expect(this.application.getPreference(prefKey)).to.equal(prefValue) }) }) diff --git a/packages/snjs/mocha/protection.test.js b/packages/snjs/mocha/protection.test.js index d84ededbf..74e1f3c2f 100644 --- a/packages/snjs/mocha/protection.test.js +++ b/packages/snjs/mocha/protection.test.js @@ -287,8 +287,8 @@ describe('protections', function () { it('handles session length', async function () { application = await Factory.createInitAppWithFakeCrypto() - await application.protectionService.setSessionLength(300) - const length = await application.protectionService.getLastSessionLength() + await application.protections.setSessionLength(300) + const length = await application.protections.getLastSessionLength() expect(length).to.equal(300) const expirey = await application.getProtectionSessionExpiryDate() expect(expirey).to.be.ok @@ -296,8 +296,8 @@ describe('protections', function () { it('handles session length', async function () { application = await Factory.createInitAppWithFakeCrypto() - await application.protectionService.setSessionLength(UnprotectedAccessSecondsDuration.OneMinute) - const length = await application.protectionService.getLastSessionLength() + await application.protections.setSessionLength(UnprotectedAccessSecondsDuration.OneMinute) + const length = await application.protections.getLastSessionLength() expect(length).to.equal(UnprotectedAccessSecondsDuration.OneMinute) const expirey = await application.getProtectionSessionExpiryDate() expect(expirey).to.be.ok @@ -388,7 +388,7 @@ describe('protections', function () { it('should return true when session length has been set', async function () { application = await Factory.createInitAppWithFakeCrypto() await application.addPasscode('passcode') - await application.protectionService.setSessionLength(UnprotectedAccessSecondsDuration.OneMinute) + await application.protections.setSessionLength(UnprotectedAccessSecondsDuration.OneMinute) expect(application.hasUnprotectedAccessSession()).to.be.true }) diff --git a/packages/snjs/mocha/protocol.test.js b/packages/snjs/mocha/protocol.test.js index 771e563ca..90bcfafea 100644 --- a/packages/snjs/mocha/protocol.test.js +++ b/packages/snjs/mocha/protocol.test.js @@ -19,43 +19,43 @@ describe('protocol', function () { }) it('checks version to make sure its 004', function () { - expect(this.application.encryptionService.getLatestVersion()).to.equal('004') + expect(this.application.encryption.getLatestVersion()).to.equal('004') }) it('checks supported versions to make sure it includes 001, 002, 003, 004', function () { - expect(this.application.encryptionService.supportedVersions()).to.eql(['001', '002', '003', '004']) + expect(this.application.encryption.supportedVersions()).to.eql(['001', '002', '003', '004']) }) it('platform derivation support', function () { expect( - this.application.encryptionService.platformSupportsKeyDerivation({ + this.application.encryption.platformSupportsKeyDerivation({ version: '001', }), ).to.equal(true) expect( - this.application.encryptionService.platformSupportsKeyDerivation({ + this.application.encryption.platformSupportsKeyDerivation({ version: '002', }), ).to.equal(true) expect( - this.application.encryptionService.platformSupportsKeyDerivation({ + this.application.encryption.platformSupportsKeyDerivation({ version: '003', }), ).to.equal(true) expect( - this.application.encryptionService.platformSupportsKeyDerivation({ + this.application.encryption.platformSupportsKeyDerivation({ version: '004', }), ).to.equal(true) expect( - this.application.encryptionService.platformSupportsKeyDerivation({ + this.application.encryption.platformSupportsKeyDerivation({ version: '005', }), ).to.equal(true) }) it('key params versions <= 002 should include pw_cost in portable value', function () { - const keyParams002 = this.application.encryptionService.createKeyParams({ + const keyParams002 = this.application.encryption.createKeyParams({ version: '002', pw_cost: 5000, }) @@ -63,15 +63,15 @@ describe('protocol', function () { }) it('version comparison of 002 should be older than library version', function () { - expect(this.application.encryptionService.isVersionNewerThanLibraryVersion('002')).to.equal(false) + expect(this.application.encryption.isVersionNewerThanLibraryVersion('002')).to.equal(false) }) it('version comparison of 005 should be newer than library version', function () { - expect(this.application.encryptionService.isVersionNewerThanLibraryVersion('005')).to.equal(true) + expect(this.application.encryption.isVersionNewerThanLibraryVersion('005')).to.equal(true) }) it('library version should not be outdated', function () { - var currentVersion = this.application.encryptionService.getLatestVersion() + var currentVersion = this.application.encryption.getLatestVersion() expect(isProtocolVersionExpired(currentVersion)).to.equal(false) }) @@ -91,7 +91,7 @@ describe('protocol', function () { const payload = Factory.createNotePayload() let error try { - await this.application.encryptionService.decryptSplitSingle({ + await this.application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -106,7 +106,7 @@ describe('protocol', function () { await this.application.addPasscode('123') const payload = Factory.createNotePayload() const result = CreateEncryptedServerSyncPushPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -121,7 +121,7 @@ describe('protocol', function () { it('encrypted payload for server should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -134,7 +134,7 @@ describe('protocol', function () { it('ejected payload for server should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedServerSyncPushPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -147,7 +147,7 @@ describe('protocol', function () { it('encrypted payload for storage should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -160,7 +160,7 @@ describe('protocol', function () { it('ejected payload for storage should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedLocalStorageContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -173,7 +173,7 @@ describe('protocol', function () { it('encrypted payload for file should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedBackupFileContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, @@ -186,7 +186,7 @@ describe('protocol', function () { it('ejected payload for file should include duplicate_of field', async function () { const payload = Factory.createNotePayload('Test') const encryptedPayload = CreateEncryptedBackupFileContextPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [payload], }, diff --git a/packages/snjs/mocha/recovery.test.js b/packages/snjs/mocha/recovery.test.js index 1ea64dc19..f4e84a86e 100644 --- a/packages/snjs/mocha/recovery.test.js +++ b/packages/snjs/mocha/recovery.test.js @@ -44,7 +44,7 @@ describe('account recovery', function () { application = await context.signout() - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok await application.signInWithRecoveryCodes.execute({ recoveryCodes: generatedRecoveryCodes.getValue(), @@ -52,7 +52,7 @@ describe('account recovery', function () { password: context.password, }) - expect(await application.encryptionService.getRootKey()).to.be.ok + expect(await application.encryption.getRootKey()).to.be.ok }) it('should automatically generate new recovery code after recovery sign in', async () => { @@ -96,7 +96,7 @@ describe('account recovery', function () { application = await context.signout() - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok await application.signInWithRecoveryCodes.execute({ recoveryCodes: generatedRecoveryCodes.getValue(), @@ -104,7 +104,7 @@ describe('account recovery', function () { password: 'foobar', }) - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok }) it('should not allow to sign in with invalid recovery code', async () => { @@ -112,7 +112,7 @@ describe('account recovery', function () { application = await context.signout() - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok await application.signInWithRecoveryCodes.execute({ recoveryCodes: 'invalid recovery code', @@ -120,13 +120,13 @@ describe('account recovery', function () { password: context.paswword, }) - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok }) it('should not allow to sign in with recovery code if user has none', async () => { application = await context.signout() - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok await application.signInWithRecoveryCodes.execute({ recoveryCodes: 'foo bar', @@ -134,6 +134,6 @@ describe('account recovery', function () { password: context.paswword, }) - expect(await application.encryptionService.getRootKey()).to.not.be.ok + expect(await application.encryption.getRootKey()).to.not.be.ok }) }) diff --git a/packages/snjs/mocha/session.test.js b/packages/snjs/mocha/session.test.js index df7f52c70..4c61a965a 100644 --- a/packages/snjs/mocha/session.test.js +++ b/packages/snjs/mocha/session.test.js @@ -30,7 +30,7 @@ describe('server session', function () { }) async function sleepUntilSessionExpires(application, basedOnAccessToken = true) { - const currentSession = application.apiService.session + const currentSession = application.legacyApi.session const timestamp = basedOnAccessToken ? currentSession.accessToken.expiresAt : currentSession.refreshToken.expiresAt const timeRemaining = (timestamp - Date.now()) / 1000 // in ms /* @@ -42,7 +42,7 @@ describe('server session', function () { } async function getSessionFromStorage(application) { - return application.diskStorageService.getValue(StorageKey.Session) + return application.storage.getValue(StorageKey.Session) } it('should succeed when a sync request is perfomed with an expired access token', async function () { @@ -56,7 +56,7 @@ describe('server session', function () { await sleepUntilSessionExpires(this.application) - const response = await this.application.apiService.sync([]) + const response = await this.application.legacyApi.sync([]) expect(response.status).to.equal(200) }) @@ -68,7 +68,7 @@ describe('server session', function () { password: this.password, }) - const response = await this.application.apiService.refreshSession() + const response = await this.application.legacyApi.refreshSession() expect(response.status).to.equal(200) expect(response.data.session.access_token).to.be.a('string') @@ -87,7 +87,7 @@ describe('server session', function () { }) // Saving the current session information for later... - const sessionBeforeSync = this.application.apiService.getSession() + const sessionBeforeSync = this.application.legacyApi.getSession() // Waiting enough time for the access token to expire, before performing a new sync request. await sleepUntilSessionExpires(this.application) @@ -96,7 +96,7 @@ describe('server session', function () { await this.application.sync.sync(syncOptions) // After the above sync request is completed, we obtain the session information. - const sessionAfterSync = this.application.apiService.getSession() + const sessionAfterSync = this.application.legacyApi.getSession() expect(sessionBeforeSync.accessToken.value).to.not.equal(sessionAfterSync.accessToken.value) expect(sessionBeforeSync.refreshToken.value).to.not.equal(sessionAfterSync.refreshToken.value) @@ -119,10 +119,10 @@ describe('server session', function () { // Apply a latency simulation so that ` this.inProgressRefreshSessionPromise = this.refreshSession()` does // not have the chance to complete before it is assigned to the variable. This test came along with a fix // where runHttp does not await a pending refreshSession promise if the request being made is itself a refreshSession request. - this.application.httpService.__latencySimulatorMs = 1000 + this.application.http.__latencySimulatorMs = 1000 await this.application.sync.sync(syncOptions) - const sessionAfterSync = this.application.apiService.getSession() + const sessionAfterSync = this.application.legacyApi.getSession() expect(sessionAfterSync.accessToken.expiresAt).to.be.greaterThan(Date.now()) }) @@ -137,7 +137,7 @@ describe('server session', function () { await this.application.signIn(this.email, this.password, false, true) - const response = await this.application.apiService.sync([]) + const response = await this.application.legacyApi.sync([]) expect(response.status).to.equal(200) }) @@ -149,7 +149,7 @@ describe('server session', function () { ephemeral: true, }) - const response = await this.application.apiService.sync([]) + const response = await this.application.legacyApi.sync([]) expect(response.status).to.equal(200) }) @@ -161,7 +161,7 @@ describe('server session', function () { }) const sessionFromStorage = await getSessionFromStorage(this.application) - const sessionFromApiService = this.application.apiService.getSession() + const sessionFromApiService = this.application.legacyApi.getSession() expect(sessionFromStorage.accessToken).to.equal(sessionFromApiService.accessToken.value) expect(sessionFromStorage.refreshToken).to.equal(sessionFromApiService.refreshToken.value) @@ -169,10 +169,10 @@ describe('server session', function () { expect(sessionFromStorage.refreshExpiration).to.equal(sessionFromApiService.refreshToken.expiresAt) expect(sessionFromStorage.readonlyAccess).to.equal(sessionFromApiService.isReadOnly()) - await this.application.apiService.refreshSession() + await this.application.legacyApi.refreshSession() const updatedSessionFromStorage = await getSessionFromStorage(this.application) - const updatedSessionFromApiService = this.application.apiService.getSession() + const updatedSessionFromApiService = this.application.legacyApi.getSession() expect(updatedSessionFromStorage.accessToken).to.equal(updatedSessionFromApiService.accessToken.value) expect(updatedSessionFromStorage.refreshToken).to.equal(updatedSessionFromApiService.refreshToken.value) @@ -188,11 +188,11 @@ describe('server session', function () { password: this.password, }) - const signOutResponse = await this.application.apiService.signOut() + const signOutResponse = await this.application.legacyApi.signOut() expect(signOutResponse.status).to.equal(204) Factory.ignoreChallenges(this.application) - const syncResponse = await this.application.apiService.sync([]) + const syncResponse = await this.application.legacyApi.sync([]) expect(syncResponse.status).to.equal(401) expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') @@ -210,11 +210,11 @@ describe('server session', function () { // Waiting enough time for the access token to expire, before performing a sign out request. await sleepUntilSessionExpires(this.application) - const signOutResponse = await this.application.apiService.signOut() + const signOutResponse = await this.application.legacyApi.signOut() expect(signOutResponse.status).to.equal(204) Factory.ignoreChallenges(this.application) - const syncResponse = await this.application.apiService.sync([]) + const syncResponse = await this.application.legacyApi.sync([]) expect(syncResponse.status).to.equal(401) expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') @@ -250,7 +250,7 @@ describe('server session', function () { registerUser: true, }) - application.diskStorageService.setValue(StorageKey.Session, { + application.storage.setValue(StorageKey.Session, { accessToken: 'this-is-a-fake-token-1234', refreshToken: 'this-is-a-fake-token-1234', accessExpiration: 999999999999999, @@ -345,7 +345,7 @@ describe('server session', function () { password: this.password, }) - this.application.diskStorageService.setValue(StorageKey.Session, { + this.application.storage.setValue(StorageKey.Session, { accessToken: 'this-is-a-fake-token-1234', refreshToken: 'this-is-a-fake-token-1234', accessExpiration: 999999999999999, @@ -385,12 +385,12 @@ describe('server session', function () { password: this.password, }) - await this.application.apiService.signOut() - this.application.apiService.session = undefined + await this.application.legacyApi.signOut() + this.application.legacyApi.session = undefined - await this.application.sessionManager.signIn(this.email, this.password) + await this.application.sessions.signIn(this.email, this.password) - const currentSession = this.application.apiService.getSession() + const currentSession = this.application.legacyApi.getSession() expect(currentSession).to.be.ok expect(currentSession.accessToken).to.be.ok @@ -409,7 +409,7 @@ describe('server session', function () { await sleepUntilSessionExpires(this.application, false) - const refreshSessionResponse = await this.application.apiService.refreshSession() + const refreshSessionResponse = await this.application.legacyApi.refreshSession() expect(refreshSessionResponse.status).to.equal(400) expect(refreshSessionResponse.data.error.tag).to.equal('expired-refresh-token') @@ -420,7 +420,7 @@ describe('server session', function () { Here we make sure that any subsequent requests will fail. */ Factory.ignoreChallenges(this.application) - const syncResponse = await this.application.apiService.sync([]) + const syncResponse = await this.application.legacyApi.sync([]) expect(syncResponse.status).to.equal(401) expect(syncResponse.data.error.tag).to.equal('invalid-auth') expect(syncResponse.data.error.message).to.equal('Invalid login credentials.') @@ -433,9 +433,9 @@ describe('server session', function () { password: this.password, }) - const originalSession = this.application.apiService.getSession() + const originalSession = this.application.legacyApi.getSession() - this.application.diskStorageService.setValue(StorageKey.Session, { + this.application.storage.setValue(StorageKey.Session, { accessToken: originalSession.accessToken.value, refreshToken: 'this-is-a-fake-token-1234', accessExpiration: originalSession.accessToken.expiresAt, @@ -444,14 +444,14 @@ describe('server session', function () { }) this.application.sessions.initializeFromDisk() - const refreshSessionResponse = await this.application.apiService.refreshSession() + const refreshSessionResponse = await this.application.legacyApi.refreshSession() expect(refreshSessionResponse.status).to.equal(400) expect(refreshSessionResponse.data.error.tag).to.equal('invalid-refresh-token') expect(refreshSessionResponse.data.error.message).to.equal('The refresh token is not valid.') // Access token should remain valid. - const syncResponse = await this.application.apiService.sync([]) + const syncResponse = await this.application.legacyApi.sync([]) expect(syncResponse.status).to.equal(200) }) @@ -462,8 +462,8 @@ describe('server session', function () { password: this.password, }) - const refreshPromise = this.application.apiService.refreshSession() - const syncResponse = await this.application.apiService.sync([]) + const refreshPromise = this.application.legacyApi.refreshSession() + const syncResponse = await this.application.legacyApi.sync([]) expect(syncResponse.data.error).to.be.ok @@ -485,19 +485,19 @@ describe('server session', function () { const notesBeforeSync = await Factory.createManyMappedNotes(this.application, 5) await sleepUntilSessionExpires(this.application) - await this.application.syncService.sync(syncOptions) - expect(this.application.syncService.isOutOfSync()).to.equal(false) + await this.application.sync.sync(syncOptions) + expect(this.application.sync.isOutOfSync()).to.equal(false) this.application = await Factory.signOutApplicationAndReturnNew(this.application) await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) const expectedNotesUuids = notesBeforeSync.map((n) => n.uuid) - const notesResults = await this.application.itemManager.findItems(expectedNotesUuids) + const notesResults = await this.application.items.findItems(expectedNotesUuids) expect(notesResults.length).to.equal(notesBeforeSync.length) for (const aNoteBeforeSync of notesBeforeSync) { - const noteResult = await this.application.itemManager.findItem(aNoteBeforeSync.uuid) + const noteResult = await this.application.items.findItem(aNoteBeforeSync.uuid) expect(aNoteBeforeSync.isItemContentEqualWith(noteResult)).to.equal(true) } }) @@ -574,10 +574,10 @@ describe('server session', function () { password: password, }) - const oldRootKey = await appA.encryptionService.getRootKey() + const oldRootKey = await appA.encryption.getRootKey() /** Set the session as nonsense */ - appA.diskStorageService.setValue(StorageKey.Session, { + appA.storage.setValue(StorageKey.Session, { accessToken: 'foo', refreshToken: 'bar', accessExpiration: 999999999999999, @@ -593,11 +593,11 @@ describe('server session', function () { await Factory.sleep(5.0) expect(didPromptForSignIn).to.equal(true) - expect(appA.apiService.session.accessToken.value).to.not.equal('foo') - expect(appA.apiService.session.refreshToken.value).to.not.equal('bar') + expect(appA.legacyApi.session.accessToken.value).to.not.equal('foo') + expect(appA.legacyApi.session.refreshToken.value).to.not.equal('bar') /** Expect that the session recovery replaces the global root key */ - const newRootKey = await appA.encryptionService.getRootKey() + const newRootKey = await appA.encryption.getRootKey() expect(oldRootKey).to.not.equal(newRootKey) await Factory.safeDeinit(appA) @@ -610,7 +610,7 @@ describe('server session', function () { password: this.password, }) - const response = await this.application.apiService.getSessionsList() + const response = await this.application.legacyApi.getSessionsList() expect(response.data[0].current).to.equal(true) }) @@ -625,12 +625,12 @@ describe('server session', function () { const app2 = await Factory.createAndInitializeApplication('app2') await app2.signIn(this.email, this.password) - const response = await this.application.apiService.getSessionsList() + const response = await this.application.legacyApi.getSessionsList() expect(response.data.length).to.equal(2) await app2.user.signOut() - const response2 = await this.application.apiService.getSessionsList() + const response2 = await this.application.legacyApi.getSessionsList() expect(response2.data.length).to.equal(1) }) @@ -699,7 +699,7 @@ describe('server session', function () { password: this.password, }) - this.application.diskStorageService.setValue(StorageKey.Session, { + this.application.storage.setValue(StorageKey.Session, { accessToken: undefined, refreshToken: undefined, accessExpiration: 999999999999999, @@ -708,7 +708,7 @@ describe('server session', function () { }) this.application.sessions.initializeFromDisk() - const storageKey = this.application.diskStorageService.getPersistenceKey() + const storageKey = this.application.storage.getPersistenceKey() expect(localStorage.getItem(storageKey)).to.be.ok await this.application.user.signOut() diff --git a/packages/snjs/mocha/settings.test.js b/packages/snjs/mocha/settings.test.js index 0fd22ec4a..ef2d27339 100644 --- a/packages/snjs/mocha/settings.test.js +++ b/packages/snjs/mocha/settings.test.js @@ -151,7 +151,7 @@ describe('settings service', function () { const response = await fetch('/packages/snjs/mocha/assets/small_file.md') const buffer = new Uint8Array(await response.arrayBuffer()) - await Files.uploadFile(application.fileService, buffer, 'my-file', 'md', 1000) + await Files.uploadFile(application.files, buffer, 'my-file', 'md', 1000) await Factory.sleep(1) diff --git a/packages/snjs/mocha/singletons.test.js b/packages/snjs/mocha/singletons.test.js index c0f028da6..f84bf5456 100644 --- a/packages/snjs/mocha/singletons.test.js +++ b/packages/snjs/mocha/singletons.test.js @@ -24,8 +24,8 @@ describe('singletons', function () { return new DecryptedPayload(params) } - function findOrCreatePrefsSingleton(application) { - return application.singletonManager.findOrCreateContentTypeSingleton(ContentType.TYPES.UserPrefs, FillItemContent({})) + function findOrCreatePrefsSingleton(context) { + return context.singletons.findOrCreateContentTypeSingleton(ContentType.TYPES.UserPrefs, FillItemContent({})) } beforeEach(async function () { @@ -49,7 +49,7 @@ describe('singletons', function () { } this.signOut = async () => { - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + this.application = await this.context.signout() } this.signIn = async () => { @@ -78,10 +78,10 @@ describe('singletons', function () { }) afterEach(async function () { - expect(this.application.syncService.isOutOfSync()).to.equal(false) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.sync.isOutOfSync()).to.equal(false) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) await Factory.safeDeinit(this.application) @@ -100,8 +100,8 @@ describe('singletons', function () { PayloadEmitSource.LocalChanged, ) await this.application.mutator.setItemsDirty(items) - await this.application.syncService.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + await this.application.sync.sync(syncOptions) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('duplicate components should auto-resolve to 1', async function () { @@ -121,30 +121,30 @@ describe('singletons', function () { await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.itemsMatchingPredicate(ContentType.TYPES.Component, this.extPred).length).to.equal(1) + expect(this.application.items.itemsMatchingPredicate(ContentType.TYPES.Component, this.extPred).length).to.equal(1) }) it('resolves via find or create', async function () { /* Set to never synced as singleton manager will attempt to sync before resolving */ - this.application.syncService.ut_clearLastSyncDate() - this.application.syncService.ut_setDatabaseLoaded(false) + this.application.sync.ut_clearLastSyncDate() + this.application.sync.ut_setDatabaseLoaded(false) const contentType = ContentType.TYPES.UserPrefs const predicate = new Predicate('content_type', '=', contentType) /* Start a sync right after we await singleton resolve below */ setTimeout(() => { - this.application.syncService.ut_setDatabaseLoaded(true) + this.application.sync.ut_setDatabaseLoaded(true) this.application.sync.sync({ /* Simulate the first sync occuring as that is handled specially by sync service */ mode: SyncMode.DownloadFirst, }) }) - const userPreferences = await this.application.singletonManager.findOrCreateContentTypeSingleton(contentType, {}) + const userPreferences = await this.context.singletons.findOrCreateContentTypeSingleton(contentType, {}) expect(userPreferences).to.be.ok const refreshedUserPrefs = this.application.items.findItem(userPreferences.uuid) expect(refreshedUserPrefs).to.be.ok await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.itemsMatchingPredicate(contentType, predicate).length).to.equal(1) + expect(this.application.items.itemsMatchingPredicate(contentType, predicate).length).to.equal(1) }) it('resolves registered predicate with signing in/out', async function () { @@ -187,7 +187,7 @@ describe('singletons', function () { let didCompleteRelevantSync = false let beginCheckingResponse = false - this.application.syncService.addEventObserver(async (eventName, data) => { + this.application.sync.addEventObserver(async (eventName, data) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { beginCheckingResponse = true } @@ -202,7 +202,7 @@ describe('singletons', function () { expect(matching).to.not.be.ok } }) - await this.application.syncService.sync({ mode: SyncMode.DownloadFirst }) + await this.application.sync.sync({ mode: SyncMode.DownloadFirst }) expect(didCompleteRelevantSync).to.equal(true) }).timeout(10000) @@ -210,12 +210,14 @@ describe('singletons', function () { await this.registerUser() /** Create prefs */ - const ogPrefs = await findOrCreatePrefsSingleton(this.application) + const ogPrefs = await findOrCreatePrefsSingleton(this.context) await this.application.sync.sync(syncOptions) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + + this.application = await this.context.signout() /** Create another instance while signed out */ - await findOrCreatePrefsSingleton(this.application) + await findOrCreatePrefsSingleton(this.context) + await Factory.loginToApplication({ application: this.application, email: this.email, @@ -223,36 +225,36 @@ describe('singletons', function () { }) /** After signing in, the instance retrieved from the server should be the one kept */ - const latestPrefs = await findOrCreatePrefsSingleton(this.application) + const latestPrefs = await findOrCreatePrefsSingleton(this.context) expect(latestPrefs.uuid).to.equal(ogPrefs.uuid) - const allPrefs = this.application.itemManager.getItems(ogPrefs.content_type) + const allPrefs = this.application.items.getItems(ogPrefs.content_type) expect(allPrefs.length).to.equal(1) }) it('resolving singleton before first sync, then signing in, should result in correct number of instances', async function () { await this.registerUser() /** Create prefs and associate them with account */ - const ogPrefs = await findOrCreatePrefsSingleton(this.application) + const ogPrefs = await findOrCreatePrefsSingleton(this.context) await this.application.sync.sync(syncOptions) - this.application = await Factory.signOutApplicationAndReturnNew(this.application) + this.application = await this.context.signout() /** Create another instance while signed out */ - await findOrCreatePrefsSingleton(this.application) + await findOrCreatePrefsSingleton(this.context) await Factory.loginToApplication({ application: this.application, email: this.email, password: this.password, }) /** After signing in, the instance retrieved from the server should be the one kept */ - const latestPrefs = await findOrCreatePrefsSingleton(this.application) + const latestPrefs = await findOrCreatePrefsSingleton(this.context) expect(latestPrefs.uuid).to.equal(ogPrefs.uuid) - const allPrefs = this.application.itemManager.getItems(ogPrefs.content_type) + const allPrefs = this.application.items.getItems(ogPrefs.content_type) expect(allPrefs.length).to.equal(1) }) it('if only result is errorDecrypting, create new item', async function () { - const item = this.application.itemManager.items.find((item) => item.content_type === ContentType.TYPES.UserPrefs) + const item = this.application.items.items.find((item) => item.content_type === ContentType.TYPES.UserPrefs) const erroredPayload = new EncryptedPayload({ ...item.payload.ejected(), @@ -260,16 +262,13 @@ describe('singletons', function () { errorDecrypting: true, }) - await this.application.payloadManager.emitPayload(erroredPayload) + await this.application.payloads.emitPayload(erroredPayload) - const resolvedItem = await this.application.singletonManager.findOrCreateContentTypeSingleton( - item.content_type, - item.content, - ) + const resolvedItem = await this.context.singletons.findOrCreateContentTypeSingleton(item.content_type, item.content) await this.application.sync.sync({ awaitAll: true }) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(resolvedItem.uuid).to.not.equal(item.uuid) expect(resolvedItem.errorDecrypting).to.not.be.ok }) @@ -318,34 +317,34 @@ describe('singletons', function () { errorDecrypting: false, }) - await this.application.payloadManager.emitPayload(notErrored) + await this.application.payloads.emitPayload(notErrored) /** Item will get decrypted on current tick, so wait one before syncing */ await Factory.sleep(0) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.itemsMatchingPredicate(ContentType.TYPES.Component, this.extPred).length).to.equal(1) + expect(this.application.items.itemsMatchingPredicate(ContentType.TYPES.Component, this.extPred).length).to.equal(1) }) it('alternating the uuid of a singleton should return correct result', async function () { const payload = createPrefsPayload() const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) const predicate = new Predicate('content_type', '=', item.content_type) - let resolvedItem = await this.application.singletonManager.findOrCreateContentTypeSingleton( + let resolvedItem = await this.context.singletons.findOrCreateContentTypeSingleton( payload.content_type, payload.content, ) const originalUuid = resolvedItem.uuid await Factory.alternateUuidForItem(this.application, resolvedItem.uuid) - await this.application.syncService.sync(syncOptions) - const resolvedItem2 = await this.application.singletonManager.findOrCreateContentTypeSingleton( + await this.application.sync.sync(syncOptions) + const resolvedItem2 = await this.context.singletons.findOrCreateContentTypeSingleton( payload.content_type, payload.content, ) resolvedItem = this.application.items.findItem(resolvedItem.uuid) expect(resolvedItem).to.not.be.ok expect(resolvedItem2.uuid).to.not.equal(originalUuid) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) }) diff --git a/packages/snjs/mocha/storage.test.js b/packages/snjs/mocha/storage.test.js index 663dabdb2..a15e6ad98 100644 --- a/packages/snjs/mocha/storage.test.js +++ b/packages/snjs/mocha/storage.test.js @@ -31,23 +31,23 @@ describe('storage manager', function () { it('should set and retrieve values', async function () { const key = 'foo' const value = 'bar' - await this.application.diskStorageService.setValue(key, value) - expect(await this.application.diskStorageService.getValue(key)).to.eql(value) + await this.application.storage.setValue(key, value) + expect(await this.application.storage.getValue(key)).to.eql(value) }) it('should set and retrieve items', async function () { const payload = Factory.createNotePayload() - await this.application.diskStorageService.savePayload(payload) - const payloads = await this.application.diskStorageService.getAllRawPayloads() + await this.application.storage.savePayload(payload) + const payloads = await this.application.storage.getAllRawPayloads() expect(payloads.length).to.equal(BaseItemCounts.DefaultItems + 1) }) it('should clear values', async function () { const key = 'foo' const value = 'bar' - await this.application.diskStorageService.setValue(key, value) - await this.application.diskStorageService.clearAllData() - expect(await this.application.diskStorageService.getValue(key)).to.not.be.ok + await this.application.storage.setValue(key, value) + await this.application.storage.clearAllData() + expect(await this.application.storage.getValue(key)).to.not.be.ok }) it('serverPassword should not be saved to keychain', async function () { @@ -57,7 +57,7 @@ describe('storage manager', function () { password: this.password, ephemeral: false, }) - const keychainValue = await this.application.deviceInterface.getNamespacedKeychainValue(this.application.identifier) + const keychainValue = await this.application.device.getNamespacedKeychainValue(this.application.identifier) expect(keychainValue.masterKey).to.be.ok expect(keychainValue.serverPassword).to.not.be.ok }) @@ -71,10 +71,10 @@ describe('storage manager', function () { }) const key = 'foo' const value = 'bar' - await this.application.diskStorageService.setValue(key, value) + await this.application.storage.setValue(key, value) /** Items are stored in local storage */ expect(Object.keys(localStorage).length).to.equal(this.expectedKeyCount + BaseItemCounts.DefaultItems) - const retrievedValue = await this.application.diskStorageService.getValue(key) + const retrievedValue = await this.application.storage.getValue(key) expect(retrievedValue).to.equal(value) }) @@ -88,12 +88,12 @@ describe('storage manager', function () { }) const key = 'foo' const value = 'bar' - await this.application.diskStorageService.setValueAndAwaitPersist(key, value) + await this.application.storage.setValueAndAwaitPersist(key, value) const expectedKeys = ['keychain'] expect(Object.keys(localStorage).length).to.equal(expectedKeys.length) - const retrievedValue = await this.application.diskStorageService.getValue(key) + const retrievedValue = await this.application.storage.getValue(key) expect(retrievedValue).to.equal(value) }) @@ -105,21 +105,21 @@ describe('storage manager', function () { ephemeral: true, }) await Factory.createSyncedNote(this.application) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(0) }) it('storage with no account and no passcode should not be encrypted', async function () { - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') - const wrappedValue = this.application.diskStorageService.values[ValueModesKeys.Wrapped] + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') + const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const payload = new DecryptedPayload(wrappedValue) expect(payload.content).to.be.an.instanceof(Object) }) it('storage aftering adding passcode should be encrypted', async function () { - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await this.application.addPasscode('123') - const wrappedValue = this.application.diskStorageService.values[ValueModesKeys.Wrapped] + const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const payload = new EncryptedPayload(wrappedValue) expect(payload.content).to.be.a('string') }) @@ -127,11 +127,11 @@ describe('storage manager', function () { it('storage after adding passcode then removing passcode should not be encrypted', async function () { const passcode = '123' Factory.handlePasswordChallenges(this.application, passcode) - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await this.application.addPasscode(passcode) - await this.application.diskStorageService.setValueAndAwaitPersist('bar', 'foo') + await this.application.storage.setValueAndAwaitPersist('bar', 'foo') await this.application.removePasscode() - const wrappedValue = this.application.diskStorageService.values[ValueModesKeys.Wrapped] + const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const payload = new DecryptedPayload(wrappedValue) expect(payload.content).to.be.an.instanceof(Object) }) @@ -148,35 +148,35 @@ describe('storage manager', function () { email: this.email, password: this.password, }) - expect(await this.application.deviceInterface.getNamespacedKeychainValue(this.application.identifier)).to.be.ok - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') + expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.be.ok + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') Factory.handlePasswordChallenges(this.application, this.password) await this.application.addPasscode(passcode) - expect(await this.application.deviceInterface.getNamespacedKeychainValue(this.application.identifier)).to.not.be.ok - await this.application.diskStorageService.setValueAndAwaitPersist('bar', 'foo') + expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.not.be.ok + await this.application.storage.setValueAndAwaitPersist('bar', 'foo') Factory.handlePasswordChallenges(this.application, passcode) await this.application.removePasscode() - expect(await this.application.deviceInterface.getNamespacedKeychainValue(this.application.identifier)).to.be.ok + expect(await this.application.device.getNamespacedKeychainValue(this.application.identifier)).to.be.ok - const wrappedValue = this.application.diskStorageService.values[ValueModesKeys.Wrapped] + const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const payload = new EncryptedPayload(wrappedValue) expect(payload.content).to.be.a('string') }) it('adding account should encrypt storage with account keys', async function () { - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await Factory.registerUserToApplication({ application: this.application, email: this.email, password: this.password, ephemeral: true, }) - const accountKey = await this.application.encryptionService.getRootKey() - expect(await this.application.diskStorageService.canDecryptWithKey(accountKey)).to.equal(true) + const accountKey = await this.application.encryption.getRootKey() + expect(await this.application.storage.canDecryptWithKey(accountKey)).to.equal(true) }) it('signing out of account should decrypt storage', async function () { - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await Factory.registerUserToApplication({ application: this.application, email: this.email, @@ -184,15 +184,15 @@ describe('storage manager', function () { ephemeral: true, }) this.application = await Factory.signOutApplicationAndReturnNew(this.application) - await this.application.diskStorageService.setValueAndAwaitPersist('bar', 'foo') - const wrappedValue = this.application.diskStorageService.values[ValueModesKeys.Wrapped] + await this.application.storage.setValueAndAwaitPersist('bar', 'foo') + const wrappedValue = this.application.storage.values[ValueModesKeys.Wrapped] const payload = new DecryptedPayload(wrappedValue) expect(payload.content).to.be.an.instanceof(Object) }) it('adding account then passcode should encrypt storage with account keys', async function () { /** Should encrypt storage with account keys and encrypt account keys with passcode */ - await this.application.diskStorageService.setValueAndAwaitPersist('foo', 'bar') + await this.application.storage.setValueAndAwaitPersist('foo', 'bar') await Factory.registerUserToApplication({ application: this.application, email: this.email, @@ -201,23 +201,23 @@ describe('storage manager', function () { }) /** Should not be wrapped root key yet */ - expect(await this.application.encryptionService.rootKeyManager.getWrappedRootKey()).to.not.be.ok + expect(await this.application.encryption.rootKeyManager.getWrappedRootKey()).to.not.be.ok const passcode = '123' Factory.handlePasswordChallenges(this.application, this.password) await this.application.addPasscode(passcode) - await this.application.diskStorageService.setValueAndAwaitPersist('bar', 'foo') + await this.application.storage.setValueAndAwaitPersist('bar', 'foo') /** Root key should now be wrapped */ - expect(await this.application.encryptionService.rootKeyManager.getWrappedRootKey()).to.be.ok + expect(await this.application.encryption.rootKeyManager.getWrappedRootKey()).to.be.ok - const accountKey = await this.application.encryptionService.getRootKey() - expect(await this.application.diskStorageService.canDecryptWithKey(accountKey)).to.equal(true) - const passcodeKey = await this.application.encryptionService.computeWrappingKey(passcode) - const wrappedRootKey = await this.application.encryptionService.rootKeyManager.getWrappedRootKey() + const accountKey = await this.application.encryption.getRootKey() + expect(await this.application.storage.canDecryptWithKey(accountKey)).to.equal(true) + const passcodeKey = await this.application.encryption.computeWrappingKey(passcode) + const wrappedRootKey = await this.application.encryption.rootKeyManager.getWrappedRootKey() /** Expect that we can decrypt wrapped root key with passcode key */ const payload = new EncryptedPayload(wrappedRootKey) - const decrypted = await this.application.encryptionService.decryptSplitSingle({ + const decrypted = await this.application.encryption.decryptSplitSingle({ usesRootKey: { items: [payload], key: passcodeKey, @@ -229,7 +229,7 @@ describe('storage manager', function () { it('stored payloads should not contain metadata fields', async function () { await this.application.addPasscode('123') await Factory.createSyncedNote(this.application) - const payloads = await this.application.diskStorageService.getAllRawPayloads() + const payloads = await this.application.storage.getAllRawPayloads() const payload = payloads[0] expect(payload.fields).to.not.be.ok expect(payload.source).to.not.be.ok @@ -239,7 +239,7 @@ describe('storage manager', function () { it('storing an offline synced payload should not include dirty flag', async function () { await this.application.addPasscode('123') await Factory.createSyncedNote(this.application) - const payloads = await this.application.diskStorageService.getAllRawPayloads() + const payloads = await this.application.storage.getAllRawPayloads() const payload = payloads[0] expect(payload.dirty).to.not.be.ok @@ -254,7 +254,7 @@ describe('storage manager', function () { }) await Factory.createSyncedNote(this.application) - const payloads = await this.application.diskStorageService.getAllRawPayloads() + const payloads = await this.application.storage.getAllRawPayloads() const payload = payloads[0] expect(payload.dirty).to.not.be.ok @@ -269,7 +269,7 @@ describe('storage manager', function () { }) this.application = await Factory.signOutApplicationAndReturnNew(this.application) - const values = this.application.diskStorageService.values[ValueModesKeys.Unwrapped] + const values = this.application.storage.values[ValueModesKeys.Unwrapped] expect(Object.keys(values).length).to.equal(0) }) diff --git a/packages/snjs/mocha/subscriptions.test.js b/packages/snjs/mocha/subscriptions.test.js index e88cf7cbf..cd56e947a 100644 --- a/packages/snjs/mocha/subscriptions.test.js +++ b/packages/snjs/mocha/subscriptions.test.js @@ -1,5 +1,5 @@ import * as Factory from './lib/factory.js' -import * as Events from './lib/Events.js' + chai.use(chaiAsPromised) const expect = chai.expect @@ -9,7 +9,6 @@ describe('subscriptions', function () { let application let context let subscriptionManager - let subscriptionId = 3001 afterEach(async function () { await Factory.safeDeinit(application) @@ -24,9 +23,9 @@ describe('subscriptions', function () { await context.launch() application = context.application - subscriptionManager = context.application.subscriptionManager + subscriptionManager = context.subscriptions - const result = await Factory.registerUserToApplication({ + await Factory.registerUserToApplication({ application: application, email: context.email, password: context.password, @@ -40,7 +39,7 @@ describe('subscriptions', function () { const existingInvites = await subscriptionManager.listSubscriptionInvitations() - const newlyCreatedInvite = existingInvites.find(invite => invite.inviteeIdentifier === 'test@test.te') + const newlyCreatedInvite = existingInvites.find((invite) => invite.inviteeIdentifier === 'test@test.te') expect(newlyCreatedInvite.status).to.equal('sent') }) @@ -69,17 +68,17 @@ describe('subscriptions', function () { let existingInvites = await subscriptionManager.listSubscriptionInvitations() - expect (existingInvites.length).to.equal(2) + expect(existingInvites.length).to.equal(2) - const newlyCreatedInvite = existingInvites.find(invite => invite.inviteeIdentifier === 'test@test.te') + const newlyCreatedInvite = existingInvites.find((invite) => invite.inviteeIdentifier === 'test@test.te') await subscriptionManager.cancelInvitation(newlyCreatedInvite.uuid) existingInvites = await subscriptionManager.listSubscriptionInvitations() - expect (existingInvites.length).to.equal(2) + expect(existingInvites.length).to.equal(2) - expect(existingInvites.filter(invite => invite.status === 'canceled').length).to.equal(1) + expect(existingInvites.filter((invite) => invite.status === 'canceled').length).to.equal(1) }) it('should invite a user by email if the limit of shared subscription is restored', async () => { @@ -99,6 +98,6 @@ describe('subscriptions', function () { existingInvites = await subscriptionManager.listSubscriptionInvitations() - expect(existingInvites.find(invite => invite.inviteeIdentifier === 'test6@test.te')).not.to.equal(undefined) + expect(existingInvites.find((invite) => invite.inviteeIdentifier === 'test6@test.te')).not.to.equal(undefined) }) }) diff --git a/packages/snjs/mocha/sync_tests/conflicting.test.js b/packages/snjs/mocha/sync_tests/conflicting.test.js index 60e659c9f..a08451958 100644 --- a/packages/snjs/mocha/sync_tests/conflicting.test.js +++ b/packages/snjs/mocha/sync_tests/conflicting.test.js @@ -30,10 +30,10 @@ describe('online conflict handling', function () { await this.context.register() this.sharedFinalAssertions = async function () { - expect(this.application.syncService.isOutOfSync()).to.equal(false) - const items = this.application.itemManager.items + expect(this.application.sync.isOutOfSync()).to.equal(false) + const items = this.application.items.items expect(items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) } }) @@ -68,7 +68,7 @@ describe('online conflict handling', function () { this.expectedItemCount++ - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) /** First modify the item without saving so that our local contents digress from the server's */ await this.application.mutator.changeItem(item, (mutator) => { @@ -85,7 +85,7 @@ describe('online conflict handling', function () { syncOptions, ) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -93,7 +93,7 @@ describe('online conflict handling', function () { const payload = createDirtyPayload(ContentType.TYPES.ItemsKey) const item = await this.application.mutator.emitItemFromPayload(payload, PayloadEmitSource.LocalChanged) this.expectedItemCount++ - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) /** First modify the item without saving so that * our local contents digress from the server's */ await this.application.mutator.changeItem(item, (mutator) => { @@ -110,7 +110,7 @@ describe('online conflict handling', function () { syncOptions, ) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -119,9 +119,9 @@ describe('online conflict handling', function () { const note = await Factory.createMappedNote(this.application) this.expectedItemCount++ await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) const originalValue = note.title @@ -141,27 +141,27 @@ describe('online conflict handling', function () { }) // Download all items from the server, which will include this note. - await this.application.syncService.clearSyncPositionTokens() - await this.application.syncService.sync({ + await this.application.sync.clearSyncPositionTokens() + await this.application.sync.sync({ ...syncOptions, awaitAll: true, }) // We expect this item to be duplicated this.expectedItemCount++ - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2) + expect(this.application.items.getDisplayableNotes().length).to.equal(2) - const allItems = this.application.itemManager.items + const allItems = this.application.items.items expect(allItems.length).to.equal(this.expectedItemCount) - const originalItem = this.application.itemManager.findItem(note.uuid) + const originalItem = this.application.items.findItem(note.uuid) const duplicateItem = allItems.find((i) => i.content.conflict_of === note.uuid) expect(originalItem.title).to.equal(dirtyValue) expect(duplicateItem.title).to.equal(originalValue) expect(originalItem.title).to.not.equal(duplicateItem.title) - const newRawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const newRawPayloads = await this.application.storage.getAllRawPayloads() expect(newRawPayloads.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -172,7 +172,7 @@ describe('online conflict handling', function () { await Factory.markDirtyAndSyncItem(this.application, note) this.expectedItemCount++ - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) /** First modify the item without saving so that * our local contents digress from the server's */ @@ -192,11 +192,11 @@ describe('online conflict handling', function () { // We expect this item to be duplicated this.expectedItemCount++ - const allItems = this.application.itemManager.items + const allItems = this.application.items.items expect(allItems.length).to.equal(this.expectedItemCount) - const note1 = this.application.itemManager.getDisplayableNotes()[0] - const note2 = this.application.itemManager.getDisplayableNotes()[1] + const note1 = this.application.items.getDisplayableNotes()[0] + const note2 = this.application.items.getDisplayableNotes()[1] expect(note1.content.title).to.not.equal(note2.content.title) await this.sharedFinalAssertions() }) @@ -222,16 +222,16 @@ describe('online conflict handling', function () { ) this.expectedItemCount++ - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) // clear sync token, clear storage, download all items, and ensure none of them have error decrypting - await this.application.syncService.clearSyncPositionTokens() - await this.application.diskStorageService.clearAllPayloads() - await this.application.payloadManager.resetState() - await this.application.itemManager.resetState() - await this.application.syncService.sync(syncOptions) + await this.application.sync.clearSyncPositionTokens() + await this.application.storage.clearAllPayloads() + await this.application.payloads.resetState() + await this.application.items.resetState() + await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -239,7 +239,7 @@ describe('online conflict handling', function () { let note = await Factory.createMappedNote(this.application) await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) this.expectedItemCount++ @@ -263,15 +263,15 @@ describe('online conflict handling', function () { // We expect this item to be duplicated this.expectedItemCount++ - await this.application.syncService.clearSyncPositionTokens() - await this.application.syncService.sync(syncOptions) + await this.application.sync.clearSyncPositionTokens() + await this.application.sync.sync(syncOptions) note = this.application.items.findItem(note.uuid) // We expect the item title to be the new title, and not rolled back to original value expect(note.content.title).to.equal(newTitle) - const allItems = this.application.itemManager.items + const allItems = this.application.items.items expect(allItems.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -280,16 +280,16 @@ describe('online conflict handling', function () { const note = await Factory.createMappedNote(this.application) this.expectedItemCount++ await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) // keep item as is and set dirty await this.application.mutator.setItemDirty(note) // clear sync token so that all items are retrieved on next sync - this.application.syncService.clearSyncPositionTokens() + this.application.sync.clearSyncPositionTokens() - await this.application.syncService.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + await this.application.sync.sync(syncOptions) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -310,7 +310,7 @@ describe('online conflict handling', function () { ) // client B - await this.application.syncService.clearSyncPositionTokens() + await this.application.sync.clearSyncPositionTokens() await this.application.mutator.changeItem( note, (mutator) => { @@ -323,7 +323,7 @@ describe('online conflict handling', function () { // conflict_of is a key to ignore when comparing content, so item should // not be duplicated. - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) await this.sharedFinalAssertions() }) @@ -348,7 +348,7 @@ describe('online conflict handling', function () { mutator.title = `${Math.random()}` }) // client B - await this.application.syncService.clearSyncPositionTokens() + await this.application.sync.clearSyncPositionTokens() await Factory.changePayloadTimeStampAndSync( this.application, @@ -370,17 +370,17 @@ describe('online conflict handling', function () { const originalPayload = note.payloadRepresentation() this.expectedItemCount++ await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + await this.application.sync.sync(syncOptions) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) // client A await this.application.mutator.setItemToBeDeleted(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) this.expectedItemCount-- - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) // client B - await this.application.syncService.clearSyncPositionTokens() + await this.application.sync.clearSyncPositionTokens() // Add the item back and say it's not deleted const mutatedPayload = new DecryptedPayload({ ...originalPayload, @@ -388,13 +388,13 @@ describe('online conflict handling', function () { updated_at: Factory.yesterday(), }) await this.application.mutator.emitItemsFromPayloads([mutatedPayload], PayloadEmitSource.LocalChanged) - const resultNote = this.application.itemManager.findItem(note.uuid) + const resultNote = this.application.items.findItem(note.uuid) expect(resultNote.uuid).to.equal(note.uuid) await this.application.mutator.setItemDirty(resultNote) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) // We expect that this item is now gone for good, and a duplicate has not been created. - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -404,11 +404,11 @@ describe('online conflict handling', function () { this.expectedItemCount++ // client A - await this.application.syncService.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + await this.application.sync.sync(syncOptions) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) // client B - await this.application.syncService.clearSyncPositionTokens() + await this.application.sync.clearSyncPositionTokens() // This client says this item is deleted, but the server is saying its not deleted. // In this case, we want to keep the server copy. @@ -420,14 +420,14 @@ describe('online conflict handling', function () { ) // We expect that this item maintained. - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) it('should create conflict if syncing an item that is stale', async function () { let note = await Factory.createMappedNote(this.application) await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) note = this.application.items.findItem(note.uuid) expect(note.dirty).to.equal(false) this.expectedItemCount++ @@ -452,7 +452,7 @@ describe('online conflict handling', function () { // We expect now that the item was conflicted this.expectedItemCount++ - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) for (const payload of rawPayloads) { expect(payload.dirty).to.not.be.ok @@ -465,7 +465,7 @@ describe('online conflict handling', function () { await this.application.mutator.setItemDirty(note) this.expectedItemCount++ - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) await Factory.changePayloadTimeStampAndSync( this.application, @@ -475,7 +475,7 @@ describe('online conflict handling', function () { syncOptions, ) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.sharedFinalAssertions() }) @@ -488,11 +488,11 @@ describe('online conflict handling', function () { await Factory.createManyMappedNotes(this.application, largeItemCount) /** Upload */ - this.application.syncService.sync(syncOptions) + this.application.sync.sync(syncOptions) await this.context.awaitNextSucessfulSync() this.expectedItemCount += largeItemCount - const items = this.application.itemManager.items + const items = this.application.items.items expect(items.length).to.equal(this.expectedItemCount) /** @@ -500,9 +500,9 @@ describe('online conflict handling', function () { * the server as dirty, with no sync token, so that the server also * gives us everything it has. */ - this.application.syncService.lockSyncing() + this.application.sync.lockSyncing() const yesterday = Factory.yesterday() - for (const note of this.application.itemManager.getDisplayableNotes()) { + for (const note of this.application.items.getDisplayableNotes()) { /** First modify the item without saving so that * our local contents digress from the server's */ await this.application.mutator.changeItem(note, (mutator) => { @@ -516,13 +516,13 @@ describe('online conflict handling', function () { // We expect all the notes to be duplicated. this.expectedItemCount++ } - this.application.syncService.unlockSyncing() + this.application.sync.unlockSyncing() - await this.application.syncService.clearSyncPositionTokens() - this.application.syncService.sync(syncOptions) + await this.application.sync.clearSyncPositionTokens() + this.application.sync.sync(syncOptions) await this.context.awaitNextSucessfulSync() - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(largeItemCount * 2) + expect(this.application.items.getDisplayableNotes().length).to.equal(largeItemCount * 2) await this.sharedFinalAssertions() }).timeout(60000) @@ -532,8 +532,8 @@ describe('online conflict handling', function () { this.expectedItemCount -= 1 /** auto-created user preferences */ await this.application.mutator.emitItemsFromPayloads([payload1, payload2], PayloadEmitSource.LocalChanged) this.expectedItemCount += 2 - let tag = this.application.itemManager.getItems(ContentType.TYPES.Tag)[0] - let userPrefs = this.application.itemManager.getItems(ContentType.TYPES.UserPrefs)[0] + let tag = this.application.items.getItems(ContentType.TYPES.Tag)[0] + let userPrefs = this.application.items.getItems(ContentType.TYPES.UserPrefs)[0] expect(tag).to.be.ok expect(userPrefs).to.be.ok @@ -544,11 +544,11 @@ describe('online conflict handling', function () { await this.application.mutator.setItemDirty(userPrefs) userPrefs = this.application.items.findItem(userPrefs.uuid) - expect(this.application.itemManager.itemsReferencingItem(userPrefs).length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(userPrefs)).to.include(tag) + expect(this.application.items.itemsReferencingItem(userPrefs).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(userPrefs)).to.include(tag) - await this.application.syncService.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + await this.application.sync.sync(syncOptions) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) tag = await Factory.changePayloadTimeStamp( this.application, @@ -559,32 +559,32 @@ describe('online conflict handling', function () { }, ) - await this.application.syncService.sync({ ...syncOptions, awaitAll: true }) + await this.application.sync.sync({ ...syncOptions, awaitAll: true }) // fooItem should now be conflicted and a copy created this.expectedItemCount++ - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + expect(this.application.items.items.length).to.equal(this.expectedItemCount) + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) - const fooItems = this.application.itemManager.getItems(ContentType.TYPES.Tag) + const fooItems = this.application.items.getItems(ContentType.TYPES.Tag) const fooItem2 = fooItems[1] expect(fooItem2.content.conflict_of).to.equal(tag.uuid) // Two items now link to this original object - const referencingItems = this.application.itemManager.itemsReferencingItem(userPrefs) + const referencingItems = this.application.items.itemsReferencingItem(userPrefs) expect(referencingItems.length).to.equal(2) expect(referencingItems[0]).to.not.equal(referencingItems[1]) - expect(this.application.itemManager.itemsReferencingItem(tag).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(fooItem2).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(tag).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(fooItem2).length).to.equal(0) expect(tag.content.references.length).to.equal(1) expect(fooItem2.content.references.length).to.equal(1) expect(userPrefs.content.references.length).to.equal(0) - expect(this.application.itemManager.getDirtyItems().length).to.equal(0) - for (const item of this.application.itemManager.items) { + expect(this.application.items.getDirtyItems().length).to.equal(0) + for (const item of this.application.items.items) { expect(item.dirty).to.not.be.ok } await this.sharedFinalAssertions() @@ -611,7 +611,7 @@ describe('online conflict handling', function () { await this.application.mutator.setItemDirty(note) this.expectedItemCount += 2 - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) // conflict the note const newText = `${Math.random()}` @@ -645,7 +645,7 @@ describe('online conflict handling', function () { * and not 2 (the note and tag) */ this.expectedItemCount += 1 - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) expect(tag.content.references.length).to.equal(2) await this.sharedFinalAssertions() }) @@ -680,7 +680,7 @@ describe('online conflict handling', function () { await this.application.sync.sync() /** Expect that no duplicates have been created, and that the note's title is now finalTitle */ - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) const finalNote = this.application.items.findItem(note.uuid) expect(finalNote.title).to.equal(finalTitle) await this.sharedFinalAssertions() @@ -713,8 +713,8 @@ describe('online conflict handling', function () { /** * Retrieve this note from the server by clearing sync token */ - await this.application.syncService.clearSyncPositionTokens() - await this.application.syncService.sync({ + await this.application.sync.clearSyncPositionTokens() + await this.application.sync.sync({ ...syncOptions, awaitAll: true, }) @@ -724,7 +724,7 @@ describe('online conflict handling', function () { */ const resultNote = await this.application.items.findItem(note.uuid) expect(resultNote.errorDecrypting).to.not.be.ok - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) await this.sharedFinalAssertions() }) @@ -748,8 +748,8 @@ describe('online conflict handling', function () { /** Create bulk data belonging to another account and sync */ const largeItemCount = SyncUpDownLimit + 10 await Factory.createManyMappedNotes(this.application, largeItemCount) - await this.application.syncService.sync(syncOptions) - const priorData = this.application.itemManager.items + await this.application.sync.sync(syncOptions) + const priorData = this.application.items.items /** Register new account and import this same data */ const newApp = await Factory.signOutApplicationAndReturnNew(this.application) @@ -759,9 +759,9 @@ describe('online conflict handling', function () { password: Utils.generateUuid(), }) await newApp.mutator.emitItemsFromPayloads(priorData.map((i) => i.payload)) - await newApp.syncService.markAllItemsAsNeedingSyncAndPersist() - await newApp.syncService.sync(syncOptions) - expect(newApp.payloadManager.invalidPayloads.length).to.equal(0) + await newApp.sync.markAllItemsAsNeedingSyncAndPersist() + await newApp.sync.sync(syncOptions) + expect(newApp.payloads.invalidPayloads.length).to.equal(0) await Factory.safeDeinit(newApp) }, ).timeout(80000) @@ -787,8 +787,8 @@ describe('online conflict handling', function () { }) Factory.handlePasswordChallenges(newApp, password) await newApp.importData(backupFile, true) - expect(newApp.itemManager.getDisplayableTags().length).to.equal(1) - expect(newApp.itemManager.getDisplayableNotes().length).to.equal(1) + expect(newApp.items.getDisplayableTags().length).to.equal(1) + expect(newApp.items.getDisplayableNotes().length).to.equal(1) await Factory.safeDeinit(newApp) }).timeout(10000) @@ -799,7 +799,7 @@ describe('online conflict handling', function () { */ /** Create primary account and export data */ await createSyncedNoteWithTag(this.application) - const tag = this.application.itemManager.getDisplayableTags()[0] + const tag = this.application.items.getDisplayableTags()[0] const note2 = await Factory.createMappedNote(this.application) await this.application.changeAndSaveItem(tag, (mutator) => { mutator.e2ePendingRefactor_addItemAsRelationship(note2) @@ -822,7 +822,7 @@ describe('online conflict handling', function () { }) Factory.handlePasswordChallenges(newApp, password) await newApp.importData(backupFile, true) - const newTag = newApp.itemManager.getDisplayableTags()[0] + const newTag = newApp.items.getDisplayableTags()[0] const notes = newApp.items.referencesForItem(newTag) expect(notes.length).to.equal(2) await Factory.safeDeinit(newApp) @@ -857,7 +857,7 @@ describe('online conflict handling', function () { }) await this.application.mutator.emitItemFromPayload(modified) await this.application.sync.sync() - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) await this.sharedFinalAssertions() }) @@ -881,7 +881,7 @@ describe('online conflict handling', function () { this.expectedItemCount++ await this.application.mutator.emitItemFromPayload(modified) await this.application.sync.sync() - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2) + expect(this.application.items.getDisplayableNotes().length).to.equal(2) await this.sharedFinalAssertions() }) @@ -913,7 +913,7 @@ describe('online conflict handling', function () { this.expectedItemCount++ await this.application.mutator.emitItemFromPayload(modified) await this.application.sync.sync() - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(2) + expect(this.application.items.getDisplayableNotes().length).to.equal(2) await this.sharedFinalAssertions() }) diff --git a/packages/snjs/mocha/sync_tests/integrity.test.js b/packages/snjs/mocha/sync_tests/integrity.test.js index a81be23d2..b290db335 100644 --- a/packages/snjs/mocha/sync_tests/integrity.test.js +++ b/packages/snjs/mocha/sync_tests/integrity.test.js @@ -28,7 +28,7 @@ describe('sync integrity', () => { const awaitSyncEventPromise = (application, targetEvent) => { return new Promise((resolve) => { - application.syncService.addEventObserver((event) => { + application.sync.addEventObserver((event) => { if (event === targetEvent) { resolve() } @@ -37,8 +37,8 @@ describe('sync integrity', () => { } afterEach(async function () { - expect(this.application.syncService.isOutOfSync()).to.equal(false) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + expect(this.application.sync.isOutOfSync()).to.equal(false) + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) await Factory.safeDeinit(this.application) }) @@ -51,10 +51,10 @@ describe('sync integrity', () => { this.expectedItemCount++ const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) - await this.application.syncService.sync({ checkIntegrity: true }) + await this.application.sync.sync({ checkIntegrity: true }) - await this.application.itemManager.removeItemLocally(item) - await this.application.syncService.sync({ checkIntegrity: true, awaitAll: true }) + await this.application.items.removeItemLocally(item) + await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) await didEnterOutOfSync }) @@ -69,11 +69,11 @@ describe('sync integrity', () => { const didEnterOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.EnterOutOfSync) const didExitOutOfSync = awaitSyncEventPromise(this.application, SyncEvent.ExitOutOfSync) - await this.application.syncService.sync({ checkIntegrity: true }) - await this.application.itemManager.removeItemLocally(item) - await this.application.syncService.sync({ checkIntegrity: true, awaitAll: true }) + await this.application.sync.sync({ checkIntegrity: true }) + await this.application.items.removeItemLocally(item) + await this.application.sync.sync({ checkIntegrity: true, awaitAll: true }) await Promise.all([didEnterOutOfSync, didExitOutOfSync]) - expect(this.application.syncService.isOutOfSync()).to.equal(false) + expect(this.application.sync.isOutOfSync()).to.equal(false) }) }) diff --git a/packages/snjs/mocha/sync_tests/notes_tags.test.js b/packages/snjs/mocha/sync_tests/notes_tags.test.js index b33bcba9c..8e234d588 100644 --- a/packages/snjs/mocha/sync_tests/notes_tags.test.js +++ b/packages/snjs/mocha/sync_tests/notes_tags.test.js @@ -34,12 +34,12 @@ describe('notes + tags syncing', function () { it('syncing an item then downloading it should include items_key_id', async function () { const note = await Factory.createMappedNote(this.application) await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) - await this.application.payloadManager.resetState() - await this.application.itemManager.resetState() - await this.application.syncService.clearSyncPositionTokens() - await this.application.syncService.sync(syncOptions) - const downloadedNote = this.application.itemManager.getDisplayableNotes()[0] + await this.application.sync.sync(syncOptions) + await this.application.payloads.resetState() + await this.application.items.resetState() + await this.application.sync.clearSyncPositionTokens() + await this.application.sync.sync(syncOptions) + const downloadedNote = this.application.items.getDisplayableNotes()[0] expect(downloadedNote.items_key_id).to.not.be.ok // Allow time for waitingForKey await Factory.sleep(0.1) @@ -53,20 +53,20 @@ describe('notes + tags syncing', function () { const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const note = this.application.itemManager.getItems([ContentType.TYPES.Note])[0] - const tag = this.application.itemManager.getItems([ContentType.TYPES.Tag])[0] - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(this.application.itemManager.getDisplayableTags().length).to.equal(1) + const note = this.application.items.getItems([ContentType.TYPES.Note])[0] + const tag = this.application.items.getItems([ContentType.TYPES.Tag])[0] + expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableTags().length).to.equal(1) for (let i = 0; i < 9; i++) { await this.application.mutator.setItemsDirty([note, tag]) - await this.application.syncService.sync(syncOptions) - this.application.syncService.clearSyncPositionTokens() + await this.application.sync.sync(syncOptions) + this.application.sync.clearSyncPositionTokens() expect(tag.content.references.length).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) expect(tag.noteCount).to.equal(1) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(this.application.itemManager.getDisplayableTags().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableTags().length).to.equal(1) console.warn('Waiting 0.1s...') await Factory.sleep(0.1) } @@ -77,32 +77,32 @@ describe('notes + tags syncing', function () { const notePayload = pair[0] const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - const originalNote = this.application.itemManager.getDisplayableNotes()[0] - const originalTag = this.application.itemManager.getDisplayableTags()[0] + const originalNote = this.application.items.getDisplayableNotes()[0] + const originalTag = this.application.items.getDisplayableTags()[0] await this.application.mutator.setItemsDirty([originalNote, originalTag]) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) expect(originalTag.content.references.length).to.equal(1) expect(originalTag.noteCount).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(originalNote).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(originalNote).length).to.equal(1) // when signing in, all local items are cleared from storage (but kept in memory; to clear desktop logs), // then resaved with alternated uuids. - await this.application.diskStorageService.clearAllPayloads() - await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() + await this.application.storage.clearAllPayloads() + await this.application.sync.markAllItemsAsNeedingSyncAndPersist() - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(this.application.itemManager.getDisplayableTags().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableTags().length).to.equal(1) - const note = this.application.itemManager.getDisplayableNotes()[0] - const tag = this.application.itemManager.getDisplayableTags()[0] + const note = this.application.items.getDisplayableNotes()[0] + const tag = this.application.items.getDisplayableTags()[0] expect(tag.content.references.length).to.equal(1) expect(note.content.references.length).to.equal(0) expect(tag.noteCount).to.equal(1) - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1) + expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) }) it('duplicating a tag should maintian its relationships', async function () { @@ -110,22 +110,22 @@ describe('notes + tags syncing', function () { const notePayload = pair[0] const tagPayload = pair[1] await this.application.mutator.emitItemsFromPayloads([notePayload, tagPayload], PayloadEmitSource.LocalChanged) - let note = this.application.itemManager.getDisplayableNotes()[0] - let tag = this.application.itemManager.getDisplayableTags()[0] - expect(this.application.itemManager.itemsReferencingItem(note).length).to.equal(1) + let note = this.application.items.getDisplayableNotes()[0] + let tag = this.application.items.getDisplayableTags()[0] + expect(this.application.items.itemsReferencingItem(note).length).to.equal(1) await this.application.mutator.setItemsDirty([note, tag]) - await this.application.syncService.sync(syncOptions) - await this.application.syncService.clearSyncPositionTokens() + await this.application.sync.sync(syncOptions) + await this.application.sync.clearSyncPositionTokens() - note = this.application.itemManager.findItem(note.uuid) - tag = this.application.itemManager.findItem(tag.uuid) + note = this.application.items.findItem(note.uuid) + tag = this.application.items.findItem(tag.uuid) expect(note.dirty).to.equal(false) expect(tag.dirty).to.equal(false) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(this.application.itemManager.getDisplayableTags().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableTags().length).to.equal(1) await Factory.changePayloadTimeStampAndSync( this.application, @@ -137,13 +137,13 @@ describe('notes + tags syncing', function () { syncOptions, ) - tag = this.application.itemManager.findItem(tag.uuid) + tag = this.application.items.findItem(tag.uuid) // tag should now be conflicted and a copy created - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(this.application.itemManager.getDisplayableTags().length).to.equal(2) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableTags().length).to.equal(2) - const tags = this.application.itemManager.getDisplayableTags() + const tags = this.application.items.getDisplayableTags() const conflictedTag = tags.find((tag) => { return !!tag.content.conflict_of }) @@ -157,11 +157,11 @@ describe('notes + tags syncing', function () { expect(conflictedTag.content.conflict_of).to.equal(originalTag.uuid) expect(conflictedTag.noteCount).to.equal(originalTag.noteCount) - expect(this.application.itemManager.itemsReferencingItem(conflictedTag).length).to.equal(0) - expect(this.application.itemManager.itemsReferencingItem(originalTag).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(conflictedTag).length).to.equal(0) + expect(this.application.items.itemsReferencingItem(originalTag).length).to.equal(0) // Two tags now link to this note - const referencingItems = this.application.itemManager.itemsReferencingItem(note) + const referencingItems = this.application.items.itemsReferencingItem(note) expect(referencingItems.length).to.equal(2) expect(referencingItems[0]).to.not.equal(referencingItems[1]) }).timeout(10000) diff --git a/packages/snjs/mocha/sync_tests/offline.test.js b/packages/snjs/mocha/sync_tests/offline.test.js index 5cf4231d3..e097d8d28 100644 --- a/packages/snjs/mocha/sync_tests/offline.test.js +++ b/packages/snjs/mocha/sync_tests/offline.test.js @@ -19,7 +19,7 @@ describe('offline syncing', () => { }) afterEach(async function () { - expect(this.application.syncService.isOutOfSync()).to.equal(false) + expect(this.application.sync.isOutOfSync()).to.equal(false) await Factory.safeDeinit(this.application) }) @@ -38,19 +38,19 @@ describe('offline syncing', () => { await Factory.alternateUuidForItem(this.application, note.uuid) await this.application.sync.sync(syncOptions) - const notes = this.application.itemManager.getDisplayableNotes() + const notes = this.application.items.getDisplayableNotes() expect(notes.length).to.equal(1) expect(notes[0].uuid).to.not.equal(note.uuid) - const items = this.application.itemManager.allTrackedItems() + const items = this.application.items.allTrackedItems() expect(items.length).to.equal(this.expectedItemCount) }) it('should sync item with no passcode', async function () { let note = await Factory.createMappedNote(this.application) - expect(Uuids(this.application.itemManager.getDirtyItems()).includes(note.uuid)) + expect(Uuids(this.application.items.getDirtyItems()).includes(note.uuid)) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) note = this.application.items.findItem(note.uuid) @@ -59,9 +59,9 @@ describe('offline syncing', () => { this.expectedItemCount++ - expect(this.application.itemManager.getDirtyItems().length).to.equal(0) + expect(this.application.items.getDirtyItems().length).to.equal(0) - const rawPayloads2 = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads2 = await this.application.storage.getAllRawPayloads() expect(rawPayloads2.length).to.equal(this.expectedItemCount) const itemsKeyRaw = (await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.ItemsKey))[0] @@ -77,26 +77,26 @@ describe('offline syncing', () => { it('should sync item encrypted with passcode', async function () { await this.application.addPasscode('foobar') await Factory.createMappedNote(this.application) - expect(this.application.itemManager.getDirtyItems().length).to.equal(1) - const rawPayloads1 = await this.application.diskStorageService.getAllRawPayloads() + expect(this.application.items.getDirtyItems().length).to.equal(1) + const rawPayloads1 = await this.application.storage.getAllRawPayloads() expect(rawPayloads1.length).to.equal(this.expectedItemCount) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) this.expectedItemCount++ - expect(this.application.itemManager.getDirtyItems().length).to.equal(0) - const rawPayloads2 = await this.application.diskStorageService.getAllRawPayloads() + expect(this.application.items.getDirtyItems().length).to.equal(0) + const rawPayloads2 = await this.application.storage.getAllRawPayloads() expect(rawPayloads2.length).to.equal(this.expectedItemCount) const payload = rawPayloads2[0] expect(typeof payload.content).to.equal('string') - expect(payload.content.startsWith(this.application.encryptionService.getLatestVersion())).to.equal(true) + expect(payload.content.startsWith(this.application.encryption.getLatestVersion())).to.equal(true) }) it('signing out while offline should succeed', async function () { await Factory.createMappedNote(this.application) this.expectedItemCount++ - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) this.application = await Factory.signOutApplicationAndReturnNew(this.application) expect(this.application.noAccount()).to.equal(true) expect(this.application.getUser()).to.not.be.ok diff --git a/packages/snjs/mocha/sync_tests/online.test.js b/packages/snjs/mocha/sync_tests/online.test.js index f315a276a..16d44a51d 100644 --- a/packages/snjs/mocha/sync_tests/online.test.js +++ b/packages/snjs/mocha/sync_tests/online.test.js @@ -42,12 +42,12 @@ describe('online syncing', function () { }) afterEach(async function () { - expect(this.application.syncService.isOutOfSync()).to.equal(false) + expect(this.application.sync.isOutOfSync()).to.equal(false) - const items = this.application.itemManager.allTrackedItems() + const items = this.application.items.allTrackedItems() expect(items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) await Factory.safeDeinit(this.application) localStorage.clear() @@ -60,11 +60,11 @@ describe('online syncing', function () { it('should register and sync basic model online', async function () { let note = await Factory.createSyncedNote(this.application) this.expectedItemCount++ - expect(this.application.itemManager.getDirtyItems().length).to.equal(0) + expect(this.application.items.getDirtyItems().length).to.equal(0) note = this.application.items.findItem(note.uuid) expect(note.dirty).to.not.be.ok - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const notePayloads = noteObjectsFromObjects(rawPayloads) expect(notePayloads.length).to.equal(1) for (const rawNote of notePayloads) { @@ -83,7 +83,7 @@ describe('online syncing', function () { password: this.password, }) - const notes = this.application.itemManager.getDisplayableNotes() + const notes = this.application.items.getDisplayableNotes() expect(notes.length).to.equal(1) expect(notes[0].title).to.equal(note.title) }) @@ -99,7 +99,7 @@ describe('online syncing', function () { this.application = await this.context.signout() - expect(this.application.itemManager.items.length).to.equal(BaseItemCounts.DefaultItems) + expect(this.application.items.items.length).to.equal(BaseItemCounts.DefaultItems) const promise = Factory.loginToApplication({ application: this.application, @@ -132,7 +132,7 @@ describe('online syncing', function () { mergeLocal: true, }) - const notes = this.application.itemManager.getDisplayableNotes() + const notes = this.application.items.getDisplayableNotes() expect(notes.length).to.equal(1) /** uuid should have been alternated */ expect(notes[0].uuid).to.equal(note.uuid) @@ -143,8 +143,8 @@ describe('online syncing', function () { let successes = 0 let events = 0 - this.application.syncService.ut_beginLatencySimulator(250) - this.application.syncService.addEventObserver((event, data) => { + this.application.sync.ut_beginLatencySimulator(250) + this.application.sync.addEventObserver((event, data) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) { events++ } @@ -153,7 +153,7 @@ describe('online syncing', function () { const promises = [] for (let i = 0; i < syncCount; i++) { promises.push( - this.application.syncService + this.application.sync .sync({ queueStrategy: SyncQueueStrategy.ResolveOnNext, }) @@ -169,7 +169,7 @@ describe('online syncing', function () { // We don't know how many will execute above. expect(events).to.be.at.least(1) - this.application.syncService.ut_endLatencySimulator() + this.application.sync.ut_endLatencySimulator() // Since the syncs all happen after one another, extra syncs may be queued on that we are not awaiting. await Factory.sleep(0.5) }) @@ -179,9 +179,9 @@ describe('online syncing', function () { let successes = 0 let events = 0 - this.application.syncService.ut_beginLatencySimulator(250) + this.application.sync.ut_beginLatencySimulator(250) - this.application.syncService.addEventObserver((event, data) => { + this.application.sync.addEventObserver((event, data) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) { events++ } @@ -190,7 +190,7 @@ describe('online syncing', function () { const promises = [] for (let i = 0; i < syncCount; i++) { promises.push( - this.application.syncService + this.application.sync .sync({ queueStrategy: SyncQueueStrategy.ForceSpawnNew, }) @@ -202,7 +202,7 @@ describe('online syncing', function () { await Promise.all(promises) expect(successes).to.equal(syncCount) expect(events).to.equal(syncCount) - this.application.syncService.ut_endLatencySimulator() + this.application.sync.ut_endLatencySimulator() }) it('retrieving new items should not mark them as dirty', async function () { @@ -211,7 +211,7 @@ describe('online syncing', function () { this.application = await Factory.signOutApplicationAndReturnNew(this.application) const promise = new Promise((resolve) => { - this.application.syncService.addEventObserver(async (event) => { + this.application.sync.addEventObserver(async (event) => { if (event === SyncEvent.PaginatedSyncRequestCompleted) { const note = this.application.items.findItem(originalNote.uuid) if (note) { @@ -226,22 +226,22 @@ describe('online syncing', function () { }) it('allows saving of data after sign out', async function () { - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) this.application = await Factory.signOutApplicationAndReturnNew(this.application) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) const note = await Factory.createMappedNote(this.application) this.expectedItemCount++ await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + await this.application.sync.sync(syncOptions) + const rawPayloads = await this.application.storage.getAllRawPayloads() const notePayload = noteObjectsFromObjects(rawPayloads) expect(notePayload.length).to.equal(1) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) // set item to be merged for when sign in occurs - await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() - expect(this.application.syncService.isOutOfSync()).to.equal(false) - expect(this.application.itemManager.getDirtyItems().length).to.equal(BaseItemCounts.DefaultItems + 1) + await this.application.sync.markAllItemsAsNeedingSyncAndPersist() + expect(this.application.sync.isOutOfSync()).to.equal(false) + expect(this.application.items.getDirtyItems().length).to.equal(BaseItemCounts.DefaultItems + 1) // Sign back in for next tests await Factory.loginToApplication({ @@ -250,16 +250,16 @@ describe('online syncing', function () { password: this.password, }) - expect(this.application.itemManager.getDirtyItems().length).to.equal(0) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) - expect(this.application.syncService.isOutOfSync()).to.equal(false) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) + expect(this.application.items.getDirtyItems().length).to.equal(0) + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.sync.isOutOfSync()).to.equal(false) + expect(this.application.items.getDisplayableNotes().length).to.equal(1) - for (const item of this.application.itemManager.getDisplayableNotes()) { + for (const item of this.application.items.getDisplayableNotes()) { expect(item.content.title).to.be.ok } - const updatedRawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const updatedRawPayloads = await this.application.storage.getAllRawPayloads() for (const payload of updatedRawPayloads) { // if an item comes back from the server, it is saved to disk immediately without a dirty value. expect(payload.dirty).to.not.be.ok @@ -274,10 +274,10 @@ describe('online syncing', function () { const originalTitle = note.content.title await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) const encrypted = CreateEncryptedServerSyncPushPayload( - await this.application.encryptionService.encryptSplitSingle({ + await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payloadRepresentation()], }, @@ -291,11 +291,11 @@ describe('online syncing', function () { const items = await this.application.mutator.emitItemsFromPayloads([errorred], PayloadEmitSource.LocalChanged) - const mappedItem = this.application.itemManager.findAnyItem(errorred.uuid) + const mappedItem = this.application.items.findAnyItem(errorred.uuid) expect(typeof mappedItem.content).to.equal('string') - const decryptedPayload = await this.application.encryptionService.decryptSplitSingle({ + const decryptedPayload = await this.application.encryption.decryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [errorred], }, @@ -319,7 +319,7 @@ describe('online syncing', function () { this.application = await Factory.signOutApplicationAndReturnNew(this.application) await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('removes item from storage upon deletion', async function () { @@ -327,41 +327,41 @@ describe('online syncing', function () { this.expectedItemCount++ await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) note = this.application.items.findItem(note.uuid) expect(note.dirty).to.equal(false) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await this.application.mutator.setItemToBeDeleted(note) note = this.application.items.findAnyItem(note.uuid) expect(note.dirty).to.equal(true) this.expectedItemCount-- - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) note = this.application.items.findItem(note.uuid) expect(note).to.not.be.ok // We expect that this item is now gone for good, and no duplicate has been created. - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) await Factory.sleep(0.5) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) }) it('retrieving item with no content should correctly map local state', async function () { const note = await Factory.createMappedNote(this.application) await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) - const syncToken = await this.application.syncService.getLastSyncToken() + const syncToken = await this.application.sync.getLastSyncToken() this.expectedItemCount++ - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) // client A await this.application.mutator.setItemToBeDeleted(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) // Subtract 1 this.expectedItemCount-- @@ -369,10 +369,10 @@ describe('online syncing', function () { // client B // Clearing sync tokens wont work as server wont return deleted items. // Set saved sync token instead - await this.application.syncService.setLastSyncToken(syncToken) - await this.application.syncService.sync(syncOptions) + await this.application.sync.setLastSyncToken(syncToken) + await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('changing an item while it is being synced should sync again', async function () { @@ -381,7 +381,7 @@ describe('online syncing', function () { this.expectedItemCount++ /** Begin syncing it with server but introduce latency so we can sneak in a delete */ - this.application.syncService.ut_beginLatencySimulator(500) + this.application.sync.ut_beginLatencySimulator(500) const sync = this.application.sync.sync() @@ -395,11 +395,11 @@ describe('online syncing', function () { await sync - this.application.syncService.ut_endLatencySimulator() + this.application.sync.ut_endLatencySimulator() await this.application.sync.sync(syncOptions) - const latestNote = this.application.itemManager.findItem(note.uuid) + const latestNote = this.application.items.findItem(note.uuid) expect(latestNote.title).to.equal('latest title') }) @@ -409,7 +409,7 @@ describe('online syncing', function () { this.expectedItemCount++ /** Begin syncing it with server but introduce latency so we can sneak in a delete */ - this.application.syncService.ut_beginLatencySimulator(500) + this.application.sync.ut_beginLatencySimulator(500) const sync = this.application.sync.sync() @@ -423,12 +423,12 @@ describe('online syncing', function () { await sync - this.application.syncService.ut_endLatencySimulator() + this.application.sync.ut_endLatencySimulator() await this.application.sync.sync(syncOptions) /** We expect that item has been deleted */ - const allItems = this.application.itemManager.items + const allItems = this.application.items.items expect(allItems.length).to.equal(this.expectedItemCount) }) @@ -440,7 +440,7 @@ describe('online syncing', function () { let success = true let didCompleteRelevantSync = false let beginCheckingResponse = false - this.application.syncService.addEventObserver((eventName, data) => { + this.application.sync.addEventObserver((eventName, data) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { beginCheckingResponse = true } @@ -456,7 +456,7 @@ describe('online syncing', function () { } } }) - await this.application.syncService.sync({ mode: SyncMode.DownloadFirst }) + await this.application.sync.sync({ mode: SyncMode.DownloadFirst }) expect(didCompleteRelevantSync).to.equal(true) expect(success).to.equal(true) }) @@ -469,7 +469,7 @@ describe('online syncing', function () { let success = true let didCompleteRelevantSync = false let beginCheckingResponse = false - this.application.syncService.addEventObserver(async (eventName, data) => { + this.application.sync.addEventObserver(async (eventName, data) => { if (eventName === SyncEvent.DownloadFirstSyncCompleted) { await this.application.mutator.setItemToBeDeleted(note) beginCheckingResponse = true @@ -486,7 +486,7 @@ describe('online syncing', function () { } } }) - await this.application.syncService.sync({ mode: SyncMode.DownloadFirst }) + await this.application.sync.sync({ mode: SyncMode.DownloadFirst }) expect(didCompleteRelevantSync).to.equal(true) expect(success).to.equal(true) }) @@ -496,16 +496,16 @@ describe('online syncing', function () { this.expectedItemCount++ - await this.application.syncService.markAllItemsAsNeedingSyncAndPersist() + await this.application.sync.markAllItemsAsNeedingSyncAndPersist() - this.application.itemManager.resetState() - this.application.payloadManager.resetState() + this.application.items.resetState() + this.application.payloads.resetState() - await this.application.syncService.clearSyncPositionTokens() + await this.application.sync.clearSyncPositionTokens() - expect(this.application.itemManager.items.length).to.equal(0) + expect(this.application.items.items.length).to.equal(0) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const encryptedPayloads = rawPayloads.map((rawPayload) => { return new EncryptedPayload(rawPayload) @@ -515,17 +515,17 @@ describe('online syncing', function () { const keyedSplit = CreateDecryptionSplitWithKeyLookup(encryptionSplit) - const decryptionResults = await this.application.encryptionService.decryptSplit(keyedSplit) + const decryptionResults = await this.application.encryption.decryptSplit(keyedSplit) await this.application.mutator.emitItemsFromPayloads(decryptionResults, PayloadEmitSource.LocalChanged) - expect(this.application.itemManager.allTrackedItems().length).to.equal(this.expectedItemCount) + expect(this.application.items.allTrackedItems().length).to.equal(this.expectedItemCount) - const foundNote = this.application.itemManager.findAnyItem(note.uuid) + const foundNote = this.application.items.findAnyItem(note.uuid) expect(foundNote.dirty).to.equal(true) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) }) /** Temporarily skipping due to long run time */ @@ -538,8 +538,8 @@ describe('online syncing', function () { this.expectedItemCount += largeItemCount - await this.application.syncService.sync(syncOptions) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + await this.application.sync.sync(syncOptions) + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) }).timeout(15000) @@ -551,32 +551,32 @@ describe('online syncing', function () { await this.application.mutator.setItemDirty(note) } /** Upload */ - this.application.syncService.sync({ awaitAll: true, checkIntegrity: false }) + this.application.sync.sync({ awaitAll: true, checkIntegrity: false }) await this.context.awaitNextSucessfulSync() this.expectedItemCount += largeItemCount /** Clear local data */ - await this.application.payloadManager.resetState() - await this.application.itemManager.resetState() - await this.application.syncService.clearSyncPositionTokens() - await this.application.diskStorageService.clearAllPayloads() - expect(this.application.itemManager.items.length).to.equal(0) + await this.application.payloads.resetState() + await this.application.items.resetState() + await this.application.sync.clearSyncPositionTokens() + await this.application.storage.clearAllPayloads() + expect(this.application.items.items.length).to.equal(0) /** Download all data */ - this.application.syncService.sync(syncOptions) + this.application.sync.sync(syncOptions) await this.context.awaitNextSucessfulSync() - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) }).timeout(30000) it('syncing an item should storage it encrypted', async function () { const note = await Factory.createMappedNote(this.application) await this.application.mutator.setItemDirty(note) - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) this.expectedItemCount++ - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(typeof notePayload.content).to.equal('string') }) @@ -587,12 +587,12 @@ describe('online syncing', function () { this.expectedItemCount++ /** Simulate database not loaded */ - await this.application.syncService.clearSyncPositionTokens() - this.application.syncService.ut_setDatabaseLoaded(false) - this.application.syncService.sync(syncOptions) + await this.application.sync.clearSyncPositionTokens() + this.application.sync.ut_setDatabaseLoaded(false) + this.application.sync.sync(syncOptions) await Factory.sleep(0.3) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(typeof notePayload.content).to.equal('string') }) @@ -610,18 +610,18 @@ describe('online syncing', function () { syncOptions, ) this.expectedItemCount++ - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() const notePayload = rawPayloads.find((p) => p.content_type === ContentType.TYPES.Note) expect(typeof notePayload.content).to.equal('string') expect(notePayload.content.length).to.be.above(text.length) }) it('syncing a new item before local data has loaded should still persist the item to disk', async function () { - this.application.syncService.ut_setDatabaseLoaded(false) + this.application.sync.ut_setDatabaseLoaded(false) /** You don't want to clear model manager state as we'll lose encrypting items key */ - // await this.application.payloadManager.resetState(); - await this.application.syncService.clearSyncPositionTokens() - expect(this.application.itemManager.getDirtyItems().length).to.equal(0) + // await this.application.payloads.resetState(); + await this.application.sync.clearSyncPositionTokens() + expect(this.application.items.getDirtyItems().length).to.equal(0) let note = await Factory.createMappedNote(this.application) note = await this.application.mutator.changeItem(note, (mutator) => { @@ -629,15 +629,15 @@ describe('online syncing', function () { }) /** This sync request should exit prematurely as we called ut_setDatabaseNotLoaded */ /** Do not await. Sleep instead. */ - this.application.syncService.sync(syncOptions) + this.application.sync.sync(syncOptions) await Factory.sleep(0.3) this.expectedItemCount++ /** Item should still be dirty */ expect(note.dirty).to.equal(true) - expect(this.application.itemManager.getDirtyItems().length).to.equal(1) + expect(this.application.items.getDirtyItems().length).to.equal(1) - const rawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await this.application.storage.getAllRawPayloads() expect(rawPayloads.length).to.equal(this.expectedItemCount) const rawPayload = rawPayloads.find((p) => p.uuid === note.uuid) expect(rawPayload.uuid).to.equal(note.uuid) @@ -645,16 +645,16 @@ describe('online syncing', function () { expect(typeof rawPayload.content).to.equal('string') /** Clear state data and upload item from storage to server */ - await this.application.syncService.clearSyncPositionTokens() - await this.application.payloadManager.resetState() - await this.application.itemManager.resetState() - await this.application.syncService.loadDatabasePayloads() - await this.application.syncService.sync(syncOptions) + await this.application.sync.clearSyncPositionTokens() + await this.application.payloads.resetState() + await this.application.items.resetState() + await this.application.sync.loadDatabasePayloads() + await this.application.sync.sync(syncOptions) - const newRawPayloads = await this.application.diskStorageService.getAllRawPayloads() + const newRawPayloads = await this.application.storage.getAllRawPayloads() expect(newRawPayloads.length).to.equal(this.expectedItemCount) - const currentItem = this.application.itemManager.findItem(note.uuid) + const currentItem = this.application.items.findItem(note.uuid) expect(currentItem.content.text).to.equal(note.content.text) expect(currentItem.text).to.equal(note.text) expect(currentItem.dirty).to.not.be.ok @@ -680,16 +680,16 @@ describe('online syncing', function () { const largeItemCount = 50 await Factory.createManyMappedNotes(this.application, largeItemCount) this.expectedItemCount += largeItemCount - await this.application.syncService.sync(syncOptions) + await this.application.sync.sync(syncOptions) this.application = await Factory.signOutApplicationAndReturnNew(this.application) await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) - this.application.syncService.ut_setDatabaseLoaded(false) - await this.application.syncService.loadDatabasePayloads() - await this.application.syncService.sync(syncOptions) + this.application.sync.ut_setDatabaseLoaded(false) + await this.application.sync.loadDatabasePayloads() + await this.application.sync.sync(syncOptions) - const items = await this.application.itemManager.items + const items = await this.application.items.items expect(items.length).to.equal(this.expectedItemCount) }).timeout(20000) @@ -717,7 +717,7 @@ describe('online syncing', function () { it('syncing twice without waiting should only execute 1 online sync', async function () { const expectedEvents = 1 let actualEvents = 0 - this.application.syncService.addEventObserver((event, data) => { + this.application.sync.addEventObserver((event, data) => { if (event === SyncEvent.SyncCompletedWithAllItemsUploaded && data.source === SyncSource.External) { actualEvents++ } @@ -742,8 +742,8 @@ describe('online syncing', function () { this.expectedItemCount++ // client A. Don't await, we want to do other stuff. - this.application.syncService.ut_beginLatencySimulator(1500) - const slowSync = this.application.syncService.sync(syncOptions) + this.application.sync.ut_beginLatencySimulator(1500) + const slowSync = this.application.sync.sync(syncOptions) await Factory.sleep(0.1) expect(note.dirty).to.equal(true) @@ -758,8 +758,8 @@ describe('online syncing', function () { expect(note.payload.dirtyIndex).to.be.above(note.payload.globalDirtyIndexAtLastSync) // Now do a regular sync with no latency. - this.application.syncService.ut_endLatencySimulator() - const midSync = this.application.syncService.sync(syncOptions) + this.application.sync.ut_endLatencySimulator() + const midSync = this.application.sync.sync(syncOptions) await slowSync await midSync @@ -770,14 +770,14 @@ describe('online syncing', function () { expect(note.content.text).to.equal(text) // client B - await this.application.payloadManager.resetState() - await this.application.itemManager.resetState() - await this.application.syncService.clearSyncPositionTokens() - await this.application.syncService.sync(syncOptions) + await this.application.payloads.resetState() + await this.application.items.resetState() + await this.application.sync.clearSyncPositionTokens() + await this.application.sync.sync(syncOptions) // Expect that the server value and client value match, and no conflicts are created. - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) - const foundItem = this.application.itemManager.findItem(note.uuid) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) + const foundItem = this.application.items.findItem(note.uuid) expect(foundItem.content.text).to.equal(text) expect(foundItem.text).to.equal(text) }) @@ -790,17 +790,17 @@ describe('online syncing', function () { /** Create an item and sync it */ let note = await Factory.createMappedNote(this.application) - this.application.itemManager.addObserver(ContentType.TYPES.Note, ({ source }) => { + this.application.items.addObserver(ContentType.TYPES.Note, ({ source }) => { if (source === PayloadEmitSource.RemoteSaved) { actualSaveCount++ } }) this.expectedItemCount++ - this.application.syncService.ut_beginLatencySimulator(150) + this.application.sync.ut_beginLatencySimulator(150) /** Dont await */ - const syncRequest = this.application.syncService.sync(syncOptions) + const syncRequest = this.application.sync.sync(syncOptions) /** Dirty the item 100ms into 150ms request */ const newText = `${Math.random()}` @@ -837,7 +837,7 @@ describe('online syncing', function () { /** Create an item and sync it */ let note = await Factory.createMappedNote(this.application) - this.application.itemManager.addObserver(ContentType.TYPES.Note, ({ source }) => { + this.application.items.addObserver(ContentType.TYPES.Note, ({ source }) => { if (source === PayloadEmitSource.RemoteSaved) { actualSaveCount++ } @@ -845,13 +845,13 @@ describe('online syncing', function () { this.expectedItemCount++ /** Dont await */ - const syncRequest = this.application.syncService.sync(syncOptions) + const syncRequest = this.application.sync.sync(syncOptions) /** Dirty the item before lastSyncBegan is set */ let didPerformMutatation = false const newText = `${Math.random()}` - this.application.syncService.addEventObserver(async (eventName) => { + this.application.sync.addEventObserver(async (eventName) => { if (eventName === SyncEvent.SyncDidBeginProcessing && !didPerformMutatation) { didPerformMutatation = true await this.application.mutator.changeItem(note, (mutator) => { @@ -876,7 +876,7 @@ describe('online syncing', function () { let didPerformMutatation = false const newText = `${Math.random()}` - this.application.itemManager.addObserver(ContentType.TYPES.Note, async ({ changed, source }) => { + this.application.items.addObserver(ContentType.TYPES.Note, async ({ changed, source }) => { if (source === PayloadEmitSource.RemoteSaved) { actualSaveCount++ } else if (source === PayloadEmitSource.PreSyncSave && !didPerformMutatation) { @@ -895,7 +895,7 @@ describe('online syncing', function () { this.expectedItemCount++ /** Dont await */ - const syncRequest = this.application.syncService.sync(syncOptions) + const syncRequest = this.application.sync.sync(syncOptions) await syncRequest expect(actualSaveCount).to.equal(expectedSaveCount) note = this.application.items.findItem(note.uuid) @@ -904,12 +904,12 @@ describe('online syncing', function () { it('retreiving a remote deleted item should succeed', async function () { const note = await Factory.createSyncedNote(this.application) - const preDeleteSyncToken = await this.application.syncService.getLastSyncToken() + const preDeleteSyncToken = await this.application.sync.getLastSyncToken() await this.application.mutator.deleteItem(note) await this.application.sync.sync() - await this.application.syncService.setLastSyncToken(preDeleteSyncToken) + await this.application.sync.setLastSyncToken(preDeleteSyncToken) await this.application.sync.sync(syncOptions) - expect(this.application.itemManager.items.length).to.equal(this.expectedItemCount) + expect(this.application.items.items.length).to.equal(this.expectedItemCount) }) it('errored items should not be synced', async function () { @@ -918,7 +918,7 @@ describe('online syncing', function () { const lastSyncBegan = note.lastSyncBegan const lastSyncEnd = note.lastSyncEnd - const encrypted = await this.application.encryptionService.encryptSplitSingle({ + const encrypted = await this.application.encryption.encryptSplitSingle({ usesItemsKeyWithKeyLookup: { items: [note.payload], }, @@ -929,7 +929,7 @@ describe('online syncing', function () { dirty: true, }) - await this.application.payloadManager.emitPayload(errored) + await this.application.payloads.emitPayload(errored) await this.application.sync.sync(syncOptions) const updatedNote = this.application.items.findAnyItem(note.uuid) @@ -957,10 +957,10 @@ describe('online syncing', function () { }, }) - await this.application.syncService.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) + await this.application.sync.handleSuccessServerResponse({ payloadsSavedOrSaving: [], options: {} }, response) - expect(this.application.payloadManager.findOne(invalidPayload.uuid)).to.not.be.ok - expect(this.application.payloadManager.findOne(validPayload.uuid)).to.be.ok + expect(this.application.payloads.findOne(invalidPayload.uuid)).to.not.be.ok + expect(this.application.payloads.findOne(validPayload.uuid)).to.be.ok }) it('retrieved items should have both updated_at and updated_at_timestamps', async function () { @@ -996,13 +996,13 @@ describe('online syncing', function () { it('should call onPresyncSave before sync begins', async function () { const events = [] - this.application.syncService.addEventObserver((event) => { + this.application.sync.addEventObserver((event) => { if (event === SyncEvent.SyncDidBeginProcessing) { events.push('sync-will-begin') } }) - await this.application.syncService.sync({ + await this.application.sync.sync({ onPresyncSave: () => { events.push('on-presync-save') }, diff --git a/packages/snjs/mocha/upgrading.test.js b/packages/snjs/mocha/upgrading.test.js index 9198efae6..a0ce3adc2 100644 --- a/packages/snjs/mocha/upgrading.test.js +++ b/packages/snjs/mocha/upgrading.test.js @@ -92,11 +92,11 @@ describe('upgrading', () => { version: oldVersion, }) - expect((await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( + expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( oldVersion, ) - expect((await this.application.encryptionService.getRootKeyParams()).version).to.equal(oldVersion) - expect((await this.application.encryptionService.getRootKey()).keyVersion).to.equal(oldVersion) + expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) + expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) this.application.setLaunchCallback({ receiveChallenge: this.receiveChallenge, @@ -104,15 +104,15 @@ describe('upgrading', () => { const result = await this.application.upgradeProtocolVersion() expect(result).to.deep.equal({ success: true }) - const wrappedRootKey = await this.application.encryptionService.rootKeyManager.getWrappedRootKey() + const wrappedRootKey = await this.application.encryption.rootKeyManager.getWrappedRootKey() const payload = new EncryptedPayload(wrappedRootKey) expect(payload.version).to.equal(newVersion) - expect((await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( + expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( newVersion, ) - expect((await this.application.encryptionService.getRootKeyParams()).version).to.equal(newVersion) - expect((await this.application.encryptionService.getRootKey()).keyVersion).to.equal(newVersion) + expect((await this.application.encryption.getRootKeyParams()).version).to.equal(newVersion) + expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(newVersion) /** * Immediately logging out ensures we don't rely on subsequent @@ -120,8 +120,8 @@ describe('upgrading', () => { */ this.application = await Factory.signOutApplicationAndReturnNew(this.application) await this.application.signIn(this.email, this.password, undefined, undefined, undefined, true) - expect(this.application.itemManager.getDisplayableNotes().length).to.equal(1) - expect(this.application.payloadManager.invalidPayloads).to.be.empty + expect(this.application.items.getDisplayableNotes().length).to.equal(1) + expect(this.application.payloads.invalidPayloads).to.be.empty }).timeout(15000) it('upgrading from 003 to 004 with passcode only then reiniting app should create valid state', async function () { @@ -155,7 +155,7 @@ describe('upgrading', () => { await appFirst.launch(true) const result = await appFirst.upgradeProtocolVersion() expect(result).to.deep.equal({ success: true }) - expect(appFirst.payloadManager.invalidPayloads).to.be.empty + expect(appFirst.payloads.invalidPayloads).to.be.empty await Factory.safeDeinit(appFirst) /** Recreate the once more */ @@ -166,15 +166,15 @@ describe('upgrading', () => { }, }) await appSecond.launch(true) - expect(appSecond.payloadManager.invalidPayloads).to.be.empty + expect(appSecond.payloads.invalidPayloads).to.be.empty await Factory.safeDeinit(appSecond) }).timeout(15000) it('protocol version should be upgraded on password change', async function () { /** Delete default items key that is created on launch */ - const itemsKey = await this.application.encryptionService.getSureDefaultItemsKey() + const itemsKey = await this.application.encryption.getSureDefaultItemsKey() await this.application.mutator.setItemToBeDeleted(itemsKey) - expect(Uuids(this.application.itemManager.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false) + expect(Uuids(this.application.items.getDisplayableItemsKeys()).includes(itemsKey.uuid)).to.equal(false) Factory.createMappedNote(this.application) @@ -186,10 +186,10 @@ describe('upgrading', () => { version: ProtocolVersion.V003, }) - expect(this.application.itemManager.getDisplayableItemsKeys().length).to.equal(1) + expect(this.application.items.getDisplayableItemsKeys().length).to.equal(1) - expect((await this.application.encryptionService.getRootKeyParams()).version).to.equal(ProtocolVersion.V003) - expect((await this.application.encryptionService.getRootKey()).keyVersion).to.equal(ProtocolVersion.V003) + expect((await this.application.encryption.getRootKeyParams()).version).to.equal(ProtocolVersion.V003) + expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(ProtocolVersion.V003) /** Ensure note is encrypted with 003 */ const notePayloads = await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.Note) @@ -199,16 +199,16 @@ describe('upgrading', () => { const { error } = await this.application.changePassword(this.password, 'foobarfoo') expect(error).to.not.exist - const latestVersion = this.application.encryptionService.getLatestVersion() - expect((await this.application.encryptionService.getRootKeyParams()).version).to.equal(latestVersion) - expect((await this.application.encryptionService.getRootKey()).keyVersion).to.equal(latestVersion) + const latestVersion = this.application.encryption.getLatestVersion() + expect((await this.application.encryption.getRootKeyParams()).version).to.equal(latestVersion) + expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(latestVersion) - const defaultItemsKey = await this.application.encryptionService.getSureDefaultItemsKey() + const defaultItemsKey = await this.application.encryption.getSureDefaultItemsKey() expect(defaultItemsKey.keyVersion).to.equal(latestVersion) /** After change, note should now be encrypted with latest protocol version */ - const note = this.application.itemManager.getDisplayableNotes()[0] + const note = this.application.items.getDisplayableNotes()[0] await Factory.markDirtyAndSyncItem(this.application, note) const refreshedNotePayloads = await Factory.getStoragePayloadsOfType(this.application, ContentType.TYPES.Note) @@ -243,44 +243,44 @@ describe('upgrading', () => { }) it('rolls back the local protocol upgrade if syncing fails', async function () { - sinon.replace(this.application.syncService, 'sync', sinon.fake()) + sinon.replace(this.application.sync, 'sync', sinon.fake()) this.application.setLaunchCallback({ receiveChallenge: this.receiveChallenge, }) - expect((await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( + expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( oldVersion, ) const errors = await this.application.upgradeProtocolVersion() expect(errors).to.not.be.empty /** Ensure we're still on 003 */ - expect((await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( + expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( oldVersion, ) - expect((await this.application.encryptionService.getRootKeyParams()).version).to.equal(oldVersion) - expect((await this.application.encryptionService.getRootKey()).keyVersion).to.equal(oldVersion) - expect((await this.application.encryptionService.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) + expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) + expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) + expect((await this.application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) }) it('rolls back the local protocol upgrade if the server responds with an error', async function () { - sinon.replace(this.application.sessionManager, 'changeCredentials', () => [Error()]) + sinon.replace(this.application.sessions, 'changeCredentials', () => [Error()]) this.application.setLaunchCallback({ receiveChallenge: this.receiveChallenge, }) - expect((await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( + expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( oldVersion, ) const errors = await this.application.upgradeProtocolVersion() expect(errors).to.not.be.empty /** Ensure we're still on 003 */ - expect((await this.application.encryptionService.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( + expect((await this.application.encryption.rootKeyManager.getRootKeyWrapperKeyParams()).version).to.equal( oldVersion, ) - expect((await this.application.encryptionService.getRootKeyParams()).version).to.equal(oldVersion) - expect((await this.application.encryptionService.getRootKey()).keyVersion).to.equal(oldVersion) - expect((await this.application.encryptionService.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) + expect((await this.application.encryption.getRootKeyParams()).version).to.equal(oldVersion) + expect((await this.application.encryption.getRootKey()).keyVersion).to.equal(oldVersion) + expect((await this.application.encryption.getSureDefaultItemsKey()).keyVersion).to.equal(oldVersion) }) }) }) diff --git a/packages/snjs/mocha/vaults/asymmetric-messages.test.js b/packages/snjs/mocha/vaults/asymmetric-messages.test.js index 449a675a2..96f4192d5 100644 --- a/packages/snjs/mocha/vaults/asymmetric-messages.test.js +++ b/packages/snjs/mocha/vaults/asymmetric-messages.test.js @@ -34,10 +34,14 @@ describe('asymmetric messages', function () { const { contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context) const eventData = { - oldKeyPair: context.encryption.getKeyPair(), - oldSigningKeyPair: context.encryption.getSigningKeyPair(), - newKeyPair: context.encryption.getKeyPair(), - newSigningKeyPair: context.encryption.getSigningKeyPair(), + current: { + encryption: context.encryption.getKeyPair(), + signing: context.encryption.getSigningKeyPair(), + }, + previous: { + encryption: context.encryption.getKeyPair(), + signing: context.encryption.getSigningKeyPair(), + }, } await service.sendOwnContactChangeEventToAllContacts(eventData) @@ -92,6 +96,38 @@ describe('asymmetric messages', function () { await deinitThirdPartyContext() }) + it('should send contact share message when a member is added to a vault', async () => { + const { sharedVault, contactContext, deinitContactContext } = + await Collaboration.createSharedVaultWithAcceptedInvite(context) + + const handleInitialContactShareMessage = contactContext.resolveWhenAsymmetricMessageProcessingCompletes() + + const { thirdPartyContext, deinitThirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault( + context, + sharedVault, + ) + + await Collaboration.acceptAllInvites(thirdPartyContext) + + const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedContactShareMessage') + const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedContactShareMessage') + const thirdPartySpy = sinon.spy(thirdPartyContext.asymmetric, 'handleTrustedContactShareMessage') + + await contactContext.sync() + await handleInitialContactShareMessage + + await context.sync() + await contactContext.sync() + await thirdPartyContext.sync() + + expect(firstPartySpy.callCount).to.equal(0) + expect(secondPartySpy.callCount).to.equal(1) + expect(thirdPartySpy.callCount).to.equal(0) + + await deinitThirdPartyContext() + await deinitContactContext() + }) + it('should not send contact share message to self or to contact who is changed', async () => { const { sharedVault, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInvite(context) @@ -100,12 +136,10 @@ describe('asymmetric messages', function () { context, sharedVault, ) - const handleInitialContactShareMessage = contactContext.resolveWhenAsymmetricMessageProcessingCompletes() await Collaboration.acceptAllInvites(thirdPartyContext) await contactContext.sync() - await handleInitialContactShareMessage const sendContactSharePromise = context.resolveWhenSharedVaultServiceSendsContactShareMessage() @@ -161,8 +195,8 @@ describe('asymmetric messages', function () { description: 'New Description', }) - const firstPartySpy = sinon.spy(context.asymmetric, 'handleVaultMetadataChangedMessage') - const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleVaultMetadataChangedMessage') + const firstPartySpy = sinon.spy(context.asymmetric, 'handleTrustedVaultMetadataChangedMessage') + const secondPartySpy = sinon.spy(contactContext.asymmetric, 'handleTrustedVaultMetadataChangedMessage') await context.sync() await contactContext.sync() @@ -201,6 +235,61 @@ describe('asymmetric messages', function () { await deinitContactContext() }) + it('should trust and process messages sent after sender keypair changed', async () => { + const { sharedVault, contactContext, deinitContactContext } = + await Collaboration.createSharedVaultWithAcceptedInvite(context) + + await context.changePassword('new password') + + await context.vaults.changeVaultNameAndDescription(sharedVault, { + name: 'New Name', + description: 'New Description', + }) + + const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes() + await contactContext.sync() + await completedProcessingMessagesPromise + + const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier }) + expect(updatedVault.name).to.equal('New Name') + expect(updatedVault.description).to.equal('New Description') + + await deinitContactContext() + }) + + it('should not send back a vault change message after receiving a vault change message', async () => { + /** + * If userA receives a vault change message and mutates their vault locally, this should not create a + * chain of vault change messages that then ping-pongs back and forth between the two users. + */ + const { sharedVault, contactContext, deinitContactContext } = + await Collaboration.createSharedVaultWithAcceptedInvite(context) + + await context.changePassword('new password') + + await context.vaults.changeVaultNameAndDescription(sharedVault, { + name: 'New Name', + description: 'New Description', + }) + + context.lockSyncing() + + const completedProcessingMessagesPromise = contactContext.resolveWhenAsymmetricMessageProcessingCompletes() + await contactContext.sync() + await completedProcessingMessagesPromise + + /** + * There's really no good way to await the exact call since + * the relevant part fires in the SharedVaultSerivce item observer + */ + await context.sleep(0.25) + + const messages = await context.asymmetric.getInboundMessages() + expect(messages.length).to.equal(0) + + await deinitContactContext() + }) + it('should process sender keypair changed message', async () => { const { contactContext, deinitContactContext } = await Collaboration.createContactContext() await Collaboration.createTrustedContactForUserOfContext(context, contactContext) @@ -272,6 +361,28 @@ describe('asymmetric messages', function () { it('should delete all inbound messages after changing user password', async () => { /** Messages to user are encrypted with old keypair and are no longer decryptable */ - console.error('TODO: implement test') + + const { sharedVault, contactContext, deinitContactContext } = + await Collaboration.createSharedVaultWithAcceptedInvite(context) + + contactContext.lockSyncing() + + await context.vaults.changeVaultNameAndDescription(sharedVault, { + name: 'New Name', + description: 'New Description', + }) + + const promise = contactContext.resolveWhenAllInboundAsymmetricMessagesAreDeleted() + await contactContext.changePassword('new-password') + await promise + + const messages = await contactContext.asymmetric.getInboundMessages() + expect(messages.length).to.equal(0) + + const updatedVault = contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier }) + expect(updatedVault.name).to.not.equal('New Name') + expect(updatedVault.description).to.not.equal('New Description') + + await deinitContactContext() }) }) diff --git a/packages/snjs/mocha/vaults/contacts.test.js b/packages/snjs/mocha/vaults/contacts.test.js index ef158cba5..88966f0e4 100644 --- a/packages/snjs/mocha/vaults/contacts.test.js +++ b/packages/snjs/mocha/vaults/contacts.test.js @@ -71,6 +71,18 @@ describe('contacts', function () { expect(updatedSelfContact.publicKeySet.signing).to.equal(context.signingPublicKey) }) + it('should update self contact reference when changed', async () => { + const selfContact = context.contacts.getSelfContact() + + await context.mutator.changeItem(selfContact, (mutator) => { + mutator.name = 'New Name' + }) + + const updatedSelfContact = context.contacts.getSelfContact() + + expect(updatedSelfContact.name).to.equal('New Name') + }) + it('should not be able to delete self contact', async () => { const selfContact = context.contacts.getSelfContact() @@ -80,4 +92,8 @@ describe('contacts', function () { it('should not be able to delete a trusted contact if it belongs to a vault I administer', async () => { console.error('TODO: implement test') }) + + it('should be able to refresh a contact using a collaborationID that includes full chain of previouos public keys', async () => { + console.error('TODO: implement test') + }) }) diff --git a/packages/snjs/mocha/vaults/crypto.test.js b/packages/snjs/mocha/vaults/crypto.test.js index c328e30a3..87ab9f02c 100644 --- a/packages/snjs/mocha/vaults/crypto.test.js +++ b/packages/snjs/mocha/vaults/crypto.test.js @@ -50,9 +50,10 @@ describe('shared vault crypto', function () { await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) await contactContext.changeNoteTitleAndSync(note, 'new title') + await context.sync() - const rawPayloads = await context.application.diskStorageService.getAllRawPayloads() + const rawPayloads = await context.application.storage.getAllRawPayloads() const noteRawPayload = rawPayloads.find((payload) => payload.uuid === note.uuid) expect(noteRawPayload.signatureData).to.not.be.undefined @@ -166,11 +167,13 @@ describe('shared vault crypto', function () { const { note, contactContext, deinitContactContext } = await Collaboration.createSharedVaultWithAcceptedInviteAndNote(context) - expect(context.contacts.isItemAuthenticallySigned(note)).to.equal('not-applicable') + expect(context.contacts.isItemAuthenticallySigned(note)).to.equal(ItemSignatureValidationResult.NotApplicable) const contactNote = contactContext.items.findItem(note.uuid) - expect(contactContext.contacts.isItemAuthenticallySigned(contactNote)).to.equal('yes') + expect(contactContext.contacts.isItemAuthenticallySigned(contactNote)).to.equal( + ItemSignatureValidationResult.Trusted, + ) await contactContext.changeNoteTitleAndSync(contactNote, 'new title') @@ -178,27 +181,9 @@ describe('shared vault crypto', function () { let updatedNote = context.items.findItem(note.uuid) - expect(context.contacts.isItemAuthenticallySigned(updatedNote)).to.equal('yes') + expect(context.contacts.isItemAuthenticallySigned(updatedNote)).to.equal(ItemSignatureValidationResult.Trusted) await deinitContactContext() }) }) - - describe('keypair revocation', () => { - it('should be able to revoke non-current keypair', async () => { - console.error('TODO') - }) - - it('revoking a keypair should send a keypair revocation event to trusted contacts', async () => { - console.error('TODO') - }) - - it('should not be able to revoke current key pair', async () => { - console.error('TODO') - }) - - it('should distrust revoked keypair as contact', async () => { - console.error('TODO') - }) - }) }) diff --git a/packages/snjs/mocha/vaults/invites.test.js b/packages/snjs/mocha/vaults/invites.test.js index 4071af7b6..a666959ff 100644 --- a/packages/snjs/mocha/vaults/invites.test.js +++ b/packages/snjs/mocha/vaults/invites.test.js @@ -30,7 +30,9 @@ describe('shared vault invites', function () { const { contactContext, deinitContactContext } = await Collaboration.createContactContext() const contact = await Collaboration.createTrustedContactForUserOfContext(context, contactContext) - const vaultInvite = await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write) + const vaultInvite = ( + await sharedVaults.inviteContactToSharedVault(sharedVault, contact, SharedVaultPermission.Write) + ).getValue() expect(vaultInvite).to.not.be.undefined expect(vaultInvite.shared_vault_uuid).to.equal(sharedVault.sharing.sharedVaultUuid) @@ -78,8 +80,10 @@ describe('shared vault invites', function () { const message = invites[0].message const delegatedContacts = message.data.trustedContacts - expect(delegatedContacts.length).to.equal(1) - expect(delegatedContacts[0].contactUuid).to.equal(contactContext.userUuid) + expect(delegatedContacts.length).to.equal(2) + + expect(delegatedContacts.some((contact) => contact.contactUuid === context.userUuid)).to.be.true + expect(delegatedContacts.some((contact) => contact.contactUuid === contactContext.userUuid)).to.be.true await deinitThirdPartyContext() await deinitContactContext() @@ -197,7 +201,17 @@ describe('shared vault invites', function () { it('should delete all inbound invites after changing user password', async () => { /** Invites to user are encrypted with old keypair and are no longer decryptable */ - console.error('TODO: implement test') + const { contactContext, deinitContactContext } = + await Collaboration.createSharedVaultWithUnacceptedButTrustedInvite(context) + + const promise = contactContext.resolveWhenAllInboundSharedVaultInvitesAreDeleted() + await contactContext.changePassword('new-password') + await promise + + const invites = await contactContext.sharedVaults.downloadInboundInvites() + expect(invites.length).to.equal(0) + + await deinitContactContext() }) it('sharing a vault with user inputted and ephemeral password should share the key as synced for the recipient', async () => { diff --git a/packages/snjs/mocha/vaults/key_rotation.test.js b/packages/snjs/mocha/vaults/key_rotation.test.js index ff45df94c..e0591e6e0 100644 --- a/packages/snjs/mocha/vaults/key_rotation.test.js +++ b/packages/snjs/mocha/vaults/key_rotation.test.js @@ -34,7 +34,7 @@ describe('shared vault key rotation', function () { contactContext.lockSyncing() - const spy = sinon.spy(context.encryption, 'reencryptKeySystemItemsKeysForVault') + const spy = sinon.spy(context.keys, 'reencryptKeySystemItemsKeysForVault') const promise = context.resolveWhenSharedVaultKeyRotationInvitesGetSent(sharedVault) await vaults.rotateVaultRootKey(sharedVault) diff --git a/packages/snjs/mocha/vaults/keypair-change.test.js b/packages/snjs/mocha/vaults/keypair-change.test.js new file mode 100644 index 000000000..53d216b9b --- /dev/null +++ b/packages/snjs/mocha/vaults/keypair-change.test.js @@ -0,0 +1,41 @@ +import * as Factory from '../lib/factory.js' +import * as Collaboration from '../lib/Collaboration.js' + +chai.use(chaiAsPromised) +const expect = chai.expect + +describe('keypair change', function () { + this.timeout(Factory.TwentySecondTimeout) + + let context + + afterEach(async function () { + await context.deinit() + localStorage.clear() + }) + + beforeEach(async function () { + localStorage.clear() + + context = await Factory.createAppContextWithRealCrypto() + + await context.launch() + await context.register() + }) + + it('contacts should be able to handle receiving multiple keypair changed messages and trust them in order', async () => { + console.error('TODO: implement test') + }) + + it('should not trust messages sent with previous key pair', async () => { + console.error('TODO: implement test') + }) + + it('should reupload invites after rotating keypair', async () => { + console.error('TODO: implement test') + }) + + it('should reupload asymmetric messages after rotating keypair', async () => { + console.error('TODO: implement test') + }) +}) diff --git a/packages/snjs/mocha/vaults/permissions.test.js b/packages/snjs/mocha/vaults/permissions.test.js index ba4924cce..4f270a03d 100644 --- a/packages/snjs/mocha/vaults/permissions.test.js +++ b/packages/snjs/mocha/vaults/permissions.test.js @@ -43,7 +43,7 @@ describe('shared vault permissions', function () { SharedVaultPermission.Write, ) - expect(isClientDisplayableError(result)).to.be.true + expect(result.isFailed()).to.be.true await deinitContactContext() }) diff --git a/packages/snjs/mocha/vaults/shared_vaults.test.js b/packages/snjs/mocha/vaults/shared_vaults.test.js index 13bb987f7..092ea757a 100644 --- a/packages/snjs/mocha/vaults/shared_vaults.test.js +++ b/packages/snjs/mocha/vaults/shared_vaults.test.js @@ -63,15 +63,15 @@ describe('shared vaults', function () { await promise expect(contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined - expect(contactContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined - expect(contactContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty + expect(contactContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined + expect(contactContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty const recreatedContext = await Factory.createAppContextWithRealCrypto(contactContext.identifier) await recreatedContext.launch() expect(recreatedContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined - expect(recreatedContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined - expect(recreatedContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty + expect(recreatedContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined + expect(recreatedContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty await deinitContactContext() await recreatedContext.deinit() @@ -90,15 +90,15 @@ describe('shared vaults', function () { await promise expect(contactContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined - expect(contactContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined - expect(contactContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty + expect(contactContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined + expect(contactContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty const recreatedContext = await Factory.createAppContextWithRealCrypto(contactContext.identifier) await recreatedContext.launch() expect(recreatedContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined - expect(recreatedContext.encryption.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined - expect(recreatedContext.encryption.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty + expect(recreatedContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined + expect(recreatedContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty await deinitContactContext() await recreatedContext.deinit() diff --git a/packages/snjs/mocha/vaults/signatures.test.js b/packages/snjs/mocha/vaults/signatures.test.js new file mode 100644 index 000000000..d6deabdd6 --- /dev/null +++ b/packages/snjs/mocha/vaults/signatures.test.js @@ -0,0 +1,33 @@ +import * as Factory from '../lib/factory.js' +import * as Collaboration from '../lib/Collaboration.js' + +chai.use(chaiAsPromised) +const expect = chai.expect + +describe('signatures', function () { + this.timeout(Factory.TwentySecondTimeout) + + let context + + afterEach(async function () { + await context.deinit() + localStorage.clear() + }) + + beforeEach(async function () { + localStorage.clear() + + context = await Factory.createAppContextWithRealCrypto() + + await context.launch() + await context.register() + }) + + it('signatures should be marked as of questionable integrity when signed with non root contact public key', async () => { + console.error('TODO: implement test') + }) + + it('items marked with questionable integrity should have option to trust the item which would resync it', async () => { + console.error('TODO: implement test') + }) +}) diff --git a/packages/web/src/javascripts/Application/Device/WebOrDesktopDevice.ts b/packages/web/src/javascripts/Application/Device/WebOrDesktopDevice.ts index 579080f66..4e8874f9a 100644 --- a/packages/web/src/javascripts/Application/Device/WebOrDesktopDevice.ts +++ b/packages/web/src/javascripts/Application/Device/WebOrDesktopDevice.ts @@ -28,7 +28,7 @@ export abstract class WebOrDesktopDevice implements WebOrDesktopDeviceInterface abstract environment: Environment setApplication(application: SNApplication): void { - const database = new Database(application.identifier, application.alertService) + const database = new Database(application.identifier, application.alerts) this.databases.push(database) } diff --git a/packages/web/src/javascripts/Application/WebApplication.ts b/packages/web/src/javascripts/Application/WebApplication.ts index 5b4955f76..34bdbaf9c 100644 --- a/packages/web/src/javascripts/Application/WebApplication.ts +++ b/packages/web/src/javascripts/Application/WebApplication.ts @@ -115,20 +115,13 @@ export class WebApplication extends SNApplication implements WebApplicationInter } this.itemControllerGroup = new ItemGroupController(this) - this.routeService = new RouteService(this, this.internalEventBus) + this.routeService = new RouteService(this, this.events) this.webServices = {} as WebServices this.webServices.keyboardService = new KeyboardService(platform, this.environment) this.webServices.archiveService = new ArchiveManager(this) - this.webServices.themeService = new ThemeManager( - this, - this.preferences, - this.componentManager, - this.internalEventBus, - ) - this.webServices.autolockService = this.isNativeMobileWeb() - ? undefined - : new AutolockService(this, this.internalEventBus) + this.webServices.themeService = new ThemeManager(this, this.preferences, this.componentManager, this.events) + this.webServices.autolockService = this.isNativeMobileWeb() ? undefined : new AutolockService(this, this.events) this.webServices.desktopService = isDesktopDevice(deviceInterface) ? new DesktopManager(this, deviceInterface, this.fileBackups as BackupServiceInterface) : undefined @@ -137,9 +130,9 @@ export class WebApplication extends SNApplication implements WebApplicationInter this.webServices.momentsService = new MomentsService( this, this.webServices.viewControllerManager.filesController, - this.internalEventBus, + this.events, ) - this.webServices.vaultDisplayService = new VaultDisplayService(this, this.internalEventBus) + this.webServices.vaultDisplayService = new VaultDisplayService(this, this.events) if (this.isNativeMobileWeb()) { this.mobileWebReceiver = new MobileWebReceiver(this) @@ -219,7 +212,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter observer(event, data) } - this.internalEventBus.publish({ type: event, payload: data }) + this.events.publish({ type: event, payload: data }) } publishPanelDidResizeEvent(name: string, width: number, collapsed: boolean) { @@ -273,8 +266,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter } public get desktopDevice(): DesktopDeviceInterface | undefined { - if (isDesktopDevice(this.deviceInterface)) { - return this.deviceInterface + if (isDesktopDevice(this.device)) { + return this.device } return undefined @@ -300,11 +293,11 @@ export class WebApplication extends SNApplication implements WebApplicationInter if (!this.isNativeMobileWeb()) { throw Error('Attempting to access device as mobile device on non mobile platform') } - return this.deviceInterface as MobileDeviceInterface + return this.device as MobileDeviceInterface } webOrDesktopDevice(): WebOrDesktopDevice { - return this.deviceInterface as WebOrDesktopDevice + return this.device as WebOrDesktopDevice } public getThemeService() { @@ -334,7 +327,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter } public get version(): string { - return (this.deviceInterface as WebOrDesktopDevice).appVersion + return (this.device as WebOrDesktopDevice).appVersion } async toggleGlobalSpellcheck() { diff --git a/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx b/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx index e6241bfcf..5e8079561 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/AdvancedOptions.tsx @@ -42,7 +42,7 @@ const AdvancedOptions: FunctionComponent = ({ if (!identifier) { if (privateUsername?.length > 0) { - application.alertService.alert('Unable to compute private username.').catch(console.error) + application.alerts.alert('Unable to compute private username.').catch(console.error) } return } diff --git a/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx b/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx index c9ba29e10..d905db190 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/GeneralAccountMenu.tsx @@ -51,7 +51,7 @@ const GeneralAccountMenu: FunctionComponent = ({ } }) .catch(() => { - application.alertService.alert(STRING_GENERIC_SYNC_ERROR).catch(console.error) + application.alerts.alert(STRING_GENERIC_SYNC_ERROR).catch(console.error) }) .finally(() => { setIsSyncingInProgress(false) diff --git a/packages/web/src/javascripts/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx b/packages/web/src/javascripts/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx index f65171bce..74e80b188 100644 --- a/packages/web/src/javascripts/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx +++ b/packages/web/src/javascripts/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu.tsx @@ -43,7 +43,7 @@ const WorkspaceSwitcherMenu: FunctionComponent = ({ }, [mainApplicationGroup]) const signoutAll = useCallback(async () => { - const confirmed = await viewControllerManager.application.alertService.confirm( + const confirmed = await viewControllerManager.application.alerts.confirm( 'Are you sure you want to sign out of all workspaces on this device?', undefined, 'Sign out all', diff --git a/packages/web/src/javascripts/Components/ChallengeModal/ChallengeModal.tsx b/packages/web/src/javascripts/Components/ChallengeModal/ChallengeModal.tsx index edb6f3c01..592cdba81 100644 --- a/packages/web/src/javascripts/Components/ChallengeModal/ChallengeModal.tsx +++ b/packages/web/src/javascripts/Components/ChallengeModal/ChallengeModal.tsx @@ -322,7 +322,7 @@ const ChallengeModal: FunctionComponent = ({