@protontech/drive-sdk 0.7.3 → 0.9.0
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/dist/crypto/driveCrypto.js +1 -1
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/interface.d.ts +3 -1
- package/dist/crypto/openPGPCrypto.d.ts +4 -1
- package/dist/crypto/openPGPCrypto.js +2 -1
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +2 -2
- package/dist/errors.js.map +1 -1
- package/dist/interface/account.d.ts +6 -0
- package/dist/interface/download.d.ts +14 -0
- package/dist/internal/apiService/driveTypes.d.ts +197 -22
- package/dist/internal/download/controller.d.ts +3 -0
- package/dist/internal/download/controller.js +7 -0
- package/dist/internal/download/controller.js.map +1 -1
- package/dist/internal/download/cryptoService.js +9 -2
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.js +9 -3
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +14 -11
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/download/interface.d.ts +14 -0
- package/dist/internal/download/interface.js +16 -0
- package/dist/internal/download/interface.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +2 -1
- package/dist/internal/nodes/apiService.js +5 -2
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +1 -0
- package/dist/internal/nodes/cryptoService.js +28 -4
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +70 -2
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +2 -1
- package/dist/internal/nodes/nodesManagement.js +6 -1
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/shares/apiService.js +2 -0
- package/dist/internal/shares/apiService.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +1 -1
- package/dist/internal/sharingPublic/nodes.js +2 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/internal/upload/apiService.d.ts +1 -0
- package/dist/internal/upload/apiService.js +12 -0
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/manager.js +19 -1
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +23 -0
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +1 -1
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/protonDriveClient.d.ts +1 -1
- package/dist/protonDriveClient.js +3 -3
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.js +1 -1
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +3 -1
- package/dist/protonDrivePublicLinkClient.js +4 -2
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +1 -0
- package/src/crypto/interface.ts +1 -0
- package/src/crypto/openPGPCrypto.ts +3 -0
- package/src/errors.ts +2 -2
- package/src/interface/account.ts +6 -0
- package/src/interface/download.ts +16 -0
- package/src/internal/apiService/driveTypes.ts +197 -22
- package/src/internal/download/controller.ts +9 -0
- package/src/internal/download/cryptoService.ts +13 -3
- package/src/internal/download/fileDownloader.test.ts +17 -11
- package/src/internal/download/fileDownloader.ts +9 -5
- package/src/internal/download/interface.ts +15 -0
- package/src/internal/nodes/apiService.ts +20 -6
- package/src/internal/nodes/cryptoService.test.ts +113 -2
- package/src/internal/nodes/cryptoService.ts +53 -8
- package/src/internal/nodes/nodesManagement.ts +7 -1
- package/src/internal/shares/apiService.ts +3 -1
- package/src/internal/sharingPublic/nodes.ts +2 -2
- package/src/internal/upload/apiService.ts +25 -0
- package/src/internal/upload/manager.test.ts +37 -0
- package/src/internal/upload/manager.ts +17 -1
- package/src/internal/upload/streamUploader.ts +1 -1
- package/src/protonDriveClient.ts +3 -3
- package/src/protonDrivePhotosClient.ts +1 -1
- package/src/protonDrivePublicLinkClient.ts +4 -2
|
@@ -491,7 +491,8 @@ export interface paths {
|
|
|
491
491
|
get?: never;
|
|
492
492
|
put?: never;
|
|
493
493
|
/**
|
|
494
|
-
* Delete
|
|
494
|
+
* Delete drafts from folder
|
|
495
|
+
* @deprecated
|
|
495
496
|
* @description Permanently delete children from folder, skipping trash. Can only be done for draft links.
|
|
496
497
|
*/
|
|
497
498
|
post: operations["post_drive-shares-{shareID}-folders-{linkID}-delete_multiple"];
|
|
@@ -531,8 +532,8 @@ export interface paths {
|
|
|
531
532
|
get?: never;
|
|
532
533
|
put?: never;
|
|
533
534
|
/**
|
|
534
|
-
* Trash children
|
|
535
|
-
* @
|
|
535
|
+
* Trash children from folder
|
|
536
|
+
* @deprecated
|
|
536
537
|
*/
|
|
537
538
|
post: operations["post_drive-shares-{shareID}-folders-{linkID}-trash_multiple"];
|
|
538
539
|
delete?: never;
|
|
@@ -672,8 +673,8 @@ export interface paths {
|
|
|
672
673
|
get?: never;
|
|
673
674
|
put?: never;
|
|
674
675
|
/**
|
|
675
|
-
* Delete
|
|
676
|
-
* @description Permanently delete
|
|
676
|
+
* Delete drafts
|
|
677
|
+
* @description Permanently delete files, skipping trash. Can only be done for draft links.
|
|
677
678
|
*/
|
|
678
679
|
post: operations["post_drive-v2-volumes-{volumeID}-delete_multiple"];
|
|
679
680
|
delete?: never;
|
|
@@ -815,6 +816,32 @@ export interface paths {
|
|
|
815
816
|
patch?: never;
|
|
816
817
|
trace?: never;
|
|
817
818
|
};
|
|
819
|
+
"/drive/v2/volumes/{volumeID}/remove-mine": {
|
|
820
|
+
parameters: {
|
|
821
|
+
query?: never;
|
|
822
|
+
header?: never;
|
|
823
|
+
path?: never;
|
|
824
|
+
cookie?: never;
|
|
825
|
+
};
|
|
826
|
+
get?: never;
|
|
827
|
+
put?: never;
|
|
828
|
+
/**
|
|
829
|
+
* Remove my nodes skipping trash
|
|
830
|
+
* @description This is called by Web SDK on public sharing to remove active nodes created by the same user
|
|
831
|
+
* as a way to delete wrongly uploaded files without going to trash. It's supported on the following conditions:
|
|
832
|
+
* - anonymous users must have created the node in their own session
|
|
833
|
+
* - for authenticated users the signature email must match
|
|
834
|
+
* - file/folder must have been created within the last 1 hour
|
|
835
|
+
* - folders must be empty
|
|
836
|
+
* - files must have all revisions created by this user
|
|
837
|
+
*/
|
|
838
|
+
post: operations["post_drive-v2-volumes-{volumeID}-remove-mine"];
|
|
839
|
+
delete?: never;
|
|
840
|
+
options?: never;
|
|
841
|
+
head?: never;
|
|
842
|
+
patch?: never;
|
|
843
|
+
trace?: never;
|
|
844
|
+
};
|
|
818
845
|
"/drive/v2/volumes/{volumeID}/links/{linkID}/rename": {
|
|
819
846
|
parameters: {
|
|
820
847
|
query?: never;
|
|
@@ -829,6 +856,11 @@ export interface paths {
|
|
|
829
856
|
*
|
|
830
857
|
* Clients renaming a file or folder MUST reuse the existing session key
|
|
831
858
|
* for the name as it is also used by shares pointing to the link.
|
|
859
|
+
*
|
|
860
|
+
* Users with access only through a public sharing URL (no editor membership) are limited to renaming
|
|
861
|
+
* their own files and folders:
|
|
862
|
+
* - Unauthenticated users must have created them in their session
|
|
863
|
+
* - Authenticated users' email must match the signature email on the node for folders or active revision for files
|
|
832
864
|
*/
|
|
833
865
|
put: operations["put_drive-v2-volumes-{volumeID}-links-{linkID}-rename"];
|
|
834
866
|
post?: never;
|
|
@@ -852,6 +884,11 @@ export interface paths {
|
|
|
852
884
|
*
|
|
853
885
|
* Clients renaming a file or folder MUST reuse the existing session key
|
|
854
886
|
* for the name as it is also used by shares pointing to the link.
|
|
887
|
+
*
|
|
888
|
+
* Users with access only through a public sharing URL (no editor membership) are limited to renaming
|
|
889
|
+
* their own files and folders:
|
|
890
|
+
* - Unauthenticated users must have created them in their session
|
|
891
|
+
* - Authenticated users' email must match the signature email on the node for folders or active revision for files
|
|
855
892
|
*/
|
|
856
893
|
put: operations["put_drive-shares-{shareID}-links-{linkID}-rename"];
|
|
857
894
|
post?: never;
|
|
@@ -2227,7 +2264,7 @@ export interface paths {
|
|
|
2227
2264
|
get?: never;
|
|
2228
2265
|
put?: never;
|
|
2229
2266
|
/**
|
|
2230
|
-
* Delete
|
|
2267
|
+
* Delete drafts
|
|
2231
2268
|
* @description See /drive/v2/volumes/{volumeID}/delete_multiple for full documentation
|
|
2232
2269
|
*/
|
|
2233
2270
|
post: operations["post_drive-unauth-v2-volumes-{volumeID}-delete_multiple"];
|
|
@@ -2297,6 +2334,26 @@ export interface paths {
|
|
|
2297
2334
|
patch?: never;
|
|
2298
2335
|
trace?: never;
|
|
2299
2336
|
};
|
|
2337
|
+
"/drive/unauth/v2/volumes/{volumeID}/remove-mine": {
|
|
2338
|
+
parameters: {
|
|
2339
|
+
query?: never;
|
|
2340
|
+
header?: never;
|
|
2341
|
+
path?: never;
|
|
2342
|
+
cookie?: never;
|
|
2343
|
+
};
|
|
2344
|
+
get?: never;
|
|
2345
|
+
put?: never;
|
|
2346
|
+
/**
|
|
2347
|
+
* Remove my nodes skipping trash
|
|
2348
|
+
* @description See /drive/v2/volumes/{volumeID}/remove-mine for full documentation
|
|
2349
|
+
*/
|
|
2350
|
+
post: operations["post_drive-unauth-v2-volumes-{volumeID}-remove-mine"];
|
|
2351
|
+
delete?: never;
|
|
2352
|
+
options?: never;
|
|
2353
|
+
head?: never;
|
|
2354
|
+
patch?: never;
|
|
2355
|
+
trace?: never;
|
|
2356
|
+
};
|
|
2300
2357
|
"/drive/unauth/v2/volumes/{volumeID}/links/{linkID}/rename": {
|
|
2301
2358
|
parameters: {
|
|
2302
2359
|
query?: never;
|
|
@@ -2978,6 +3035,30 @@ export interface paths {
|
|
|
2978
3035
|
patch?: never;
|
|
2979
3036
|
trace?: never;
|
|
2980
3037
|
};
|
|
3038
|
+
"/drive/organization/volumes": {
|
|
3039
|
+
parameters: {
|
|
3040
|
+
query?: never;
|
|
3041
|
+
header?: never;
|
|
3042
|
+
path?: never;
|
|
3043
|
+
cookie?: never;
|
|
3044
|
+
};
|
|
3045
|
+
get?: never;
|
|
3046
|
+
put?: never;
|
|
3047
|
+
/**
|
|
3048
|
+
* Create Organization volume
|
|
3049
|
+
* @description Only allowed to Org administrators
|
|
3050
|
+
*
|
|
3051
|
+
* This new volume would have:
|
|
3052
|
+
* + OwnerOrgID filled with the orgID of the request
|
|
3053
|
+
* + specific membership for the owner (OrgAdmin to true)
|
|
3054
|
+
*/
|
|
3055
|
+
post: operations["post_drive-organization-volumes"];
|
|
3056
|
+
delete?: never;
|
|
3057
|
+
options?: never;
|
|
3058
|
+
head?: never;
|
|
3059
|
+
patch?: never;
|
|
3060
|
+
trace?: never;
|
|
3061
|
+
};
|
|
2981
3062
|
"/drive/volumes": {
|
|
2982
3063
|
parameters: {
|
|
2983
3064
|
query?: never;
|
|
@@ -4852,6 +4933,31 @@ export interface components {
|
|
|
4852
4933
|
/** @description Order and visibility of Photo Tags, tags not in the list should not be shown; Use defaults when NULL; Show no tags if empty array. */
|
|
4853
4934
|
PhotoTags?: components["schemas"]["TagType"][] | null;
|
|
4854
4935
|
};
|
|
4936
|
+
CreateOrgVolumeRequestDto: {
|
|
4937
|
+
AddressID: components["schemas"]["AddressID"];
|
|
4938
|
+
/** @description XX's encrypted AddressKeyID. Must be the primary key from the AddressID */
|
|
4939
|
+
AddressKeyID: string;
|
|
4940
|
+
ShareKey: components["schemas"]["PGPPrivateKey"];
|
|
4941
|
+
SharePassphrase: components["schemas"]["PGPMessage"];
|
|
4942
|
+
SharePassphraseSignature: components["schemas"]["PGPSignature"];
|
|
4943
|
+
FolderName: components["schemas"]["PGPMessage"];
|
|
4944
|
+
FolderKey: components["schemas"]["PGPPrivateKey"];
|
|
4945
|
+
FolderPassphrase: components["schemas"]["PGPMessage"];
|
|
4946
|
+
FolderPassphraseSignature: components["schemas"]["PGPSignature"];
|
|
4947
|
+
FolderHashKey: components["schemas"]["PGPMessage"];
|
|
4948
|
+
OrganizationID: components["schemas"]["Id"];
|
|
4949
|
+
/** @description Name of the org. volume. It's plain text so that name can be displayed in UI menu */
|
|
4950
|
+
VolumeName: string;
|
|
4951
|
+
};
|
|
4952
|
+
GetVolumeResponseDto: {
|
|
4953
|
+
Volume: components["schemas"]["VolumeResponseDto"];
|
|
4954
|
+
/**
|
|
4955
|
+
* ProtonResponseCode
|
|
4956
|
+
* @example 1000
|
|
4957
|
+
* @enum {integer}
|
|
4958
|
+
*/
|
|
4959
|
+
Code: 1000;
|
|
4960
|
+
};
|
|
4855
4961
|
CreateVolumeRequestDto: {
|
|
4856
4962
|
AddressID: components["schemas"]["AddressID"];
|
|
4857
4963
|
ShareKey: components["schemas"]["PGPPrivateKey"];
|
|
@@ -4875,15 +4981,6 @@ export interface components {
|
|
|
4875
4981
|
*/
|
|
4876
4982
|
ShareName: string | null;
|
|
4877
4983
|
};
|
|
4878
|
-
GetVolumeResponseDto: {
|
|
4879
|
-
Volume: components["schemas"]["VolumeResponseDto"];
|
|
4880
|
-
/**
|
|
4881
|
-
* ProtonResponseCode
|
|
4882
|
-
* @example 1000
|
|
4883
|
-
* @enum {integer}
|
|
4884
|
-
*/
|
|
4885
|
-
Code: 1000;
|
|
4886
|
-
};
|
|
4887
4984
|
ListVolumesResponseDto: {
|
|
4888
4985
|
Volumes: components["schemas"]["VolumeResponseDto"][];
|
|
4889
4986
|
/**
|
|
@@ -6213,20 +6310,20 @@ export interface components {
|
|
|
6213
6310
|
Album: null | null;
|
|
6214
6311
|
};
|
|
6215
6312
|
/**
|
|
6216
|
-
* @description <p>1=Main, 2=Standard, 3=Device, 4=Photo</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Name</th><th>Description</th></tr><tr><td>1</td><td>Main</td><td>* Root share for my files</td></tr><tr><td>2</td><td>Standard</td><td>* Collaborative share anywhere in the link tree (but not at the root folder as it cannot be shared)</td></tr><tr><td>3</td><td>Device</td><td>* Root share of devices</td></tr><tr><td>4</td><td>Photo</td><td>* Root share for photos</td></tr></table></details></details>
|
|
6313
|
+
* @description <p>1=Main, 2=Standard, 3=Device, 4=Photo</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Name</th><th>Description</th></tr><tr><td>1</td><td>Main</td><td>* Root share for my files</td></tr><tr><td>2</td><td>Standard</td><td>* Collaborative share anywhere in the link tree (but not at the root folder as it cannot be shared)</td></tr><tr><td>3</td><td>Device</td><td>* Root share of devices</td></tr><tr><td>4</td><td>Photo</td><td>* Root share for photos</td></tr><tr><td>5</td><td>Organization</td><td>* Root share for organization</td></tr></table></details></details>
|
|
6217
6314
|
* @enum {integer}
|
|
6218
6315
|
*/
|
|
6219
|
-
ShareType: 1 | 2 | 3 | 4;
|
|
6316
|
+
ShareType: 1 | 2 | 3 | 4 | 5;
|
|
6220
6317
|
/**
|
|
6221
6318
|
* @description <p>1=Active, 3=Restored</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Active</td></tr><tr><td>2</td><td>Deleted</td></tr><tr><td>3</td><td>Restored</td></tr><tr><td>6</td><td>Locked</td></tr></table></details></details>
|
|
6222
6319
|
* @enum {integer}
|
|
6223
6320
|
*/
|
|
6224
6321
|
ShareState: 1 | 2 | 3 | 6;
|
|
6225
6322
|
/**
|
|
6226
|
-
* @description <p>1=Regular, 2=Photo</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Regular</td></tr><tr><td>2</td><td>Photo</td></tr></table></details></details>
|
|
6323
|
+
* @description <p>1=Regular, 2=Photo</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Regular</td></tr><tr><td>2</td><td>Photo</td></tr><tr><td>3</td><td>Organization</td></tr></table></details></details>
|
|
6227
6324
|
* @enum {integer}
|
|
6228
6325
|
*/
|
|
6229
|
-
VolumeType: 1 | 2;
|
|
6326
|
+
VolumeType: 1 | 2 | 3;
|
|
6230
6327
|
/**
|
|
6231
6328
|
* @description <p>1=folder, 2=file</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Folder</td></tr><tr><td>2</td><td>File</td></tr><tr><td>3</td><td>Album</td></tr></table></details></details>
|
|
6232
6329
|
* @enum {integer}
|
|
@@ -6776,10 +6873,10 @@ export interface components {
|
|
|
6776
6873
|
LinkID: components["schemas"]["Id2"];
|
|
6777
6874
|
};
|
|
6778
6875
|
/**
|
|
6779
|
-
* @description <details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Regular</td></tr><tr><td>2</td><td>Photo</td></tr></table></details></details>
|
|
6876
|
+
* @description <details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>1</td><td>Regular</td></tr><tr><td>2</td><td>Photo</td></tr><tr><td>3</td><td>Organization</td></tr></table></details></details>
|
|
6780
6877
|
* @enum {integer}
|
|
6781
6878
|
*/
|
|
6782
|
-
VolumeType2: 1 | 2;
|
|
6879
|
+
VolumeType2: 1 | 2 | 3;
|
|
6783
6880
|
/**
|
|
6784
6881
|
* @description <p>Can be null if the Link was deleted</p><details><summary>See values descriptions</summary><details><summary>See values descriptions</summary><table><tr><th>Value</th><th>Description</th></tr><tr><td>0</td><td>Draft</td></tr><tr><td>1</td><td>Active</td></tr><tr><td>2</td><td>Trashed</td></tr></table></details></details>
|
|
6785
6882
|
* @enum {integer}
|
|
@@ -9119,6 +9216,36 @@ export interface operations {
|
|
|
9119
9216
|
};
|
|
9120
9217
|
};
|
|
9121
9218
|
};
|
|
9219
|
+
"post_drive-v2-volumes-{volumeID}-remove-mine": {
|
|
9220
|
+
parameters: {
|
|
9221
|
+
query?: never;
|
|
9222
|
+
header?: never;
|
|
9223
|
+
path: {
|
|
9224
|
+
volumeID: string;
|
|
9225
|
+
};
|
|
9226
|
+
cookie?: never;
|
|
9227
|
+
};
|
|
9228
|
+
requestBody?: {
|
|
9229
|
+
content: {
|
|
9230
|
+
"application/json": components["schemas"]["LinkIDsRequestDto"];
|
|
9231
|
+
};
|
|
9232
|
+
};
|
|
9233
|
+
responses: {
|
|
9234
|
+
/** @description Ok */
|
|
9235
|
+
200: {
|
|
9236
|
+
headers: {
|
|
9237
|
+
[name: string]: unknown;
|
|
9238
|
+
};
|
|
9239
|
+
content: {
|
|
9240
|
+
"application/json": {
|
|
9241
|
+
/** @enum {integer} */
|
|
9242
|
+
Code?: 1001;
|
|
9243
|
+
Responses?: components["schemas"]["MultiDeleteTransformer"][];
|
|
9244
|
+
};
|
|
9245
|
+
};
|
|
9246
|
+
};
|
|
9247
|
+
};
|
|
9248
|
+
};
|
|
9122
9249
|
"put_drive-v2-volumes-{volumeID}-links-{linkID}-rename": {
|
|
9123
9250
|
parameters: {
|
|
9124
9251
|
query?: never;
|
|
@@ -12150,6 +12277,29 @@ export interface operations {
|
|
|
12150
12277
|
};
|
|
12151
12278
|
};
|
|
12152
12279
|
};
|
|
12280
|
+
"post_drive-unauth-v2-volumes-{volumeID}-remove-mine": {
|
|
12281
|
+
parameters: {
|
|
12282
|
+
query?: never;
|
|
12283
|
+
header?: never;
|
|
12284
|
+
path: {
|
|
12285
|
+
volumeID: string;
|
|
12286
|
+
};
|
|
12287
|
+
cookie?: never;
|
|
12288
|
+
};
|
|
12289
|
+
requestBody?: {
|
|
12290
|
+
content: {
|
|
12291
|
+
"application/json": components["schemas"]["LinkIDsRequestDto"];
|
|
12292
|
+
};
|
|
12293
|
+
};
|
|
12294
|
+
responses: {
|
|
12295
|
+
default: {
|
|
12296
|
+
headers: {
|
|
12297
|
+
[name: string]: unknown;
|
|
12298
|
+
};
|
|
12299
|
+
content?: never;
|
|
12300
|
+
};
|
|
12301
|
+
};
|
|
12302
|
+
};
|
|
12153
12303
|
"put_drive-unauth-v2-volumes-{volumeID}-links-{linkID}-rename": {
|
|
12154
12304
|
parameters: {
|
|
12155
12305
|
query?: never;
|
|
@@ -12404,7 +12554,7 @@ export interface operations {
|
|
|
12404
12554
|
/** @description Show disabled shares as well, i.e. Shares where the ShareMemberShip for the user is non-active (locked), otherwise only return with active Membership */
|
|
12405
12555
|
ShowAll?: 0 | 1;
|
|
12406
12556
|
/** @description Filter on Share Type */
|
|
12407
|
-
ShareType?: 1 | 2 | 3 | 4;
|
|
12557
|
+
ShareType?: 1 | 2 | 3 | 4 | 5;
|
|
12408
12558
|
};
|
|
12409
12559
|
header?: never;
|
|
12410
12560
|
path?: never;
|
|
@@ -13477,6 +13627,31 @@ export interface operations {
|
|
|
13477
13627
|
};
|
|
13478
13628
|
};
|
|
13479
13629
|
};
|
|
13630
|
+
"post_drive-organization-volumes": {
|
|
13631
|
+
parameters: {
|
|
13632
|
+
query?: never;
|
|
13633
|
+
header?: never;
|
|
13634
|
+
path?: never;
|
|
13635
|
+
cookie?: never;
|
|
13636
|
+
};
|
|
13637
|
+
requestBody?: {
|
|
13638
|
+
content: {
|
|
13639
|
+
"application/json": components["schemas"]["CreateOrgVolumeRequestDto"];
|
|
13640
|
+
};
|
|
13641
|
+
};
|
|
13642
|
+
responses: {
|
|
13643
|
+
/** @description Success */
|
|
13644
|
+
200: {
|
|
13645
|
+
headers: {
|
|
13646
|
+
"x-pm-code": 1000;
|
|
13647
|
+
[name: string]: unknown;
|
|
13648
|
+
};
|
|
13649
|
+
content: {
|
|
13650
|
+
"application/json": components["schemas"]["GetVolumeResponseDto"];
|
|
13651
|
+
};
|
|
13652
|
+
};
|
|
13653
|
+
};
|
|
13654
|
+
};
|
|
13480
13655
|
"get_drive-volumes": {
|
|
13481
13656
|
parameters: {
|
|
13482
13657
|
query?: {
|
|
@@ -4,6 +4,7 @@ import { waitForCondition } from '../wait';
|
|
|
4
4
|
export class DownloadController {
|
|
5
5
|
private paused = false;
|
|
6
6
|
public promise?: Promise<void>;
|
|
7
|
+
private _isDownloadCompleteWithSignatureIssues = false;
|
|
7
8
|
|
|
8
9
|
constructor(private signal?: AbortSignal) {
|
|
9
10
|
this.signal = signal;
|
|
@@ -31,4 +32,12 @@ export class DownloadController {
|
|
|
31
32
|
async completion(): Promise<void> {
|
|
32
33
|
await this.promise;
|
|
33
34
|
}
|
|
35
|
+
|
|
36
|
+
isDownloadCompleteWithSignatureIssues(): boolean {
|
|
37
|
+
return this._isDownloadCompleteWithSignatureIssues;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setIsDownloadCompleteWithSignatureIssues(value: boolean): void {
|
|
41
|
+
this._isDownloadCompleteWithSignatureIssues = value;
|
|
42
|
+
}
|
|
34
43
|
}
|
|
@@ -12,7 +12,7 @@ import { ProtonDriveAccount, Revision } from '../../interface';
|
|
|
12
12
|
import { DecryptionError, IntegrityError } from '../../errors';
|
|
13
13
|
import { getErrorMessage } from '../errors';
|
|
14
14
|
import { mergeUint8Arrays } from '../utils';
|
|
15
|
-
import { RevisionKeys } from './interface';
|
|
15
|
+
import { RevisionKeys, SignatureVerificationError } from './interface';
|
|
16
16
|
|
|
17
17
|
export class DownloadCryptoService {
|
|
18
18
|
constructor(
|
|
@@ -90,13 +90,23 @@ export class DownloadCryptoService {
|
|
|
90
90
|
allBlockHashes: Uint8Array[],
|
|
91
91
|
armoredManifestSignature?: string,
|
|
92
92
|
): Promise<void> {
|
|
93
|
-
const verificationKeys = await this.getRevisionVerificationKeys(revision, nodeKey);
|
|
94
93
|
const hash = mergeUint8Arrays(allBlockHashes);
|
|
95
94
|
|
|
96
95
|
if (!armoredManifestSignature) {
|
|
97
96
|
throw new IntegrityError(c('Error').t`Missing integrity signature`);
|
|
98
97
|
}
|
|
99
98
|
|
|
99
|
+
let verificationKeys;
|
|
100
|
+
try {
|
|
101
|
+
verificationKeys = await this.getRevisionVerificationKeys(revision, nodeKey);
|
|
102
|
+
} catch (error: unknown) {
|
|
103
|
+
throw new SignatureVerificationError(
|
|
104
|
+
c('Error').t`Failed to get verification keys`,
|
|
105
|
+
{ revisionUid: revision.uid, contentAuthor: revision.contentAuthor },
|
|
106
|
+
{ cause: error },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
100
110
|
const { verified, verificationErrors } = await this.driveCrypto.verifyManifest(
|
|
101
111
|
hash,
|
|
102
112
|
armoredManifestSignature,
|
|
@@ -104,7 +114,7 @@ export class DownloadCryptoService {
|
|
|
104
114
|
);
|
|
105
115
|
|
|
106
116
|
if (verified !== VERIFICATION_STATUS.SIGNED_AND_VALID) {
|
|
107
|
-
throw new
|
|
117
|
+
throw new SignatureVerificationError(c('Error').t`Data integrity check failed`, {
|
|
108
118
|
verificationErrors,
|
|
109
119
|
});
|
|
110
120
|
}
|
|
@@ -4,6 +4,8 @@ import { FileDownloader } from './fileDownloader';
|
|
|
4
4
|
import { DownloadTelemetry } from './telemetry';
|
|
5
5
|
import { DownloadAPIService } from './apiService';
|
|
6
6
|
import { DownloadCryptoService } from './cryptoService';
|
|
7
|
+
import { SignatureVerificationError } from './interface';
|
|
8
|
+
import { IntegrityError } from '../..';
|
|
7
9
|
|
|
8
10
|
function mockBlockDownload(_: string, token: string, onProgress: (downloadedBytes: number) => void) {
|
|
9
11
|
const index = parseInt(token.slice(5, 6));
|
|
@@ -94,8 +96,6 @@ describe('FileDownloader', () => {
|
|
|
94
96
|
|
|
95
97
|
expect(apiService.iterateRevisionBlocks).toHaveBeenCalledWith('revisionUid', undefined);
|
|
96
98
|
expect(cryptoService.verifyManifest).toHaveBeenCalledTimes(1);
|
|
97
|
-
expect(writer.close).toHaveBeenCalledTimes(1);
|
|
98
|
-
expect(writer.abort).not.toHaveBeenCalled();
|
|
99
99
|
expect(telemetry.downloadFinished).toHaveBeenCalledTimes(1);
|
|
100
100
|
expect(telemetry.downloadFinished).toHaveBeenCalledWith('revisionUid', fileProgress);
|
|
101
101
|
expect(telemetry.downloadFailed).not.toHaveBeenCalled();
|
|
@@ -108,8 +108,6 @@ describe('FileDownloader', () => {
|
|
|
108
108
|
await expect(controller.completion()).rejects.toThrow(error);
|
|
109
109
|
|
|
110
110
|
expect(apiService.iterateRevisionBlocks).toHaveBeenCalledWith('revisionUid', undefined);
|
|
111
|
-
expect(writer.close).not.toHaveBeenCalled();
|
|
112
|
-
expect(writer.abort).toHaveBeenCalledTimes(1);
|
|
113
111
|
expect(telemetry.downloadFinished).not.toHaveBeenCalled();
|
|
114
112
|
expect(telemetry.downloadFailed).toHaveBeenCalledTimes(1);
|
|
115
113
|
expect(telemetry.downloadFailed).toHaveBeenCalledWith(
|
|
@@ -119,6 +117,8 @@ describe('FileDownloader', () => {
|
|
|
119
117
|
revision.claimedSize,
|
|
120
118
|
);
|
|
121
119
|
expect(onFinish).toHaveBeenCalledTimes(1);
|
|
120
|
+
|
|
121
|
+
return controller;
|
|
122
122
|
};
|
|
123
123
|
|
|
124
124
|
const verifyOnProgress = async (downloadedBytes: number[]) => {
|
|
@@ -137,8 +137,6 @@ describe('FileDownloader', () => {
|
|
|
137
137
|
// @ts-expect-error Mocking WritableStreamDefaultWriter
|
|
138
138
|
writer = {
|
|
139
139
|
write: jest.fn(),
|
|
140
|
-
close: jest.fn(),
|
|
141
|
-
abort: jest.fn(),
|
|
142
140
|
};
|
|
143
141
|
// @ts-expect-error Mocking WritableStream
|
|
144
142
|
stream = {
|
|
@@ -338,12 +336,22 @@ describe('FileDownloader', () => {
|
|
|
338
336
|
await verifyOnProgress([1, 2, 3]);
|
|
339
337
|
});
|
|
340
338
|
|
|
341
|
-
it('should handle failure when verifying manifest', async () => {
|
|
339
|
+
it('should handle failure when verifying manifest with non-recoverable integrity error', async () => {
|
|
340
|
+
cryptoService.verifyManifest = jest.fn().mockImplementation(async function () {
|
|
341
|
+
throw new IntegrityError('Failed to verify manifest');
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const controller = await verifyFailure('Failed to verify manifest', 6); // All blocks of length 1, 2, 3.
|
|
345
|
+
expect(controller.isDownloadCompleteWithSignatureIssues()).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('should handle failure when verifying manifest with recoverable signature verification error', async () => {
|
|
342
349
|
cryptoService.verifyManifest = jest.fn().mockImplementation(async function () {
|
|
343
|
-
throw new
|
|
350
|
+
throw new SignatureVerificationError('Failed to verify manifest');
|
|
344
351
|
});
|
|
345
352
|
|
|
346
|
-
await verifyFailure('Failed to verify manifest', 6); // All blocks of length 1, 2, 3.
|
|
353
|
+
const controller = await verifyFailure('Failed to verify manifest', 6); // All blocks of length 1, 2, 3.
|
|
354
|
+
expect(controller.isDownloadCompleteWithSignatureIssues()).toBe(true);
|
|
347
355
|
});
|
|
348
356
|
});
|
|
349
357
|
|
|
@@ -389,8 +397,6 @@ describe('FileDownloader', () => {
|
|
|
389
397
|
expect(apiService.downloadBlock).toHaveBeenCalledTimes(3);
|
|
390
398
|
expect(cryptoService.verifyBlockIntegrity).not.toHaveBeenCalled();
|
|
391
399
|
expect(cryptoService.decryptBlock).toHaveBeenCalledTimes(3);
|
|
392
|
-
expect(writer.close).toHaveBeenCalledTimes(1);
|
|
393
|
-
expect(writer.abort).not.toHaveBeenCalled();
|
|
394
400
|
expect(telemetry.downloadFinished).toHaveBeenCalledTimes(1);
|
|
395
401
|
expect(telemetry.downloadFinished).toHaveBeenCalledWith('revisionUid', 6); // 3 blocks of length 1, 2, 3.
|
|
396
402
|
expect(telemetry.downloadFailed).not.toHaveBeenCalled();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
3
|
import { PrivateKey, SessionKey, base64StringToUint8Array } from '../../crypto';
|
|
4
|
-
import { AbortError } from '../../errors';
|
|
4
|
+
import { AbortError, IntegrityError } from '../../errors';
|
|
5
5
|
import { Logger } from '../../interface';
|
|
6
6
|
import { LoggerWithPrefix } from '../../telemetry';
|
|
7
7
|
import { APIHTTPError, HTTPErrorCode } from '../apiService';
|
|
@@ -10,7 +10,7 @@ import { DownloadAPIService } from './apiService';
|
|
|
10
10
|
import { getBlockIndex } from './blockIndex';
|
|
11
11
|
import { DownloadController } from './controller';
|
|
12
12
|
import { DownloadCryptoService } from './cryptoService';
|
|
13
|
-
import { BlockMetadata, RevisionKeys } from './interface';
|
|
13
|
+
import { BlockMetadata, RevisionKeys, SignatureVerificationError } from './interface';
|
|
14
14
|
import { BufferedSeekableStream } from './seekableStream';
|
|
15
15
|
import { DownloadTelemetry } from './telemetry';
|
|
16
16
|
|
|
@@ -233,13 +233,17 @@ export class FileDownloader {
|
|
|
233
233
|
);
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
await writer.close();
|
|
237
236
|
void this.telemetry.downloadFinished(this.revision.uid, fileProgress);
|
|
238
237
|
this.logger.info(`Download succeeded`);
|
|
239
238
|
} catch (error: unknown) {
|
|
240
|
-
|
|
239
|
+
if (error instanceof SignatureVerificationError) {
|
|
240
|
+
this.logger.warn(`Download finished with signature verification issues`);
|
|
241
|
+
this.controller.setIsDownloadCompleteWithSignatureIssues(true);
|
|
242
|
+
error = new IntegrityError(error.message, error.debug, { cause: error });
|
|
243
|
+
} else {
|
|
244
|
+
this.logger.error(`Download failed`, error);
|
|
245
|
+
}
|
|
241
246
|
void this.telemetry.downloadFailed(this.revision.uid, error, fileProgress, this.getClaimedSizeInBytes());
|
|
242
|
-
await writer.abort?.();
|
|
243
247
|
throw error;
|
|
244
248
|
} finally {
|
|
245
249
|
this.logger.debug(`Download cleanup`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PrivateKey, PublicKey, SessionKey } from '../../crypto';
|
|
2
|
+
import { IntegrityError } from '../../errors';
|
|
2
3
|
import { NodeType, Result, MissingNode, MetricVolumeType } from '../../interface';
|
|
3
4
|
import { DecryptedNode, DecryptedRevision } from '../nodes';
|
|
4
5
|
|
|
@@ -35,3 +36,17 @@ export interface NodesServiceNode {
|
|
|
35
36
|
export interface RevisionsService {
|
|
36
37
|
getRevision(nodeRevisionUid: string): Promise<DecryptedRevision>;
|
|
37
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Error thrown when the manifest signature verification fails.
|
|
42
|
+
* This is a special case that is reported as download complete with signature
|
|
43
|
+
* issues. The client must then ask the user to agree to save the file anyway
|
|
44
|
+
* or abort and clean up the file.
|
|
45
|
+
*
|
|
46
|
+
* This error is not exposed to the client. It is only used internally to track
|
|
47
|
+
* the signature verification issues. For the client it must be reported as
|
|
48
|
+
* the IntegrityError.
|
|
49
|
+
*/
|
|
50
|
+
export class SignatureVerificationError extends IntegrityError {
|
|
51
|
+
name = 'SignatureVerificationError';
|
|
52
|
+
}
|
|
@@ -57,6 +57,9 @@ type PostCopyNodeRequest = Extract<
|
|
|
57
57
|
type PostCopyNodeResponse =
|
|
58
58
|
drivePaths['/drive/volumes/{volumeID}/links/{linkID}/copy']['post']['responses']['200']['content']['application/json'];
|
|
59
59
|
|
|
60
|
+
type EmptyTrashResponse =
|
|
61
|
+
drivePaths['/drive/volumes/{volumeID}/trash']['delete']['responses']['200']['content']['application/json'];
|
|
62
|
+
|
|
60
63
|
type PostTrashNodesRequest = Extract<
|
|
61
64
|
drivePaths['/drive/v2/volumes/{volumeID}/trash_multiple']['post']['requestBody'],
|
|
62
65
|
{ content: object }
|
|
@@ -71,13 +74,20 @@ type PutRestoreNodesRequest = Extract<
|
|
|
71
74
|
type PutRestoreNodesResponse =
|
|
72
75
|
drivePaths['/drive/v2/volumes/{volumeID}/trash/restore_multiple']['put']['responses']['200']['content']['application/json'];
|
|
73
76
|
|
|
74
|
-
type
|
|
77
|
+
type PostDeleteTrashedNodesRequest = Extract<
|
|
75
78
|
drivePaths['/drive/v2/volumes/{volumeID}/trash/delete_multiple']['post']['requestBody'],
|
|
76
79
|
{ content: object }
|
|
77
80
|
>['content']['application/json'];
|
|
78
|
-
type
|
|
81
|
+
type PostDeleteTrashedNodesResponse =
|
|
79
82
|
drivePaths['/drive/v2/volumes/{volumeID}/trash/delete_multiple']['post']['responses']['200']['content']['application/json'];
|
|
80
83
|
|
|
84
|
+
type PostDeleteMyNodesRequest = Extract<
|
|
85
|
+
drivePaths['/drive/v2/volumes/{volumeID}/remove-mine']['post']['requestBody'],
|
|
86
|
+
{ content: object }
|
|
87
|
+
>['content']['application/json'];
|
|
88
|
+
type PostDeleteMyNodesResponse =
|
|
89
|
+
drivePaths['/drive/v2/volumes/{volumeID}/remove-mine']['post']['responses']['200']['content']['application/json'];
|
|
90
|
+
|
|
81
91
|
type PostCreateFolderRequest = Extract<
|
|
82
92
|
drivePaths['/drive/v2/volumes/{volumeID}/folders']['post']['requestBody'],
|
|
83
93
|
{ content: object }
|
|
@@ -429,6 +439,10 @@ export abstract class NodeAPIServiceBase<
|
|
|
429
439
|
}
|
|
430
440
|
}
|
|
431
441
|
|
|
442
|
+
async emptyTrash(volumeId: string): Promise<void> {
|
|
443
|
+
await this.apiService.delete<EmptyTrashResponse>(`drive/volumes/${volumeId}/trash`);
|
|
444
|
+
}
|
|
445
|
+
|
|
432
446
|
async *restoreNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
433
447
|
for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
|
|
434
448
|
const response = await this.apiService.put<PutRestoreNodesRequest, PutRestoreNodesResponse>(
|
|
@@ -446,7 +460,7 @@ export abstract class NodeAPIServiceBase<
|
|
|
446
460
|
|
|
447
461
|
async *deleteTrashedNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
448
462
|
for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
|
|
449
|
-
const response = await this.apiService.post<
|
|
463
|
+
const response = await this.apiService.post<PostDeleteTrashedNodesRequest, PostDeleteTrashedNodesResponse>(
|
|
450
464
|
`drive/v2/volumes/${volumeId}/trash/delete_multiple`,
|
|
451
465
|
{
|
|
452
466
|
LinkIDs: batchNodeIds,
|
|
@@ -459,10 +473,10 @@ export abstract class NodeAPIServiceBase<
|
|
|
459
473
|
}
|
|
460
474
|
}
|
|
461
475
|
|
|
462
|
-
async *
|
|
476
|
+
async *deleteMyNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
463
477
|
for (const { volumeId, batchNodeIds, batchNodeUids } of groupNodeUidsByVolumeAndIteratePerBatch(nodeUids)) {
|
|
464
|
-
const response = await this.apiService.post<
|
|
465
|
-
`drive/v2/volumes/${volumeId}/
|
|
478
|
+
const response = await this.apiService.post<PostDeleteMyNodesRequest, PostDeleteMyNodesResponse>(
|
|
479
|
+
`drive/v2/volumes/${volumeId}/remove-mine`,
|
|
466
480
|
{
|
|
467
481
|
LinkIDs: batchNodeIds,
|
|
468
482
|
},
|