chore: designated survivor test suite (#2540)

* chore: designated survivor test suite

* add more tests

* fix: tests exclusivness

* fix removing account from shared vaults test
This commit is contained in:
Karol Sójko
2023-09-27 14:27:12 +02:00
committed by GitHub
parent 70cc15c6d9
commit 981417f952
17 changed files with 537 additions and 95 deletions

View File

@@ -36,7 +36,7 @@
}, },
"dependencies": { "dependencies": {
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/models": "workspace:*", "@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*", "@standardnotes/responses": "workspace:*",
"@standardnotes/utils": "workspace:*", "@standardnotes/utils": "workspace:*",

View File

@@ -35,7 +35,7 @@
}, },
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.9", "@electron/remote": "^2.0.9",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/electron-clear-data": "1.1.1", "@standardnotes/electron-clear-data": "1.1.1",
"@standardnotes/web": "workspace:*", "@standardnotes/web": "workspace:*",
"axios": "^1.1.3", "axios": "^1.1.3",

View File

@@ -29,7 +29,7 @@
}, },
"dependencies": { "dependencies": {
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/models": "workspace:*", "@standardnotes/models": "workspace:*",
"@standardnotes/responses": "workspace:*", "@standardnotes/responses": "workspace:*",
"@standardnotes/sncrypto-common": "workspace:*", "@standardnotes/sncrypto-common": "workspace:*",

View File

@@ -26,7 +26,7 @@
}, },
"dependencies": { "dependencies": {
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"reflect-metadata": "^0.1.13" "reflect-metadata": "^0.1.13"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -20,7 +20,7 @@
}, },
"dependencies": { "dependencies": {
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/features": "workspace:*", "@standardnotes/features": "workspace:*",
"@standardnotes/responses": "workspace:*", "@standardnotes/responses": "workspace:*",
"@standardnotes/sncrypto-common": "workspace:^", "@standardnotes/sncrypto-common": "workspace:^",

View File

@@ -18,7 +18,7 @@
"dependencies": { "dependencies": {
"@standardnotes/api": "workspace:^", "@standardnotes/api": "workspace:^",
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/encryption": "workspace:^", "@standardnotes/encryption": "workspace:^",
"@standardnotes/features": "workspace:^", "@standardnotes/features": "workspace:^",
"@standardnotes/files": "workspace:^", "@standardnotes/files": "workspace:^",

View File

@@ -96,7 +96,7 @@ export class SharedVaultService
await this.handleNotification(event.payload as NotificationServiceEventPayload) await this.handleNotification(event.payload as NotificationServiceEventPayload)
break break
case SyncEvent.ReceivedRemoteSharedVaults: case SyncEvent.ReceivedRemoteSharedVaults:
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged) void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged)
break break
} }
} }
@@ -113,7 +113,20 @@ export class SharedVaultService
break break
} }
case NotificationType.TYPES.UserRemovedFromSharedVault: { case NotificationType.TYPES.UserRemovedFromSharedVault: {
this.vaultUsers.invalidateVaultUsersCache(event.eventPayload.props.primaryIdentifier.value).catch(console.error) const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.primaryIdentifier.value,
})
if (!vaultOrError.isFailed()) {
const vault = vaultOrError.getValue()
this.vaultUsers
.invalidateVaultUsersCache(event.eventPayload.props.primaryIdentifier.value)
.catch(console.error)
await this._syncLocalVaultsWithRemoteSharedVaults.execute([vault])
void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged)
}
break break
} }
case NotificationType.TYPES.SharedVaultItemRemoved: { case NotificationType.TYPES.SharedVaultItemRemoved: {
@@ -124,14 +137,15 @@ export class SharedVaultService
break break
} }
case NotificationType.TYPES.SharedVaultFileRemoved: case NotificationType.TYPES.SharedVaultFileRemoved:
case NotificationType.TYPES.SharedVaultFileUploaded: { case NotificationType.TYPES.SharedVaultFileUploaded:
case NotificationType.TYPES.UserDesignatedAsSurvivor: {
const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({ const vaultOrError = this._getVault.execute<SharedVaultListingInterface>({
sharedVaultUuid: event.eventPayload.props.primaryIdentifier.value, sharedVaultUuid: event.eventPayload.props.primaryIdentifier.value,
}) })
if (!vaultOrError.isFailed()) { if (!vaultOrError.isFailed()) {
await this._syncLocalVaultsWithRemoteSharedVaults.execute([vaultOrError.getValue()]) await this._syncLocalVaultsWithRemoteSharedVaults.execute([vaultOrError.getValue()])
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged) void this.notifyEvent(SharedVaultServiceEvent.SharedVaultStatusChanged)
} }
break break

View File

@@ -1,59 +1,63 @@
export const BaseTests = [ export const BaseTests = {
'memory.test.js', enabled: true,
'protocol.test.js', exclusive: false,
'utils.test.js', files: [
'000.test.js', 'memory.test.js',
'001.test.js', 'protocol.test.js',
'002.test.js', 'utils.test.js',
'003.test.js', '000.test.js',
'004.test.js', '001.test.js',
'username.test.js', '002.test.js',
'app-group.test.js', '003.test.js',
'application.test.js', '004.test.js',
'payload.test.js', 'username.test.js',
'payload_encryption.test.js', 'app-group.test.js',
'item.test.js', 'application.test.js',
'item_manager.test.js', 'payload.test.js',
'features.test.js', 'payload_encryption.test.js',
'settings.test.js', 'item.test.js',
'mfa_service.test.js', 'item_manager.test.js',
'mutator.test.js', 'features.test.js',
'mutator_service.test.js', 'settings.test.js',
'payload_manager.test.js', 'mfa_service.test.js',
'collections.test.js', 'mutator.test.js',
'note_display_criteria.test.js', 'mutator_service.test.js',
'keys.test.js', 'payload_manager.test.js',
'key_params.test.js', 'collections.test.js',
'key_recovery_service.test.js', 'note_display_criteria.test.js',
'backups.test.js', 'keys.test.js',
'upgrading.test.js', 'key_params.test.js',
'model_tests/importing.test.js', 'key_recovery_service.test.js',
'model_tests/appmodels.test.js', 'backups.test.js',
'model_tests/items.test.js', 'upgrading.test.js',
'model_tests/mapping.test.js', 'model_tests/importing.test.js',
'model_tests/notes_smart_tags.test.js', 'model_tests/appmodels.test.js',
'model_tests/notes_tags.test.js', 'model_tests/items.test.js',
'model_tests/notes_tags_folders.test.js', 'model_tests/mapping.test.js',
'model_tests/performance.test.js', 'model_tests/notes_smart_tags.test.js',
'sync_tests/offline.test.js', 'model_tests/notes_tags.test.js',
'sync_tests/notes_tags.test.js', 'model_tests/notes_tags_folders.test.js',
'sync_tests/online.test.js', 'model_tests/performance.test.js',
'sync_tests/conflicting.test.js', 'sync_tests/offline.test.js',
'sync_tests/integrity.test.js', 'sync_tests/notes_tags.test.js',
'auth-fringe-cases.test.js', 'sync_tests/online.test.js',
'auth.test.js', 'sync_tests/conflicting.test.js',
'device_auth.test.js', 'sync_tests/integrity.test.js',
'storage.test.js', 'auth-fringe-cases.test.js',
'protection.test.js', 'auth.test.js',
'singletons.test.js', 'device_auth.test.js',
'migrations/migration.test.js', 'storage.test.js',
'migrations/tags-to-folders.test.js', 'protection.test.js',
'history.test.js', 'singletons.test.js',
'actions.test.js', 'migrations/migration.test.js',
'preferences.test.js', 'migrations/tags-to-folders.test.js',
'files.test.js', 'history.test.js',
'session.test.js', 'actions.test.js',
'session-invalidation.test.js', 'preferences.test.js',
'subscriptions.test.js', 'files.test.js',
'recovery.test.js', 'session.test.js',
] 'session-invalidation.test.js',
'subscriptions.test.js',
'recovery.test.js',
]
}

View File

@@ -23,5 +23,6 @@ export const VaultTests = {
'vaults/files.test.js', 'vaults/files.test.js',
'vaults/limits.test.js', 'vaults/limits.test.js',
'vaults/quota.test.js', 'vaults/quota.test.js',
'vaults/surviving.test.js',
], ],
} }

View File

@@ -41,7 +41,7 @@ export const acceptAllInvites = async (context) => {
} }
} }
const inviteContext = async (context, contactContext, sharedVault, contact, permission) => { export const inviteContext = async (context, contactContext, sharedVault, contact, permission) => {
contactContext.lockSyncing() contactContext.lockSyncing()
const inviteOrError = await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permission) const inviteOrError = await context.vaultInvites.inviteContactToSharedVault(sharedVault, contact, permission)
@@ -207,3 +207,12 @@ export const moveItemToVault = async (context, sharedVault, item) => {
return result.getValue() return result.getValue()
} }
export const designateSharedVaultSurvior = async (context, sharedVault, survivorUuid) => {
const result = await context.vaultUsers.designateSurvivor(sharedVault, survivorUuid)
if (result.isFailed()) {
throw new Error(result.getError())
}
return result.getValue()
}

View File

@@ -38,6 +38,14 @@
<script type="module"> <script type="module">
import MainRegistry from './TestRegistry/MainRegistry.js' import MainRegistry from './TestRegistry/MainRegistry.js'
const urlSearchParams = new URLSearchParams(window.location.search);
const testSuite = urlSearchParams.get('suite');
if (testSuite === 'vaults') {
MainRegistry.VaultTests.exclusive = true;
} else if (testSuite === 'base') {
MainRegistry.BaseTests.exclusive = true;
}
const loadTest = (fileName) => { const loadTest = (fileName) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const script = document.createElement('script'); const script = document.createElement('script');
@@ -56,18 +64,17 @@
} }
} }
if (MainRegistry.VaultTests.enabled) { InternalFeatureService.get().enableFeature(InternalFeature.Vaults);
InternalFeatureService.get().enableFeature(InternalFeature.Vaults);
if (MainRegistry.VaultTests.exclusive) { if (MainRegistry.VaultTests.exclusive) {
await loadTests(MainRegistry.VaultTests.files); await loadTests(MainRegistry.VaultTests.files);
} else { } else if (MainRegistry.BaseTests.exclusive) {
await loadTests([ await loadTests(MainRegistry.BaseTests.files);
...MainRegistry.BaseTests,
...MainRegistry.VaultTests.files
]);
}
} else { } else {
await loadTests(MainRegistry.BaseTests); await loadTests([
...MainRegistry.BaseTests.files,
...MainRegistry.VaultTests.files
]);
} }
mocha.run() mocha.run()

View File

@@ -1,4 +1,5 @@
import * as Factory from '../lib/factory.js' import * as Factory from '../lib/factory.js'
import * as Utils from '../lib/Utils.js'
import * as Collaboration from '../lib/Collaboration.js' import * as Collaboration from '../lib/Collaboration.js'
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
@@ -167,4 +168,47 @@ describe('shared vault deletion', function () {
await deinitContactContext() await deinitContactContext()
}) })
it('should remove a user from all shared vaults upon account removal', async () => {
const secondContext = await Factory.createVaultsContextWithRealCrypto()
await secondContext.launch()
await secondContext.register()
const thirdContext = await Factory.createVaultsContextWithRealCrypto()
await thirdContext.launch()
await thirdContext.register()
const firstVault = await Collaboration.createSharedVault(context)
const secondVault = await Collaboration.createSharedVault(thirdContext)
const firstToSecondContact = await Collaboration.createTrustedContactForUserOfContext(context, secondContext)
await Collaboration.createTrustedContactForUserOfContext(secondContext, context)
const thirdToSecondContact = await Collaboration.createTrustedContactForUserOfContext(thirdContext, secondContext)
await Collaboration.createTrustedContactForUserOfContext(thirdContext, context)
await Collaboration.inviteContext(context, secondContext, firstVault, firstToSecondContact, SharedVaultUserPermission.PERMISSIONS.Write)
await Collaboration.inviteContext(thirdContext, secondContext, secondVault, thirdToSecondContact, SharedVaultUserPermission.PERMISSIONS.Write)
const promise = secondContext.awaitNextSyncSharedVaultFromScratchEvent()
await Collaboration.acceptAllInvites(secondContext)
await Utils.awaitPromiseOrThrow(promise, 2.0, 'Waiting for vault to sync')
Factory.handlePasswordChallenges(secondContext.application, secondContext.password)
await secondContext.application.user.deleteAccount()
await context.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const sharedVaultUsersInFirstVault = await context.vaultUsers.getSharedVaultUsersFromServer(firstVault)
expect(sharedVaultUsersInFirstVault.length).to.equal(1)
const sharedVaultUsersInSecondVault = await thirdContext.vaultUsers.getSharedVaultUsersFromServer(secondVault)
expect(sharedVaultUsersInSecondVault.length).to.equal(1)
await secondContext.deinit()
await thirdContext.deinit()
})
}) })

View File

@@ -0,0 +1,363 @@
import * as Factory from '../lib/factory.js'
import * as Files from '../lib/Files.js'
import * as Collaboration from '../lib/Collaboration.js'
chai.use(chaiAsPromised)
const expect = chai.expect
describe('designated survival', function () {
this.timeout(Factory.TwentySecondTimeout)
let context
let secondContext
let thirdContext
beforeEach(async function () {
localStorage.clear()
context = await Factory.createVaultsContextWithRealCrypto()
await context.launch()
await context.register()
})
afterEach(async function () {
await context.deinit()
localStorage.clear()
sinon.restore()
context = undefined
if (secondContext) {
await secondContext.deinit()
secondContext = undefined
}
if (thirdContext) {
await thirdContext.deinit()
thirdContext = undefined
}
})
it('should indicate that a vault has a designated survivor', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
let vault = context.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(vault.sharing.designatedSurvivor).to.be.null
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
await context.syncAndAwaitNotificationsProcessing()
vault = context.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(vault.sharing.designatedSurvivor).to.equal(contactContext.userUuid)
})
describe('owner of a shared vault with a designated survivor removing the vault', () => {
it('should not remove all users from the vault upon shared vault removal', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
await context.sharedVaults.deleteSharedVault(sharedVault)
await context.syncAndAwaitNotificationsProcessing()
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const sharedVaultUsers = await secondContext.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
expect(sharedVaultUsers.length).to.equal(2)
expect(context.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
expect(context.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
expect(context.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
expect(secondContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.not.be.undefined
expect(secondContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.not.be.undefined
expect(secondContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.not.be.empty
expect(thirdContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.not.be.undefined
expect(thirdContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.not.be.undefined
expect(thirdContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.not.be.empty
})
it('should transition items of the owner in the vault to the designated survivor', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
const note = await context.createSyncedNote('foo', 'bar')
await Collaboration.moveItemToVault(context, sharedVault, note)
await context.sharedVaults.deleteSharedVault(sharedVault)
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const sharedVaultUsers = await secondContext.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
expect(sharedVaultUsers.length).to.equal(2)
const contactNote = secondContext.items.findItem(note.uuid)
expect(contactNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
expect(contactNote.user_uuid).to.equal(secondContext.userUuid)
const thirdPartyNote = thirdContext.items.findItem(note.uuid)
expect(thirdPartyNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
expect(thirdPartyNote.user_uuid).to.equal(secondContext.userUuid)
})
it('should still allow to download files of the owner in the vault', async () => {
await context.activatePaidSubscriptionForUser()
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
const response = await fetch('/mocha/assets/small_file.md')
const buffer = new Uint8Array(await response.arrayBuffer())
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
await secondContext.syncAndAwaitNotificationsProcessing()
const sharedFileBefore = secondContext.items.findItem(uploadedFile.uuid)
expect(sharedFileBefore).to.not.be.undefined
expect(sharedFileBefore.remoteIdentifier).to.equal(uploadedFile.remoteIdentifier)
await context.sharedVaults.deleteSharedVault(sharedVault)
await secondContext.syncAndAwaitNotificationsProcessing()
const sharedFileAfter = secondContext.items.findItem(uploadedFile.uuid)
expect(sharedFileAfter).to.not.be.undefined
expect(sharedFileAfter.remoteIdentifier).to.equal(uploadedFile.remoteIdentifier)
const downloadedBytes = await Files.downloadFile(secondContext.files, sharedFileAfter)
expect(downloadedBytes).to.eql(buffer)
})
it('should transition vault ownership to the designated survivor', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
await context.sharedVaults.deleteSharedVault(sharedVault)
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const contactVault = secondContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(contactVault.sharing.ownerUserUuid).to.not.equal(context.userUuid)
expect(contactVault.sharing.ownerUserUuid).to.equal(secondContext.userUuid)
const thirdPartyVault = thirdContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(thirdPartyVault.sharing.ownerUserUuid).to.not.equal(context.userUuid)
expect(thirdPartyVault.sharing.ownerUserUuid).to.equal(secondContext.userUuid)
})
})
describe('owner of a shared vault with a designated survivor deleting their account', () => {
it('should not remove all users from the vault upon account removal', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
Factory.handlePasswordChallenges(context.application, context.password)
await context.application.user.deleteAccount()
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const sharedVaultUsers = await secondContext.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
expect(sharedVaultUsers.length).to.equal(2)
expect(secondContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.not.be.undefined
expect(secondContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.not.be.undefined
expect(secondContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.not.be.empty
expect(thirdContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.not.be.undefined
expect(thirdContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.not.be.undefined
expect(thirdContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.not.be.empty
})
it('should transition items of the owner in the vault to the designated survivor', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
const note = await context.createSyncedNote('foo', 'bar')
await Collaboration.moveItemToVault(context, sharedVault, note)
Factory.handlePasswordChallenges(context.application, context.password)
await context.application.user.deleteAccount()
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const sharedVaultUsers = await secondContext.vaultUsers.getSharedVaultUsersFromServer(sharedVault)
expect(sharedVaultUsers.length).to.equal(2)
const contactNote = secondContext.items.findItem(note.uuid)
expect(contactNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
expect(contactNote.user_uuid).to.equal(secondContext.userUuid)
const thirdPartyNote = thirdContext.items.findItem(note.uuid)
expect(thirdPartyNote.key_system_identifier).to.equal(sharedVault.systemIdentifier)
expect(thirdPartyNote.user_uuid).to.equal(secondContext.userUuid)
})
it('should still allow to download files of the owner in the vault', async () => {
await context.activatePaidSubscriptionForUser()
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
const response = await fetch('/mocha/assets/small_file.md')
const buffer = new Uint8Array(await response.arrayBuffer())
const uploadedFile = await Files.uploadFile(context.files, buffer, 'my-file', 'md', 1000, sharedVault)
await secondContext.syncAndAwaitNotificationsProcessing()
const sharedFileBefore = secondContext.items.findItem(uploadedFile.uuid)
expect(sharedFileBefore).to.not.be.undefined
expect(sharedFileBefore.remoteIdentifier).to.equal(uploadedFile.remoteIdentifier)
Factory.handlePasswordChallenges(context.application, context.password)
await context.application.user.deleteAccount()
await secondContext.syncAndAwaitNotificationsProcessing()
const sharedFileAfter = secondContext.items.findItem(uploadedFile.uuid)
expect(sharedFileAfter).to.not.be.undefined
expect(sharedFileAfter.remoteIdentifier).to.equal(uploadedFile.remoteIdentifier)
const downloadedBytes = await Files.downloadFile(secondContext.files, sharedFileAfter)
expect(downloadedBytes).to.eql(buffer)
})
it('should transition vault ownership to the designated survivor', async () => {
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
await Collaboration.designateSharedVaultSurvior(context, sharedVault, contactContext.userUuid)
const { thirdPartyContext } = await Collaboration.inviteNewPartyToSharedVault(
context,
sharedVault,
)
thirdContext = thirdPartyContext
await Collaboration.acceptAllInvites(thirdContext)
Factory.handlePasswordChallenges(context.application, context.password)
await context.application.user.deleteAccount()
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
const contactVault = secondContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(contactVault.sharing.ownerUserUuid).to.equal(secondContext.userUuid)
const thirdPartyVault = thirdContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })
expect(thirdPartyVault.sharing.ownerUserUuid).to.equal(secondContext.userUuid)
})
})
describe('owner of a shared vault without a designated survivor deleting their account', () => {
it('should remove all users from all shared vaults upon account removal', async () => {
await context.activatePaidSubscriptionForUser()
const { sharedVault, contactContext } =
await Collaboration.createSharedVaultWithAcceptedInvite(context)
secondContext = contactContext
const result = await Collaboration.createSharedVaultWithAcceptedInvite(context)
thirdContext = result.contactContext
const secondSharedVault = result.sharedVault
Factory.handlePasswordChallenges(context.application, context.password)
await context.application.user.deleteAccount()
await secondContext.syncAndAwaitNotificationsProcessing()
await thirdContext.syncAndAwaitNotificationsProcessing()
expect(secondContext.vaults.getVault({ keySystemIdentifier: sharedVault.systemIdentifier })).to.be.undefined
expect(secondContext.keys.getPrimaryKeySystemRootKey(sharedVault.systemIdentifier)).to.be.undefined
expect(secondContext.keys.getKeySystemItemsKeys(sharedVault.systemIdentifier)).to.be.empty
expect(thirdContext.vaults.getVault({ keySystemIdentifier: secondSharedVault.systemIdentifier })).to.be.undefined
expect(thirdContext.keys.getPrimaryKeySystemRootKey(secondSharedVault.systemIdentifier)).to.be.undefined
expect(thirdContext.keys.getKeySystemItemsKeys(secondSharedVault.systemIdentifier)).to.be.empty
})
})
})

View File

@@ -37,7 +37,7 @@
"@babel/preset-env": "*", "@babel/preset-env": "*",
"@standardnotes/api": "workspace:*", "@standardnotes/api": "workspace:*",
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/domain-events": "^2.122.0", "@standardnotes/domain-events": "^2.122.0",
"@standardnotes/encryption": "workspace:*", "@standardnotes/encryption": "workspace:*",
"@standardnotes/features": "workspace:*", "@standardnotes/features": "workspace:*",

View File

@@ -16,7 +16,7 @@
}, },
"dependencies": { "dependencies": {
"@standardnotes/common": "^1.50.0", "@standardnotes/common": "^1.50.0",
"@standardnotes/domain-core": "^1.33.1", "@standardnotes/domain-core": "^1.34.1",
"@standardnotes/features": "workspace:^", "@standardnotes/features": "workspace:^",
"@standardnotes/filepicker": "workspace:^", "@standardnotes/filepicker": "workspace:^",
"@standardnotes/models": "workspace:^", "@standardnotes/models": "workspace:^",

View File

@@ -4138,7 +4138,7 @@ __metadata:
resolution: "@standardnotes/api@workspace:packages/api" resolution: "@standardnotes/api@workspace:packages/api"
dependencies: dependencies:
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/models": "workspace:*" "@standardnotes/models": "workspace:*"
"@standardnotes/responses": "workspace:*" "@standardnotes/responses": "workspace:*"
"@standardnotes/utils": "workspace:*" "@standardnotes/utils": "workspace:*"
@@ -4288,7 +4288,7 @@ __metadata:
"@babel/core": "*" "@babel/core": "*"
"@babel/preset-env": "*" "@babel/preset-env": "*"
"@electron/remote": ^2.0.9 "@electron/remote": ^2.0.9
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/electron-clear-data": 1.1.1 "@standardnotes/electron-clear-data": 1.1.1
"@standardnotes/web": "workspace:*" "@standardnotes/web": "workspace:*"
"@types/fs-extra": ^11.0.1 "@types/fs-extra": ^11.0.1
@@ -4340,12 +4340,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@standardnotes/domain-core@npm:^1.33.1": "@standardnotes/domain-core@npm:^1.34.1":
version: 1.33.1 version: 1.34.1
resolution: "@standardnotes/domain-core@npm:1.33.1" resolution: "@standardnotes/domain-core@npm:1.34.1"
dependencies: dependencies:
uuid: ^9.0.0 uuid: ^9.0.0
checksum: 4caba6cd6fa667158aa326e43762bc466b726dfbd89e04cfc6fd6b89dce573b1be231350470fc7f1d027868bfd096096de7733256d35a7be699d693e84d29f2a checksum: 29fa0ee7868b08a5255ce737535aa67b9fe8f0562f7d5a308f4be4a63d46821f34b4ea1d4241064e3025968ae45e09eba3dcbd1ff8d510618114541c42a8e6ae
languageName: node languageName: node
linkType: hard linkType: hard
@@ -4392,7 +4392,7 @@ __metadata:
dependencies: dependencies:
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/config": 2.4.3 "@standardnotes/config": 2.4.3
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/models": "workspace:*" "@standardnotes/models": "workspace:*"
"@standardnotes/responses": "workspace:*" "@standardnotes/responses": "workspace:*"
"@standardnotes/sncrypto-common": "workspace:*" "@standardnotes/sncrypto-common": "workspace:*"
@@ -4427,7 +4427,7 @@ __metadata:
resolution: "@standardnotes/features@workspace:packages/features" resolution: "@standardnotes/features@workspace:packages/features"
dependencies: dependencies:
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@types/jest": ^29.2.3 "@types/jest": ^29.2.3
"@typescript-eslint/eslint-plugin": "*" "@typescript-eslint/eslint-plugin": "*"
eslint: "*" eslint: "*"
@@ -4633,7 +4633,7 @@ __metadata:
resolution: "@standardnotes/models@workspace:packages/models" resolution: "@standardnotes/models@workspace:packages/models"
dependencies: dependencies:
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/features": "workspace:*" "@standardnotes/features": "workspace:*"
"@standardnotes/responses": "workspace:*" "@standardnotes/responses": "workspace:*"
"@standardnotes/sncrypto-common": "workspace:^" "@standardnotes/sncrypto-common": "workspace:^"
@@ -4729,7 +4729,7 @@ __metadata:
dependencies: dependencies:
"@standardnotes/api": "workspace:^" "@standardnotes/api": "workspace:^"
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/encryption": "workspace:^" "@standardnotes/encryption": "workspace:^"
"@standardnotes/features": "workspace:^" "@standardnotes/features": "workspace:^"
"@standardnotes/files": "workspace:^" "@standardnotes/files": "workspace:^"
@@ -4830,7 +4830,7 @@ __metadata:
"@babel/preset-env": "*" "@babel/preset-env": "*"
"@standardnotes/api": "workspace:*" "@standardnotes/api": "workspace:*"
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/domain-events": ^2.122.0 "@standardnotes/domain-events": ^2.122.0
"@standardnotes/encryption": "workspace:*" "@standardnotes/encryption": "workspace:*"
"@standardnotes/features": "workspace:*" "@standardnotes/features": "workspace:*"
@@ -4955,7 +4955,7 @@ __metadata:
resolution: "@standardnotes/ui-services@workspace:packages/ui-services" resolution: "@standardnotes/ui-services@workspace:packages/ui-services"
dependencies: dependencies:
"@standardnotes/common": ^1.50.0 "@standardnotes/common": ^1.50.0
"@standardnotes/domain-core": ^1.33.1 "@standardnotes/domain-core": ^1.34.1
"@standardnotes/features": "workspace:^" "@standardnotes/features": "workspace:^"
"@standardnotes/filepicker": "workspace:^" "@standardnotes/filepicker": "workspace:^"
"@standardnotes/models": "workspace:^" "@standardnotes/models": "workspace:^"