Skip to content

Commit

Permalink
internal: change password preprocessing step (#2347)
Browse files Browse the repository at this point in the history
  • Loading branch information
moughxyz authored and amanharwara committed Jul 12, 2023
1 parent 425144c commit bdc4107
Show file tree
Hide file tree
Showing 39 changed files with 647 additions and 332 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ export enum AsymmetricMessagePayloadType {
ContactShare = 'contact-share',
SharedVaultRootKeyChanged = 'shared-vault-root-key-changed',
SenderKeypairChanged = 'sender-keypair-changed',
SharedVaultInvite = 'shared-vault-invite',
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.
*/
SharedVaultInvite = 'shared-vault-invite',
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/AsymmetricMessageServiceInterface'
import { SyncOptions } from './../Sync/SyncOptions'
import { ImportDataReturnType } from './../Mutator/ImportDataUseCase'
import { ChallengeServiceInterface } from './../Challenge/ChallengeServiceInterface'
Expand Down Expand Up @@ -102,6 +103,8 @@ export interface ApplicationInterface {
get vaults(): VaultServiceInterface
get challenges(): ChallengeServiceInterface
get alerts(): AlertService
get asymmetric(): AsymmetricMessageServiceInterface

readonly identifier: ApplicationIdentifier
readonly platform: Platform
deviceInterface: DeviceInterface
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
import { ContactServiceInterface } from './../Contacts/ContactServiceInterface'
import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses'
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent'
import { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
import { InternalEventHandlerInterface } from '../Internal/InternalEventHandlerInterface'
Expand All @@ -27,8 +27,12 @@ import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessa
import { GetOutboundAsymmetricMessages } from './UseCase/GetOutboundAsymmetricMessages'
import { GetInboundAsymmetricMessages } from './UseCase/GetInboundAsymmetricMessages'
import { GetVaultUseCase } from '../Vaults/UseCase/GetVault'
import { AsymmetricMessageServiceInterface } from './AsymmetricMessageServiceInterface'

export class AsymmetricMessageService extends AbstractService implements InternalEventHandlerInterface {
export class AsymmetricMessageService
extends AbstractService
implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface
{
private messageServer: AsymmetricMessageServer

constructor(
Expand Down Expand Up @@ -69,7 +73,16 @@ export class AsymmetricMessageService extends AbstractService implements Interna
return usecase.execute()
}

async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
public async downloadAndProcessInboundMessages(): Promise<void> {
const messages = await this.getInboundMessages()
if (isClientDisplayableError(messages)) {
return
}

await this.handleRemoteReceivedAsymmetricMessages(messages)
}

private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
if (!data.oldKeyPair || !data.oldSigningKeyPair) {
return
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AsymmetricMessageServerHash, ClientDisplayableError } from '@standardnotes/responses'

export interface AsymmetricMessageServiceInterface {
getOutboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError>
getInboundMessages(): Promise<AsymmetricMessageServerHash[] | ClientDisplayableError>
downloadAndProcessInboundMessages(): Promise<void>
}
2 changes: 1 addition & 1 deletion packages/services/src/Domain/Contacts/ContactService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export class ContactService
}

findTrustedContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined {
return this.findTrustedContact(invite.user_uuid)
return this.findTrustedContact(invite.sender_uuid)
}

getCollaborationIDForTrustedContact(contact: TrustedContactInterface): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { PublicKeySet } from '@standardnotes/encryption'

export class SelfContactManager {
public selfContact?: TrustedContactInterface
private shouldReloadSelfContact = true

private isReloadingSelfContact = false
private eventDisposers: (() => void)[] = []

Expand All @@ -32,28 +32,34 @@ export class SelfContactManager {
private session: SessionsClientInterface,
private singletons: SingletonManagerInterface,
) {
this.eventDisposers.push(
items.addObserver(ContentType.TrustedContact, () => {
this.shouldReloadSelfContact = true
}),
)

this.eventDisposers.push(
sync.addEventObserver((event) => {
if (event === SyncEvent.SyncCompletedWithAllItemsUploaded || event === SyncEvent.LocalDataIncrementalLoad) {
void this.reloadSelfContact()
if (event === SyncEvent.LocalDataIncrementalLoad) {
this.loadSelfContactFromDatabase()
}

if (event === SyncEvent.SyncCompletedWithAllItemsUploaded) {
void this.reloadSelfContactAndCreateIfNecessary()
}
}),
)
}

public async handleApplicationStage(stage: ApplicationStage): Promise<void> {
if (stage === ApplicationStage.LoadedDatabase_12) {
this.selfContact = this.singletons.findSingleton<TrustedContactInterface>(
ContentType.UserPrefs,
TrustedContact.singletonPredicate,
)
this.loadSelfContactFromDatabase()
}
}

private loadSelfContactFromDatabase(): void {
if (this.selfContact) {
return
}

this.selfContact = this.singletons.findSingleton<TrustedContactInterface>(
ContentType.TrustedContact,
TrustedContact.singletonPredicate,
)
}

public async updateWithNewPublicKeySet(publicKeySet: PublicKeySet) {
Expand All @@ -74,12 +80,16 @@ export class SelfContactManager {
})
}

private async reloadSelfContact() {
private async reloadSelfContactAndCreateIfNecessary() {
if (!InternalFeatureService.get().isFeatureEnabled(InternalFeature.Vaults)) {
return
}

if (!this.shouldReloadSelfContact || this.isReloadingSelfContact) {
if (this.selfContact) {
return
}

if (this.isReloadingSelfContact) {
return
}

Expand All @@ -105,17 +115,13 @@ export class SelfContactManager {
}),
}

try {
this.selfContact = await this.singletons.findOrCreateSingleton<TrustedContactContent, TrustedContact>(
TrustedContact.singletonPredicate,
ContentType.TrustedContact,
FillItemContent<TrustedContactContent>(content),
)
this.selfContact = await this.singletons.findOrCreateSingleton<TrustedContactContent, TrustedContact>(
TrustedContact.singletonPredicate,
ContentType.TrustedContact,
FillItemContent<TrustedContactContent>(content),
)

this.shouldReloadSelfContact = false
} finally {
this.isReloadingSelfContact = false
}
this.isReloadingSelfContact = false
}

deinit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export class EncryptionService
const usecase = new CreateNewItemsKeyWithRollbackUseCase(
this.mutator,
this.items,
this.storage,
this.operators,
this.rootKeyManager,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { StorageServiceInterface } from './../../../Storage/StorageServiceInterface'
import { ItemsKeyMutator, OperatorManager, findDefaultItemsKey } 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(
Expand All @@ -12,9 +14,12 @@ export class CreateNewItemsKeyWithRollbackUseCase {
this.rootKeyManager,
)

private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage)

constructor(
private mutator: MutatorClientInterface,
private items: ItemManagerInterface,
private storage: StorageServiceInterface,
private operatorManager: OperatorManager,
private rootKeyManager: RootKeyManager,
) {}
Expand All @@ -24,7 +29,7 @@ export class CreateNewItemsKeyWithRollbackUseCase {
const newDefaultItemsKey = await this.createDefaultItemsKeyUseCase.execute()

const rollback = async () => {
await this.mutator.setItemToBeDeleted(newDefaultItemsKey)
await this.removeItemsLocallyUsecase.execute([newDefaultItemsKey])

if (currentDefaultItemsKey) {
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
Expand Down
29 changes: 5 additions & 24 deletions packages/services/src/Domain/SharedVaults/SharedVaultService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ export class SharedVaultService
)

this.eventDisposers.push(
items.addObserver<TrustedContactInterface>(ContentType.TrustedContact, ({ changed, inserted, source }) => {
items.addObserver<TrustedContactInterface>(ContentType.TrustedContact, async ({ changed, inserted, source }) => {
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()

if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
void this.handleCreationOfNewTrustedContacts(inserted)
}
Expand Down Expand Up @@ -250,8 +252,6 @@ export class SharedVaultService
}

private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()

for (const contact of contacts) {
await this.shareContactWithUserAdministeredSharedVaults(contact)
}
Expand Down Expand Up @@ -328,28 +328,9 @@ export class SharedVaultService
}

private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
const cachedInvites = this.getCachedPendingInviteRecords()

for (const record of cachedInvites) {
if (record.trusted) {
continue
}
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)

const trustedMessageUseCase = new GetAsymmetricMessageTrustedPayload<AsymmetricMessageSharedVaultInvite>(
this.encryption,
this.contacts,
)

const trustedMessage = trustedMessageUseCase.execute({
message: record.invite,
privateKey: this.encryption.getKeyPair().privateKey,
})

if (trustedMessage) {
record.message = trustedMessage
record.trusted = true
}
}
await this.processInboundInvites(cachedInvites)
}

private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { StorageServiceInterface } from '../../Storage/StorageServiceInterface'
import { EncryptionProviderInterface } from '@standardnotes/encryption'
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils'
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
import { RemoveItemsLocallyUseCase } from '../../UseCase/RemoveItemsLocally'

export class DeleteExternalSharedVaultUseCase {
private removeItemsLocallyUsecase = new RemoveItemsLocallyUseCase(this.items, this.storage)

constructor(
private items: ItemManagerInterface,
private mutator: MutatorClientInterface,
Expand All @@ -28,15 +30,13 @@ export class DeleteExternalSharedVaultUseCase {
* The data will be removed locally without syncing the items
*/
private async deleteDataSharedByVaultUsers(vault: VaultListingInterface): Promise<void> {
const vaultItems = this.items
.allTrackedItems()
.filter((item) => item.key_system_identifier === vault.systemIdentifier)
this.items.removeItemsLocally(vaultItems as AnyItemInterface[])
const vaultItems = <AnyItemInterface[]>(
this.items.allTrackedItems().filter((item) => item.key_system_identifier === vault.systemIdentifier)
)

const itemsKeys = this.encryption.keys.getKeySystemItemsKeys(vault.systemIdentifier)
this.items.removeItemsLocally(itemsKeys)

await this.storage.deletePayloadsWithUuids([...Uuids(vaultItems), ...Uuids(itemsKeys)])
await this.removeItemsLocallyUsecase.execute([...vaultItems, ...itemsKeys])
}

private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> {
Expand Down
14 changes: 14 additions & 0 deletions packages/services/src/Domain/UseCase/RemoveItemsLocally.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { StorageServiceInterface } from '../Storage/StorageServiceInterface'
import { ItemManagerInterface } from '../Item/ItemManagerInterface'
import { AnyItemInterface } from '@standardnotes/models'
import { Uuids } from '@standardnotes/utils'

export class RemoveItemsLocallyUseCase {
constructor(private readonly items: ItemManagerInterface, private readonly storage: StorageServiceInterface) {}

async execute(items: AnyItemInterface[]): Promise<void> {
this.items.removeItemsLocally(items)

await this.storage.deletePayloadsWithUuids(Uuids(items))
}
}
1 change: 0 additions & 1 deletion packages/services/src/Domain/User/UserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,6 @@ export class UserService

this.lockSyncing()

/** Now, change the credentials on the server. Roll back on failure */
const { response } = await this.sessionManager.changeCredentials({
currentServerPassword: currentRootKey.serverPassword as string,
newRootKey: newRootKey,
Expand Down
1 change: 1 addition & 0 deletions packages/services/src/Domain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './Application/DeinitMode'
export * from './Application/DeinitSource'

export * from './AsymmetricMessage/AsymmetricMessageService'
export * from './AsymmetricMessage/AsymmetricMessageServiceInterface'

export * from './Auth/AuthClientInterface'
export * from './Auth/AuthManager'
Expand Down
4 changes: 4 additions & 0 deletions packages/snjs/lib/Application/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.challengeService
}

public get asymmetric(): ExternalServices.AsymmetricMessageServiceInterface {
return this.asymmetricMessageService
}

get homeServer(): ExternalServices.HomeServerServiceInterface | undefined {
return this.homeServerService
}
Expand Down
5 changes: 5 additions & 0 deletions packages/snjs/lib/Services/KeyRecovery/KeyRecoveryService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
PayloadEmitSource,
EncryptedItemInterface,
getIncrementedDirtyIndex,
ContentTypeUsesRootKeyEncryption,
} from '@standardnotes/models'
import { SNSyncService } from '../Sync/SyncService'
import { DiskStorageService } from '../Storage/DiskStorageService'
Expand Down Expand Up @@ -187,6 +188,10 @@ export class SNKeyRecoveryService extends AbstractService<KeyRecoveryEvent, Decr
}

public canAttemptDecryptionOfItem(item: EncryptedItemInterface): ClientDisplayableError | true {
if (ContentTypeUsesRootKeyEncryption(item.content_type)) {
return true
}

const keyId = item.payload.items_key_id

if (!keyId) {
Expand Down

0 comments on commit bdc4107

Please sign in to comment.