mirror of
https://github.com/standardnotes/app
synced 2026-09-13 09:46:02 -04:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86fc4c684d | ||
|
|
ef2d1b9228 | ||
|
|
868a84f587 | ||
|
|
7c463b36af | ||
|
|
908cbaf895 | ||
|
|
51cb14c010 | ||
|
|
47a36c4cbd | ||
|
|
7222ca7fd0 | ||
|
|
6ad5d028af | ||
|
|
596e041c42 | ||
|
|
80436cd0b9 | ||
|
|
40c0a08e74 | ||
|
|
560e91bce9 | ||
|
|
2a3db43df5 | ||
|
|
2e59aa5a07 | ||
|
|
98c0228139 | ||
|
|
d6bcc808d5 | ||
|
|
e18978d4ba | ||
|
|
ac4231d8eb | ||
|
|
3281ac9d37 | ||
|
|
f2d089ab24 | ||
|
|
a91ab4abbf | ||
|
|
0af41cbcad | ||
|
|
1adb21a06e | ||
|
|
412b192b85 | ||
|
|
8ad9b8ae2a | ||
|
|
c52d8e7309 | ||
|
|
406abb4929 | ||
|
|
814a156e1b |
@@ -6,6 +6,3 @@ DEFAULT_SYNC_SERVER=https://api.standardnotes.com
|
||||
DASHBOARD_URL=http://standardnotes.com/dashboard
|
||||
PLANS_URL=https://standardnotes.com/plans
|
||||
PURCHASE_URL=https://standardnotes.com/purchase
|
||||
|
||||
# Used by Rails internals and not Standard Notes related
|
||||
SECRET_KEY_BASE=<enter a secret string here>
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.26.31](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
## [1.26.30](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/api
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/api",
|
||||
"version": "1.26.30",
|
||||
"version": "1.26.31",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.1.115](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.114](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.113](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.112](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.111](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.110](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.109](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.108](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.107](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
## [1.1.106](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/clipper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/clipper",
|
||||
"description": "Web clipper browser extension for Standard Notes",
|
||||
"version": "1.1.106",
|
||||
"version": "1.1.115",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
|
||||
|
||||
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.108.46](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "chore: upgrade desktop deps" ([ef2d1b9](https://github.com/standardnotes/app/commit/ef2d1b9228c90a9cb9cf88535526a312bb28fca4))
|
||||
|
||||
## [3.108.45](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.44](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.43](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.42](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.41](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.40](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.39](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.38](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
## [3.108.37](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/desktop
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@standardnotes/desktop",
|
||||
"main": "./app/dist/index.js",
|
||||
"version": "3.108.37",
|
||||
"version": "3.108.46",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.21.54](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
## [1.21.53](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/encryption
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/encryption",
|
||||
"version": "1.21.53",
|
||||
"version": "1.21.54",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ import { V004Algorithm } from '../../../../Algorithm'
|
||||
import {
|
||||
KeySystemRootKeyInterface,
|
||||
KeySystemRootKeyParamsInterface,
|
||||
KeySystemRootKeyPasswordType,
|
||||
KeySystemPasswordType,
|
||||
} from '@standardnotes/models'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey'
|
||||
@@ -20,7 +20,7 @@ export class CreateRandomKeySystemRootKey {
|
||||
|
||||
const keyParams: KeySystemRootKeyParamsInterface = {
|
||||
systemIdentifier: dto.systemIdentifier,
|
||||
passwordType: KeySystemRootKeyPasswordType.Randomized,
|
||||
passwordType: KeySystemPasswordType.Randomized,
|
||||
creationTimestamp: new Date().getTime(),
|
||||
seed,
|
||||
version,
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ import {
|
||||
KeySystemIdentifier,
|
||||
KeySystemRootKeyInterface,
|
||||
KeySystemRootKeyParamsInterface,
|
||||
KeySystemRootKeyPasswordType,
|
||||
KeySystemPasswordType,
|
||||
} from '@standardnotes/models'
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { DeriveKeySystemRootKeyUseCase } from './DeriveKeySystemRootKey'
|
||||
@@ -19,7 +19,7 @@ export class CreateUserInputKeySystemRootKey {
|
||||
|
||||
const keyParams: KeySystemRootKeyParamsInterface = {
|
||||
systemIdentifier: dto.systemIdentifier,
|
||||
passwordType: KeySystemRootKeyPasswordType.UserInputted,
|
||||
passwordType: KeySystemPasswordType.UserInputted,
|
||||
creationTimestamp: new Date().getTime(),
|
||||
seed,
|
||||
version,
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.28.63](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
## [1.28.62](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/filepicker
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/filepicker",
|
||||
"version": "1.28.62",
|
||||
"version": "1.28.63",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.16.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
## [1.16.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/files",
|
||||
"version": "1.16.8",
|
||||
"version": "1.16.9",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.56.26](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.25](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.24](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.23](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.22](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.21](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.20](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.19](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.18](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.17](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
## [3.56.16](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/mobile
|
||||
|
||||
@@ -118,7 +118,24 @@ class ReceiveSharingIntent: NSObject {
|
||||
|
||||
@objc
|
||||
func clearFileNames(){
|
||||
print("clearFileNames");
|
||||
let appDomain = Bundle.main.bundleIdentifier!
|
||||
let groupName = "group.\(appDomain)"
|
||||
let userDefaults = UserDefaults(suiteName: groupName)
|
||||
userDefaults?.set(nil, forKey: "ShareKey.media")
|
||||
userDefaults?.set(nil, forKey: "ShareKey.text")
|
||||
userDefaults?.set(nil, forKey: "ShareKey.url")
|
||||
let containerURL = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: groupName)!
|
||||
let shareTempDirPath = containerURL.appendingPathComponent("ShareTemp", isDirectory: true)
|
||||
do {
|
||||
let fileNames = try FileManager.default.contentsOfDirectory(atPath: shareTempDirPath.relativePath)
|
||||
for fileName in fileNames {
|
||||
let filePath = shareTempDirPath.appendingPathComponent(fileName)
|
||||
try FileManager.default.removeItem(at: filePath)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -95,16 +95,11 @@ class ShareViewController: SLComposeServiceViewController {
|
||||
attachment.loadItem(forTypeIdentifier: imageContentType, options: nil) { [weak self] data, error in
|
||||
|
||||
if error == nil, let url = data as? URL, let this = self {
|
||||
// this.redirectToHostApp(type: .media)
|
||||
// Always copy
|
||||
let fileExtension = this.getExtension(from: url, type: .video)
|
||||
let newName = UUID().uuidString
|
||||
let newPath = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(newName).\(fileExtension)")
|
||||
let copied = this.copyFile(at: url, to: newPath)
|
||||
if(copied) {
|
||||
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .image))
|
||||
if let copiedPath = this.copyToTemporaryPath(url: url, name: "\(newName).\(fileExtension)") {
|
||||
this.sharedMedia.append(SharedMediaFile(path: copiedPath, thumbnail: nil, duration: nil, type: .image))
|
||||
}
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
@@ -125,15 +120,8 @@ class ShareViewController: SLComposeServiceViewController {
|
||||
// Always copy
|
||||
let fileExtension = this.getExtension(from: url, type: .video)
|
||||
let newName = UUID().uuidString
|
||||
let newPath = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(newName).\(fileExtension)")
|
||||
let copied = this.copyFile(at: url, to: newPath)
|
||||
if(copied) {
|
||||
guard let sharedFile = this.getSharedMediaFile(forVideo: newPath) else {
|
||||
return
|
||||
}
|
||||
this.sharedMedia.append(sharedFile)
|
||||
if let copiedPath = this.copyToTemporaryPath(url: url, name: "\(newName).\(fileExtension)") {
|
||||
this.sharedMedia.append(SharedMediaFile(path: copiedPath, thumbnail: nil, duration: nil, type: .video))
|
||||
}
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
@@ -151,14 +139,9 @@ class ShareViewController: SLComposeServiceViewController {
|
||||
|
||||
if error == nil, let url = data as? URL, let this = self {
|
||||
|
||||
// Always copy
|
||||
let newName = this.getFileName(from :url)
|
||||
let newPath = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(newName)")
|
||||
let copied = this.copyFile(at: url, to: newPath)
|
||||
if (copied) {
|
||||
this.sharedMedia.append(SharedMediaFile(path: newPath.absoluteString, thumbnail: nil, duration: nil, type: .file))
|
||||
if let copiedPath = this.copyToTemporaryPath(url: url, name: newName) {
|
||||
this.sharedMedia.append(SharedMediaFile(path: copiedPath, thumbnail: nil, duration: nil, type: .file))
|
||||
}
|
||||
|
||||
if index == (content.attachments?.count)! - 1 {
|
||||
@@ -171,6 +154,32 @@ class ShareViewController: SLComposeServiceViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func directoryExistsAtPath(_ path: String) -> Bool {
|
||||
var isDirectory = ObjCBool(true)
|
||||
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
|
||||
return exists && isDirectory.boolValue
|
||||
}
|
||||
|
||||
private func copyToTemporaryPath(url: URL, name: String) -> String? {
|
||||
let containerURL = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
let shareTempDirPath = containerURL.appendingPathComponent("ShareTemp", isDirectory: true)
|
||||
if !directoryExistsAtPath(shareTempDirPath.absoluteString) {
|
||||
do {
|
||||
try FileManager.default.createDirectory(atPath: shareTempDirPath.relativePath, withIntermediateDirectories: true, attributes: nil)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
let newPath = shareTempDirPath
|
||||
.appendingPathComponent("\(name)")
|
||||
let copied = self.copyFile(at: url, to: newPath)
|
||||
if (copied) {
|
||||
return newPath.absoluteString
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func redirectToHostApp() {
|
||||
let userDefaults = UserDefaults(suiteName: "group.\(hostAppBundleIdentifier)")
|
||||
userDefaults?.set(self.toData(data: self.sharedMedia), forKey: "\(self.sharedKey).media")
|
||||
@@ -263,40 +272,6 @@ class ShareViewController: SLComposeServiceViewController {
|
||||
return true
|
||||
}
|
||||
|
||||
private func getSharedMediaFile(forVideo: URL) -> SharedMediaFile? {
|
||||
let asset = AVAsset(url: forVideo)
|
||||
let duration = (CMTimeGetSeconds(asset.duration) * 1000).rounded()
|
||||
let thumbnailPath = getThumbnailPath(for: forVideo)
|
||||
|
||||
if FileManager.default.fileExists(atPath: thumbnailPath.path) {
|
||||
return SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video)
|
||||
}
|
||||
|
||||
var saved = false
|
||||
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
|
||||
assetImgGenerate.appliesPreferredTrackTransform = true
|
||||
// let scale = UIScreen.main.scale
|
||||
assetImgGenerate.maximumSize = CGSize(width: 360, height: 360)
|
||||
do {
|
||||
let img = try assetImgGenerate.copyCGImage(at: CMTimeMakeWithSeconds(600, preferredTimescale: Int32(1.0)), actualTime: nil)
|
||||
try UIImage.pngData(UIImage(cgImage: img))()?.write(to: thumbnailPath)
|
||||
saved = true
|
||||
} catch {
|
||||
saved = false
|
||||
}
|
||||
|
||||
return saved ? SharedMediaFile(path: forVideo.absoluteString, thumbnail: thumbnailPath.absoluteString, duration: duration, type: .video) : nil
|
||||
|
||||
}
|
||||
|
||||
private func getThumbnailPath(for url: URL) -> URL {
|
||||
let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(of: "==", with: "")
|
||||
let path = FileManager.default
|
||||
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
|
||||
.appendingPathComponent("\(fileName).jpg")
|
||||
return path
|
||||
}
|
||||
|
||||
class SharedMediaFile: Codable {
|
||||
var path: String; // can be image, video or url path. It can also be text content
|
||||
var thumbnail: String?; // video thumbnail
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/mobile",
|
||||
"version": "3.56.16",
|
||||
"version": "3.56.26",
|
||||
"author": "Standard Notes.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-or-later",
|
||||
|
||||
@@ -71,6 +71,7 @@ export class ReceivedSharedItemsHandler {
|
||||
}
|
||||
|
||||
deinit() {
|
||||
ReceiveSharingIntent.clearFileNames()
|
||||
this.receivedItemsQueue = []
|
||||
this.eventSub?.remove()
|
||||
}
|
||||
@@ -100,17 +101,13 @@ export class ReceivedSharedItemsHandler {
|
||||
}
|
||||
|
||||
handleItemsQueue = async () => {
|
||||
if (!this.receivedItemsQueue.length) {
|
||||
return
|
||||
}
|
||||
|
||||
const item = this.receivedItemsQueue.shift()
|
||||
if (!item) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isReceivedAndroidFile(item) || isReceivedIosFile(item)) {
|
||||
this.sendFileToWebView(item).catch(console.error)
|
||||
await this.sendFileToWebView(item).catch(console.error)
|
||||
} else if (isReceivedWeblink(item)) {
|
||||
this.webViewRef.current?.postMessage(
|
||||
JSON.stringify({
|
||||
@@ -134,12 +131,20 @@ export class ReceivedSharedItemsHandler {
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.receivedItemsQueue.length) {
|
||||
if (Platform.OS === 'ios') {
|
||||
ReceiveSharingIntent.clearFileNames()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.handleItemsQueue().catch(console.error)
|
||||
}
|
||||
|
||||
sendFileToWebView = async (item: ReceivedAndroidFile | ReceivedIosFile) => {
|
||||
const path = isReceivedAndroidFile(item) ? item.contentUri : item.path
|
||||
const data = await readFile(path, 'base64')
|
||||
const data = await readFile(decodeURIComponent(path), 'base64')
|
||||
const file = {
|
||||
name: item.fileName || item.path,
|
||||
data,
|
||||
@@ -157,7 +162,12 @@ export class ReceivedSharedItemsHandler {
|
||||
private addSharedItemsToQueue = async (url?: string) => {
|
||||
const received =
|
||||
Platform.OS === 'ios' ? await ReceiveSharingIntent.getFileNames(url) : await ReceiveSharingIntent.getFileNames()
|
||||
ReceiveSharingIntent.clearFileNames()
|
||||
|
||||
// On iOS, we need to wait for the whole queue
|
||||
// to finish before clearing
|
||||
if (Platform.OS === 'android') {
|
||||
ReceiveSharingIntent.clearFileNames()
|
||||
}
|
||||
|
||||
if (!received) {
|
||||
return
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.46.13](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
## [1.46.12](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/models",
|
||||
"version": "1.46.12",
|
||||
"version": "1.46.13",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
export enum KeySystemRootKeyPasswordType {
|
||||
export enum KeySystemPasswordType {
|
||||
UserInputted = 'user_inputted',
|
||||
Randomized = 'randomized',
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ProtocolVersion } from '@standardnotes/common'
|
||||
import { KeySystemIdentifier } from '../../Syncable/KeySystemRootKey/KeySystemIdentifier'
|
||||
import { KeySystemRootKeyPasswordType } from './KeySystemRootKeyPasswordType'
|
||||
import { KeySystemPasswordType } from './KeySystemPasswordType'
|
||||
|
||||
/**
|
||||
* Key params are public data that contain information about how a root key was created.
|
||||
@@ -11,6 +11,6 @@ export interface KeySystemRootKeyParamsInterface {
|
||||
systemIdentifier: KeySystemIdentifier
|
||||
seed: string
|
||||
version: ProtocolVersion
|
||||
passwordType: KeySystemRootKeyPasswordType
|
||||
passwordType: KeySystemPasswordType
|
||||
creationTimestamp: number
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
Collection,
|
||||
DecryptedCollectionElement,
|
||||
DeletedCollectionElement,
|
||||
EncryptedCollectionElement,
|
||||
} from './Collection'
|
||||
import { FullyFormedPayloadInterface } from '../../Abstract/Payload'
|
||||
|
||||
class TestCollection<P extends FullyFormedPayloadInterface = FullyFormedPayloadInterface> extends Collection<
|
||||
P,
|
||||
DecryptedCollectionElement,
|
||||
EncryptedCollectionElement,
|
||||
DeletedCollectionElement
|
||||
> {}
|
||||
|
||||
describe('Collection', () => {
|
||||
let collection: TestCollection
|
||||
|
||||
beforeEach(() => {
|
||||
collection = new TestCollection()
|
||||
})
|
||||
|
||||
it('should initialize correctly', () => {
|
||||
expect(collection.map).toEqual({})
|
||||
expect(collection.typedMap).toEqual({})
|
||||
expect(collection.referenceMap).toBeDefined()
|
||||
expect(collection.conflictMap).toBeDefined()
|
||||
})
|
||||
|
||||
it('should set and get element correctly', () => {
|
||||
const testElement = {
|
||||
uuid: 'test-uuid',
|
||||
content_type: 'test-type',
|
||||
content: {},
|
||||
references: [],
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
collection.set(testElement)
|
||||
const element = collection.find('test-uuid')
|
||||
|
||||
expect(element).toBe(testElement)
|
||||
})
|
||||
|
||||
it('should check existence of an element correctly', () => {
|
||||
const testElement = {
|
||||
uuid: 'test-uuid',
|
||||
content_type: 'test-type',
|
||||
content: {},
|
||||
references: [],
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
collection.set(testElement)
|
||||
const hasElement = collection.has('test-uuid')
|
||||
|
||||
expect(hasElement).toBe(true)
|
||||
})
|
||||
|
||||
it('should return all elements', () => {
|
||||
const testElement1 = {
|
||||
uuid: 'test-uuid-1',
|
||||
content_type: 'test-type',
|
||||
content: {},
|
||||
references: [],
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
const testElement2 = {
|
||||
uuid: 'test-uuid-2',
|
||||
content_type: 'test-type',
|
||||
content: {},
|
||||
references: [],
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
collection.set(testElement1)
|
||||
collection.set(testElement2)
|
||||
|
||||
const allElements = collection.all()
|
||||
|
||||
expect(allElements).toEqual([testElement1, testElement2])
|
||||
})
|
||||
|
||||
it('should add uuid to invalidsIndex if element is error decrypting', () => {
|
||||
const testElement = {
|
||||
uuid: 'test-uuid',
|
||||
content_type: 'test-type',
|
||||
content: 'encrypted content',
|
||||
errorDecrypting: true,
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
collection.set(testElement)
|
||||
|
||||
expect(collection.invalidsIndex.has(testElement.uuid)).toBe(true)
|
||||
})
|
||||
|
||||
it('should add uuid to invalidsIndex if element is encrypted', () => {
|
||||
const testElement = {
|
||||
uuid: 'test-uuid',
|
||||
content_type: 'test-type',
|
||||
content: 'encrypted content',
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
collection.set(testElement)
|
||||
|
||||
expect(collection.invalidsIndex.has(testElement.uuid)).toBe(true)
|
||||
})
|
||||
|
||||
it('should remove uuid from invalidsIndex if element is not encrypted', () => {
|
||||
const testElement1 = {
|
||||
uuid: 'test-uuid-1',
|
||||
content_type: 'test-type',
|
||||
content: 'encrypted content',
|
||||
errorDecrypting: true,
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
const testElement2 = {
|
||||
uuid: 'test-uuid-1',
|
||||
content_type: 'test-type',
|
||||
content: {},
|
||||
references: [],
|
||||
} as unknown as FullyFormedPayloadInterface
|
||||
|
||||
collection.set(testElement1)
|
||||
expect(collection.invalidsIndex.has(testElement1.uuid)).toBe(true)
|
||||
|
||||
collection.set(testElement2)
|
||||
expect(collection.invalidsIndex.has(testElement2.uuid)).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -59,7 +59,7 @@ export abstract class Collection<
|
||||
}
|
||||
|
||||
isErrorDecryptingElement = (e: Decrypted | Encrypted | Deleted): e is Encrypted => {
|
||||
return this.isEncryptedElement(e) && e.errorDecrypting === true
|
||||
return this.isEncryptedElement(e)
|
||||
}
|
||||
|
||||
isDeletedElement = (e: Decrypted | Encrypted | Deleted): e is Deleted => {
|
||||
@@ -78,10 +78,10 @@ export abstract class Collection<
|
||||
conflictMapCopy?: UuidMap,
|
||||
) {
|
||||
if (copy) {
|
||||
this.map = mapCopy!
|
||||
this.typedMap = typedMapCopy!
|
||||
this.referenceMap = referenceMapCopy!
|
||||
this.conflictMap = conflictMapCopy!
|
||||
this.map = mapCopy as Record<string, Element>
|
||||
this.typedMap = typedMapCopy as Record<string, Element[]>
|
||||
this.referenceMap = referenceMapCopy as UuidMap
|
||||
this.conflictMap = conflictMapCopy as UuidMap
|
||||
} else {
|
||||
this.referenceMap = new UuidMap()
|
||||
this.conflictMap = new UuidMap()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ConflictStrategy, DecryptedItem } from '../../Abstract/Item'
|
||||
import { DecryptedPayloadInterface } from '../../Abstract/Payload'
|
||||
import { HistoryEntryInterface } from '../../Runtime/History'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
import { KeySystemRootKeyPasswordType } from '../../Local/KeyParams/KeySystemRootKeyPasswordType'
|
||||
import { KeySystemPasswordType } from '../../Local/KeyParams/KeySystemPasswordType'
|
||||
import { SharedVaultListingInterface, VaultListingInterface } from './VaultListingInterface'
|
||||
import { VaultListingContent } from './VaultListingContent'
|
||||
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
@@ -44,7 +44,7 @@ export class VaultListing extends DecryptedItem<VaultListingContent> implements
|
||||
return incomingKeyTimestamp > baseKeyTimestamp ? ConflictStrategy.KeepApply : ConflictStrategy.KeepBase
|
||||
}
|
||||
|
||||
get keyPasswordType(): KeySystemRootKeyPasswordType {
|
||||
get keyPasswordType(): KeySystemPasswordType {
|
||||
return this.rootKeyParams.passwordType
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier'
|
||||
import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
import { KeySystemRootKeyPasswordType } from '../../Local/KeyParams/KeySystemRootKeyPasswordType'
|
||||
import { KeySystemPasswordType } from '../../Local/KeyParams/KeySystemPasswordType'
|
||||
import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode'
|
||||
import { VaultListingSharingInfo } from './VaultListingSharingInfo'
|
||||
import { VaultListingContent } from './VaultListingContent'
|
||||
@@ -17,7 +17,7 @@ export interface VaultListingInterface extends DecryptedItemInterface<VaultListi
|
||||
|
||||
sharing?: VaultListingSharingInfo
|
||||
|
||||
get keyPasswordType(): KeySystemRootKeyPasswordType
|
||||
get keyPasswordType(): KeySystemPasswordType
|
||||
isSharedVaultListing(): this is SharedVaultListingInterface
|
||||
|
||||
get key_system_identifier(): undefined
|
||||
|
||||
@@ -37,7 +37,7 @@ export * from './Device/Platform'
|
||||
|
||||
export * from './Local/KeyParams/RootKeyParamsInterface'
|
||||
export * from './Local/KeyParams/KeySystemRootKeyParamsInterface'
|
||||
export * from './Local/KeyParams/KeySystemRootKeyPasswordType'
|
||||
export * from './Local/KeyParams/KeySystemPasswordType'
|
||||
export * from './Local/RootKey/KeychainTypes'
|
||||
export * from './Local/RootKey/RootKeyContent'
|
||||
export * from './Local/RootKey/RootKeyInterface'
|
||||
|
||||
@@ -3,6 +3,46 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.4.398](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.397](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.396](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.395](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.394](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.393](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.392](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.391](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.390](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.389](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
## [1.4.388](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/releases
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/releases",
|
||||
"version": "1.4.388",
|
||||
"version": "1.4.398",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "dist/releases.json",
|
||||
"types": "dist/index.d.ts",
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [1.63.11](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.10](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-26)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-25)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.7](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-24)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
## [1.63.6](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-07-23)
|
||||
|
||||
**Note:** Version bump only for package @standardnotes/services
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@standardnotes/services",
|
||||
"version": "1.63.6",
|
||||
"version": "1.63.11",
|
||||
"engines": {
|
||||
"node": ">=16.0.0 <17.0.0"
|
||||
},
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { VaultUserServiceInterface, VaultInviteServiceInterface } from '@standardnotes/services'
|
||||
import { VaultLockServiceInterface } from './../VaultLock/VaultLockServiceInterface'
|
||||
import { HistoryServiceInterface } from './../History/HistoryServiceInterface'
|
||||
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
|
||||
import { PreferenceServiceInterface } from './../Preferences/PreferenceServiceInterface'
|
||||
@@ -5,7 +7,7 @@ import { AsymmetricMessageServiceInterface } from './../AsymmetricMessage/Asymme
|
||||
import { SyncOptions } from './../Sync/SyncOptions'
|
||||
import { ImportDataReturnType } from './../Mutator/ImportDataUseCase'
|
||||
import { ChallengeServiceInterface } from './../Challenge/ChallengeServiceInterface'
|
||||
import { VaultServiceInterface } from './../Vaults/VaultServiceInterface'
|
||||
import { VaultServiceInterface } from '../Vault/VaultServiceInterface'
|
||||
import { ApplicationIdentifier } from '@standardnotes/common'
|
||||
import {
|
||||
BackupFile,
|
||||
@@ -95,24 +97,27 @@ export interface ApplicationInterface {
|
||||
syncOptions?: SyncOptions,
|
||||
): Promise<void>
|
||||
|
||||
get features(): FeaturesClientInterface
|
||||
get componentManager(): ComponentManagerInterface
|
||||
get items(): ItemManagerInterface
|
||||
get mutator(): MutatorClientInterface
|
||||
get user(): UserClientInterface
|
||||
get files(): FilesClientInterface
|
||||
get subscriptions(): SubscriptionManagerInterface
|
||||
get fileBackups(): BackupServiceInterface | undefined
|
||||
get sessions(): SessionsClientInterface
|
||||
get homeServer(): HomeServerServiceInterface | undefined
|
||||
get vaults(): VaultServiceInterface
|
||||
get challenges(): ChallengeServiceInterface
|
||||
get alerts(): AlertService
|
||||
get asymmetric(): AsymmetricMessageServiceInterface
|
||||
get preferences(): PreferenceServiceInterface
|
||||
get events(): InternalEventBusInterface
|
||||
get history(): HistoryServiceInterface
|
||||
get challenges(): ChallengeServiceInterface
|
||||
get componentManager(): ComponentManagerInterface
|
||||
get encryption(): EncryptionProviderInterface
|
||||
get events(): InternalEventBusInterface
|
||||
get features(): FeaturesClientInterface
|
||||
get fileBackups(): BackupServiceInterface | undefined
|
||||
get files(): FilesClientInterface
|
||||
get history(): HistoryServiceInterface
|
||||
get homeServer(): HomeServerServiceInterface | undefined
|
||||
get items(): ItemManagerInterface
|
||||
get mutator(): MutatorClientInterface
|
||||
get preferences(): PreferenceServiceInterface
|
||||
get sessions(): SessionsClientInterface
|
||||
get subscriptions(): SubscriptionManagerInterface
|
||||
get user(): UserClientInterface
|
||||
get vaults(): VaultServiceInterface
|
||||
get vaultLocks(): VaultLockServiceInterface
|
||||
get vaultUsers(): VaultUserServiceInterface
|
||||
get vaultInvites(): VaultInviteServiceInterface
|
||||
|
||||
readonly identifier: ApplicationIdentifier
|
||||
readonly platform: Platform
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { GetVault } from './../Vault/UseCase/GetVault'
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
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'
|
||||
@@ -31,6 +30,7 @@ describe('AsymmetricMessageService', () => {
|
||||
let sync: jest.Mocked<SyncServiceInterface>
|
||||
let mutator: jest.Mocked<MutatorClientInterface>
|
||||
let encryption: jest.Mocked<EncryptionProviderInterface>
|
||||
let sessions: jest.Mocked<SessionsClientInterface>
|
||||
let service: AsymmetricMessageService
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -40,12 +40,10 @@ describe('AsymmetricMessageService', () => {
|
||||
encryption = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
const createOrEditContact = {} as jest.Mocked<CreateOrEditContact>
|
||||
const findContact = {} as jest.Mocked<FindContact>
|
||||
const getAllContacts = {} as jest.Mocked<GetAllContacts>
|
||||
const replaceContactData = {} as jest.Mocked<ReplaceContactData>
|
||||
const getTrustedPayload = {} as jest.Mocked<GetTrustedPayload>
|
||||
const getVault = {} as jest.Mocked<GetVault>
|
||||
const handleRootKeyChangedMessage = {} as jest.Mocked<HandleRootKeyChangedMessage>
|
||||
const sendOwnContactChangedMessage = {} as jest.Mocked<SendOwnContactChangeMessage>
|
||||
const getOutboundMessagesUseCase = {} as jest.Mocked<GetOutboundMessages>
|
||||
const getInboundMessagesUseCase = {} as jest.Mocked<GetInboundMessages>
|
||||
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
|
||||
@@ -60,17 +58,16 @@ describe('AsymmetricMessageService', () => {
|
||||
eventBus.addEventHandler = jest.fn()
|
||||
|
||||
service = new AsymmetricMessageService(
|
||||
messageServer,
|
||||
encryption,
|
||||
mutator,
|
||||
sessions,
|
||||
messageServer,
|
||||
createOrEditContact,
|
||||
findContact,
|
||||
getAllContacts,
|
||||
replaceContactData,
|
||||
getTrustedPayload,
|
||||
getVault,
|
||||
handleRootKeyChangedMessage,
|
||||
sendOwnContactChangedMessage,
|
||||
getOutboundMessagesUseCase,
|
||||
getInboundMessagesUseCase,
|
||||
getUntrustedPayload,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { SessionsClientInterface } from './../Session/SessionsClientInterface'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { AsymmetricMessageServerHash, ClientDisplayableError, isClientDisplayableError } from '@standardnotes/responses'
|
||||
import { SyncEvent, SyncEventReceivedAsymmetricMessagesData } from '../Event/SyncEvent'
|
||||
@@ -21,17 +22,14 @@ import {
|
||||
import { HandleRootKeyChangedMessage } from './UseCase/HandleRootKeyChangedMessage'
|
||||
import { SessionEvent } from '../Session/SessionEvent'
|
||||
import { AsymmetricMessageServer } from '@standardnotes/api'
|
||||
import { UserKeyPairChangedEventData } from '../Session/UserKeyPairChangedEventData'
|
||||
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
|
||||
import { GetOutboundMessages } from './UseCase/GetOutboundMessages'
|
||||
import { GetInboundMessages } from './UseCase/GetInboundMessages'
|
||||
import { GetVault } from '../Vaults/UseCase/GetVault'
|
||||
import { GetVault } from '../Vault/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
|
||||
@@ -39,17 +37,16 @@ export class AsymmetricMessageService
|
||||
implements AsymmetricMessageServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
constructor(
|
||||
private messageServer: AsymmetricMessageServer,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private mutator: MutatorClientInterface,
|
||||
private sessions: SessionsClientInterface,
|
||||
private messageServer: AsymmetricMessageServer,
|
||||
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,
|
||||
@@ -58,11 +55,26 @@ export class AsymmetricMessageService
|
||||
super(eventBus)
|
||||
}
|
||||
|
||||
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._replaceContactData as unknown) = undefined
|
||||
;(this._getTrustedPayload as unknown) = undefined
|
||||
;(this._getVault as unknown) = undefined
|
||||
;(this._handleRootKeyChangedMessage as unknown) = undefined
|
||||
;(this._getOutboundMessagesUseCase as unknown) = undefined
|
||||
;(this._getInboundMessagesUseCase as unknown) = undefined
|
||||
;(this._getUntrustedPayload as unknown) = undefined
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
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)
|
||||
@@ -87,31 +99,6 @@ export class AsymmetricMessageService
|
||||
await this.handleRemoteReceivedAsymmetricMessages(messages)
|
||||
}
|
||||
|
||||
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
|
||||
if (!data.previous) {
|
||||
return
|
||||
}
|
||||
|
||||
const contacts = this._getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const contact of contacts.getValue()) {
|
||||
if (contact.isMe) {
|
||||
continue
|
||||
}
|
||||
|
||||
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]
|
||||
|
||||
@@ -184,10 +171,6 @@ export class AsymmetricMessageService
|
||||
message: AsymmetricMessageServerHash,
|
||||
payload: AsymmetricMessagePayload,
|
||||
): Promise<void> {
|
||||
if (payload.data.recipientUuid !== message.user_uuid) {
|
||||
return
|
||||
}
|
||||
|
||||
if (payload.type === AsymmetricMessagePayloadType.ContactShare) {
|
||||
await this.handleTrustedContactShareMessage(message, payload)
|
||||
} else if (payload.type === AsymmetricMessagePayloadType.SenderKeypairChanged) {
|
||||
@@ -225,6 +208,7 @@ export class AsymmetricMessageService
|
||||
const result = this._getTrustedPayload.execute({
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
sender: contact.getValue(),
|
||||
ownUserUuid: this.sessions.userUuid,
|
||||
message,
|
||||
})
|
||||
|
||||
@@ -289,22 +273,4 @@ export class AsymmetricMessageService
|
||||
): Promise<void> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { AsymmetricMessagePayload, TrustedContactInterface } from '@standardnotes/models'
|
||||
import { DecryptMessage } from '../../Encryption/UseCase/Asymmetric/DecryptMessage'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { GetTrustedPayload } from './GetTrustedPayload'
|
||||
|
||||
describe('GetTrustedPayload', () => {
|
||||
let decryptMessage: jest.Mocked<DecryptMessage>
|
||||
let getTrustedPayload: GetTrustedPayload
|
||||
|
||||
beforeEach(() => {
|
||||
decryptMessage = {} as jest.Mocked<DecryptMessage>
|
||||
decryptMessage.execute = jest.fn()
|
||||
|
||||
getTrustedPayload = new GetTrustedPayload(decryptMessage)
|
||||
})
|
||||
|
||||
describe('execute', () => {
|
||||
const mockDto = {
|
||||
privateKey: 'test-private-key',
|
||||
message: {} as AsymmetricMessageServerHash,
|
||||
sender: {} as TrustedContactInterface,
|
||||
ownUserUuid: 'test-user-uuid',
|
||||
}
|
||||
|
||||
it('should return failure when decryption fails', () => {
|
||||
decryptMessage.execute = jest.fn().mockReturnValue(Result.fail('Decryption failed'))
|
||||
|
||||
const result = getTrustedPayload.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Decryption failed')
|
||||
})
|
||||
|
||||
it('should return failure when recipientUuid is not equal to ownUserUuid', () => {
|
||||
const mockPayload: AsymmetricMessagePayload = {
|
||||
data: {
|
||||
recipientUuid: 'another-user-uuid',
|
||||
},
|
||||
} as AsymmetricMessagePayload
|
||||
decryptMessage.execute = jest.fn().mockReturnValue(Result.ok(mockPayload))
|
||||
|
||||
const result = getTrustedPayload.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Message is not for this user')
|
||||
})
|
||||
|
||||
it('should return success when recipientUuid is equal to ownUserUuid', () => {
|
||||
const mockPayload: AsymmetricMessagePayload = {
|
||||
data: {
|
||||
recipientUuid: 'test-user-uuid',
|
||||
},
|
||||
} as AsymmetricMessagePayload
|
||||
decryptMessage.execute = jest.fn().mockReturnValue(Result.ok(mockPayload))
|
||||
|
||||
const result = getTrustedPayload.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
expect(result.getValue()).toBe(mockPayload)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,7 @@ export class GetTrustedPayload implements SyncUseCaseInterface<AsymmetricMessage
|
||||
privateKey: string
|
||||
message: AsymmetricMessageServerHash
|
||||
sender: TrustedContactInterface
|
||||
ownUserUuid: string
|
||||
}): Result<M> {
|
||||
const result = this.decryptMessage.execute<M>({
|
||||
message: dto.message.encrypted_message,
|
||||
@@ -17,6 +18,16 @@ export class GetTrustedPayload implements SyncUseCaseInterface<AsymmetricMessage
|
||||
privateKey: dto.privateKey,
|
||||
})
|
||||
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
|
||||
const recipientUuid = result.getValue().data.recipientUuid
|
||||
|
||||
if (recipientUuid !== dto.ownUserUuid) {
|
||||
return Result.fail('Message is not for this user')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
|
||||
import { ContentType } from '@standardnotes/domain-core'
|
||||
import { GetVault } from '../../Vaults/UseCase/GetVault'
|
||||
import { GetVault } from '../../Vault/UseCase/GetVault'
|
||||
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
|
||||
|
||||
export class HandleRootKeyChangedMessage {
|
||||
|
||||
@@ -32,7 +32,7 @@ export class ResendAllMessages implements UseCaseInterface<void> {
|
||||
|
||||
for (const message of messages.data.messages) {
|
||||
const recipient = this.findContact.execute({ userUuid: message.user_uuid })
|
||||
if (recipient) {
|
||||
if (recipient.isFailed()) {
|
||||
errors.push(`Contact not found for invite ${message.user_uuid}`)
|
||||
continue
|
||||
}
|
||||
@@ -41,7 +41,7 @@ export class ResendAllMessages implements UseCaseInterface<void> {
|
||||
keys: params.keys,
|
||||
previousKeys: params.previousKeys,
|
||||
message: message,
|
||||
recipient,
|
||||
recipient: recipient.getValue(),
|
||||
})
|
||||
|
||||
await this.messageServer.deleteMessage({
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { SendOwnContactChangeMessage } from './UseCase/SendOwnContactChangeMessage'
|
||||
import { DeleteContact } from './UseCase/DeleteContact'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
|
||||
import { SessionEvent } from './../Session/SessionEvent'
|
||||
@@ -21,6 +23,7 @@ import { CreateOrEditContact } from './UseCase/CreateOrEditContact'
|
||||
import { EditContact } from './UseCase/EditContact'
|
||||
import { GetAllContacts } from './UseCase/GetAllContacts'
|
||||
import { EncryptionProviderInterface } from '../Encryption/EncryptionProviderInterface'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export class ContactService
|
||||
extends AbstractService<ContactServiceEvent>
|
||||
@@ -34,11 +37,13 @@ export class ContactService
|
||||
private user: UserClientInterface,
|
||||
private selfContactManager: SelfContactManager,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private _deleteContact: DeleteContact,
|
||||
private _findContact: FindContact,
|
||||
private _getAllContacts: GetAllContacts,
|
||||
private _createOrEditContact: CreateOrEditContact,
|
||||
private _editContact: EditContact,
|
||||
private _validateItemSigner: ValidateItemSigner,
|
||||
private _sendOwnContactChangedMessage: SendOwnContactChangeMessage,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
@@ -49,11 +54,36 @@ export class ContactService
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === SessionEvent.UserKeyPairChanged) {
|
||||
const data = event.payload as UserKeyPairChangedEventData
|
||||
|
||||
await this.selfContactManager.updateWithNewPublicKeySet({
|
||||
encryption: data.current.encryption.publicKey,
|
||||
signing: data.current.signing.publicKey,
|
||||
})
|
||||
void this.sendOwnContactChangeEventToAllContacts(event.payload as UserKeyPairChangedEventData)
|
||||
}
|
||||
}
|
||||
|
||||
private async sendOwnContactChangeEventToAllContacts(data: UserKeyPairChangedEventData): Promise<void> {
|
||||
if (!data.previous) {
|
||||
return
|
||||
}
|
||||
|
||||
const contacts = this._getAllContacts.execute()
|
||||
if (contacts.isFailed()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const contact of contacts.getValue()) {
|
||||
if (contact.isMe) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this._sendOwnContactChangedMessage.execute({
|
||||
senderOldKeyPair: data.previous.encryption,
|
||||
senderOldSigningKeyPair: data.previous.signing,
|
||||
senderNewKeyPair: data.current.encryption,
|
||||
senderNewSigningKeyPair: data.current.signing,
|
||||
contact,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,20 +197,15 @@ export class ContactService
|
||||
return contact
|
||||
}
|
||||
|
||||
async deleteContact(contact: TrustedContactInterface): Promise<void> {
|
||||
if (contact.isMe) {
|
||||
throw new Error('Cannot delete self')
|
||||
}
|
||||
|
||||
await this.mutator.setItemToBeDeleted(contact)
|
||||
await this.sync.sync()
|
||||
async deleteContact(contact: TrustedContactInterface): Promise<Result<void>> {
|
||||
return this._deleteContact.execute({ contact, ownUserUuid: this.session.userUuid })
|
||||
}
|
||||
|
||||
getAllContacts(): TrustedContactInterface[] {
|
||||
return this._getAllContacts.execute().getValue()
|
||||
}
|
||||
|
||||
findTrustedContact(userUuid: string): TrustedContactInterface | undefined {
|
||||
findContact(userUuid: string): TrustedContactInterface | undefined {
|
||||
const result = this._findContact.execute({ userUuid })
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
@@ -188,12 +213,12 @@ export class ContactService
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
findTrustedContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined {
|
||||
return this.findTrustedContact(user.user_uuid)
|
||||
findContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined {
|
||||
return this.findContact(user.user_uuid)
|
||||
}
|
||||
|
||||
findTrustedContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined {
|
||||
return this.findTrustedContact(invite.sender_uuid)
|
||||
findContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined {
|
||||
return this.findContact(invite.sender_uuid)
|
||||
}
|
||||
|
||||
getCollaborationIDForTrustedContact(contact: TrustedContactInterface): string {
|
||||
@@ -205,7 +230,7 @@ export class ContactService
|
||||
})
|
||||
}
|
||||
|
||||
isItemAuthenticallySigned(item: DecryptedItemInterface): ItemSignatureValidationResult {
|
||||
getItemSignatureStatus(item: DecryptedItemInterface): ItemSignatureValidationResult {
|
||||
return this._validateItemSigner.execute(item)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { DecryptedItemInterface, TrustedContactInterface } from '@standardnotes/
|
||||
import { AbstractService } from '../Service/AbstractService'
|
||||
import { SharedVaultInviteServerHash, SharedVaultUserServerHash } from '@standardnotes/responses'
|
||||
import { ItemSignatureValidationResult } from './UseCase/Types/ItemSignatureValidationResult'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
|
||||
export enum ContactServiceEvent {}
|
||||
|
||||
@@ -26,13 +27,13 @@ export interface ContactServiceInterface extends AbstractService<ContactServiceE
|
||||
contact: TrustedContactInterface,
|
||||
params: { name: string; collaborationID: string },
|
||||
): Promise<TrustedContactInterface>
|
||||
deleteContact(contact: TrustedContactInterface): Promise<void>
|
||||
deleteContact(contact: TrustedContactInterface): Promise<Result<void>>
|
||||
|
||||
getAllContacts(): TrustedContactInterface[]
|
||||
getSelfContact(): TrustedContactInterface | undefined
|
||||
findTrustedContact(userUuid: string): TrustedContactInterface | undefined
|
||||
findTrustedContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined
|
||||
findTrustedContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined
|
||||
findContact(userUuid: string): TrustedContactInterface | undefined
|
||||
findContactForServerUser(user: SharedVaultUserServerHash): TrustedContactInterface | undefined
|
||||
findContactForInvite(invite: SharedVaultInviteServerHash): TrustedContactInterface | undefined
|
||||
|
||||
isItemAuthenticallySigned(item: DecryptedItemInterface): ItemSignatureValidationResult
|
||||
getItemSignatureStatus(item: DecryptedItemInterface): ItemSignatureValidationResult
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ContactBelongsToVault } from './../../SharedVaults/UseCase/ContactBelongsToVault'
|
||||
import { GetOwnedSharedVaults } from './../../SharedVaults/UseCase/GetOwnedSharedVaults'
|
||||
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
|
||||
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
|
||||
import { Result } from '@standardnotes/domain-core'
|
||||
import { TrustedContactInterface } from '@standardnotes/models'
|
||||
import { DeleteContact } from './DeleteContact'
|
||||
|
||||
describe('DeleteContact', () => {
|
||||
let mutator: jest.Mocked<MutatorClientInterface>
|
||||
let sync: jest.Mocked<SyncServiceInterface>
|
||||
let getOwnedVaults: jest.Mocked<GetOwnedSharedVaults>
|
||||
let contactBelongsToVault: jest.Mocked<ContactBelongsToVault>
|
||||
let deleteContact: DeleteContact
|
||||
let mockDto: {
|
||||
contact: TrustedContactInterface
|
||||
ownUserUuid: string
|
||||
}
|
||||
let mockVaults: { id: string }[]
|
||||
|
||||
beforeEach(() => {
|
||||
mutator = {} as jest.Mocked<MutatorClientInterface>
|
||||
sync = {} as jest.Mocked<SyncServiceInterface>
|
||||
|
||||
getOwnedVaults = {} as jest.Mocked<GetOwnedSharedVaults>
|
||||
getOwnedVaults.execute = jest.fn()
|
||||
|
||||
contactBelongsToVault = {} as jest.Mocked<ContactBelongsToVault>
|
||||
contactBelongsToVault.execute = jest.fn()
|
||||
|
||||
deleteContact = new DeleteContact(mutator, sync, getOwnedVaults, contactBelongsToVault)
|
||||
|
||||
mockDto = {
|
||||
contact: { isMe: false } as TrustedContactInterface,
|
||||
ownUserUuid: 'test-user-uuid',
|
||||
}
|
||||
|
||||
mockVaults = [{ id: 'vault1' }, { id: 'vault2' }]
|
||||
})
|
||||
|
||||
describe('execute', () => {
|
||||
it('should throw error when deleting self', async () => {
|
||||
mockDto.contact.isMe = true
|
||||
await expect(deleteContact.execute(mockDto)).rejects.toThrow('Cannot delete self')
|
||||
})
|
||||
|
||||
it('should return failure when getting owned vaults fails', async () => {
|
||||
getOwnedVaults.execute = jest.fn().mockReturnValue(Result.fail('Failed to get owned vaults'))
|
||||
|
||||
const result = await deleteContact.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Failed to get owned vaults')
|
||||
})
|
||||
|
||||
it('should return failure when checking contact membership fails', async () => {
|
||||
getOwnedVaults.execute = jest.fn().mockReturnValue(Result.ok(mockVaults))
|
||||
contactBelongsToVault.execute = jest.fn().mockResolvedValue(Result.fail('Failed to check contact membership'))
|
||||
|
||||
const result = await deleteContact.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Failed to check contact membership')
|
||||
})
|
||||
|
||||
it('should return failure when contact belongs to an owned vault', async () => {
|
||||
getOwnedVaults.execute = jest.fn().mockReturnValue(Result.ok(mockVaults))
|
||||
contactBelongsToVault.execute = jest.fn().mockResolvedValue(Result.ok(true))
|
||||
|
||||
const result = await deleteContact.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(true)
|
||||
expect(result.getError()).toBe('Cannot delete contact that belongs to an owned vault')
|
||||
})
|
||||
|
||||
it('should return success when contact is successfully deleted', async () => {
|
||||
getOwnedVaults.execute = jest.fn().mockReturnValue(Result.ok(mockVaults))
|
||||
contactBelongsToVault.execute = jest.fn().mockResolvedValue(Result.ok(false))
|
||||
mutator.setItemToBeDeleted = jest.fn().mockResolvedValue(undefined)
|
||||
sync.sync = jest.fn().mockResolvedValue(undefined)
|
||||
|
||||
const result = await deleteContact.execute(mockDto)
|
||||
|
||||
expect(result.isFailed()).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ContactBelongsToVault } from './../../SharedVaults/UseCase/ContactBelongsToVault'
|
||||
import { GetOwnedSharedVaults } from './../../SharedVaults/UseCase/GetOwnedSharedVaults'
|
||||
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
|
||||
import { MutatorClientInterface } from './../../Mutator/MutatorClientInterface'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { TrustedContactInterface } from '@standardnotes/models'
|
||||
|
||||
export class DeleteContact implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private getOwnedVaults: GetOwnedSharedVaults,
|
||||
private contactBelongsToVault: ContactBelongsToVault,
|
||||
) {}
|
||||
|
||||
async execute(dto: { contact: TrustedContactInterface; ownUserUuid: string }): Promise<Result<void>> {
|
||||
if (dto.contact.isMe) {
|
||||
throw new Error('Cannot delete self')
|
||||
}
|
||||
|
||||
const vaults = this.getOwnedVaults.execute({ userUuid: dto.ownUserUuid })
|
||||
if (vaults.isFailed()) {
|
||||
return Result.fail('Failed to get owned vaults')
|
||||
}
|
||||
|
||||
for (const vault of vaults.getValue()) {
|
||||
const contactBelongsToVault = await this.contactBelongsToVault.execute({
|
||||
contact: dto.contact,
|
||||
vault: vault,
|
||||
})
|
||||
if (contactBelongsToVault.isFailed()) {
|
||||
return Result.fail('Failed to check contact membership')
|
||||
}
|
||||
|
||||
if (contactBelongsToVault.getValue()) {
|
||||
return Result.fail('Cannot delete contact that belongs to an owned vault')
|
||||
}
|
||||
}
|
||||
|
||||
await this.mutator.setItemToBeDeleted(dto.contact)
|
||||
await this.sync.sync()
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { ReuploadAllInvites } from '../../SharedVaults/UseCase/ReuploadAllInvites'
|
||||
import { ReuploadAllInvites } from '../../VaultInvite/UseCase/ReuploadAllInvites'
|
||||
import { ResendAllMessages } from '../../AsymmetricMessage/UseCase/ResendAllMessages'
|
||||
|
||||
export class HandleKeyPairChange implements UseCaseInterface<void> {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import {
|
||||
AsymmetricMessageSenderKeypairChanged,
|
||||
} from '@standardnotes/models'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { SendMessage } from './SendMessage'
|
||||
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
@@ -112,7 +112,6 @@ export class ValidateItemSigner {
|
||||
const signerPublicKey = signatureResult.publicKey
|
||||
|
||||
const trustedContact = this.findContact.execute({ signingPublicKey: signerPublicKey })
|
||||
|
||||
if (trustedContact.isFailed()) {
|
||||
return ItemSignatureValidationResult.NotTrusted
|
||||
}
|
||||
|
||||
+7
-7
@@ -2,24 +2,24 @@ import { ItemsKeyMutator } from '@standardnotes/encryption'
|
||||
import { MutatorClientInterface } from '../../../Mutator/MutatorClientInterface'
|
||||
import { ItemManagerInterface } from '../../../Item/ItemManagerInterface'
|
||||
import { CreateNewDefaultItemsKey } from './CreateNewDefaultItemsKey'
|
||||
import { RemoveItemsLocally } from '../../../UseCase/RemoveItemsLocally'
|
||||
import { DiscardItemsLocally } from '../../../UseCase/DiscardItemsLocally'
|
||||
import { FindDefaultItemsKey } from './FindDefaultItemsKey'
|
||||
|
||||
export class CreateNewItemsKeyWithRollback {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private createDefaultItemsKey: CreateNewDefaultItemsKey,
|
||||
private removeItemsLocally: RemoveItemsLocally,
|
||||
private findDefaultItemsKey: FindDefaultItemsKey,
|
||||
private _createDefaultItemsKey: CreateNewDefaultItemsKey,
|
||||
private _discardItemsLocally: DiscardItemsLocally,
|
||||
private _findDefaultItemsKey: FindDefaultItemsKey,
|
||||
) {}
|
||||
|
||||
async execute(): Promise<() => Promise<void>> {
|
||||
const currentDefaultItemsKey = this.findDefaultItemsKey.execute(this.items.getDisplayableItemsKeys()).getValue()
|
||||
const newDefaultItemsKey = await this.createDefaultItemsKey.execute()
|
||||
const currentDefaultItemsKey = this._findDefaultItemsKey.execute(this.items.getDisplayableItemsKeys()).getValue()
|
||||
const newDefaultItemsKey = await this._createDefaultItemsKey.execute()
|
||||
|
||||
const rollback = async () => {
|
||||
await this.removeItemsLocally.execute([newDefaultItemsKey])
|
||||
await this._discardItemsLocally.execute([newDefaultItemsKey])
|
||||
|
||||
if (currentDefaultItemsKey) {
|
||||
await this.mutator.changeItem<ItemsKeyMutator>(currentDefaultItemsKey, (mutator) => {
|
||||
|
||||
@@ -80,7 +80,7 @@ export interface ItemManagerInterface extends AbstractService {
|
||||
): T[]
|
||||
subItemsMatchingPredicates<T extends DecryptedItemInterface>(items: T[], predicates: PredicateInterface<T>[]): T[]
|
||||
removeAllItemsFromMemory(): Promise<void>
|
||||
removeItemsLocally(items: AnyItemInterface[]): void
|
||||
removeItemsFromMemory(items: AnyItemInterface[]): void
|
||||
getDirtyItems(): (DecryptedItemInterface | DeletedItemInterface)[]
|
||||
getTagLongTitle(tag: SNTag): string
|
||||
getSortedTagsForItem(item: DecryptedItemInterface<ItemContent>): SNTag[]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { RemoveItemsFromMemory } from './../Storage/UseCase/RemoveItemsFromMemory'
|
||||
import { InternalEventHandlerInterface } from './../Internal/InternalEventHandlerInterface'
|
||||
import { MutatorClientInterface } from './../Mutator/MutatorClientInterface'
|
||||
import { ApplicationStage } from './../Application/ApplicationStage'
|
||||
@@ -36,11 +37,20 @@ export class KeySystemKeyManager
|
||||
private readonly items: ItemManagerInterface,
|
||||
private readonly mutator: MutatorClientInterface,
|
||||
private readonly storage: StorageServiceInterface,
|
||||
private readonly _removeItemsFromMemory: RemoveItemsFromMemory,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
}
|
||||
|
||||
public override deinit(): void {
|
||||
;(this.items as unknown) = undefined
|
||||
;(this.mutator as unknown) = undefined
|
||||
;(this.storage as unknown) = undefined
|
||||
;(this._removeItemsFromMemory as unknown) = undefined
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
if (event.type === ApplicationEvent.ApplicationStageChanged) {
|
||||
const stage = (event.payload as ApplicationStageChangedEventPayload).stage
|
||||
@@ -60,9 +70,28 @@ export class KeySystemKeyManager
|
||||
const keyPayloads = keyRawPayloads.map((rawPayload) => new DecryptedPayload<KeySystemRootKeyContent>(rawPayload))
|
||||
|
||||
const keys = keyPayloads.map((payload) => new KeySystemRootKey(payload))
|
||||
keys.forEach((key) => {
|
||||
|
||||
for (const key of keys) {
|
||||
this.rootKeyMemoryCache[key.systemIdentifier] = key
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public getRootKeyFromStorageForVault(
|
||||
keySystemIdentifier: KeySystemIdentifier,
|
||||
): KeySystemRootKeyInterface | undefined {
|
||||
const payload = this.storage.getValue<DecryptedTransferPayload<KeySystemRootKeyContent>>(
|
||||
this.storageKeyForRootKey(keySystemIdentifier),
|
||||
)
|
||||
|
||||
if (!payload) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const keyPayload = new DecryptedPayload<KeySystemRootKeyContent>(payload)
|
||||
|
||||
const key = new KeySystemRootKey(keyPayload)
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
private storageKeyForRootKey(systemIdentifier: KeySystemIdentifier): string {
|
||||
@@ -73,17 +102,14 @@ export class KeySystemKeyManager
|
||||
* 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<void> {
|
||||
public async queueVaultItemsKeysForReencryption(keySystemIdentifier: KeySystemIdentifier): Promise<void> {
|
||||
const keySystemItemsKeys = this.getKeySystemItemsKeys(keySystemIdentifier)
|
||||
if (keySystemItemsKeys.length > 0) {
|
||||
await this.mutator.setItemsDirty(keySystemItemsKeys)
|
||||
}
|
||||
}
|
||||
|
||||
public intakeNonPersistentKeySystemRootKey(
|
||||
key: KeySystemRootKeyInterface,
|
||||
storage: KeySystemRootKeyStorageMode,
|
||||
): void {
|
||||
public cacheKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void {
|
||||
this.rootKeyMemoryCache[key.systemIdentifier] = key
|
||||
|
||||
if (storage === KeySystemRootKeyStorageMode.Local) {
|
||||
@@ -91,7 +117,7 @@ export class KeySystemKeyManager
|
||||
}
|
||||
}
|
||||
|
||||
public undoIntakeNonPersistentKeySystemRootKey(systemIdentifier: KeySystemIdentifier): void {
|
||||
public removeKeyFromCache(systemIdentifier: KeySystemIdentifier): void {
|
||||
delete this.rootKeyMemoryCache[systemIdentifier]
|
||||
void this.storage.removeValue(this.storageKeyForRootKey(systemIdentifier))
|
||||
}
|
||||
@@ -100,11 +126,11 @@ export class KeySystemKeyManager
|
||||
return this.items.getItems(ContentType.TYPES.KeySystemRootKey)
|
||||
}
|
||||
|
||||
public clearMemoryOfKeysRelatedToVault(vault: VaultListingInterface): void {
|
||||
public async wipeVaultKeysFromMemory(vault: VaultListingInterface): Promise<void> {
|
||||
delete this.rootKeyMemoryCache[vault.systemIdentifier]
|
||||
|
||||
const itemsKeys = this.getKeySystemItemsKeys(vault.systemIdentifier)
|
||||
this.items.removeItemsLocally(itemsKeys)
|
||||
await this._removeItemsFromMemory.execute(itemsKeys)
|
||||
}
|
||||
|
||||
public getSyncedKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[] {
|
||||
@@ -131,19 +157,6 @@ export class KeySystemKeyManager
|
||||
await this.mutator.setItemsToBeDeleted(keys)
|
||||
}
|
||||
|
||||
public getKeySystemRootKeyWithToken(
|
||||
systemIdentifier: KeySystemIdentifier,
|
||||
rootKeyToken: string,
|
||||
): KeySystemRootKeyInterface | undefined {
|
||||
const keys = this.getAllKeySystemRootKeysForVault(systemIdentifier).filter((key) => key.token === rootKeyToken)
|
||||
|
||||
if (keys.length > 1) {
|
||||
throw new Error('Multiple synced key system root keys found for token')
|
||||
}
|
||||
|
||||
return keys[0]
|
||||
}
|
||||
|
||||
public getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined {
|
||||
const keys = this.getAllKeySystemRootKeysForVault(systemIdentifier)
|
||||
|
||||
|
||||
@@ -16,17 +16,13 @@ export interface KeySystemKeyManagerInterface {
|
||||
getAllKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[]
|
||||
getSyncedKeySystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface[]
|
||||
getAllSyncedKeySystemRootKeys(): KeySystemRootKeyInterface[]
|
||||
getKeySystemRootKeyWithToken(
|
||||
systemIdentifier: KeySystemIdentifier,
|
||||
keyIdentifier: string,
|
||||
): KeySystemRootKeyInterface | undefined
|
||||
getPrimaryKeySystemRootKey(systemIdentifier: KeySystemIdentifier): KeySystemRootKeyInterface | undefined
|
||||
reencryptKeySystemItemsKeysForVault(keySystemIdentifier: KeySystemIdentifier): Promise<void>
|
||||
queueVaultItemsKeysForReencryption(keySystemIdentifier: KeySystemIdentifier): Promise<void>
|
||||
|
||||
intakeNonPersistentKeySystemRootKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void
|
||||
undoIntakeNonPersistentKeySystemRootKey(systemIdentifier: KeySystemIdentifier): void
|
||||
cacheKey(key: KeySystemRootKeyInterface, storage: KeySystemRootKeyStorageMode): void
|
||||
removeKeyFromCache(systemIdentifier: KeySystemIdentifier): void
|
||||
|
||||
clearMemoryOfKeysRelatedToVault(vault: VaultListingInterface): void
|
||||
wipeVaultKeysFromMemory(vault: VaultListingInterface): Promise<void>
|
||||
deleteNonPersistentSystemRootKeysForVault(systemIdentifier: KeySystemIdentifier): Promise<void>
|
||||
deleteAllSyncedKeySystemRootKeys(systemIdentifier: KeySystemIdentifier): Promise<void>
|
||||
}
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
import { DiscardItemsLocally } from './../UseCase/DiscardItemsLocally'
|
||||
import { InternalEventBusInterface } from './../Internal/InternalEventBusInterface'
|
||||
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
|
||||
import { IsVaultOwner } from './../VaultUser/UseCase/IsVaultOwner'
|
||||
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 { GetVault } from './../Vault/UseCase/GetVault'
|
||||
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', () => {
|
||||
@@ -39,57 +31,40 @@ describe('SharedVaultService', () => {
|
||||
|
||||
const encryption = {} as jest.Mocked<EncryptionProviderInterface>
|
||||
const session = {} as jest.Mocked<SessionsClientInterface>
|
||||
const vaults = {} as jest.Mocked<VaultServiceInterface>
|
||||
const invitesServer = {} as jest.Mocked<SharedVaultInvitesServer>
|
||||
const getVault = {} as jest.Mocked<GetVault>
|
||||
const getOwnedVaults = {} as jest.Mocked<GetOwnedSharedVaults>
|
||||
const createSharedVaultUseCase = {} as jest.Mocked<CreateSharedVault>
|
||||
const handleKeyPairChange = {} as jest.Mocked<HandleKeyPairChange>
|
||||
const notifyVaultUsersOfKeyRotation = {} as jest.Mocked<NotifyVaultUsersOfKeyRotation>
|
||||
const sendVaultDataChangeMessage = {} as jest.Mocked<SendVaultDataChangedMessage>
|
||||
const getTrustedPayload = {} as jest.Mocked<GetTrustedPayload>
|
||||
const getUntrustedPayload = {} as jest.Mocked<GetUntrustedPayload>
|
||||
const findContact = {} as jest.Mocked<FindContact>
|
||||
const getAllContacts = {} as jest.Mocked<GetAllContacts>
|
||||
const getVaultContacts = {} as jest.Mocked<GetVaultContacts>
|
||||
const acceptVaultInvite = {} as jest.Mocked<AcceptVaultInvite>
|
||||
const inviteToVault = {} as jest.Mocked<InviteToVault>
|
||||
const leaveVault = {} as jest.Mocked<LeaveVault>
|
||||
const deleteThirdPartyVault = {} as jest.Mocked<DeleteThirdPartyVault>
|
||||
const shareContactWithVault = {} as jest.Mocked<ShareContactWithVault>
|
||||
const convertToSharedVault = {} as jest.Mocked<ConvertToSharedVault>
|
||||
const deleteSharedVaultUseCase = {} as jest.Mocked<DeleteSharedVault>
|
||||
const removeVaultMember = {} as jest.Mocked<RemoveVaultMember>
|
||||
const getSharedVaultUsersUseCase = {} as jest.Mocked<GetVaultUsers>
|
||||
const isVaultAdmin = {} as jest.Mocked<IsVaultOwner>
|
||||
const discardItemsLocally = {} as jest.Mocked<DiscardItemsLocally>
|
||||
|
||||
const eventBus = {} as jest.Mocked<InternalEventBusInterface>
|
||||
eventBus.addEventHandler = jest.fn()
|
||||
|
||||
service = new SharedVaultService(
|
||||
sync,
|
||||
items,
|
||||
encryption,
|
||||
session,
|
||||
vaults,
|
||||
invitesServer,
|
||||
getVault,
|
||||
getOwnedVaults,
|
||||
createSharedVaultUseCase,
|
||||
handleKeyPairChange,
|
||||
notifyVaultUsersOfKeyRotation,
|
||||
sendVaultDataChangeMessage,
|
||||
getTrustedPayload,
|
||||
getUntrustedPayload,
|
||||
findContact,
|
||||
getAllContacts,
|
||||
getVaultContacts,
|
||||
acceptVaultInvite,
|
||||
inviteToVault,
|
||||
leaveVault,
|
||||
deleteThirdPartyVault,
|
||||
shareContactWithVault,
|
||||
convertToSharedVault,
|
||||
deleteSharedVaultUseCase,
|
||||
removeVaultMember,
|
||||
getSharedVaultUsersUseCase,
|
||||
isVaultAdmin,
|
||||
discardItemsLocally,
|
||||
eventBus,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,114 +1,72 @@
|
||||
import { DiscardItemsLocally } from './../UseCase/DiscardItemsLocally'
|
||||
import { UserKeyPairChangedEventData } from './../Session/UserKeyPairChangedEventData'
|
||||
import { InviteToVault } from './UseCase/InviteToVault'
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
isErrorResponse,
|
||||
SharedVaultUserServerHash,
|
||||
isClientDisplayableError,
|
||||
SharedVaultPermission,
|
||||
UserEventType,
|
||||
} from '@standardnotes/responses'
|
||||
import { SharedVaultInvitesServer } from '@standardnotes/api'
|
||||
import { ClientDisplayableError, UserEventType } from '@standardnotes/responses'
|
||||
import {
|
||||
DecryptedItemInterface,
|
||||
PayloadEmitSource,
|
||||
TrustedContactInterface,
|
||||
SharedVaultListingInterface,
|
||||
VaultListingInterface,
|
||||
AsymmetricMessageSharedVaultInvite,
|
||||
KeySystemRootKeyStorageMode,
|
||||
} from '@standardnotes/models'
|
||||
import { SharedVaultServiceInterface } from './SharedVaultServiceInterface'
|
||||
import { SharedVaultServiceEvent, SharedVaultServiceEventPayload } from './SharedVaultServiceEvent'
|
||||
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 { InternalEventBusInterface } from '../Internal/InternalEventBusInterface'
|
||||
import { SyncEvent, SyncEventReceivedSharedVaultInvitesData } from '../Event/SyncEvent'
|
||||
import { SyncEvent } from '../Event/SyncEvent'
|
||||
import { SessionEvent } from '../Session/SessionEvent'
|
||||
import { InternalEventInterface } from '../Internal/InternalEventInterface'
|
||||
import { LeaveVault } from './UseCase/LeaveSharedVault'
|
||||
import { VaultServiceInterface } from '../Vaults/VaultServiceInterface'
|
||||
import { UserEventServiceEvent, UserEventServiceEventPayload } from '../UserEvent/UserEventServiceEvent'
|
||||
import { DeleteThirdPartyVault } from './UseCase/DeleteExternalSharedVault'
|
||||
import { DeleteSharedVault } from './UseCase/DeleteSharedVault'
|
||||
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vaults/VaultServiceEvent'
|
||||
import { AcceptVaultInvite } from './UseCase/AcceptVaultInvite'
|
||||
import { GetTrustedPayload } from '../AsymmetricMessage/UseCase/GetTrustedPayload'
|
||||
import { PendingSharedVaultInviteRecord } from './PendingSharedVaultInviteRecord'
|
||||
import { GetUntrustedPayload } from '../AsymmetricMessage/UseCase/GetUntrustedPayload'
|
||||
import { VaultServiceEvent, VaultServiceEventPayload } from '../Vault/VaultServiceEvent'
|
||||
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 { GetVault } from '../Vault/UseCase/GetVault'
|
||||
import { ContentType } 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'
|
||||
import { IsVaultOwner } from '../VaultUser/UseCase/IsVaultOwner'
|
||||
import { GetOwnedSharedVaults } from './UseCase/GetOwnedSharedVaults'
|
||||
|
||||
export class SharedVaultService
|
||||
extends AbstractService<SharedVaultServiceEvent, SharedVaultServiceEventPayload>
|
||||
implements SharedVaultServiceInterface, InternalEventHandlerInterface
|
||||
{
|
||||
private pendingInvites: Record<string, PendingSharedVaultInviteRecord> = {}
|
||||
|
||||
constructor(
|
||||
private sync: SyncServiceInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private session: SessionsClientInterface,
|
||||
private vaults: VaultServiceInterface,
|
||||
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,
|
||||
private _getVault: GetVault,
|
||||
private _getOwnedSharedVaults: GetOwnedSharedVaults,
|
||||
private _createSharedVault: CreateSharedVault,
|
||||
private _handleKeyPairChange: HandleKeyPairChange,
|
||||
private _notifyVaultUsersOfKeyRotation: NotifyVaultUsersOfKeyRotation,
|
||||
private _sendVaultDataChangeMessage: SendVaultDataChangedMessage,
|
||||
private _findContact: FindContact,
|
||||
private _deleteThirdPartyVault: DeleteThirdPartyVault,
|
||||
private _shareContactWithVault: ShareContactWithVault,
|
||||
private _convertToSharedVault: ConvertToSharedVault,
|
||||
private _deleteSharedVault: DeleteSharedVault,
|
||||
private _isVaultAdmin: IsVaultOwner,
|
||||
private _discardItemsLocally: DiscardItemsLocally,
|
||||
eventBus: InternalEventBusInterface,
|
||||
) {
|
||||
super(eventBus)
|
||||
|
||||
eventBus.addEventHandler(this, SessionEvent.UserKeyPairChanged)
|
||||
eventBus.addEventHandler(this, UserEventServiceEvent.UserEventReceived)
|
||||
eventBus.addEventHandler(this, VaultServiceEvent.VaultRootKeyRotated)
|
||||
|
||||
this.eventDisposers.push(
|
||||
items.addObserver<TrustedContactInterface>(
|
||||
ContentType.TYPES.TrustedContact,
|
||||
async ({ changed, inserted, source }) => {
|
||||
await this.reprocessCachedInvitesTrustStatusAfterTrustedContactsChange()
|
||||
|
||||
if (source === PayloadEmitSource.LocalChanged && inserted.length > 0) {
|
||||
void this.handleCreationOfNewTrustedContacts(inserted)
|
||||
}
|
||||
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
|
||||
void this.handleTrustedContactsChange(changed)
|
||||
}
|
||||
},
|
||||
),
|
||||
items.addObserver<TrustedContactInterface>(ContentType.TYPES.TrustedContact, async ({ changed, source }) => {
|
||||
if (source === PayloadEmitSource.LocalChanged && changed.length > 0) {
|
||||
void this.handleTrustedContactsChange(changed)
|
||||
}
|
||||
}),
|
||||
)
|
||||
|
||||
this.eventDisposers.push(
|
||||
@@ -120,54 +78,87 @@ export class SharedVaultService
|
||||
)
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.items as unknown) = undefined
|
||||
;(this.encryption as unknown) = undefined
|
||||
;(this.session as unknown) = undefined
|
||||
;(this._getVault as unknown) = undefined
|
||||
;(this._createSharedVault as unknown) = undefined
|
||||
;(this._handleKeyPairChange as unknown) = undefined
|
||||
;(this._notifyVaultUsersOfKeyRotation as unknown) = undefined
|
||||
;(this._sendVaultDataChangeMessage as unknown) = undefined
|
||||
;(this._findContact as unknown) = undefined
|
||||
;(this._deleteThirdPartyVault as unknown) = undefined
|
||||
;(this._shareContactWithVault as unknown) = undefined
|
||||
;(this._convertToSharedVault as unknown) = undefined
|
||||
;(this._deleteSharedVault as unknown) = undefined
|
||||
;(this._isVaultAdmin as unknown) = undefined
|
||||
}
|
||||
|
||||
async handleEvent(event: InternalEventInterface): Promise<void> {
|
||||
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()
|
||||
switch (event.type) {
|
||||
case SessionEvent.UserKeyPairChanged: {
|
||||
const eventData = event.payload as UserKeyPairChangedEventData
|
||||
void this._handleKeyPairChange.execute({
|
||||
newKeys: eventData.current,
|
||||
previousKeys: eventData.previous,
|
||||
})
|
||||
break
|
||||
}
|
||||
case UserEventServiceEvent.UserEventReceived:
|
||||
await this.handleUserEvent(event.payload as UserEventServiceEventPayload)
|
||||
break
|
||||
case VaultServiceEvent.VaultRootKeyRotated: {
|
||||
const payload = event.payload as VaultServiceEventPayload[VaultServiceEvent.VaultRootKeyRotated]
|
||||
await this.handleVaultRootKeyRotatedEvent(payload.vault)
|
||||
break
|
||||
}
|
||||
case SyncEvent.ReceivedRemoteSharedVaults:
|
||||
void this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private async handleUserEvent(event: UserEventServiceEventPayload): Promise<void> {
|
||||
if (event.eventPayload.eventType === UserEventType.RemovedFromSharedVault) {
|
||||
const vault = this.getVault.execute<SharedVaultListingInterface>({
|
||||
sharedVaultUuid: event.eventPayload.sharedVaultUuid,
|
||||
})
|
||||
if (!vault.isFailed()) {
|
||||
await this.deleteThirdPartyVault.execute(vault.getValue())
|
||||
switch (event.eventPayload.eventType) {
|
||||
case UserEventType.RemovedFromSharedVault: {
|
||||
const vault = this._getVault.execute<SharedVaultListingInterface>({
|
||||
sharedVaultUuid: event.eventPayload.sharedVaultUuid,
|
||||
})
|
||||
if (!vault.isFailed()) {
|
||||
await this._deleteThirdPartyVault.execute(vault.getValue())
|
||||
}
|
||||
break
|
||||
}
|
||||
} else if (event.eventPayload.eventType === UserEventType.SharedVaultItemRemoved) {
|
||||
const item = this.items.findItem(event.eventPayload.itemUuid)
|
||||
if (item) {
|
||||
this.items.removeItemsLocally([item])
|
||||
case UserEventType.SharedVaultItemRemoved: {
|
||||
const item = this.items.findItem(event.eventPayload.itemUuid)
|
||||
if (item) {
|
||||
void this._discardItemsLocally.execute([item])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isCurrentUserVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
|
||||
if (!sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
|
||||
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
|
||||
}
|
||||
|
||||
private async handleVaultRootKeyRotatedEvent(vault: VaultListingInterface): Promise<void> {
|
||||
if (!vault.isSharedVaultListing()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.isCurrentUserSharedVaultOwner(vault)) {
|
||||
if (!this.isCurrentUserVaultOwner(vault)) {
|
||||
return
|
||||
}
|
||||
|
||||
await this.notifyVaultUsersOfKeyRotation.execute({
|
||||
await this._notifyVaultUsersOfKeyRotation.execute({
|
||||
sharedVault: vault,
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
keys: {
|
||||
@@ -183,7 +174,7 @@ export class SharedVaultService
|
||||
userInputtedPassword: string | undefined
|
||||
storagePreference?: KeySystemRootKeyStorageMode
|
||||
}): Promise<VaultListingInterface | ClientDisplayableError> {
|
||||
return this.createSharedVaultUseCase.execute({
|
||||
return this._createSharedVault.execute({
|
||||
vaultName: dto.name,
|
||||
vaultDescription: dto.description,
|
||||
userInputtedPassword: dto.userInputtedPassword,
|
||||
@@ -194,48 +185,7 @@ export class SharedVaultService
|
||||
async convertVaultToSharedVault(
|
||||
vault: VaultListingInterface,
|
||||
): Promise<SharedVaultListingInterface | ClientDisplayableError> {
|
||||
return this.convertToSharedVault.execute({ vault })
|
||||
}
|
||||
|
||||
public getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[] {
|
||||
return Object.values(this.pendingInvites)
|
||||
}
|
||||
|
||||
private getAllSharedVaults(): SharedVaultListingInterface[] {
|
||||
const vaults = this.vaults.getVaults().filter((vault) => vault.isSharedVaultListing())
|
||||
return vaults as SharedVaultListingInterface[]
|
||||
}
|
||||
|
||||
private findSharedVault(sharedVaultUuid: string): SharedVaultListingInterface | undefined {
|
||||
const result = this.getVault.execute<SharedVaultListingInterface>({ sharedVaultUuid })
|
||||
if (result.isFailed()) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return result.getValue()
|
||||
}
|
||||
|
||||
public isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean {
|
||||
if (!sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
|
||||
}
|
||||
|
||||
public isCurrentUserSharedVaultOwner(sharedVault: SharedVaultListingInterface): boolean {
|
||||
if (!sharedVault.sharing.ownerUserUuid) {
|
||||
throw new Error(`Shared vault ${sharedVault.sharing.sharedVaultUuid} does not have an owner user uuid`)
|
||||
}
|
||||
return sharedVault.sharing.ownerUserUuid === this.session.userUuid
|
||||
}
|
||||
|
||||
public isSharedVaultUserSharedVaultOwner(user: SharedVaultUserServerHash): boolean {
|
||||
const vault = this.findSharedVault(user.shared_vault_uuid)
|
||||
return vault != undefined && vault.sharing.ownerUserUuid === user.user_uuid
|
||||
}
|
||||
|
||||
private async handleCreationOfNewTrustedContacts(_contacts: TrustedContactInterface[]): Promise<void> {
|
||||
await this.downloadInboundInvites()
|
||||
return this._convertToSharedVault.execute({ vault })
|
||||
}
|
||||
|
||||
private async handleTrustedContactsChange(contacts: TrustedContactInterface[]): Promise<void> {
|
||||
@@ -254,7 +204,7 @@ export class SharedVaultService
|
||||
continue
|
||||
}
|
||||
|
||||
await this.sendVaultDataChangeMessage.execute({
|
||||
await this._sendVaultDataChangeMessage.execute({
|
||||
vault,
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
keys: {
|
||||
@@ -265,220 +215,8 @@ export class SharedVaultService
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]> {
|
||||
const response = await this.invitesServer.getInboundUserInvites()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to get inbound user invites ${response}`)
|
||||
}
|
||||
|
||||
this.pendingInvites = {}
|
||||
|
||||
await this.processInboundInvites(response.data.invites)
|
||||
|
||||
return response.data.invites
|
||||
}
|
||||
|
||||
public async getOutboundInvites(
|
||||
sharedVault?: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError> {
|
||||
const response = await this.invitesServer.getOutboundUserInvites()
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to get outbound user invites ${response}`)
|
||||
}
|
||||
|
||||
if (sharedVault) {
|
||||
return response.data.invites.filter((invite) => invite.shared_vault_uuid === sharedVault.sharing.sharedVaultUuid)
|
||||
}
|
||||
|
||||
return response.data.invites
|
||||
}
|
||||
|
||||
public async deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void> {
|
||||
const response = await this.invitesServer.deleteInvite({
|
||||
sharedVaultUuid: invite.shared_vault_uuid,
|
||||
inviteUuid: invite.uuid,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromString(`Failed to delete invite ${response}`)
|
||||
}
|
||||
|
||||
delete this.pendingInvites[invite.uuid]
|
||||
}
|
||||
|
||||
public async deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
|
||||
return this.deleteSharedVaultUseCase.execute({ sharedVault })
|
||||
}
|
||||
|
||||
private async reprocessCachedInvitesTrustStatusAfterTrustedContactsChange(): Promise<void> {
|
||||
const cachedInvites = this.getCachedPendingInviteRecords().map((record) => record.invite)
|
||||
|
||||
await this.processInboundInvites(cachedInvites)
|
||||
}
|
||||
|
||||
private async processInboundInvites(invites: SharedVaultInviteServerHash[]): Promise<void> {
|
||||
if (invites.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const invite of invites) {
|
||||
const sender = this.findContact.execute({ userUuid: invite.sender_uuid })
|
||||
if (!sender.isFailed()) {
|
||||
const trustedMessage = this.getTrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
message: invite,
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
sender: sender.getValue(),
|
||||
})
|
||||
|
||||
if (!trustedMessage.isFailed()) {
|
||||
this.pendingInvites[invite.uuid] = {
|
||||
invite,
|
||||
message: trustedMessage.getValue(),
|
||||
trusted: true,
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const untrustedMessage = this.getUntrustedPayload.execute<AsymmetricMessageSharedVaultInvite>({
|
||||
message: invite,
|
||||
privateKey: this.encryption.getKeyPair().privateKey,
|
||||
})
|
||||
|
||||
if (!untrustedMessage.isFailed()) {
|
||||
this.pendingInvites[invite.uuid] = {
|
||||
invite,
|
||||
message: untrustedMessage.getValue(),
|
||||
trusted: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.notifyCollaborationStatusChanged()
|
||||
}
|
||||
|
||||
private async notifyCollaborationStatusChanged(): Promise<void> {
|
||||
await this.notifyEventSync(SharedVaultServiceEvent.SharedVaultStatusChanged)
|
||||
}
|
||||
|
||||
async acceptPendingSharedVaultInvite(pendingInvite: PendingSharedVaultInviteRecord): Promise<void> {
|
||||
if (!pendingInvite.trusted) {
|
||||
throw new Error('Cannot accept untrusted invite')
|
||||
}
|
||||
|
||||
await this.acceptVaultInvite.execute({ invite: pendingInvite.invite, message: pendingInvite.message })
|
||||
|
||||
delete this.pendingInvites[pendingInvite.invite.uuid]
|
||||
|
||||
void this.sync.sync()
|
||||
|
||||
await this.decryptErroredItemsAfterInviteAccept()
|
||||
|
||||
await this.sync.syncSharedVaultsFromScratch([pendingInvite.invite.shared_vault_uuid])
|
||||
}
|
||||
|
||||
private async decryptErroredItemsAfterInviteAccept(): Promise<void> {
|
||||
await this.encryption.decryptErroredPayloads()
|
||||
}
|
||||
|
||||
public async getInvitableContactsForSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<TrustedContactInterface[]> {
|
||||
const users = await this.getSharedVaultUsers(sharedVault)
|
||||
if (!users) {
|
||||
return []
|
||||
}
|
||||
|
||||
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<TrustedContactInterface[]> {
|
||||
const contacts = await this.getVaultContacts.execute(sharedVault.sharing.sharedVaultUuid)
|
||||
if (contacts.isFailed()) {
|
||||
return []
|
||||
}
|
||||
|
||||
return contacts.getValue()
|
||||
}
|
||||
|
||||
async inviteContactToSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
contact: TrustedContactInterface,
|
||||
permissions: SharedVaultPermission,
|
||||
): Promise<Result<SharedVaultInviteServerHash>> {
|
||||
const sharedVaultContacts = await this.getSharedVaultContacts(sharedVault)
|
||||
|
||||
const result = await this.inviteToVault.execute({
|
||||
keys: {
|
||||
encryption: this.encryption.getKeyPair(),
|
||||
signing: this.encryption.getSigningKeyPair(),
|
||||
},
|
||||
senderUuid: this.session.getSureUser().uuid,
|
||||
sharedVault,
|
||||
recipient: contact,
|
||||
sharedVaultContacts,
|
||||
permissions,
|
||||
})
|
||||
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async removeUserFromSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
userUuid: string,
|
||||
): Promise<ClientDisplayableError | void> {
|
||||
if (!this.isCurrentUserSharedVaultAdmin(sharedVault)) {
|
||||
throw new Error('Only vault admins can remove users')
|
||||
}
|
||||
|
||||
if (this.vaults.isVaultLocked(sharedVault)) {
|
||||
throw new Error('Cannot remove user from locked vault')
|
||||
}
|
||||
|
||||
const result = await this.removeVaultMember.execute({
|
||||
sharedVaultUuid: sharedVault.sharing.sharedVaultUuid,
|
||||
userUuid,
|
||||
})
|
||||
if (isClientDisplayableError(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
|
||||
await this.vaults.rotateVaultRootKey(sharedVault)
|
||||
}
|
||||
|
||||
async leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void> {
|
||||
const result = await this.leaveVault.execute({
|
||||
sharedVault: sharedVault,
|
||||
userUuid: this.session.getSureUser().uuid,
|
||||
})
|
||||
|
||||
if (isClientDisplayableError(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
void this.notifyCollaborationStatusChanged()
|
||||
}
|
||||
|
||||
async getSharedVaultUsers(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultUserServerHash[] | undefined> {
|
||||
return this.getSharedVaultUsersUseCase.execute({ sharedVaultUuid: sharedVault.sharing.sharedVaultUuid })
|
||||
return this._deleteSharedVault.execute({ sharedVault })
|
||||
}
|
||||
|
||||
async shareContactWithVaults(contact: TrustedContactInterface): Promise<void> {
|
||||
@@ -486,10 +224,10 @@ export class SharedVaultService
|
||||
throw new Error('Cannot share self contact')
|
||||
}
|
||||
|
||||
const ownedVaults = this.getAllSharedVaults().filter(this.isCurrentUserSharedVaultAdmin.bind(this))
|
||||
const ownedVaults = this._getOwnedSharedVaults.execute({ userUuid: this.session.userUuid }).getValue()
|
||||
|
||||
for (const vault of ownedVaults) {
|
||||
await this.shareContactWithVault.execute({
|
||||
await this._shareContactWithVault.execute({
|
||||
keys: {
|
||||
encryption: this.encryption.getKeyPair(),
|
||||
signing: this.encryption.getSigningKeyPair(),
|
||||
@@ -506,7 +244,7 @@ export class SharedVaultService
|
||||
return undefined
|
||||
}
|
||||
|
||||
const contact = this.findContact.execute({ userUuid: item.last_edited_by_uuid })
|
||||
const contact = this._findContact.execute({ userUuid: item.last_edited_by_uuid })
|
||||
|
||||
return contact.isFailed() ? undefined : contact.getValue()
|
||||
}
|
||||
@@ -516,37 +254,8 @@ export class SharedVaultService
|
||||
return undefined
|
||||
}
|
||||
|
||||
const contact = this.findContact.execute({ userUuid: item.user_uuid })
|
||||
const contact = this._findContact.execute({ userUuid: item.user_uuid })
|
||||
|
||||
return contact.isFailed() ? undefined : contact.getValue()
|
||||
}
|
||||
|
||||
override deinit(): void {
|
||||
super.deinit()
|
||||
;(this.sync 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
SharedVaultUserServerHash,
|
||||
SharedVaultPermission,
|
||||
} from '@standardnotes/responses'
|
||||
import { ClientDisplayableError } from '@standardnotes/responses'
|
||||
import {
|
||||
DecryptedItemInterface,
|
||||
TrustedContactInterface,
|
||||
@@ -13,8 +8,6 @@ import {
|
||||
} from '@standardnotes/models'
|
||||
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<SharedVaultServiceEvent, SharedVaultServiceEventPayload> {
|
||||
@@ -25,32 +18,8 @@ export interface SharedVaultServiceInterface
|
||||
storagePreference?: KeySystemRootKeyStorageMode
|
||||
}): Promise<VaultListingInterface | ClientDisplayableError>
|
||||
deleteSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
|
||||
convertVaultToSharedVault(vault: VaultListingInterface): Promise<SharedVaultListingInterface | ClientDisplayableError>
|
||||
|
||||
inviteContactToSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
contact: TrustedContactInterface,
|
||||
permissions: SharedVaultPermission,
|
||||
): Promise<Result<SharedVaultInviteServerHash>>
|
||||
removeUserFromSharedVault(
|
||||
sharedVault: SharedVaultListingInterface,
|
||||
userUuid: string,
|
||||
): Promise<ClientDisplayableError | void>
|
||||
leaveSharedVault(sharedVault: SharedVaultListingInterface): Promise<ClientDisplayableError | void>
|
||||
getSharedVaultUsers(sharedVault: SharedVaultListingInterface): Promise<SharedVaultUserServerHash[] | undefined>
|
||||
isSharedVaultUserSharedVaultOwner(user: SharedVaultUserServerHash): boolean
|
||||
isCurrentUserSharedVaultAdmin(sharedVault: SharedVaultListingInterface): boolean
|
||||
|
||||
getItemLastEditedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined
|
||||
getItemSharedBy(item: DecryptedItemInterface): TrustedContactInterface | undefined
|
||||
|
||||
downloadInboundInvites(): Promise<ClientDisplayableError | SharedVaultInviteServerHash[]>
|
||||
getOutboundInvites(
|
||||
sharedVault?: SharedVaultListingInterface,
|
||||
): Promise<SharedVaultInviteServerHash[] | ClientDisplayableError>
|
||||
acceptPendingSharedVaultInvite(pendingInvite: PendingSharedVaultInviteRecord): Promise<void>
|
||||
getCachedPendingInviteRecords(): PendingSharedVaultInviteRecord[]
|
||||
getInvitableContactsForSharedVault(sharedVault: SharedVaultListingInterface): Promise<TrustedContactInterface[]>
|
||||
deleteInvite(invite: SharedVaultInviteServerHash): Promise<ClientDisplayableError | void>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { GetVaultUsers } from './../../VaultUser/UseCase/GetVaultUsers'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SharedVaultListingInterface, TrustedContactInterface } from '@standardnotes/models'
|
||||
|
||||
export class ContactBelongsToVault implements UseCaseInterface<boolean> {
|
||||
constructor(private getVaultUsers: GetVaultUsers) {}
|
||||
|
||||
async execute(dto: {
|
||||
contact: TrustedContactInterface
|
||||
vault: SharedVaultListingInterface
|
||||
}): Promise<Result<boolean>> {
|
||||
const users = await this.getVaultUsers.execute({
|
||||
sharedVaultUuid: dto.vault.sharing.sharedVaultUuid,
|
||||
readFromCache: false,
|
||||
})
|
||||
|
||||
if (users.isFailed()) {
|
||||
return Result.fail('Failed to get vault users')
|
||||
}
|
||||
|
||||
return Result.ok(users.getValue().some((u) => u.user_uuid === dto.contact.contactUuid))
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { SharedVaultListingInterface, VaultListingInterface, VaultListingMutator
|
||||
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
|
||||
import { SharedVaultServerInterface } from '@standardnotes/api'
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault'
|
||||
import { MoveItemsToVault } from '../../Vault/UseCase/MoveItemsToVault'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
|
||||
export class ConvertToSharedVault {
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
import { ClientDisplayableError, isErrorResponse } from '@standardnotes/responses'
|
||||
import { SharedVaultServerInterface } from '@standardnotes/api'
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { CreateVault } from '../../Vaults/UseCase/CreateVault'
|
||||
import { MoveItemsToVault } from '../../Vaults/UseCase/MoveItemsToVault'
|
||||
import { CreateVault } from '../../Vault/UseCase/CreateVault'
|
||||
import { MoveItemsToVault } from '../../Vault/UseCase/MoveItemsToVault'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
|
||||
export class CreateSharedVault {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { AnyItemInterface, VaultListingInterface } from '@standardnotes/models'
|
||||
import { MutatorClientInterface } from '../../Mutator/MutatorClientInterface'
|
||||
import { RemoveItemsLocally } from '../../UseCase/RemoveItemsLocally'
|
||||
import { DiscardItemsLocally } from '../../UseCase/DiscardItemsLocally'
|
||||
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
|
||||
|
||||
export class DeleteThirdPartyVault {
|
||||
@@ -11,7 +11,7 @@ export class DeleteThirdPartyVault {
|
||||
private mutator: MutatorClientInterface,
|
||||
private keys: KeySystemKeyManagerInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private removeItemsLocally: RemoveItemsLocally,
|
||||
private _discardItemsLocally: DiscardItemsLocally,
|
||||
) {}
|
||||
|
||||
async execute(vault: VaultListingInterface): Promise<void> {
|
||||
@@ -33,7 +33,7 @@ export class DeleteThirdPartyVault {
|
||||
|
||||
const itemsKeys = this.keys.getKeySystemItemsKeys(vault.systemIdentifier)
|
||||
|
||||
await this.removeItemsLocally.execute([...vaultItems, ...itemsKeys])
|
||||
await this._discardItemsLocally.execute([...vaultItems, ...itemsKeys])
|
||||
}
|
||||
|
||||
private async deleteDataOwnedByThisUser(vault: VaultListingInterface): Promise<void> {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ClientDisplayableError, isErrorResponse } from '@standardnotes/response
|
||||
import { SharedVaultServerInterface } from '@standardnotes/api'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import { DeleteVault } from '../../Vaults/UseCase/DeleteVault'
|
||||
import { DeleteVault } from '../../Vault/UseCase/DeleteVault'
|
||||
|
||||
export class DeleteSharedVault {
|
||||
constructor(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { IsVaultOwner } from './../../VaultUser/UseCase/IsVaultOwner'
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
import { GetSharedVaults } from './GetSharedVaults'
|
||||
|
||||
export class GetOwnedSharedVaults implements SyncUseCaseInterface<SharedVaultListingInterface[]> {
|
||||
constructor(private getSharedVaults: GetSharedVaults, private isVaultOwnwer: IsVaultOwner) {}
|
||||
|
||||
execute(dto: { userUuid: string }): Result<SharedVaultListingInterface[]> {
|
||||
const sharedVaults = this.getSharedVaults.execute().getValue()
|
||||
|
||||
const ownedVaults = sharedVaults.filter((vault) => {
|
||||
return this.isVaultOwnwer
|
||||
.execute({
|
||||
sharedVault: vault,
|
||||
userUuid: dto.userUuid,
|
||||
})
|
||||
.getValue()
|
||||
})
|
||||
|
||||
return Result.ok(ownedVaults)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { GetVaults } from '../../Vault/UseCase/GetVaults'
|
||||
import { Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { SharedVaultListingInterface } from '@standardnotes/models'
|
||||
|
||||
export class GetSharedVaults implements SyncUseCaseInterface<SharedVaultListingInterface[]> {
|
||||
constructor(private getVaults: GetVaults) {}
|
||||
|
||||
execute(): Result<SharedVaultListingInterface[]> {
|
||||
const vaults = this.getVaults
|
||||
.execute()
|
||||
.getValue()
|
||||
.filter((vault) => vault.isSharedVaultListing())
|
||||
|
||||
return Result.ok(vaults as SharedVaultListingInterface[])
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { SharedVaultUserServerHash, isErrorResponse } from '@standardnotes/responses'
|
||||
import { SharedVaultUsersServerInterface } from '@standardnotes/api'
|
||||
|
||||
export class GetVaultUsers {
|
||||
constructor(private vaultUsersServer: SharedVaultUsersServerInterface) {}
|
||||
|
||||
async execute(params: { sharedVaultUuid: string }): Promise<SharedVaultUserServerHash[] | undefined> {
|
||||
const response = await this.vaultUsersServer.getSharedVaultUsers({ sharedVaultUuid: params.sharedVaultUuid })
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return response.data.users
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import { SharedVaultInviteServerHash, isErrorResponse } from '@standardnotes/res
|
||||
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 { InviteToVault } from '../../VaultInvite/UseCase/InviteToVault'
|
||||
import { GetVaultContacts } from '../../VaultUser/UseCase/GetVaultContacts'
|
||||
import { DecryptOwnMessage } from '../../Encryption/UseCase/Asymmetric/DecryptOwnMessage'
|
||||
import { FindContact } from '../../Contacts/UseCase/FindContact'
|
||||
|
||||
@@ -44,7 +44,10 @@ export class NotifyVaultUsersOfKeyRotation implements UseCaseInterface<void> {
|
||||
|
||||
await this.deleteAllInvites(params.sharedVault.sharing.sharedVaultUuid)
|
||||
|
||||
const contacts = await this.getVaultContacts.execute(params.sharedVault.sharing.sharedVaultUuid)
|
||||
const contacts = await this.getVaultContacts.execute({
|
||||
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
|
||||
readFromCache: false,
|
||||
})
|
||||
|
||||
for (const invite of existingInvites.getValue()) {
|
||||
const recipient = this.findContact.execute({ userUuid: invite.user_uuid })
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { GetVaultUsers } from './GetVaultUsers'
|
||||
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
@@ -29,13 +29,16 @@ export class SendVaultDataChangedMessage implements UseCaseInterface<void> {
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
}): Promise<Result<void>> {
|
||||
const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.vault.sharing.sharedVaultUuid })
|
||||
if (!users) {
|
||||
const users = await this.getVaultUsers.execute({
|
||||
sharedVaultUuid: params.vault.sharing.sharedVaultUuid,
|
||||
readFromCache: false,
|
||||
})
|
||||
if (users.isFailed()) {
|
||||
return Result.fail('Cannot send metadata changed message; users not found')
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
for (const user of users) {
|
||||
for (const user of users.getValue()) {
|
||||
if (user.user_uuid === params.senderUuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
TrustedContactInterface,
|
||||
} from '@standardnotes/models'
|
||||
import { AsymmetricMessageServerHash } from '@standardnotes/responses'
|
||||
import { GetVaultUsers } from './GetVaultUsers'
|
||||
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
|
||||
import { PkcKeyPair } from '@standardnotes/sncrypto-common'
|
||||
import { SendMessage } from '../../AsymmetricMessage/UseCase/SendMessage'
|
||||
import { EncryptMessage } from '../../Encryption/UseCase/Asymmetric/EncryptMessage'
|
||||
@@ -32,14 +32,14 @@ export class SendVaultKeyChangedMessage implements UseCaseInterface<void> {
|
||||
signing: PkcKeyPair
|
||||
}
|
||||
}): Promise<Result<void>> {
|
||||
const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.sharedVaultUuid })
|
||||
if (!users) {
|
||||
const users = await this.getVaultUsers.execute({ sharedVaultUuid: params.sharedVaultUuid, readFromCache: false })
|
||||
if (users.isFailed()) {
|
||||
return Result.fail('Cannot send root key changed message; users not found')
|
||||
}
|
||||
|
||||
const errors: string[] = []
|
||||
|
||||
for (const user of users) {
|
||||
for (const user of users.getValue()) {
|
||||
if (user.user_uuid === params.senderUuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ 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'
|
||||
import { GetVaultUsers } from '../../VaultUser/UseCase/GetVaultUsers'
|
||||
|
||||
export class ShareContactWithVault implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
@@ -33,17 +33,18 @@ export class ShareContactWithVault implements UseCaseInterface<void> {
|
||||
|
||||
const users = await this.getVaultUsers.execute({
|
||||
sharedVaultUuid: params.sharedVault.sharing.sharedVaultUuid,
|
||||
readFromCache: false,
|
||||
})
|
||||
|
||||
if (!users) {
|
||||
if (users.isFailed()) {
|
||||
return Result.fail('Cannot share contact; shared vault users not found')
|
||||
}
|
||||
|
||||
if (users.length === 0) {
|
||||
if (users.getValue().length === 0) {
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
for (const vaultUser of users) {
|
||||
for (const vaultUser of users.getValue()) {
|
||||
if (vaultUser.user_uuid === params.senderUserUuid) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import {
|
||||
ClientDisplayableError,
|
||||
SharedVaultInviteServerHash,
|
||||
isErrorResponse,
|
||||
SharedVaultPermission,
|
||||
} from '@standardnotes/responses'
|
||||
import { SharedVaultInvitesServerInterface } from '@standardnotes/api'
|
||||
|
||||
export class UpdateSharedVaultInviteUseCase {
|
||||
constructor(private vaultInvitesServer: SharedVaultInvitesServerInterface) {}
|
||||
|
||||
async execute(params: {
|
||||
sharedVaultUuid: string
|
||||
inviteUuid: string
|
||||
encryptedMessage: string
|
||||
permissions: SharedVaultPermission
|
||||
}): Promise<SharedVaultInviteServerHash | ClientDisplayableError> {
|
||||
const response = await this.vaultInvitesServer.updateInvite({
|
||||
sharedVaultUuid: params.sharedVaultUuid,
|
||||
inviteUuid: params.inviteUuid,
|
||||
encryptedMessage: params.encryptedMessage,
|
||||
permissions: params.permissions,
|
||||
})
|
||||
|
||||
if (isErrorResponse(response)) {
|
||||
return ClientDisplayableError.FromNetworkError(response)
|
||||
}
|
||||
|
||||
return response.data.invite
|
||||
}
|
||||
}
|
||||
@@ -14,14 +14,17 @@ export interface StorageServiceInterface {
|
||||
getAllKeys(mode?: StorageValueModes): string[]
|
||||
getValue<T>(key: string, mode?: StorageValueModes, defaultValue?: T): T
|
||||
canDecryptWithKey(key: RootKeyInterface): Promise<boolean>
|
||||
savePayload(payload: PayloadInterface): Promise<void>
|
||||
savePayloads(decryptedPayloads: PayloadInterface[]): Promise<void>
|
||||
setValue<T>(key: string, value: T, mode?: StorageValueModes): void
|
||||
removeValue(key: string, mode?: StorageValueModes): Promise<void>
|
||||
setPersistencePolicy(persistencePolicy: StoragePersistencePolicies): Promise<void>
|
||||
clearAllData(): Promise<void>
|
||||
|
||||
getRawPayloads(uuids: string[]): Promise<FullyFormedTransferPayload[]>
|
||||
savePayload(payload: PayloadInterface): Promise<void>
|
||||
savePayloads(decryptedPayloads: PayloadInterface[]): Promise<void>
|
||||
deletePayloads(payloads: FullyFormedPayloadInterface[]): Promise<void>
|
||||
deletePayloadsWithUuids(uuids: string[]): Promise<void>
|
||||
|
||||
clearAllPayloads(): Promise<void>
|
||||
isEphemeralSession(): boolean
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { RemoveItemsFromMemory } from './RemoveItemsFromMemory'
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { StorageServiceInterface } from '../StorageServiceInterface'
|
||||
import { PayloadManagerInterface } from '../../Payloads/PayloadManagerInterface'
|
||||
import { PayloadEmitSource, DecryptedItemInterface } from '@standardnotes/models'
|
||||
import { Uuids } from '@standardnotes/utils'
|
||||
|
||||
describe('RemoveItemsFromMemory', () => {
|
||||
let storage: StorageServiceInterface
|
||||
let items: ItemManagerInterface
|
||||
let payloads: PayloadManagerInterface
|
||||
let removeItemsFromMemory: RemoveItemsFromMemory
|
||||
|
||||
beforeEach(() => {
|
||||
storage = {
|
||||
getRawPayloads: jest.fn().mockImplementation(() => Promise.resolve([])),
|
||||
} as unknown as StorageServiceInterface
|
||||
|
||||
items = {
|
||||
removeItemsFromMemory: jest.fn(),
|
||||
} as unknown as ItemManagerInterface
|
||||
|
||||
payloads = {
|
||||
emitPayloads: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
} as unknown as PayloadManagerInterface
|
||||
|
||||
removeItemsFromMemory = new RemoveItemsFromMemory(storage, items, payloads)
|
||||
})
|
||||
|
||||
it('should execute removeItemsFromMemory use case correctly', async () => {
|
||||
const testItems: DecryptedItemInterface[] = [
|
||||
<DecryptedItemInterface>{
|
||||
uuid: 'uuid1',
|
||||
content_type: 'type1',
|
||||
},
|
||||
<DecryptedItemInterface>{
|
||||
uuid: 'uuid2',
|
||||
content_type: 'type2',
|
||||
},
|
||||
]
|
||||
|
||||
await removeItemsFromMemory.execute(testItems)
|
||||
|
||||
expect(items.removeItemsFromMemory).toHaveBeenCalledWith(testItems)
|
||||
expect(storage.getRawPayloads).toHaveBeenCalledWith(Uuids(testItems))
|
||||
expect(payloads.emitPayloads).toHaveBeenCalledWith([], PayloadEmitSource.LocalDatabaseLoaded)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
import { StorageServiceInterface } from '../StorageServiceInterface'
|
||||
import { CreatePayload, DecryptedItemInterface, PayloadEmitSource, PayloadSource } from '@standardnotes/models'
|
||||
import { Uuids } from '@standardnotes/utils'
|
||||
import { PayloadManagerInterface } from '../../Payloads/PayloadManagerInterface'
|
||||
|
||||
export class RemoveItemsFromMemory implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private storage: StorageServiceInterface,
|
||||
private items: ItemManagerInterface,
|
||||
private payloads: PayloadManagerInterface,
|
||||
) {}
|
||||
|
||||
async execute(items: DecryptedItemInterface[]): Promise<Result<void>> {
|
||||
this.items.removeItemsFromMemory(items)
|
||||
|
||||
const rawPayloads = await this.storage.getRawPayloads(Uuids(items))
|
||||
|
||||
const encryptedPayloads = rawPayloads.map((payload) => CreatePayload(payload, PayloadSource.LocalDatabaseLoaded))
|
||||
|
||||
await this.payloads.emitPayloads(encryptedPayloads, PayloadEmitSource.LocalDatabaseLoaded)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -3,11 +3,11 @@ import { ItemManagerInterface } from '../Item/ItemManagerInterface'
|
||||
import { AnyItemInterface } from '@standardnotes/models'
|
||||
import { Uuids } from '@standardnotes/utils'
|
||||
|
||||
export class RemoveItemsLocally {
|
||||
export class DiscardItemsLocally {
|
||||
constructor(private readonly items: ItemManagerInterface, private readonly storage: StorageServiceInterface) {}
|
||||
|
||||
async execute(items: AnyItemInterface[]): Promise<void> {
|
||||
this.items.removeItemsLocally(items)
|
||||
this.items.removeItemsFromMemory(items)
|
||||
|
||||
await this.storage.deletePayloadsWithUuids(Uuids(items))
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
import { MutatorClientInterface, SyncServiceInterface } from '@standardnotes/services'
|
||||
import {
|
||||
KeySystemPasswordType,
|
||||
KeySystemRootKeyStorageMode,
|
||||
VaultListingInterface,
|
||||
VaultListingMutator,
|
||||
} from '@standardnotes/models'
|
||||
import { ChangeVaultKeyOptionsDTO } from './ChangeVaultKeyOptionsDTO'
|
||||
import { GetVault } from './GetVault'
|
||||
import { EncryptionProviderInterface } from '../../Encryption/EncryptionProviderInterface'
|
||||
import { KeySystemKeyManagerInterface } from '../../KeySystem/KeySystemKeyManagerInterface'
|
||||
import { Result, UseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class ChangeVaultKeyOptions implements UseCaseInterface<void> {
|
||||
constructor(
|
||||
private mutator: MutatorClientInterface,
|
||||
private sync: SyncServiceInterface,
|
||||
private encryption: EncryptionProviderInterface,
|
||||
private keys: KeySystemKeyManagerInterface,
|
||||
private getVault: GetVault,
|
||||
) {}
|
||||
|
||||
async execute(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
|
||||
if (dto.newPasswordType) {
|
||||
const result = await this.handleNewPasswordType(dto)
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.newStorageMode) {
|
||||
const result = await this.handleNewStorageMode(dto)
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async handleNewPasswordType(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
|
||||
if (!dto.newPasswordType) {
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
if (dto.vault.keyPasswordType === dto.newPasswordType.passwordType) {
|
||||
return Result.fail('Vault password type is already set to this type')
|
||||
}
|
||||
|
||||
if (dto.newPasswordType.passwordType === KeySystemPasswordType.UserInputted) {
|
||||
if (!dto.newPasswordType.userInputtedPassword) {
|
||||
return Result.fail('User inputted password is required')
|
||||
}
|
||||
const useStorageMode = dto.newStorageMode ?? dto.vault.keyStorageMode
|
||||
const result = await this.changePasswordTypeToUserInputted(
|
||||
dto.vault,
|
||||
dto.newPasswordType.userInputtedPassword,
|
||||
useStorageMode,
|
||||
)
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
} else if (dto.newPasswordType.passwordType === KeySystemPasswordType.Randomized) {
|
||||
const result = await this.changePasswordTypeToRandomized(dto.vault)
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async handleNewStorageMode(dto: ChangeVaultKeyOptionsDTO): Promise<Result<void>> {
|
||||
if (!dto.newStorageMode) {
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
const result = this.getVault.execute({ keySystemIdentifier: dto.vault.systemIdentifier })
|
||||
if (result.isFailed()) {
|
||||
return Result.fail('Vault not found')
|
||||
}
|
||||
|
||||
const latestVault = result.getValue()
|
||||
|
||||
if (latestVault.rootKeyParams.passwordType !== KeySystemPasswordType.UserInputted) {
|
||||
return Result.fail('Vault uses randomized password and cannot change its storage preference')
|
||||
}
|
||||
|
||||
if (dto.newStorageMode === latestVault.keyStorageMode) {
|
||||
return Result.fail('Vault already uses this storage preference')
|
||||
}
|
||||
|
||||
if (
|
||||
dto.newStorageMode === KeySystemRootKeyStorageMode.Local ||
|
||||
dto.newStorageMode === KeySystemRootKeyStorageMode.Ephemeral
|
||||
) {
|
||||
const result = await this.changeStorageModeToLocalOrEphemeral(latestVault, dto.newStorageMode)
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
} else if (dto.newStorageMode === KeySystemRootKeyStorageMode.Synced) {
|
||||
const result = await this.changeStorageModeToSynced(latestVault)
|
||||
if (result.isFailed()) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async changePasswordTypeToUserInputted(
|
||||
vault: VaultListingInterface,
|
||||
userInputtedPassword: string,
|
||||
storageMode: KeySystemRootKeyStorageMode,
|
||||
): Promise<Result<void>> {
|
||||
const newRootKey = this.encryption.createUserInputtedKeySystemRootKey({
|
||||
systemIdentifier: vault.systemIdentifier,
|
||||
userInputtedPassword: userInputtedPassword,
|
||||
})
|
||||
|
||||
if (storageMode === KeySystemRootKeyStorageMode.Synced) {
|
||||
await this.mutator.insertItem(newRootKey, true)
|
||||
} else {
|
||||
this.keys.cacheKey(newRootKey, storageMode)
|
||||
}
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
mutator.rootKeyParams = newRootKey.keyParams
|
||||
})
|
||||
|
||||
await this.keys.queueVaultItemsKeysForReencryption(vault.systemIdentifier)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async changePasswordTypeToRandomized(vault: VaultListingInterface): Promise<Result<void>> {
|
||||
if (vault.keyStorageMode !== KeySystemRootKeyStorageMode.Synced) {
|
||||
this.keys.removeKeyFromCache(vault.systemIdentifier)
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
mutator.keyStorageMode = KeySystemRootKeyStorageMode.Synced
|
||||
})
|
||||
}
|
||||
|
||||
const newRootKey = this.encryption.createRandomizedKeySystemRootKey({
|
||||
systemIdentifier: vault.systemIdentifier,
|
||||
})
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
mutator.rootKeyParams = newRootKey.keyParams
|
||||
})
|
||||
|
||||
await this.mutator.insertItem(newRootKey, true)
|
||||
|
||||
await this.keys.queueVaultItemsKeysForReencryption(vault.systemIdentifier)
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async changeStorageModeToLocalOrEphemeral(
|
||||
vault: VaultListingInterface,
|
||||
newStorageMode: KeySystemRootKeyStorageMode,
|
||||
): Promise<Result<void>> {
|
||||
const primaryKey = this.keys.getPrimaryKeySystemRootKey(vault.systemIdentifier)
|
||||
if (!primaryKey) {
|
||||
return Result.fail('No primary key found')
|
||||
}
|
||||
|
||||
if (newStorageMode === KeySystemRootKeyStorageMode.Ephemeral) {
|
||||
this.keys.removeKeyFromCache(vault.systemIdentifier)
|
||||
}
|
||||
|
||||
this.keys.cacheKey(primaryKey, newStorageMode)
|
||||
await this.keys.deleteAllSyncedKeySystemRootKeys(vault.systemIdentifier)
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
mutator.keyStorageMode = newStorageMode
|
||||
})
|
||||
|
||||
await this.sync.sync()
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
|
||||
private async changeStorageModeToSynced(vault: VaultListingInterface): Promise<Result<void>> {
|
||||
const allRootKeys = this.keys.getAllKeySystemRootKeysForVault(vault.systemIdentifier)
|
||||
const syncedRootKeys = this.keys.getSyncedKeySystemRootKeysForVault(vault.systemIdentifier)
|
||||
|
||||
this.keys.removeKeyFromCache(vault.systemIdentifier)
|
||||
|
||||
for (const key of allRootKeys) {
|
||||
const existingSyncedKey = syncedRootKeys.find((syncedKey) => syncedKey.token === key.token)
|
||||
if (existingSyncedKey) {
|
||||
continue
|
||||
}
|
||||
|
||||
await this.mutator.insertItem(key)
|
||||
}
|
||||
|
||||
await this.mutator.changeItem<VaultListingMutator>(vault, (mutator) => {
|
||||
mutator.keyStorageMode = KeySystemRootKeyStorageMode.Synced
|
||||
})
|
||||
|
||||
return Result.ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { KeySystemPasswordType, KeySystemRootKeyStorageMode, VaultListingInterface } from '@standardnotes/models'
|
||||
|
||||
export type ChangeVaultKeyOptionsDTO = {
|
||||
vault: VaultListingInterface
|
||||
newPasswordType:
|
||||
| { passwordType: KeySystemPasswordType.Randomized }
|
||||
| { passwordType: KeySystemPasswordType.UserInputted; userInputtedPassword: string }
|
||||
| undefined
|
||||
newStorageMode: KeySystemRootKeyStorageMode | undefined
|
||||
}
|
||||
+5
-7
@@ -1,8 +1,8 @@
|
||||
import { SyncServiceInterface } from './../../Sync/SyncServiceInterface'
|
||||
import { SyncServiceInterface } from '../../Sync/SyncServiceInterface'
|
||||
import { UuidGenerator } from '@standardnotes/utils'
|
||||
import {
|
||||
KeySystemRootKeyParamsInterface,
|
||||
KeySystemRootKeyPasswordType,
|
||||
KeySystemPasswordType,
|
||||
VaultListingContentSpecialized,
|
||||
VaultListingInterface,
|
||||
KeySystemRootKeyStorageMode,
|
||||
@@ -44,9 +44,7 @@ export class CreateVault {
|
||||
keySystemIdentifier,
|
||||
vaultName: dto.vaultName,
|
||||
vaultDescription: dto.vaultDescription,
|
||||
passwordType: dto.userInputtedPassword
|
||||
? KeySystemRootKeyPasswordType.UserInputted
|
||||
: KeySystemRootKeyPasswordType.Randomized,
|
||||
passwordType: dto.userInputtedPassword ? KeySystemPasswordType.UserInputted : KeySystemPasswordType.Randomized,
|
||||
rootKeyParams: rootKey.keyParams,
|
||||
storage: dto.storagePreference,
|
||||
})
|
||||
@@ -60,7 +58,7 @@ export class CreateVault {
|
||||
keySystemIdentifier: string
|
||||
vaultName: string
|
||||
vaultDescription?: string
|
||||
passwordType: KeySystemRootKeyPasswordType
|
||||
passwordType: KeySystemPasswordType
|
||||
rootKeyParams: KeySystemRootKeyParamsInterface
|
||||
storage: KeySystemRootKeyStorageMode
|
||||
}): Promise<VaultListingInterface> {
|
||||
@@ -109,7 +107,7 @@ export class CreateVault {
|
||||
if (dto.storagePreference === KeySystemRootKeyStorageMode.Synced) {
|
||||
await this.mutator.insertItem(newRootKey, true)
|
||||
} else {
|
||||
this.keys.intakeNonPersistentKeySystemRootKey(newRootKey, dto.storagePreference)
|
||||
this.keys.cacheKey(newRootKey, dto.storagePreference)
|
||||
}
|
||||
|
||||
return newRootKey
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { VaultListingInterface } from '@standardnotes/models'
|
||||
import { ItemManagerInterface } from './../../Item/ItemManagerInterface'
|
||||
import { ItemManagerInterface } from '../../Item/ItemManagerInterface'
|
||||
import { ContentType, Result, SyncUseCaseInterface } from '@standardnotes/domain-core'
|
||||
|
||||
export class GetVault implements SyncUseCaseInterface<VaultListingInterface> {
|
||||
@@ -20,7 +20,7 @@ export class GetVault implements SyncUseCaseInterface<VaultListingInterface> {
|
||||
} else {
|
||||
const result = vaults.find((listing) => listing.sharing?.sharedVaultUuid === query.sharedVaultUuid) as T
|
||||
if (!result) {
|
||||
return Result.fail('Vault not found')
|
||||
return Result.fail('Shared vault not found')
|
||||
}
|
||||
|
||||
return Result.ok(result)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user