oro-sdk 2.1.4-dev1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/client.d.ts +464 -0
- package/dist/helpers/client.d.ts +23 -0
- package/dist/helpers/index.d.ts +4 -0
- package/dist/helpers/patient-registration.d.ts +16 -0
- package/dist/helpers/vault-grants.d.ts +20 -0
- package/dist/helpers/workflow.d.ts +23 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +8 -0
- package/dist/models/client.d.ts +28 -0
- package/dist/models/consult.d.ts +102 -0
- package/dist/models/diagnosis.d.ts +122 -0
- package/dist/models/error.d.ts +26 -0
- package/dist/models/guard.d.ts +119 -0
- package/dist/models/index.d.ts +9 -0
- package/dist/models/practice.d.ts +353 -0
- package/dist/models/shared.d.ts +8 -0
- package/dist/models/vault.d.ts +124 -0
- package/dist/models/workflow.d.ts +106 -0
- package/dist/oro-sdk.cjs.development.js +7685 -0
- package/dist/oro-sdk.cjs.development.js.map +1 -0
- package/dist/oro-sdk.cjs.production.min.js +2 -0
- package/dist/oro-sdk.cjs.production.min.js.map +1 -0
- package/dist/oro-sdk.esm.js +7692 -0
- package/dist/oro-sdk.esm.js.map +1 -0
- package/dist/sdk-revision/client.d.ts +21 -0
- package/dist/sdk-revision/index.d.ts +1 -0
- package/dist/services/api.d.ts +11 -0
- package/dist/services/axios.d.ts +14 -0
- package/dist/services/consult.d.ts +54 -0
- package/dist/services/diagnosis.d.ts +44 -0
- package/dist/services/external/clinia.d.ts +82 -0
- package/dist/services/external/index.d.ts +1 -0
- package/dist/services/guard.d.ts +92 -0
- package/dist/services/index.d.ts +10 -0
- package/dist/services/practice.d.ts +100 -0
- package/dist/services/teller.d.ts +9 -0
- package/dist/services/vault.d.ts +54 -0
- package/dist/services/workflow.d.ts +21 -0
- package/package.json +63 -0
- package/src/client.ts +1843 -0
- package/src/helpers/client.ts +199 -0
- package/src/helpers/index.ts +4 -0
- package/src/helpers/patient-registration.ts +490 -0
- package/src/helpers/vault-grants.ts +51 -0
- package/src/helpers/workflow.ts +261 -0
- package/src/index.ts +61 -0
- package/src/models/client.ts +33 -0
- package/src/models/consult.ts +110 -0
- package/src/models/diagnosis.ts +141 -0
- package/src/models/error.ts +13 -0
- package/src/models/guard.ts +136 -0
- package/src/models/index.ts +9 -0
- package/src/models/practice.ts +411 -0
- package/src/models/shared.ts +6 -0
- package/src/models/vault.ts +158 -0
- package/src/models/workflow.ts +142 -0
- package/src/sdk-revision/client.ts +62 -0
- package/src/sdk-revision/index.ts +1 -0
- package/src/services/api.ts +77 -0
- package/src/services/axios.ts +91 -0
- package/src/services/consult.ts +265 -0
- package/src/services/diagnosis.ts +144 -0
- package/src/services/external/clinia.ts +133 -0
- package/src/services/external/index.ts +1 -0
- package/src/services/guard.ts +228 -0
- package/src/services/index.ts +10 -0
- package/src/services/practice.ts +537 -0
- package/src/services/teller.ts +39 -0
- package/src/services/vault.ts +178 -0
- package/src/services/workflow.ts +36 -0
package/src/client.ts
ADDED
@@ -0,0 +1,1843 @@
|
|
1
|
+
import * as OroToolbox from 'oro-toolbox'
|
2
|
+
import { CryptoRSA } from 'oro-toolbox'
|
3
|
+
import { registerPatient, sessionStorePrivateKeyName, decryptGrants, decryptConsultLockboxGrants} from './helpers'
|
4
|
+
import {
|
5
|
+
AssociatedLockboxNotFound,
|
6
|
+
AuthTokenRequest,
|
7
|
+
Consult,
|
8
|
+
ConsultRequest,
|
9
|
+
DataCreateResponse,
|
10
|
+
Document,
|
11
|
+
DocumentType,
|
12
|
+
EncryptedIndexEntry,
|
13
|
+
EncryptedVaultIndex,
|
14
|
+
Grant,
|
15
|
+
IdentityCreateRequest,
|
16
|
+
IdentityResponse,
|
17
|
+
IncompleteAuthentication,
|
18
|
+
IndexBuildError,
|
19
|
+
IndexConsultLockbox,
|
20
|
+
IndexKey,
|
21
|
+
LocalEncryptedData,
|
22
|
+
LocalizedData,
|
23
|
+
LockboxDataRequest,
|
24
|
+
LockboxGrantRequest,
|
25
|
+
LockboxManifest,
|
26
|
+
ManifestEntry,
|
27
|
+
Meta,
|
28
|
+
Metadata,
|
29
|
+
MetadataCategory,
|
30
|
+
MissingGrant,
|
31
|
+
MissingLockbox,
|
32
|
+
MissingLockboxOwner,
|
33
|
+
PersonalMeta,
|
34
|
+
PopulatedWorkflowData,
|
35
|
+
Practice,
|
36
|
+
PreferenceMeta,
|
37
|
+
RecoveryData,
|
38
|
+
RecoveryMeta,
|
39
|
+
RegisterPatientOutput,
|
40
|
+
SecretShard,
|
41
|
+
TokenData,
|
42
|
+
TosAndCpAcceptanceRequest,
|
43
|
+
UserPreference,
|
44
|
+
Uuid,
|
45
|
+
VaultIndex,
|
46
|
+
WorkflowData,
|
47
|
+
} from './models'
|
48
|
+
import { filterGrantsWithLockboxMetadata, buildLegacyVaultIndex } from './sdk-revision'
|
49
|
+
import {
|
50
|
+
ConsultService,
|
51
|
+
DiagnosisService,
|
52
|
+
GuardService,
|
53
|
+
PracticeService,
|
54
|
+
TellerService,
|
55
|
+
VaultService,
|
56
|
+
WorkflowService,
|
57
|
+
} from './services'
|
58
|
+
|
59
|
+
export class OroClient {
|
60
|
+
private rsa?: CryptoRSA
|
61
|
+
private secrets: {
|
62
|
+
lockboxUuid: string
|
63
|
+
cryptor: OroToolbox.CryptoChaCha
|
64
|
+
}[] = []
|
65
|
+
private cachedMetadataGrants: {
|
66
|
+
[filter: string]: Grant[]
|
67
|
+
} = {}
|
68
|
+
|
69
|
+
private cachedManifest: {
|
70
|
+
[filter: string]: ManifestEntry[]
|
71
|
+
} = {}
|
72
|
+
|
73
|
+
private vaultIndex?: VaultIndex
|
74
|
+
|
75
|
+
constructor(
|
76
|
+
private toolbox: typeof OroToolbox,
|
77
|
+
public tellerClient: TellerService,
|
78
|
+
public vaultClient: VaultService,
|
79
|
+
public guardClient: GuardService,
|
80
|
+
public practiceClient: PracticeService,
|
81
|
+
public consultClient: ConsultService,
|
82
|
+
public workflowClient: WorkflowService,
|
83
|
+
public diagnosisClient: DiagnosisService,
|
84
|
+
private authenticationCallback?: (err: Error) => void
|
85
|
+
) {}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* clears the vaultIndex and cached metadata grants
|
89
|
+
*/
|
90
|
+
public async cleanIndex() {
|
91
|
+
this.vaultIndex = undefined
|
92
|
+
this.cachedMetadataGrants = {}
|
93
|
+
this.cachedManifest = {}
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* Generates an RSA key pair and password payload (rsa private key encrypted with the password)
|
98
|
+
* Calls Guard to sign up with the email address, password, practice, legal and token data
|
99
|
+
*
|
100
|
+
* @param email
|
101
|
+
* @param password
|
102
|
+
* @param practice
|
103
|
+
* @param legal
|
104
|
+
* @param tokenData
|
105
|
+
* @returns
|
106
|
+
*/
|
107
|
+
public async signUp(
|
108
|
+
email: string,
|
109
|
+
password: string,
|
110
|
+
practice: Practice,
|
111
|
+
tosAndCpAcceptance: TosAndCpAcceptanceRequest,
|
112
|
+
tokenData?: TokenData,
|
113
|
+
subscription?: boolean
|
114
|
+
): Promise<IdentityResponse> {
|
115
|
+
this.rsa = new CryptoRSA()
|
116
|
+
const privateKey = this.rsa.private()
|
117
|
+
|
118
|
+
const symmetricEncryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
119
|
+
password
|
120
|
+
)
|
121
|
+
const recoveryPassword = symmetricEncryptor.bytesEncryptToBase64Payload(
|
122
|
+
privateKey
|
123
|
+
)
|
124
|
+
|
125
|
+
const hashedPassword = this.toolbox.hashStringToBase64(
|
126
|
+
this.toolbox.hashStringToBase64(password)
|
127
|
+
)
|
128
|
+
|
129
|
+
const signupRequest: IdentityCreateRequest = {
|
130
|
+
practiceUuid: practice.uuid,
|
131
|
+
email: email.toLowerCase(),
|
132
|
+
password: hashedPassword,
|
133
|
+
publicKey: this.toolbox.encodeToBase64(this.rsa.public()),
|
134
|
+
recoveryPassword,
|
135
|
+
tosAndCpAcceptance,
|
136
|
+
tokenData,
|
137
|
+
subscription,
|
138
|
+
}
|
139
|
+
|
140
|
+
const identity = await this.guardClient.identityCreate(signupRequest)
|
141
|
+
|
142
|
+
if (identity.recoveryLogin) {
|
143
|
+
//Ensure we can recover from a page reload
|
144
|
+
let symetricEncryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
145
|
+
identity.recoveryLogin
|
146
|
+
)
|
147
|
+
sessionStorage.setItem(
|
148
|
+
sessionStorePrivateKeyName(identity.id),
|
149
|
+
symetricEncryptor.bytesEncryptToBase64Payload(privateKey)
|
150
|
+
)
|
151
|
+
}
|
152
|
+
|
153
|
+
return identity
|
154
|
+
}
|
155
|
+
|
156
|
+
/**
|
157
|
+
* Parse the given accessToken claims by calling guard whoami and update theidentity to set it's emailConfirmed flag
|
158
|
+
* @param accessToken
|
159
|
+
* @returns The identity related to confirmedEmail
|
160
|
+
*/
|
161
|
+
public async confirmEmail(accessToken: string): Promise<IdentityResponse> {
|
162
|
+
this.guardClient.setTokens({ accessToken })
|
163
|
+
const claims = await this.guardClient.whoAmI()
|
164
|
+
return this.guardClient.identityUpdate(claims.sub, {
|
165
|
+
emailConfirmed: true,
|
166
|
+
})
|
167
|
+
}
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Calls Guard to sign in with the email address, password and one time password (if MFA is enabled)
|
171
|
+
* Then recover's the rsa private key from the recovery payload
|
172
|
+
*
|
173
|
+
* @param practiceUuid
|
174
|
+
* @param email
|
175
|
+
* @param password
|
176
|
+
* @param otp
|
177
|
+
* @returns the user identity
|
178
|
+
*/
|
179
|
+
public async signIn(
|
180
|
+
practiceUuid: Uuid,
|
181
|
+
email: string,
|
182
|
+
password: string,
|
183
|
+
otp?: string
|
184
|
+
): Promise<IdentityResponse> {
|
185
|
+
const hashedPassword = this.toolbox.hashStringToBase64(
|
186
|
+
this.toolbox.hashStringToBase64(password)
|
187
|
+
)
|
188
|
+
const tokenRequest: AuthTokenRequest = {
|
189
|
+
practiceUuid,
|
190
|
+
email: email.toLowerCase(),
|
191
|
+
password: hashedPassword,
|
192
|
+
otp,
|
193
|
+
}
|
194
|
+
|
195
|
+
await this.guardClient.authToken(tokenRequest)
|
196
|
+
const userUuid = (await this.guardClient.whoAmI()).sub
|
197
|
+
|
198
|
+
// Updates the rsa key to the one generated on the backend
|
199
|
+
await this.recoverPrivateKeyFromPassword(userUuid, password)
|
200
|
+
return await this.guardClient.identityGet(userUuid)
|
201
|
+
}
|
202
|
+
|
203
|
+
/**
|
204
|
+
* Will attempt to recover an existing login session and set back
|
205
|
+
* the private key in scope
|
206
|
+
*/
|
207
|
+
public async resumeSession() {
|
208
|
+
const id = (await this.guardClient.whoAmI()).sub
|
209
|
+
const recoveryPayload = sessionStorage.getItem(
|
210
|
+
sessionStorePrivateKeyName(id)
|
211
|
+
)
|
212
|
+
const recoveryKey = (await this.guardClient.identityGet(id))
|
213
|
+
.recoveryLogin
|
214
|
+
|
215
|
+
if (!recoveryKey || !recoveryPayload) throw IncompleteAuthentication
|
216
|
+
|
217
|
+
const symmetricDecryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
218
|
+
recoveryKey
|
219
|
+
)
|
220
|
+
let privateKey = symmetricDecryptor.base64PayloadDecryptToBytes(
|
221
|
+
recoveryPayload
|
222
|
+
)
|
223
|
+
this.rsa = this.toolbox.CryptoRSA.fromKey(privateKey)
|
224
|
+
}
|
225
|
+
|
226
|
+
/**
|
227
|
+
* This function let's you encrypt locally an Object
|
228
|
+
* @param value the Object to encrypt
|
229
|
+
* @returns a LocalEncryptedData Object
|
230
|
+
* @throws IncompleteAuthentication if rsa is not set
|
231
|
+
* @calls authenticationCallback if rsa is not set
|
232
|
+
*/
|
233
|
+
public localEncryptToJsonPayload(value: any): LocalEncryptedData {
|
234
|
+
if (!this.rsa) {
|
235
|
+
if (this.authenticationCallback) {
|
236
|
+
this.authenticationCallback(new IncompleteAuthentication())
|
237
|
+
}
|
238
|
+
|
239
|
+
throw new IncompleteAuthentication()
|
240
|
+
}
|
241
|
+
|
242
|
+
const chaChaKey = new this.toolbox.CryptoChaCha()
|
243
|
+
|
244
|
+
const encryptedData = chaChaKey.jsonEncryptToBase64Payload(value)
|
245
|
+
const encryptedKey = this.toolbox.encodeToBase64(
|
246
|
+
this.rsa.encryptToBytes(chaChaKey.key())
|
247
|
+
)
|
248
|
+
|
249
|
+
return { encryptedData, encryptedKey }
|
250
|
+
}
|
251
|
+
|
252
|
+
/**
|
253
|
+
* This function let's you decrypt a LocalEncryptedData object
|
254
|
+
* @param value a LocalEncryptedData object
|
255
|
+
* @returns a decrypted Object
|
256
|
+
* @throws IncompleteAuthentication if rsa is not set
|
257
|
+
* @calls authenticationCallback if rsa is not set
|
258
|
+
*/
|
259
|
+
public localDecryptJsonPayload({
|
260
|
+
encryptedKey,
|
261
|
+
encryptedData,
|
262
|
+
}: LocalEncryptedData): any {
|
263
|
+
if (!this.rsa) {
|
264
|
+
if (this.authenticationCallback) {
|
265
|
+
this.authenticationCallback(new IncompleteAuthentication())
|
266
|
+
}
|
267
|
+
|
268
|
+
throw new IncompleteAuthentication()
|
269
|
+
}
|
270
|
+
|
271
|
+
const chaChaKey = this.rsa.base64DecryptToBytes(encryptedKey)
|
272
|
+
const decryptedData = this.toolbox.CryptoChaCha.fromKey(
|
273
|
+
chaChaKey
|
274
|
+
).base64PayloadDecryptToJson(encryptedData)
|
275
|
+
|
276
|
+
return decryptedData
|
277
|
+
}
|
278
|
+
|
279
|
+
/**
|
280
|
+
* Effectively kills your "session"
|
281
|
+
*/
|
282
|
+
public async signOut() {
|
283
|
+
this.rsa = undefined
|
284
|
+
this.secrets = []
|
285
|
+
this.guardClient.setTokens({
|
286
|
+
accessToken: undefined,
|
287
|
+
refreshToken: undefined,
|
288
|
+
})
|
289
|
+
await this.guardClient.authLogout()
|
290
|
+
}
|
291
|
+
|
292
|
+
/**
|
293
|
+
* @name registerPatient
|
294
|
+
* @description The complete flow to register a patient
|
295
|
+
*
|
296
|
+
* Steps:
|
297
|
+
* 1. Create a consult (checks if payment has been done)
|
298
|
+
* 2. Creates a lockbox
|
299
|
+
* 3. Grants lockbox access to all practice personnel
|
300
|
+
* 4. Creates secure identification, medical, onboarding data
|
301
|
+
* 5. Generates and stores the rsa key pair and recovery payloads
|
302
|
+
*
|
303
|
+
* @param patientUuid
|
304
|
+
* @param consult
|
305
|
+
* @param workflow
|
306
|
+
* @param recoveryQA
|
307
|
+
* @returns
|
308
|
+
*/
|
309
|
+
public async registerPatient(
|
310
|
+
patientUuid: Uuid,
|
311
|
+
consult: ConsultRequest,
|
312
|
+
workflow: WorkflowData,
|
313
|
+
recoveryQA?: {
|
314
|
+
recoverySecurityQuestions: string[]
|
315
|
+
recoverySecurityAnswers: string[]
|
316
|
+
}
|
317
|
+
): Promise<RegisterPatientOutput> {
|
318
|
+
if (!this.rsa) throw IncompleteAuthentication
|
319
|
+
return registerPatient(
|
320
|
+
patientUuid,
|
321
|
+
consult,
|
322
|
+
workflow,
|
323
|
+
this,
|
324
|
+
this.toolbox.uuid(),
|
325
|
+
recoveryQA
|
326
|
+
)
|
327
|
+
}
|
328
|
+
|
329
|
+
/**
|
330
|
+
* Builds the vault index for the logged user
|
331
|
+
*
|
332
|
+
* Steps:
|
333
|
+
* 1. Retrieves, decrypts and sets the lockbox IndexSnapshot
|
334
|
+
* 2. Retrieves, decrypts and adds all other index entries starting at the snapshot timestamp
|
335
|
+
* 3. Updates the IndexSnapshot if changed
|
336
|
+
* @deprecated
|
337
|
+
* @returns the latest vault index
|
338
|
+
*/
|
339
|
+
public async buildVaultIndex(forceRefresh: boolean = false) {
|
340
|
+
if (!this.vaultIndex || forceRefresh)
|
341
|
+
await buildLegacyVaultIndex(this)
|
342
|
+
}
|
343
|
+
|
344
|
+
/**
|
345
|
+
* Setter for the vault index
|
346
|
+
* @param index
|
347
|
+
*/
|
348
|
+
public setVaultIndex(index: VaultIndex) {
|
349
|
+
this.vaultIndex = index
|
350
|
+
}
|
351
|
+
|
352
|
+
/**
|
353
|
+
* Fetches all grants, and consultations that exist in each lockbox
|
354
|
+
* Then updates the index for the current user with the lockbox consult relationship
|
355
|
+
*/
|
356
|
+
public async forceUpdateIndexEntries() {
|
357
|
+
let grants = await this.getGrants()
|
358
|
+
|
359
|
+
let indexConsultLockbox: IndexConsultLockbox[] = await Promise.all(
|
360
|
+
grants.map(
|
361
|
+
async (grant: Grant) =>
|
362
|
+
await this.vaultClient
|
363
|
+
.lockboxMetadataGet(
|
364
|
+
grant.lockboxUuid!,
|
365
|
+
['consultationId'],
|
366
|
+
[],
|
367
|
+
{ category: MetadataCategory.Consultation },
|
368
|
+
grant.lockboxOwnerUuid
|
369
|
+
)
|
370
|
+
.then((consults) => {
|
371
|
+
try {
|
372
|
+
return consults[0].map((consult: any) => {
|
373
|
+
return {
|
374
|
+
...consult,
|
375
|
+
grant: {
|
376
|
+
lockboxOwnerUuid:
|
377
|
+
grant.lockboxOwnerUuid,
|
378
|
+
lockboxUuid: grant.lockboxUuid,
|
379
|
+
},
|
380
|
+
}
|
381
|
+
})
|
382
|
+
} catch (e) {
|
383
|
+
// No consultations in lockbox or index could not be created
|
384
|
+
return []
|
385
|
+
}
|
386
|
+
})
|
387
|
+
.catch(() => [])
|
388
|
+
)
|
389
|
+
).then((consults) => consults.flat())
|
390
|
+
this.vaultIndexAdd({
|
391
|
+
[IndexKey.Consultation]: indexConsultLockbox,
|
392
|
+
})
|
393
|
+
.then(() => alert('The Index was successfully updated!'))
|
394
|
+
.catch(() => console.error('The index failed to update!'))
|
395
|
+
}
|
396
|
+
|
397
|
+
/**
|
398
|
+
* Generates, encrypts and adds entries to vault index for a given index owner
|
399
|
+
*
|
400
|
+
* @param entries
|
401
|
+
* @param indexOwnerUuid
|
402
|
+
*/
|
403
|
+
public async vaultIndexAdd(entries: VaultIndex, indexOwnerUuid?: Uuid) {
|
404
|
+
if (!this.rsa) throw IncompleteAuthentication
|
405
|
+
|
406
|
+
let rsaPub: Uint8Array
|
407
|
+
if (indexOwnerUuid) {
|
408
|
+
let base64IndexOwnerPubKey = (
|
409
|
+
await this.guardClient.identityGet(indexOwnerUuid)
|
410
|
+
).publicKey
|
411
|
+
rsaPub = this.toolbox.decodeFromBase64(base64IndexOwnerPubKey)
|
412
|
+
} else {
|
413
|
+
rsaPub = this.rsa.public()
|
414
|
+
}
|
415
|
+
|
416
|
+
let encryptedIndex: EncryptedVaultIndex = {}
|
417
|
+
|
418
|
+
for (let keyString of Object.keys(entries)) {
|
419
|
+
let key = keyString as keyof VaultIndex
|
420
|
+
switch (key) {
|
421
|
+
case IndexKey.ConsultationLockbox:
|
422
|
+
encryptedIndex[key] = (entries[
|
423
|
+
key
|
424
|
+
] as IndexConsultLockbox[])
|
425
|
+
.map((e) => ({
|
426
|
+
...e,
|
427
|
+
uniqueHash: e.consultationId,
|
428
|
+
}))
|
429
|
+
.map(
|
430
|
+
(e: IndexConsultLockbox) =>
|
431
|
+
({
|
432
|
+
uuid: e.uuid,
|
433
|
+
timestamp: e.timestamp,
|
434
|
+
uniqueHash: e.uniqueHash,
|
435
|
+
encryptedIndexEntry: CryptoRSA.jsonWithPubEncryptToBase64(
|
436
|
+
{
|
437
|
+
consultationId: e.consultationId,
|
438
|
+
grant: e.grant,
|
439
|
+
},
|
440
|
+
rsaPub
|
441
|
+
),
|
442
|
+
} as EncryptedIndexEntry)
|
443
|
+
)
|
444
|
+
break
|
445
|
+
//// DEPRECATED : REMOVE ME : BEGIN ///////////////////////////////////////////
|
446
|
+
case IndexKey.Consultation:
|
447
|
+
encryptedIndex[key] = (entries[
|
448
|
+
key
|
449
|
+
] as IndexConsultLockbox[])
|
450
|
+
.map((e) => ({
|
451
|
+
...e,
|
452
|
+
uniqueHash: this.toolbox.hashStringToBase64(
|
453
|
+
JSON.stringify({
|
454
|
+
consultationId: e.consultationId,
|
455
|
+
grant: e.grant,
|
456
|
+
})
|
457
|
+
),
|
458
|
+
}))
|
459
|
+
.filter(
|
460
|
+
(e) =>
|
461
|
+
!this.vaultIndex ||
|
462
|
+
!this.vaultIndex[IndexKey.Consultation]?.find(
|
463
|
+
(v) => v.uniqueHash === e.uniqueHash
|
464
|
+
)
|
465
|
+
)
|
466
|
+
.map(
|
467
|
+
(e: IndexConsultLockbox) =>
|
468
|
+
({
|
469
|
+
uuid: e.uuid,
|
470
|
+
timestamp: e.timestamp,
|
471
|
+
uniqueHash: e.uniqueHash,
|
472
|
+
encryptedIndexEntry: CryptoRSA.jsonWithPubEncryptToBase64(
|
473
|
+
{
|
474
|
+
consultationId: e.consultationId,
|
475
|
+
grant: e.grant,
|
476
|
+
},
|
477
|
+
rsaPub
|
478
|
+
),
|
479
|
+
} as EncryptedIndexEntry)
|
480
|
+
)
|
481
|
+
break
|
482
|
+
//// DEPRECATED : REMOVE ME : END ///////////////////////////////////////////
|
483
|
+
}
|
484
|
+
}
|
485
|
+
await this.vaultClient.vaultIndexPut(encryptedIndex, indexOwnerUuid)
|
486
|
+
}
|
487
|
+
|
488
|
+
/**
|
489
|
+
* adds or updates the index snapshot for the logged user
|
490
|
+
* @param index
|
491
|
+
*/
|
492
|
+
public async indexSnapshotAdd(index: VaultIndex) {
|
493
|
+
if (!this.rsa) throw IncompleteAuthentication
|
494
|
+
let rsaPub: Uint8Array = this.rsa.public()
|
495
|
+
|
496
|
+
let cleanedIndex: VaultIndex = {
|
497
|
+
[IndexKey.Consultation]: index[IndexKey.Consultation]
|
498
|
+
?.filter((c) => c)
|
499
|
+
.map((c) => {
|
500
|
+
return {
|
501
|
+
grant: c.grant,
|
502
|
+
consultationId: c.consultationId,
|
503
|
+
}
|
504
|
+
}),
|
505
|
+
}
|
506
|
+
|
507
|
+
// the data of the snapshot should not contain the `IndexEntry` data
|
508
|
+
// (will create conflicts while updating)
|
509
|
+
let encryptedIndexEntry = CryptoRSA.jsonWithPubEncryptToBase64(
|
510
|
+
cleanedIndex,
|
511
|
+
rsaPub
|
512
|
+
)
|
513
|
+
|
514
|
+
// The encryptedIndexEntry can have the uuid and timstamp (for updating)
|
515
|
+
let encryptedIndex: EncryptedIndexEntry = {
|
516
|
+
uuid: index.uuid,
|
517
|
+
timestamp: index.timestamp,
|
518
|
+
encryptedIndexEntry,
|
519
|
+
}
|
520
|
+
this.vaultClient.vaultIndexSnapshotPut(encryptedIndex)
|
521
|
+
}
|
522
|
+
|
523
|
+
/**
|
524
|
+
* @name grantLockbox
|
525
|
+
* @description Grants a lockbox by retrieving the shared secret of the lockbox and encrypting it with the grantees public key
|
526
|
+
* @param granteeUuid
|
527
|
+
* @param lockboxUuid
|
528
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
529
|
+
*/
|
530
|
+
public async grantLockbox(
|
531
|
+
granteeUuid: Uuid,
|
532
|
+
lockboxUuid: Uuid,
|
533
|
+
lockboxOwnerUuid?: Uuid
|
534
|
+
) {
|
535
|
+
if (!this.rsa) throw IncompleteAuthentication
|
536
|
+
|
537
|
+
let secret = (
|
538
|
+
await this.getCachedSecretCryptor(lockboxUuid, lockboxOwnerUuid)
|
539
|
+
).key()
|
540
|
+
let base64GranteePublicKey = (
|
541
|
+
await this.guardClient.identityGet(granteeUuid)
|
542
|
+
).publicKey
|
543
|
+
let granteePublicKey = this.toolbox.decodeFromBase64(
|
544
|
+
base64GranteePublicKey
|
545
|
+
)
|
546
|
+
|
547
|
+
let granteeEncryptedSecret = CryptoRSA.bytesWithPubEncryptToBase64(
|
548
|
+
secret,
|
549
|
+
granteePublicKey
|
550
|
+
)
|
551
|
+
let request: LockboxGrantRequest = {
|
552
|
+
encryptedSecret: granteeEncryptedSecret,
|
553
|
+
granteeUuid: granteeUuid,
|
554
|
+
}
|
555
|
+
await this.vaultClient.lockboxGrant(
|
556
|
+
lockboxUuid,
|
557
|
+
request,
|
558
|
+
lockboxOwnerUuid
|
559
|
+
)
|
560
|
+
}
|
561
|
+
|
562
|
+
/**
|
563
|
+
* @name createMessageData
|
564
|
+
* @description Creates a Base64 encrypted Payload to send and store in the vault from a message string
|
565
|
+
* @param lockboxUuid
|
566
|
+
* @param message
|
567
|
+
* @param consultationId the consultation for which this message is sent
|
568
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
569
|
+
* @param previousDataUuid if it's a revision of existing file, specify the previous data uuid
|
570
|
+
* @returns the data uuid
|
571
|
+
*/
|
572
|
+
public async createMessageData(
|
573
|
+
lockboxUuid: Uuid,
|
574
|
+
message: string,
|
575
|
+
consultationId: string,
|
576
|
+
lockboxOwnerUuid?: Uuid,
|
577
|
+
previousDataUuid?: Uuid
|
578
|
+
): Promise<DataCreateResponse> {
|
579
|
+
if (!this.rsa) throw IncompleteAuthentication
|
580
|
+
|
581
|
+
let symmetricEncryptor = await this.getCachedSecretCryptor(
|
582
|
+
lockboxUuid,
|
583
|
+
lockboxOwnerUuid
|
584
|
+
)
|
585
|
+
|
586
|
+
let encryptedData = symmetricEncryptor.jsonEncryptToBase64Payload(
|
587
|
+
message
|
588
|
+
)
|
589
|
+
let encryptedPrivateMeta = symmetricEncryptor.jsonEncryptToBase64Payload(
|
590
|
+
{ author: (await this.guardClient.whoAmI()).sub }
|
591
|
+
)
|
592
|
+
|
593
|
+
let meta = {
|
594
|
+
consultationId,
|
595
|
+
category: MetadataCategory.Consultation,
|
596
|
+
documentType: DocumentType.Message,
|
597
|
+
contentType: 'text/plain',
|
598
|
+
}
|
599
|
+
|
600
|
+
let request: LockboxDataRequest = {
|
601
|
+
data: encryptedData,
|
602
|
+
publicMetadata: meta,
|
603
|
+
privateMetadata: encryptedPrivateMeta,
|
604
|
+
}
|
605
|
+
|
606
|
+
return this.tellerClient.lockboxDataStore(
|
607
|
+
lockboxUuid,
|
608
|
+
request,
|
609
|
+
lockboxOwnerUuid,
|
610
|
+
previousDataUuid
|
611
|
+
)
|
612
|
+
}
|
613
|
+
|
614
|
+
/**
|
615
|
+
* @name createMessageAttachmentData
|
616
|
+
* @description Creates a Base64 encrypted Payload to send and store in the vault from a file
|
617
|
+
* @param lockboxUuid
|
618
|
+
* @param data the file stored
|
619
|
+
* @param consultationId the consultation for which this message is sent
|
620
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
621
|
+
* @param previousDataUuid if it's a revision of existing file, specify the previous data uuid
|
622
|
+
* @returns the data uuid
|
623
|
+
*/
|
624
|
+
public async createMessageAttachmentData(
|
625
|
+
lockboxUuid: Uuid,
|
626
|
+
data: File,
|
627
|
+
consultationId: string,
|
628
|
+
lockboxOwnerUuid?: Uuid,
|
629
|
+
previousDataUuid?: Uuid
|
630
|
+
): Promise<DataCreateResponse> {
|
631
|
+
if (!this.rsa) throw IncompleteAuthentication
|
632
|
+
|
633
|
+
let symmetricEncryptor = await this.getCachedSecretCryptor(
|
634
|
+
lockboxUuid,
|
635
|
+
lockboxOwnerUuid
|
636
|
+
)
|
637
|
+
let encryptedData = symmetricEncryptor.bytesEncryptToBase64Payload(
|
638
|
+
new Uint8Array(await data.arrayBuffer())
|
639
|
+
)
|
640
|
+
let encryptedPrivateMeta = symmetricEncryptor.jsonEncryptToBase64Payload(
|
641
|
+
{
|
642
|
+
author: (await this.guardClient.whoAmI()).sub,
|
643
|
+
fileName: data.name,
|
644
|
+
lastModified: data.lastModified,
|
645
|
+
size: data.size,
|
646
|
+
}
|
647
|
+
)
|
648
|
+
|
649
|
+
let meta = {
|
650
|
+
consultationId,
|
651
|
+
category: MetadataCategory.Consultation,
|
652
|
+
documentType: DocumentType.Message,
|
653
|
+
contentType: data.type,
|
654
|
+
}
|
655
|
+
|
656
|
+
let request: LockboxDataRequest = {
|
657
|
+
data: encryptedData,
|
658
|
+
publicMetadata: meta,
|
659
|
+
privateMetadata: encryptedPrivateMeta,
|
660
|
+
}
|
661
|
+
|
662
|
+
return this.tellerClient.lockboxDataStore(
|
663
|
+
lockboxUuid,
|
664
|
+
request,
|
665
|
+
lockboxOwnerUuid,
|
666
|
+
previousDataUuid
|
667
|
+
)
|
668
|
+
}
|
669
|
+
|
670
|
+
/**
|
671
|
+
* @name createAttachmentData
|
672
|
+
* @description Creates a Base64 encrypted Payload to send and store in the vault from a file
|
673
|
+
* @param lockboxUuid
|
674
|
+
* @param data the file stored
|
675
|
+
* @param consultationId the consultation for which this message is sent
|
676
|
+
* @param category the category for the attachment data
|
677
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
678
|
+
* @param previousDataUuid if it's a revision of existing file, specify the previous data uuid
|
679
|
+
* @returns the data uuid
|
680
|
+
*/
|
681
|
+
public async createConsultationAttachmentData(
|
682
|
+
lockboxUuid: Uuid,
|
683
|
+
data: File,
|
684
|
+
consultationId: string,
|
685
|
+
documentType: DocumentType,
|
686
|
+
lockboxOwnerUuid?: Uuid,
|
687
|
+
previousDataUuid?: Uuid
|
688
|
+
): Promise<DataCreateResponse> {
|
689
|
+
if (!this.rsa) throw IncompleteAuthentication
|
690
|
+
|
691
|
+
return this.createBytesData<Meta | any>(
|
692
|
+
lockboxUuid,
|
693
|
+
new Uint8Array(await data.arrayBuffer()),
|
694
|
+
{
|
695
|
+
consultationId,
|
696
|
+
category: MetadataCategory.Consultation,
|
697
|
+
documentType,
|
698
|
+
contentType: data.type,
|
699
|
+
},
|
700
|
+
{
|
701
|
+
author: (await this.guardClient.whoAmI()).sub,
|
702
|
+
fileName: data.name,
|
703
|
+
},
|
704
|
+
lockboxOwnerUuid,
|
705
|
+
previousDataUuid
|
706
|
+
)
|
707
|
+
}
|
708
|
+
|
709
|
+
/**
|
710
|
+
* @name createJsonData
|
711
|
+
* @description Creates a Base64 encrypted Payload to send and store in the vault. With the data input as a JSON
|
712
|
+
* @param lockboxUuid
|
713
|
+
* @param data
|
714
|
+
* @param meta
|
715
|
+
* @param privateMeta the metadata that will be secured in the vault
|
716
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
717
|
+
* @param previousDataUuid if it's a revision of existing data, specify the previous data uuid
|
718
|
+
* @returns the data uuid
|
719
|
+
*/
|
720
|
+
public async createJsonData<T = Meta>(
|
721
|
+
lockboxUuid: Uuid,
|
722
|
+
data: any,
|
723
|
+
meta?: T,
|
724
|
+
privateMeta?: { [val: string]: any },
|
725
|
+
lockboxOwnerUuid?: Uuid,
|
726
|
+
previousDataUuid?: Uuid
|
727
|
+
): Promise<DataCreateResponse> {
|
728
|
+
if (!this.rsa) throw IncompleteAuthentication
|
729
|
+
|
730
|
+
let symmetricEncryptor = await this.getCachedSecretCryptor(
|
731
|
+
lockboxUuid,
|
732
|
+
lockboxOwnerUuid
|
733
|
+
)
|
734
|
+
let encryptedData = symmetricEncryptor.jsonEncryptToBase64Payload(data)
|
735
|
+
let encryptedPrivateMeta = symmetricEncryptor.jsonEncryptToBase64Payload(
|
736
|
+
privateMeta
|
737
|
+
)
|
738
|
+
|
739
|
+
let request: LockboxDataRequest = {
|
740
|
+
data: encryptedData,
|
741
|
+
publicMetadata: meta,
|
742
|
+
privateMetadata: encryptedPrivateMeta,
|
743
|
+
}
|
744
|
+
|
745
|
+
return this.tellerClient.lockboxDataStore(
|
746
|
+
lockboxUuid,
|
747
|
+
request,
|
748
|
+
lockboxOwnerUuid,
|
749
|
+
previousDataUuid
|
750
|
+
)
|
751
|
+
}
|
752
|
+
|
753
|
+
/**
|
754
|
+
* Get or upsert a data in lockbox
|
755
|
+
* @param lockboxUuid the lockbox uuid
|
756
|
+
* @param data the data to insert
|
757
|
+
* @param publicMetadata the public Metadata
|
758
|
+
* @param privateMetadata the private Metadata
|
759
|
+
* @param forceReplace set true when the insertion of data requires to replace the data when it exists already
|
760
|
+
* @returns the data uuid
|
761
|
+
*/
|
762
|
+
public async getOrInsertJsonData<M = Metadata>(
|
763
|
+
lockboxUuid: Uuid,
|
764
|
+
data: any,
|
765
|
+
publicMetadata: M,
|
766
|
+
privateMetadata: Metadata,
|
767
|
+
forceReplace: boolean = false
|
768
|
+
): Promise<Uuid> {
|
769
|
+
let manifest = await this.vaultClient.lockboxManifestGet(
|
770
|
+
lockboxUuid,
|
771
|
+
publicMetadata
|
772
|
+
)
|
773
|
+
if (!forceReplace && manifest.length > 0) {
|
774
|
+
console.log(
|
775
|
+
`The data for ${JSON.stringify(publicMetadata)} already exist`
|
776
|
+
)
|
777
|
+
return manifest[0].dataUuid
|
778
|
+
} else
|
779
|
+
return (
|
780
|
+
await this.createJsonData<M>(
|
781
|
+
lockboxUuid,
|
782
|
+
data,
|
783
|
+
publicMetadata,
|
784
|
+
privateMetadata,
|
785
|
+
undefined,
|
786
|
+
forceReplace && manifest.length > 0
|
787
|
+
? manifest[0].dataUuid
|
788
|
+
: undefined // if forceReplace and data already exist, then replace data. Otherwise insert it
|
789
|
+
).catch((err) => {
|
790
|
+
console.error(
|
791
|
+
`Error while upserting data ${JSON.stringify(
|
792
|
+
publicMetadata
|
793
|
+
)} data`,
|
794
|
+
err
|
795
|
+
)
|
796
|
+
throw err
|
797
|
+
})
|
798
|
+
).dataUuid
|
799
|
+
}
|
800
|
+
|
801
|
+
/**
|
802
|
+
* @name createBytesData
|
803
|
+
* @description Creates a Base64 encrypted Payload to send and store in the vault. With the data input as a Bytes
|
804
|
+
* @param lockboxUuid
|
805
|
+
* @param data
|
806
|
+
* @param meta
|
807
|
+
* @param privateMeta the metadata that will be secured in the vault
|
808
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
809
|
+
* @param previousDataUuid if it's a revision of existing data, specify the previous data uuid
|
810
|
+
* @returns the data uuid
|
811
|
+
*/
|
812
|
+
public async createBytesData<T = Meta>(
|
813
|
+
lockboxUuid: Uuid,
|
814
|
+
data: Uint8Array,
|
815
|
+
meta: T,
|
816
|
+
privateMeta: { [val: string]: any },
|
817
|
+
lockboxOwnerUuid?: Uuid,
|
818
|
+
previousDataUuid?: Uuid
|
819
|
+
): Promise<DataCreateResponse> {
|
820
|
+
if (!this.rsa) throw IncompleteAuthentication
|
821
|
+
let symmetricEncryptor = await this.getCachedSecretCryptor(
|
822
|
+
lockboxUuid,
|
823
|
+
lockboxOwnerUuid
|
824
|
+
)
|
825
|
+
let encryptedData = symmetricEncryptor.bytesEncryptToBase64Payload(data)
|
826
|
+
let encryptedPrivateMeta = symmetricEncryptor.jsonEncryptToBase64Payload(
|
827
|
+
privateMeta
|
828
|
+
)
|
829
|
+
|
830
|
+
let request: LockboxDataRequest = {
|
831
|
+
data: encryptedData,
|
832
|
+
publicMetadata: meta,
|
833
|
+
privateMetadata: encryptedPrivateMeta,
|
834
|
+
}
|
835
|
+
|
836
|
+
return this.tellerClient.lockboxDataStore(
|
837
|
+
lockboxUuid,
|
838
|
+
request,
|
839
|
+
lockboxOwnerUuid,
|
840
|
+
previousDataUuid
|
841
|
+
)
|
842
|
+
}
|
843
|
+
|
844
|
+
/**
|
845
|
+
* @name getJsonData
|
846
|
+
* @description Fetches and decrypts the lockbox data with the cached shared secret.
|
847
|
+
* Decrypts the data to a valid JSON object. If this is impossible, the call to the WASM binary will fail
|
848
|
+
*
|
849
|
+
* @type T is the generic type specifying the return type object of the function
|
850
|
+
* @param lockboxUuid
|
851
|
+
* @param dataUuid
|
852
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
853
|
+
* @returns the data specified by the generic type <T>
|
854
|
+
*/
|
855
|
+
public async getJsonData<T = any>(
|
856
|
+
lockboxUuid: Uuid,
|
857
|
+
dataUuid: Uuid,
|
858
|
+
lockboxOwnerUuid?: Uuid
|
859
|
+
): Promise<T> {
|
860
|
+
if (!this.rsa) throw IncompleteAuthentication
|
861
|
+
|
862
|
+
let [encryptedPayload, symmetricDecryptor] = await Promise.all([
|
863
|
+
this.vaultClient.lockboxDataGet(
|
864
|
+
lockboxUuid,
|
865
|
+
dataUuid,
|
866
|
+
lockboxOwnerUuid
|
867
|
+
),
|
868
|
+
this.getCachedSecretCryptor(lockboxUuid, lockboxOwnerUuid),
|
869
|
+
])
|
870
|
+
|
871
|
+
return symmetricDecryptor.base64PayloadDecryptToJson(
|
872
|
+
encryptedPayload.data
|
873
|
+
)
|
874
|
+
}
|
875
|
+
/**
|
876
|
+
* @description Fetches and decrypts the lockbox data with the cached shared secret.
|
877
|
+
* @param lockboxUuid
|
878
|
+
* @param dataUuid
|
879
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
880
|
+
* @returns the bytes data
|
881
|
+
*/
|
882
|
+
public async getBytesData(
|
883
|
+
lockboxUuid: Uuid,
|
884
|
+
dataUuid: Uuid,
|
885
|
+
lockboxOwnerUuid?: Uuid
|
886
|
+
): Promise<Uint8Array> {
|
887
|
+
if (!this.rsa) throw IncompleteAuthentication
|
888
|
+
|
889
|
+
let [encryptedPayload, symmetricDecryptor] = await Promise.all([
|
890
|
+
this.vaultClient.lockboxDataGet(
|
891
|
+
lockboxUuid,
|
892
|
+
dataUuid,
|
893
|
+
lockboxOwnerUuid
|
894
|
+
),
|
895
|
+
this.getCachedSecretCryptor(lockboxUuid, lockboxOwnerUuid),
|
896
|
+
])
|
897
|
+
|
898
|
+
return symmetricDecryptor.base64PayloadDecryptToBytes(
|
899
|
+
encryptedPayload.data
|
900
|
+
)
|
901
|
+
}
|
902
|
+
|
903
|
+
/**
|
904
|
+
* @name getGrants
|
905
|
+
* @description Get all lockboxes granted to user with the applied filter
|
906
|
+
* @note this function returns cached grants and will not update unless the page is refreshed
|
907
|
+
* @todo some versions of lockboxes do not make use of lockbox metadata
|
908
|
+
* in this case, all lockboxes need to be filtered one-by-one to find the correct one
|
909
|
+
* Remove if this is no longer the case
|
910
|
+
* @param filter: the consultationId in which the grant exists
|
911
|
+
* @returns decrypted lockboxes granted to user
|
912
|
+
*/
|
913
|
+
public async getGrants(
|
914
|
+
filter?: { consultationId: Uuid },
|
915
|
+
forceRefresh: boolean = false
|
916
|
+
): Promise<Grant[]> {
|
917
|
+
if (!this.rsa) throw IncompleteAuthentication
|
918
|
+
|
919
|
+
let filterString = JSON.stringify(filter)
|
920
|
+
// retrieves cached grants
|
921
|
+
// Note: if filters is set to empty, it will be stored in the `undefined` key
|
922
|
+
if (!forceRefresh && this.cachedMetadataGrants[filterString])
|
923
|
+
return this.cachedMetadataGrants[filterString]
|
924
|
+
|
925
|
+
// retrieves the consult lockbox from the vault directly if it exists
|
926
|
+
// Note: will work only if the filter being applied is exclusively a consult id
|
927
|
+
const grantsByConsultLockbox = (await this.vaultClient.vaultIndexGet([IndexKey.ConsultationLockbox], [filter?.consultationId!]))[IndexKey.ConsultationLockbox]
|
928
|
+
const decryptedConsults = decryptConsultLockboxGrants(grantsByConsultLockbox ?? [], this.rsa)
|
929
|
+
if (decryptedConsults.length > 0) {
|
930
|
+
console.info('[sdk:index] Grants found in user`s constant time secure index')
|
931
|
+
this.cachedMetadataGrants[JSON.stringify(filter)] = decryptedConsults
|
932
|
+
return this.cachedMetadataGrants[filterString]
|
933
|
+
}
|
934
|
+
|
935
|
+
let encryptedGrants
|
936
|
+
// if there are no grants with the applied filter from index, attempt for naive filter with backwards compatibility
|
937
|
+
if (filter) {
|
938
|
+
encryptedGrants = await filterGrantsWithLockboxMetadata(
|
939
|
+
this,
|
940
|
+
filter,
|
941
|
+
this.vaultIndex,
|
942
|
+
forceRefresh
|
943
|
+
)
|
944
|
+
} else {
|
945
|
+
encryptedGrants = (await this.vaultClient.grantsGet()).grants
|
946
|
+
}
|
947
|
+
|
948
|
+
const decryptedGrants = await decryptGrants(encryptedGrants, this.rsa)
|
949
|
+
// sets the cached grant
|
950
|
+
this.cachedMetadataGrants[filterString] = decryptedGrants
|
951
|
+
return decryptedGrants
|
952
|
+
}
|
953
|
+
|
954
|
+
/**
|
955
|
+
* @name getCachedSecretCryptor
|
956
|
+
* @description Retrieves the cached lockbox secret or fetches the secret from vault, then creates the symmetric cryptor and stores it in memory
|
957
|
+
* @param lockboxUuid
|
958
|
+
* @param lockboxOwnerUuid the lockbox owner (ignored if lockbox is owned by self)
|
959
|
+
* @returns
|
960
|
+
*/
|
961
|
+
async getCachedSecretCryptor(
|
962
|
+
lockboxUuid: string,
|
963
|
+
lockboxOwnerUuid?: string
|
964
|
+
): Promise<OroToolbox.CryptoChaCha> {
|
965
|
+
if (!this.rsa) throw IncompleteAuthentication
|
966
|
+
|
967
|
+
let index = this.secrets.findIndex(
|
968
|
+
(secret) => secret.lockboxUuid === lockboxUuid
|
969
|
+
)
|
970
|
+
if (index === -1) {
|
971
|
+
let encryptedSecret = (
|
972
|
+
await this.vaultClient.lockboxSecretGet(
|
973
|
+
lockboxUuid,
|
974
|
+
lockboxOwnerUuid
|
975
|
+
)
|
976
|
+
).sharedSecret
|
977
|
+
|
978
|
+
let secret = this.rsa.base64DecryptToBytes(encryptedSecret)
|
979
|
+
let cryptor = this.toolbox.CryptoChaCha.fromKey(secret)
|
980
|
+
this.secrets.push({ lockboxUuid, cryptor })
|
981
|
+
return cryptor
|
982
|
+
} else {
|
983
|
+
return this.secrets[index].cryptor
|
984
|
+
}
|
985
|
+
}
|
986
|
+
|
987
|
+
/**
|
988
|
+
* Retrieves the patient personal information associated to the `consultationId`
|
989
|
+
* The `consultationId` only helps to retrieve the patient lockboxes
|
990
|
+
* Note: it is possible to have several personal informations data
|
991
|
+
* @param consultationId The consultation Id
|
992
|
+
* @param category The personal MetadataCategory to fetch
|
993
|
+
* @param forceRefresh force data refresh (default to false)
|
994
|
+
* @returns the personal data
|
995
|
+
*/
|
996
|
+
public async getPersonalInformationsFromConsultId(
|
997
|
+
consultationId: Uuid,
|
998
|
+
category:
|
999
|
+
| MetadataCategory.Personal
|
1000
|
+
| MetadataCategory.ChildPersonal
|
1001
|
+
| MetadataCategory.OtherPersonal,
|
1002
|
+
forceRefresh = false
|
1003
|
+
): Promise<LocalizedData<PopulatedWorkflowData>[]> {
|
1004
|
+
return this.getMetaCategoryFromConsultId(
|
1005
|
+
consultationId,
|
1006
|
+
category,
|
1007
|
+
forceRefresh
|
1008
|
+
)
|
1009
|
+
}
|
1010
|
+
|
1011
|
+
/**
|
1012
|
+
* Retrieves the patient medical data associated to the `consultationId`
|
1013
|
+
* The `consultationId` only helps to retrieve the patient lockboxes
|
1014
|
+
* Note: it is possible to have several medical data
|
1015
|
+
* @param consultationId The consultation Id
|
1016
|
+
* @param forceRefresh force data refresh (default to false)
|
1017
|
+
* @returns the medical data
|
1018
|
+
*/
|
1019
|
+
public async getMedicalDataFromConsultId(
|
1020
|
+
consultationId: Uuid,
|
1021
|
+
forceRefresh = false
|
1022
|
+
): Promise<LocalizedData<PopulatedWorkflowData>[]> {
|
1023
|
+
return this.getMetaCategoryFromConsultId(
|
1024
|
+
consultationId,
|
1025
|
+
MetadataCategory.Medical,
|
1026
|
+
forceRefresh
|
1027
|
+
)
|
1028
|
+
}
|
1029
|
+
|
1030
|
+
private async getMetaCategoryFromConsultId(
|
1031
|
+
consultationId: Uuid,
|
1032
|
+
category: MetadataCategory,
|
1033
|
+
forceRefresh = false
|
1034
|
+
): Promise<LocalizedData<PopulatedWorkflowData>[]> {
|
1035
|
+
let grants = await this.getGrants({ consultationId })
|
1036
|
+
let workflowData: LocalizedData<PopulatedWorkflowData>[] = []
|
1037
|
+
for (let grant of grants) {
|
1038
|
+
let manifest = await this.getLockboxManifest(
|
1039
|
+
grant.lockboxUuid!,
|
1040
|
+
{
|
1041
|
+
category,
|
1042
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1043
|
+
consultationIds: [consultationId],
|
1044
|
+
},
|
1045
|
+
true,
|
1046
|
+
grant.lockboxOwnerUuid,
|
1047
|
+
forceRefresh
|
1048
|
+
)
|
1049
|
+
|
1050
|
+
// TODO: find another solution for backwards compatibility (those without the metadata consultationIds)
|
1051
|
+
if (manifest.length === 0) {
|
1052
|
+
manifest = (
|
1053
|
+
await this.getLockboxManifest(
|
1054
|
+
grant.lockboxUuid!,
|
1055
|
+
{
|
1056
|
+
category,
|
1057
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1058
|
+
// backward compatiblility with TonTest
|
1059
|
+
},
|
1060
|
+
true,
|
1061
|
+
grant.lockboxOwnerUuid,
|
1062
|
+
forceRefresh
|
1063
|
+
)
|
1064
|
+
).filter((entry) => !entry.metadata.consultationIds) // Keep only entries without associated consultationIds
|
1065
|
+
}
|
1066
|
+
let data = await Promise.all(
|
1067
|
+
manifest.map(async (entry) => {
|
1068
|
+
return {
|
1069
|
+
lockboxOwnerUuid: grant.lockboxOwnerUuid,
|
1070
|
+
lockboxUuid: grant.lockboxUuid!,
|
1071
|
+
dataUuid: entry.dataUuid,
|
1072
|
+
data: await this.getJsonData<PopulatedWorkflowData>(
|
1073
|
+
grant.lockboxUuid!,
|
1074
|
+
entry.dataUuid
|
1075
|
+
),
|
1076
|
+
}
|
1077
|
+
})
|
1078
|
+
)
|
1079
|
+
workflowData = { ...workflowData, ...data }
|
1080
|
+
}
|
1081
|
+
return workflowData
|
1082
|
+
}
|
1083
|
+
|
1084
|
+
/**
|
1085
|
+
* @description retrieves the personal information stored in the first owned lockbox
|
1086
|
+
* @param userId The user Id
|
1087
|
+
* @returns the personal data
|
1088
|
+
*/
|
1089
|
+
public async getPersonalInformations(
|
1090
|
+
userId: Uuid
|
1091
|
+
): Promise<LocalizedData<PopulatedWorkflowData>> {
|
1092
|
+
const grant = (await this.getGrants()).find(
|
1093
|
+
(lockbox) => lockbox.lockboxOwnerUuid === userId
|
1094
|
+
)
|
1095
|
+
|
1096
|
+
if (!grant) {
|
1097
|
+
throw MissingGrant
|
1098
|
+
}
|
1099
|
+
|
1100
|
+
const { lockboxUuid, lockboxOwnerUuid } = grant
|
1101
|
+
|
1102
|
+
if (!lockboxUuid) throw MissingLockbox
|
1103
|
+
|
1104
|
+
if (!lockboxOwnerUuid) throw MissingLockboxOwner
|
1105
|
+
|
1106
|
+
const identificationDataUuid = (
|
1107
|
+
await this.getLockboxManifest(
|
1108
|
+
lockboxUuid,
|
1109
|
+
{
|
1110
|
+
category: MetadataCategory.Personal,
|
1111
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1112
|
+
},
|
1113
|
+
false,
|
1114
|
+
userId
|
1115
|
+
)
|
1116
|
+
)[0].dataUuid
|
1117
|
+
|
1118
|
+
return {
|
1119
|
+
lockboxOwnerUuid,
|
1120
|
+
lockboxUuid,
|
1121
|
+
dataUuid: identificationDataUuid,
|
1122
|
+
data: await this.getJsonData<PopulatedWorkflowData>(
|
1123
|
+
lockboxUuid,
|
1124
|
+
identificationDataUuid
|
1125
|
+
),
|
1126
|
+
}
|
1127
|
+
}
|
1128
|
+
|
1129
|
+
/**
|
1130
|
+
* Retrieves the grant associated to a consultationId
|
1131
|
+
* @note returns the first grant only
|
1132
|
+
* @param consultationId The consultationId
|
1133
|
+
* @returns the grant
|
1134
|
+
*/
|
1135
|
+
public async getGrantFromConsultId(
|
1136
|
+
consultationId: Uuid
|
1137
|
+
): Promise<Grant | undefined> {
|
1138
|
+
let grants = await this.getGrants({ consultationId })
|
1139
|
+
|
1140
|
+
if (grants.length === 0) {
|
1141
|
+
throw AssociatedLockboxNotFound
|
1142
|
+
}
|
1143
|
+
|
1144
|
+
return grants[0]
|
1145
|
+
}
|
1146
|
+
|
1147
|
+
/**
|
1148
|
+
* retrieves the identity associated to the `consultationId`
|
1149
|
+
* @param consultationId The consultation Id
|
1150
|
+
* @returns the identity
|
1151
|
+
*/
|
1152
|
+
public async getIdentityFromConsultId(
|
1153
|
+
consultationId: Uuid
|
1154
|
+
): Promise<IdentityResponse | undefined> {
|
1155
|
+
const grant = await this.getGrantFromConsultId(consultationId)
|
1156
|
+
|
1157
|
+
if (grant && grant.lockboxOwnerUuid) {
|
1158
|
+
return await this.guardClient.identityGet(grant.lockboxOwnerUuid)
|
1159
|
+
} else {
|
1160
|
+
return undefined
|
1161
|
+
}
|
1162
|
+
}
|
1163
|
+
|
1164
|
+
/**
|
1165
|
+
* retrieves the lockbox manifest for a given lockbox and add's its private metadata
|
1166
|
+
* @note the lockbox manifest will retrieved the cached manifest first unless force refresh is enabled
|
1167
|
+
* @param lockboxUuid
|
1168
|
+
* @param filter
|
1169
|
+
* @param expandPrivateMetadata
|
1170
|
+
* @param lockboxOwnerUuid
|
1171
|
+
* @param forceRefresh
|
1172
|
+
* @returns the lockbox manifest
|
1173
|
+
*/
|
1174
|
+
public async getLockboxManifest(
|
1175
|
+
lockboxUuid: Uuid,
|
1176
|
+
filter: Metadata,
|
1177
|
+
expandPrivateMetadata: boolean,
|
1178
|
+
lockboxOwnerUuid?: Uuid,
|
1179
|
+
forceRefresh: boolean = false
|
1180
|
+
): Promise<LockboxManifest> {
|
1181
|
+
let manifestKey = JSON.stringify({
|
1182
|
+
lockboxUuid,
|
1183
|
+
filter,
|
1184
|
+
expandPrivateMetadata,
|
1185
|
+
lockboxOwnerUuid,
|
1186
|
+
})
|
1187
|
+
if (!forceRefresh && this.cachedManifest[manifestKey])
|
1188
|
+
return this.cachedManifest[manifestKey]
|
1189
|
+
|
1190
|
+
return this.vaultClient
|
1191
|
+
.lockboxManifestGet(lockboxUuid, filter, lockboxOwnerUuid)
|
1192
|
+
.then((manifest) => {
|
1193
|
+
return Promise.all(
|
1194
|
+
manifest.map(async (entry) => {
|
1195
|
+
if (
|
1196
|
+
expandPrivateMetadata &&
|
1197
|
+
entry.metadata.privateMetadata
|
1198
|
+
) {
|
1199
|
+
let privateMeta = await this.getJsonData<Metadata>(
|
1200
|
+
lockboxUuid!,
|
1201
|
+
entry.metadata.privateMetadata,
|
1202
|
+
lockboxOwnerUuid
|
1203
|
+
)
|
1204
|
+
entry.metadata = {
|
1205
|
+
...entry.metadata,
|
1206
|
+
...privateMeta,
|
1207
|
+
}
|
1208
|
+
}
|
1209
|
+
return entry
|
1210
|
+
})
|
1211
|
+
).then(
|
1212
|
+
(manifest) => (this.cachedManifest[manifestKey] = manifest)
|
1213
|
+
)
|
1214
|
+
})
|
1215
|
+
}
|
1216
|
+
|
1217
|
+
/**
|
1218
|
+
* @description Create or update the personal information and store it in the first owned lockbox
|
1219
|
+
* @param identity The identity to use
|
1220
|
+
* @param data The personal data to store
|
1221
|
+
* @param dataUuid (optional) The dataUuid to update
|
1222
|
+
* @returns
|
1223
|
+
*/
|
1224
|
+
public async createPersonalInformations(
|
1225
|
+
identity: IdentityResponse,
|
1226
|
+
data: PopulatedWorkflowData,
|
1227
|
+
dataUuid?: string
|
1228
|
+
): Promise<DataCreateResponse> {
|
1229
|
+
const lockboxUuid = (await this.getGrants()).find(
|
1230
|
+
(lockbox) => lockbox.lockboxOwnerUuid === identity.id
|
1231
|
+
)?.lockboxUuid
|
1232
|
+
|
1233
|
+
if (lockboxUuid) {
|
1234
|
+
return this.createJsonData<PersonalMeta>(
|
1235
|
+
lockboxUuid,
|
1236
|
+
data,
|
1237
|
+
{
|
1238
|
+
category: MetadataCategory.Personal,
|
1239
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1240
|
+
},
|
1241
|
+
{},
|
1242
|
+
undefined,
|
1243
|
+
dataUuid
|
1244
|
+
)
|
1245
|
+
} else {
|
1246
|
+
throw MissingLockbox
|
1247
|
+
}
|
1248
|
+
}
|
1249
|
+
|
1250
|
+
/**
|
1251
|
+
* Create or update user Preference
|
1252
|
+
* @param identity
|
1253
|
+
* @param preference
|
1254
|
+
* @param dataUuid
|
1255
|
+
* @returns
|
1256
|
+
*/
|
1257
|
+
public async createUserPreference(
|
1258
|
+
identity: IdentityResponse,
|
1259
|
+
preference: UserPreference,
|
1260
|
+
dataUuid?: string
|
1261
|
+
): Promise<DataCreateResponse> {
|
1262
|
+
const lockboxUuid = (await this.getGrants()).find(
|
1263
|
+
(lockbox) => lockbox.lockboxOwnerUuid === identity.id
|
1264
|
+
)?.lockboxUuid
|
1265
|
+
|
1266
|
+
if (lockboxUuid) {
|
1267
|
+
return this.createJsonData<PreferenceMeta>(
|
1268
|
+
lockboxUuid,
|
1269
|
+
preference,
|
1270
|
+
{
|
1271
|
+
category: MetadataCategory.Preference,
|
1272
|
+
contentType: 'application/json',
|
1273
|
+
},
|
1274
|
+
{},
|
1275
|
+
undefined,
|
1276
|
+
dataUuid
|
1277
|
+
)
|
1278
|
+
} else {
|
1279
|
+
throw MissingLockbox
|
1280
|
+
}
|
1281
|
+
}
|
1282
|
+
|
1283
|
+
/**
|
1284
|
+
* retrieves the user preference from a grant
|
1285
|
+
* @param grant The grant
|
1286
|
+
* @returns the user preference
|
1287
|
+
*/
|
1288
|
+
public async getDataFromGrant<T = any>(
|
1289
|
+
grant: Grant,
|
1290
|
+
filter: Metadata
|
1291
|
+
): Promise<LocalizedData<T>> {
|
1292
|
+
const { lockboxUuid, lockboxOwnerUuid } = grant
|
1293
|
+
|
1294
|
+
if (!lockboxUuid) throw MissingLockbox
|
1295
|
+
if (!lockboxOwnerUuid) throw MissingLockboxOwner
|
1296
|
+
const identificationDataUuid = (
|
1297
|
+
await this.getLockboxManifest(
|
1298
|
+
lockboxUuid,
|
1299
|
+
|
1300
|
+
filter,
|
1301
|
+
false,
|
1302
|
+
grant.lockboxOwnerUuid,
|
1303
|
+
true
|
1304
|
+
)
|
1305
|
+
)[0].dataUuid
|
1306
|
+
|
1307
|
+
return {
|
1308
|
+
lockboxOwnerUuid,
|
1309
|
+
lockboxUuid,
|
1310
|
+
dataUuid: identificationDataUuid,
|
1311
|
+
data: await this.getJsonData<T>(
|
1312
|
+
lockboxUuid,
|
1313
|
+
identificationDataUuid
|
1314
|
+
),
|
1315
|
+
}
|
1316
|
+
}
|
1317
|
+
|
1318
|
+
/**
|
1319
|
+
* retrieves the user preference from a consultation id
|
1320
|
+
* @param consultationId The related consultationId
|
1321
|
+
* @returns the user preference
|
1322
|
+
*/
|
1323
|
+
public async getUserPreferenceFromConsultId(
|
1324
|
+
consultationId: string
|
1325
|
+
): Promise<LocalizedData<UserPreference>> {
|
1326
|
+
const grant = await this.getGrantFromConsultId(consultationId)
|
1327
|
+
|
1328
|
+
if (!grant) throw MissingGrant
|
1329
|
+
|
1330
|
+
return this.getDataFromGrant<UserPreference>(grant, {
|
1331
|
+
category: MetadataCategory.Preference,
|
1332
|
+
contentType: 'application/json',
|
1333
|
+
})
|
1334
|
+
}
|
1335
|
+
|
1336
|
+
/**
|
1337
|
+
* retrieves the user preference stored in the first owned lockbox from identity
|
1338
|
+
* @param identity The identity to use
|
1339
|
+
* @returns the user preference
|
1340
|
+
*/
|
1341
|
+
public async getUserPreference(
|
1342
|
+
identity: IdentityResponse
|
1343
|
+
): Promise<LocalizedData<UserPreference>> {
|
1344
|
+
const grant = (await this.getGrants()).find(
|
1345
|
+
(lockbox) => lockbox.lockboxOwnerUuid === identity.id
|
1346
|
+
)
|
1347
|
+
|
1348
|
+
if (!grant) throw MissingGrant
|
1349
|
+
|
1350
|
+
return this.getDataFromGrant<UserPreference>(grant, {
|
1351
|
+
category: MetadataCategory.Preference,
|
1352
|
+
contentType: 'application/json',
|
1353
|
+
})
|
1354
|
+
}
|
1355
|
+
|
1356
|
+
/**
|
1357
|
+
* retrieves the user preference from a consultation id
|
1358
|
+
* @param consultationId The related consultationId
|
1359
|
+
* @returns the user preference
|
1360
|
+
*/
|
1361
|
+
public async getRecoveryDataFromConsultId(
|
1362
|
+
consultationId: string
|
1363
|
+
): Promise<LocalizedData<RecoveryData>> {
|
1364
|
+
const grant = await this.getGrantFromConsultId(consultationId)
|
1365
|
+
|
1366
|
+
if (!grant) throw MissingGrant
|
1367
|
+
|
1368
|
+
return this.getDataFromGrant<RecoveryData>(grant, {
|
1369
|
+
category: MetadataCategory.Recovery,
|
1370
|
+
contentType: 'application/json',
|
1371
|
+
})
|
1372
|
+
}
|
1373
|
+
|
1374
|
+
/**
|
1375
|
+
* retrieves the user preference stored in the first owned lockbox from identity
|
1376
|
+
* @param identity The identity to use
|
1377
|
+
* @returns the user preference
|
1378
|
+
*/
|
1379
|
+
public async getRecoveryData(
|
1380
|
+
identity: IdentityResponse
|
1381
|
+
): Promise<LocalizedData<RecoveryData>> {
|
1382
|
+
const grant = (await this.getGrants()).find(
|
1383
|
+
(lockbox) => lockbox.lockboxOwnerUuid === identity.id
|
1384
|
+
)
|
1385
|
+
|
1386
|
+
if (!grant) throw MissingGrant
|
1387
|
+
|
1388
|
+
return this.getDataFromGrant(grant, {
|
1389
|
+
category: MetadataCategory.Recovery,
|
1390
|
+
contentType: 'application/json',
|
1391
|
+
})
|
1392
|
+
}
|
1393
|
+
|
1394
|
+
/**
|
1395
|
+
* @name getAssignedConsultations
|
1396
|
+
* @description finds all assigned or owned consultations for the logged user
|
1397
|
+
* Steps:
|
1398
|
+
* - Retrieves all granted lockboxes given to the logged user
|
1399
|
+
* - for each lockbox, find all consultation ids
|
1400
|
+
* - for each consultation id, retrieve the consult information
|
1401
|
+
* @param practiceUuid the uuid of the practice to look consult into
|
1402
|
+
* @returns the list of consults
|
1403
|
+
*/
|
1404
|
+
public async getAssignedConsultations(
|
1405
|
+
practiceUuid: Uuid,
|
1406
|
+
forceRefresh: boolean = false
|
1407
|
+
): Promise<Consult[]> {
|
1408
|
+
return Promise.all(
|
1409
|
+
(await this.getGrants(undefined, forceRefresh)).map((grant) =>
|
1410
|
+
this.getLockboxManifest(
|
1411
|
+
grant.lockboxUuid!,
|
1412
|
+
{
|
1413
|
+
category: MetadataCategory.Consultation,
|
1414
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1415
|
+
},
|
1416
|
+
true,
|
1417
|
+
undefined,
|
1418
|
+
forceRefresh
|
1419
|
+
).then((manifest) =>
|
1420
|
+
Promise.all(
|
1421
|
+
manifest.map(
|
1422
|
+
async (entry) =>
|
1423
|
+
await this.consultClient.getConsultByUUID(
|
1424
|
+
entry.metadata.consultationId,
|
1425
|
+
practiceUuid
|
1426
|
+
)
|
1427
|
+
)
|
1428
|
+
).then((promise) => promise.flat())
|
1429
|
+
)
|
1430
|
+
)
|
1431
|
+
).then((consults) => consults.flat())
|
1432
|
+
}
|
1433
|
+
|
1434
|
+
/**
|
1435
|
+
* Gets the past consultations of the patient as well as his relatives if any
|
1436
|
+
* @param consultationId any consultation uuid from which we will fetch all the other consultations of the same patient as the owner of this consultation id
|
1437
|
+
* @param practiceUuid
|
1438
|
+
*/
|
1439
|
+
public async getPastConsultationsFromConsultId(
|
1440
|
+
consultationId: string,
|
1441
|
+
practiceUuid: string
|
1442
|
+
): Promise<Consult[] | undefined> {
|
1443
|
+
const grant = await this.getGrantFromConsultId(consultationId)
|
1444
|
+
if (!grant) return undefined
|
1445
|
+
|
1446
|
+
let consultationsInLockbox: string[] = (
|
1447
|
+
await this.vaultClient.lockboxMetadataGet(
|
1448
|
+
grant.lockboxUuid!,
|
1449
|
+
['consultationId'],
|
1450
|
+
['consultationId'],
|
1451
|
+
{
|
1452
|
+
category: MetadataCategory.Consultation,
|
1453
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1454
|
+
},
|
1455
|
+
grant.lockboxOwnerUuid
|
1456
|
+
)
|
1457
|
+
)
|
1458
|
+
.flat()
|
1459
|
+
.map(
|
1460
|
+
(metadata: { consultationId: string }) =>
|
1461
|
+
metadata.consultationId
|
1462
|
+
)
|
1463
|
+
|
1464
|
+
if (consultationsInLockbox.length == 0) return []
|
1465
|
+
|
1466
|
+
return await Promise.all(
|
1467
|
+
consultationsInLockbox.map(async (consultId: string) => {
|
1468
|
+
return await this.consultClient.getConsultByUUID(
|
1469
|
+
consultId,
|
1470
|
+
practiceUuid
|
1471
|
+
)
|
1472
|
+
})
|
1473
|
+
)
|
1474
|
+
}
|
1475
|
+
|
1476
|
+
/**
|
1477
|
+
* @name getPatientConsultationData
|
1478
|
+
* @description retrieves the consultation data
|
1479
|
+
* @param consultationId
|
1480
|
+
* @returns
|
1481
|
+
*/
|
1482
|
+
public async getPatientConsultationData(
|
1483
|
+
consultationId: Uuid,
|
1484
|
+
forceRefresh: boolean = false
|
1485
|
+
): Promise<PopulatedWorkflowData[]> {
|
1486
|
+
//TODO: make use of getPatientDocumentsList instead of doing it manually here
|
1487
|
+
return Promise.all(
|
1488
|
+
(await this.getGrants({ consultationId }, forceRefresh))
|
1489
|
+
.map((grant) =>
|
1490
|
+
this.getLockboxManifest(
|
1491
|
+
grant.lockboxUuid!,
|
1492
|
+
{
|
1493
|
+
category: MetadataCategory.Consultation,
|
1494
|
+
documentType: DocumentType.PopulatedWorkflowData,
|
1495
|
+
consultationId, //since we want to update the cached manifest (if another consult data exists)
|
1496
|
+
},
|
1497
|
+
true,
|
1498
|
+
grant.lockboxOwnerUuid,
|
1499
|
+
forceRefresh
|
1500
|
+
).then((manifest) =>
|
1501
|
+
Promise.all(
|
1502
|
+
manifest.map((e) =>
|
1503
|
+
this.getJsonData<PopulatedWorkflowData>(
|
1504
|
+
grant.lockboxUuid!,
|
1505
|
+
e.dataUuid,
|
1506
|
+
grant.lockboxOwnerUuid
|
1507
|
+
)
|
1508
|
+
)
|
1509
|
+
)
|
1510
|
+
)
|
1511
|
+
)
|
1512
|
+
.flat()
|
1513
|
+
).then((data) => data.flat())
|
1514
|
+
}
|
1515
|
+
|
1516
|
+
/**
|
1517
|
+
* This function returns the patient prescriptions
|
1518
|
+
* @param consultationId
|
1519
|
+
* @returns
|
1520
|
+
*/
|
1521
|
+
public async getPatientPrescriptionsList(
|
1522
|
+
consultationId: Uuid
|
1523
|
+
): Promise<Document[]> {
|
1524
|
+
return this.getPatientDocumentsList(
|
1525
|
+
{
|
1526
|
+
category: MetadataCategory.Consultation,
|
1527
|
+
documentType: DocumentType.Prescription,
|
1528
|
+
},
|
1529
|
+
true,
|
1530
|
+
consultationId
|
1531
|
+
)
|
1532
|
+
}
|
1533
|
+
|
1534
|
+
/**
|
1535
|
+
* This function returns the patient results
|
1536
|
+
* @param consultationId
|
1537
|
+
* @returns
|
1538
|
+
*/
|
1539
|
+
public async getPatientResultsList(
|
1540
|
+
consultationId: Uuid
|
1541
|
+
): Promise<Document[]> {
|
1542
|
+
return this.getPatientDocumentsList(
|
1543
|
+
{
|
1544
|
+
category: MetadataCategory.Consultation,
|
1545
|
+
documentType: DocumentType.Result,
|
1546
|
+
},
|
1547
|
+
true,
|
1548
|
+
consultationId
|
1549
|
+
)
|
1550
|
+
}
|
1551
|
+
|
1552
|
+
/**
|
1553
|
+
* returns the patient treatment plan options
|
1554
|
+
* @param consultationId
|
1555
|
+
* @returns Document[] corresponding to the patient treatment plan options
|
1556
|
+
*/
|
1557
|
+
public async getPatientTreatmentPlans(
|
1558
|
+
consultationId: Uuid
|
1559
|
+
): Promise<Document[]> {
|
1560
|
+
return this.getPatientDocumentsList(
|
1561
|
+
{
|
1562
|
+
category: MetadataCategory.Consultation,
|
1563
|
+
documentType: DocumentType.TreatmentPlan,
|
1564
|
+
},
|
1565
|
+
true,
|
1566
|
+
consultationId
|
1567
|
+
)
|
1568
|
+
}
|
1569
|
+
|
1570
|
+
/**
|
1571
|
+
* returns a specific patient treatment plan option
|
1572
|
+
* @param consultationId
|
1573
|
+
* @param treatmentPlanId
|
1574
|
+
* @returns
|
1575
|
+
*/
|
1576
|
+
public async getPatientTreatmentPlanByUuid(
|
1577
|
+
consultationId: Uuid,
|
1578
|
+
treatmentPlanId: Uuid
|
1579
|
+
): Promise<Document[]> {
|
1580
|
+
return this.getPatientDocumentsList(
|
1581
|
+
{
|
1582
|
+
category: MetadataCategory.Consultation,
|
1583
|
+
documentType: DocumentType.TreatmentPlan,
|
1584
|
+
treatmentPlanId,
|
1585
|
+
},
|
1586
|
+
true,
|
1587
|
+
consultationId
|
1588
|
+
)
|
1589
|
+
}
|
1590
|
+
|
1591
|
+
/**
|
1592
|
+
* @name getPatientDocumentsList
|
1593
|
+
* @description applies the provided filter to the vault to only find those documents
|
1594
|
+
* @param filters the applied filters (e.g. type of documents)
|
1595
|
+
* @param expandPrivateMetadata whether or not, the private metadata needs to be retrieved
|
1596
|
+
* (more computationally expensive)
|
1597
|
+
* @param consultationId
|
1598
|
+
* @returns the filtered document list
|
1599
|
+
*/
|
1600
|
+
public async getPatientDocumentsList(
|
1601
|
+
filters: Object,
|
1602
|
+
expandPrivateMetadata: boolean,
|
1603
|
+
consultationId: Uuid
|
1604
|
+
): Promise<Document[]> {
|
1605
|
+
return Promise.all(
|
1606
|
+
(await this.getGrants({ consultationId }))
|
1607
|
+
.map((grant) =>
|
1608
|
+
this.getLockboxManifest(
|
1609
|
+
grant.lockboxUuid!,
|
1610
|
+
{ ...filters, consultationId },
|
1611
|
+
expandPrivateMetadata,
|
1612
|
+
grant.lockboxOwnerUuid,
|
1613
|
+
true
|
1614
|
+
).then((manifest) =>
|
1615
|
+
Promise.all(
|
1616
|
+
manifest.map(
|
1617
|
+
async (entry): Promise<Document> => {
|
1618
|
+
return {
|
1619
|
+
lockboxOwnerUuid:
|
1620
|
+
grant.lockboxOwnerUuid,
|
1621
|
+
lockboxUuid: grant.lockboxUuid!,
|
1622
|
+
...entry,
|
1623
|
+
}
|
1624
|
+
}
|
1625
|
+
)
|
1626
|
+
)
|
1627
|
+
)
|
1628
|
+
)
|
1629
|
+
.flat()
|
1630
|
+
).then((data) => data.flat())
|
1631
|
+
}
|
1632
|
+
|
1633
|
+
/****************************************************************************************************************
|
1634
|
+
* RECOVERY *
|
1635
|
+
****************************************************************************************************************/
|
1636
|
+
|
1637
|
+
/**
|
1638
|
+
* @name recoverPrivateKeyFromSecurityQuestions
|
1639
|
+
* @description Recovers and sets the rsa private key from the answered security questions
|
1640
|
+
* @param id
|
1641
|
+
* @param recoverySecurityQuestions
|
1642
|
+
* @param recoverySecurityAnswers
|
1643
|
+
* @param threshold the number of answers needed to recover the key
|
1644
|
+
*/
|
1645
|
+
public async recoverPrivateKeyFromSecurityQuestions(
|
1646
|
+
id: Uuid,
|
1647
|
+
recoverySecurityQuestions: string[],
|
1648
|
+
recoverySecurityAnswers: string[],
|
1649
|
+
threshold: number
|
1650
|
+
) {
|
1651
|
+
let shards: SecretShard[] = (await this.guardClient.identityGet(id))
|
1652
|
+
.recoverySecurityQuestions
|
1653
|
+
let answeredShards = shards
|
1654
|
+
.filter((shard: any) => {
|
1655
|
+
// filters all answered security questions
|
1656
|
+
let indexOfQuestion = recoverySecurityQuestions.indexOf(
|
1657
|
+
shard.securityQuestion
|
1658
|
+
)
|
1659
|
+
if (indexOfQuestion === -1) return false
|
1660
|
+
return (
|
1661
|
+
recoverySecurityAnswers[indexOfQuestion] &&
|
1662
|
+
recoverySecurityAnswers[indexOfQuestion] != ''
|
1663
|
+
)
|
1664
|
+
})
|
1665
|
+
.map((item: any) => {
|
1666
|
+
// appends the security answer to the answered shards
|
1667
|
+
let index = recoverySecurityQuestions.indexOf(
|
1668
|
+
item.securityQuestion
|
1669
|
+
)
|
1670
|
+
item.securityAnswer = recoverySecurityAnswers[index]
|
1671
|
+
return item
|
1672
|
+
})
|
1673
|
+
try {
|
1674
|
+
// reconstructs the key from the answered security answers
|
1675
|
+
let privateKey = this.toolbox.reconstructSecret(
|
1676
|
+
answeredShards,
|
1677
|
+
threshold
|
1678
|
+
)
|
1679
|
+
this.rsa = this.toolbox.CryptoRSA.fromKey(privateKey)
|
1680
|
+
} catch (e) {
|
1681
|
+
console.error(e)
|
1682
|
+
}
|
1683
|
+
}
|
1684
|
+
|
1685
|
+
/**
|
1686
|
+
* @name recoverPrivateKeyFromPassword
|
1687
|
+
* @description Recovers and sets the rsa private key from the password
|
1688
|
+
* @param id
|
1689
|
+
* @param password
|
1690
|
+
*/
|
1691
|
+
public async recoverPrivateKeyFromPassword(id: Uuid, password: string) {
|
1692
|
+
let identity = await this.guardClient.identityGet(id)
|
1693
|
+
|
1694
|
+
let recoveryPayload = identity.recoveryPassword
|
1695
|
+
let symmetricDecryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
1696
|
+
password
|
1697
|
+
)
|
1698
|
+
let privateKey = symmetricDecryptor.base64PayloadDecryptToBytes(
|
1699
|
+
recoveryPayload
|
1700
|
+
)
|
1701
|
+
|
1702
|
+
if (identity.recoveryLogin) {
|
1703
|
+
//Ensure we can recover from a page reload
|
1704
|
+
let symetricEncryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
1705
|
+
identity.recoveryLogin
|
1706
|
+
)
|
1707
|
+
sessionStorage.setItem(
|
1708
|
+
sessionStorePrivateKeyName(id),
|
1709
|
+
symetricEncryptor.bytesEncryptToBase64Payload(privateKey)
|
1710
|
+
)
|
1711
|
+
}
|
1712
|
+
|
1713
|
+
this.rsa = this.toolbox.CryptoRSA.fromKey(privateKey)
|
1714
|
+
}
|
1715
|
+
|
1716
|
+
/**
|
1717
|
+
* @name recoverPrivateKeyFromMasterKey
|
1718
|
+
* @description Recovers and sets the rsa private key from the master key
|
1719
|
+
* @param id
|
1720
|
+
* @param masterKey
|
1721
|
+
*/
|
1722
|
+
public async recoverPrivateKeyFromMasterKey(id: Uuid, masterKey: string) {
|
1723
|
+
let recoveryPayload = (await this.guardClient.identityGet(id))
|
1724
|
+
.recoveryMasterKey
|
1725
|
+
let symmetricDecryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
1726
|
+
masterKey
|
1727
|
+
)
|
1728
|
+
let privateKey = symmetricDecryptor.base64PayloadDecryptToBytes(
|
1729
|
+
recoveryPayload
|
1730
|
+
)
|
1731
|
+
this.rsa = this.toolbox.CryptoRSA.fromKey(privateKey)
|
1732
|
+
}
|
1733
|
+
|
1734
|
+
/**
|
1735
|
+
* @description Generates and updates the security questions and answers payload using new recovery questions and answers
|
1736
|
+
* Important: Since the security questions generate a payload for the private key, they will never be stored on the device as they must remain secret!!!
|
1737
|
+
* @param id
|
1738
|
+
* @param recoverySecurityQuestions
|
1739
|
+
* @param recoverySecurityAnswers
|
1740
|
+
* @param threshold the number of answers needed to rebuild the secret
|
1741
|
+
*/
|
1742
|
+
public async updateSecurityQuestions(
|
1743
|
+
id: Uuid,
|
1744
|
+
recoverySecurityQuestions: string[],
|
1745
|
+
recoverySecurityAnswers: string[],
|
1746
|
+
threshold: number
|
1747
|
+
) {
|
1748
|
+
if (!this.rsa) throw IncompleteAuthentication
|
1749
|
+
let securityQuestionPayload = this.toolbox.breakSecretIntoShards(
|
1750
|
+
recoverySecurityQuestions,
|
1751
|
+
recoverySecurityAnswers,
|
1752
|
+
this.rsa.private(),
|
1753
|
+
threshold
|
1754
|
+
)
|
1755
|
+
let updateRequest = {
|
1756
|
+
recoverySecurityQuestions: securityQuestionPayload,
|
1757
|
+
}
|
1758
|
+
|
1759
|
+
return await this.guardClient.identityUpdate(id, updateRequest)
|
1760
|
+
}
|
1761
|
+
|
1762
|
+
/**
|
1763
|
+
* @description Generates and stores the payload encrypted payload and updates the password itself (double hash)
|
1764
|
+
* @important
|
1765
|
+
* the recovery payload uses a singly hashed password and the password stored is doubly hashed so
|
1766
|
+
* the stored password cannot derive the decryption key in the payload
|
1767
|
+
* @note
|
1768
|
+
* the old password must be provided when not performing an account recovery
|
1769
|
+
* @param id
|
1770
|
+
* @param newPassword
|
1771
|
+
* @param oldPassword
|
1772
|
+
*/
|
1773
|
+
public async updatePassword(
|
1774
|
+
id: Uuid,
|
1775
|
+
newPassword: string,
|
1776
|
+
oldPassword?: string
|
1777
|
+
) {
|
1778
|
+
if (!this.rsa) throw IncompleteAuthentication
|
1779
|
+
|
1780
|
+
let symmetricEncryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
1781
|
+
newPassword
|
1782
|
+
)
|
1783
|
+
let passwordPayload = symmetricEncryptor.bytesEncryptToBase64Payload(
|
1784
|
+
this.rsa.private()
|
1785
|
+
)
|
1786
|
+
if (oldPassword) {
|
1787
|
+
oldPassword = this.toolbox.hashStringToBase64(
|
1788
|
+
this.toolbox.hashStringToBase64(oldPassword)
|
1789
|
+
)
|
1790
|
+
}
|
1791
|
+
|
1792
|
+
newPassword = this.toolbox.hashStringToBase64(
|
1793
|
+
this.toolbox.hashStringToBase64(newPassword)
|
1794
|
+
)
|
1795
|
+
|
1796
|
+
let updateRequest = {
|
1797
|
+
password: {
|
1798
|
+
oldPassword,
|
1799
|
+
newPassword,
|
1800
|
+
},
|
1801
|
+
recoveryPassword: passwordPayload,
|
1802
|
+
}
|
1803
|
+
|
1804
|
+
return await this.guardClient.identityUpdate(id, updateRequest)
|
1805
|
+
}
|
1806
|
+
|
1807
|
+
/**
|
1808
|
+
* @description Generates and stores the master key encrypted payload
|
1809
|
+
* Important
|
1810
|
+
* Since the master key is used to generate a payload for the private key, it will never be stored on the device as it must remain secret!
|
1811
|
+
* @param id
|
1812
|
+
* @param masterKey
|
1813
|
+
* @param lockboxUuid
|
1814
|
+
*/
|
1815
|
+
async updateMasterKey(id: Uuid, masterKey: string, lockboxUuid: Uuid) {
|
1816
|
+
if (!this.rsa) throw IncompleteAuthentication
|
1817
|
+
|
1818
|
+
let symmetricEncryptor = this.toolbox.CryptoChaCha.fromPassphrase(
|
1819
|
+
masterKey
|
1820
|
+
)
|
1821
|
+
let masterKeyPayload = symmetricEncryptor.bytesEncryptToBase64Payload(
|
1822
|
+
this.rsa.private()
|
1823
|
+
)
|
1824
|
+
let updateRequest = { recoveryMasterKey: masterKeyPayload }
|
1825
|
+
const updatedIdentity = await this.guardClient.identityUpdate(
|
1826
|
+
id,
|
1827
|
+
updateRequest
|
1828
|
+
)
|
1829
|
+
|
1830
|
+
await this.getOrInsertJsonData<RecoveryMeta>(
|
1831
|
+
lockboxUuid,
|
1832
|
+
{ masterKey },
|
1833
|
+
{
|
1834
|
+
category: MetadataCategory.Recovery,
|
1835
|
+
contentType: 'application/json',
|
1836
|
+
},
|
1837
|
+
{},
|
1838
|
+
true
|
1839
|
+
)
|
1840
|
+
|
1841
|
+
return updatedIdentity
|
1842
|
+
}
|
1843
|
+
}
|