@protontech/drive-sdk 0.6.2 → 0.7.1
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/interface/index.d.ts +1 -0
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +14 -10
- package/dist/interface/nodes.js +5 -8
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/photos.d.ts +62 -0
- package/dist/interface/photos.js +3 -0
- package/dist/interface/photos.js.map +1 -0
- package/dist/internal/apiService/apiService.d.ts +2 -2
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +1294 -517
- package/dist/internal/apiService/errors.js +4 -3
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/download/cryptoService.js +8 -6
- package/dist/internal/download/cryptoService.js.map +1 -1
- package/dist/internal/download/fileDownloader.d.ts +2 -1
- package/dist/internal/download/fileDownloader.js +6 -3
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/index.d.ts +1 -1
- package/dist/internal/download/index.js +3 -3
- package/dist/internal/download/index.js.map +1 -1
- package/dist/internal/errors.d.ts +1 -0
- package/dist/internal/errors.js +4 -0
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/nodes/apiService.d.ts +68 -16
- package/dist/internal/nodes/apiService.js +138 -85
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +7 -5
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.d.ts +16 -8
- package/dist/internal/nodes/cache.js +19 -5
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +1 -0
- package/dist/internal/nodes/cache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoReporter.d.ts +3 -3
- package/dist/internal/nodes/cryptoReporter.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +13 -22
- package/dist/internal/nodes/cryptoService.js +47 -16
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +262 -17
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/events.d.ts +2 -2
- package/dist/internal/nodes/events.js.map +1 -1
- package/dist/internal/nodes/index.test.js +1 -0
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +14 -3
- package/dist/internal/nodes/nodesAccess.d.ts +36 -20
- package/dist/internal/nodes/nodesAccess.js +54 -29
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.d.ts +34 -14
- package/dist/internal/nodes/nodesManagement.js +44 -31
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.test.js +60 -14
- package/dist/internal/nodes/nodesManagement.test.js.map +1 -1
- package/dist/internal/nodes/nodesRevisions.d.ts +2 -2
- package/dist/internal/nodes/nodesRevisions.js.map +1 -1
- package/dist/internal/photos/albums.d.ts +2 -2
- package/dist/internal/photos/albums.js.map +1 -1
- package/dist/internal/photos/index.d.ts +19 -3
- package/dist/internal/photos/index.js +38 -8
- package/dist/internal/photos/index.js.map +1 -1
- package/dist/internal/photos/interface.d.ts +18 -9
- package/dist/internal/photos/nodes.d.ts +57 -0
- package/dist/internal/photos/nodes.js +165 -0
- package/dist/internal/photos/nodes.js.map +1 -0
- package/dist/internal/photos/timeline.d.ts +2 -2
- package/dist/internal/photos/timeline.js.map +1 -1
- package/dist/internal/photos/timeline.test.js.map +1 -1
- package/dist/internal/photos/upload.d.ts +2 -2
- package/dist/internal/photos/upload.js.map +1 -1
- package/dist/internal/sharingPublic/index.d.ts +6 -6
- package/dist/internal/sharingPublic/index.js +8 -7
- package/dist/internal/sharingPublic/index.js.map +1 -1
- package/dist/internal/sharingPublic/nodes.d.ts +16 -3
- package/dist/internal/sharingPublic/nodes.js +34 -2
- package/dist/internal/sharingPublic/nodes.js.map +1 -1
- package/dist/internal/sharingPublic/unauthApiService.d.ts +17 -0
- package/dist/internal/sharingPublic/unauthApiService.js +31 -0
- package/dist/internal/sharingPublic/unauthApiService.js.map +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.d.ts +1 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js +27 -0
- package/dist/internal/sharingPublic/unauthApiService.test.js.map +1 -0
- package/dist/internal/upload/apiService.d.ts +4 -3
- package/dist/internal/upload/apiService.js.map +1 -1
- package/dist/internal/upload/cryptoService.d.ts +8 -3
- package/dist/internal/upload/cryptoService.js +45 -9
- package/dist/internal/upload/cryptoService.js.map +1 -1
- package/dist/internal/upload/fileUploader.test.js +1 -1
- package/dist/internal/upload/fileUploader.test.js.map +1 -1
- package/dist/internal/upload/interface.d.ts +25 -13
- package/dist/internal/upload/manager.js +7 -4
- package/dist/internal/upload/manager.js.map +1 -1
- package/dist/internal/upload/manager.test.js +5 -4
- package/dist/internal/upload/manager.test.js.map +1 -1
- package/dist/internal/upload/streamUploader.js +9 -4
- package/dist/internal/upload/streamUploader.js.map +1 -1
- package/dist/internal/upload/streamUploader.test.js +8 -5
- package/dist/internal/upload/streamUploader.test.js.map +1 -1
- package/dist/protonDriveClient.d.ts +11 -2
- package/dist/protonDriveClient.js +20 -4
- package/dist/protonDriveClient.js.map +1 -1
- package/dist/protonDrivePhotosClient.d.ts +8 -8
- package/dist/protonDrivePhotosClient.js +8 -9
- package/dist/protonDrivePhotosClient.js.map +1 -1
- package/dist/protonDrivePublicLinkClient.d.ts +9 -2
- package/dist/protonDrivePublicLinkClient.js +16 -5
- package/dist/protonDrivePublicLinkClient.js.map +1 -1
- package/dist/transformers.d.ts +7 -2
- package/dist/transformers.js +37 -0
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/interface/index.ts +1 -0
- package/src/interface/nodes.ts +14 -11
- package/src/interface/photos.ts +67 -0
- package/src/internal/apiService/apiService.ts +2 -2
- package/src/internal/apiService/driveTypes.ts +1294 -517
- package/src/internal/apiService/errors.ts +5 -4
- package/src/internal/download/cryptoService.ts +13 -6
- package/src/internal/download/fileDownloader.ts +4 -2
- package/src/internal/download/index.ts +3 -0
- package/src/internal/errors.ts +4 -0
- package/src/internal/nodes/apiService.test.ts +7 -5
- package/src/internal/nodes/apiService.ts +210 -124
- package/src/internal/nodes/cache.test.ts +1 -0
- package/src/internal/nodes/cache.ts +32 -13
- package/src/internal/nodes/cryptoReporter.ts +3 -3
- package/src/internal/nodes/cryptoService.test.ts +380 -18
- package/src/internal/nodes/cryptoService.ts +77 -36
- package/src/internal/nodes/events.ts +2 -2
- package/src/internal/nodes/index.test.ts +1 -0
- package/src/internal/nodes/interface.ts +17 -2
- package/src/internal/nodes/nodesAccess.ts +99 -54
- package/src/internal/nodes/nodesManagement.test.ts +69 -14
- package/src/internal/nodes/nodesManagement.ts +94 -48
- package/src/internal/nodes/nodesRevisions.ts +3 -3
- package/src/internal/photos/albums.ts +2 -2
- package/src/internal/photos/index.ts +45 -3
- package/src/internal/photos/interface.ts +21 -9
- package/src/internal/photos/nodes.ts +233 -0
- package/src/internal/photos/timeline.test.ts +2 -2
- package/src/internal/photos/timeline.ts +2 -2
- package/src/internal/photos/upload.ts +3 -3
- package/src/internal/sharingPublic/index.ts +7 -3
- package/src/internal/sharingPublic/nodes.ts +43 -2
- package/src/internal/sharingPublic/unauthApiService.test.ts +29 -0
- package/src/internal/sharingPublic/unauthApiService.ts +32 -0
- package/src/internal/upload/apiService.ts +4 -3
- package/src/internal/upload/cryptoService.ts +73 -12
- package/src/internal/upload/fileUploader.test.ts +1 -1
- package/src/internal/upload/interface.ts +24 -13
- package/src/internal/upload/manager.test.ts +5 -4
- package/src/internal/upload/manager.ts +7 -4
- package/src/internal/upload/streamUploader.test.ts +8 -5
- package/src/internal/upload/streamUploader.ts +10 -4
- package/src/protonDriveClient.ts +27 -5
- package/src/protonDrivePhotosClient.ts +23 -23
- package/src/protonDrivePublicLinkClient.ts +19 -3
- package/src/transformers.ts +49 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import { PrivateKey } from '../../crypto';
|
|
2
|
+
import { DecryptionError } from '../../errors';
|
|
3
|
+
import { NodeType } from '../../interface';
|
|
4
|
+
import { drivePaths } from '../apiService';
|
|
5
|
+
import { NodeAPIServiceBase, linkToEncryptedNode, linkToEncryptedNodeBaseMetadata } from '../nodes/apiService';
|
|
6
|
+
import { NodesCacheBase, serialiseNode, deserialiseNode } from '../nodes/cache';
|
|
7
|
+
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
8
|
+
import { DecryptedNodeKeys } from '../nodes/interface';
|
|
9
|
+
import { NodesAccessBase, parseNode as parseNodeBase } from '../nodes/nodesAccess';
|
|
10
|
+
import { NodesManagementBase } from '../nodes/nodesManagement';
|
|
11
|
+
import { makeNodeUid } from '../uids';
|
|
12
|
+
import { EncryptedPhotoNode, DecryptedPhotoNode, DecryptedUnparsedPhotoNode } from './interface';
|
|
13
|
+
|
|
14
|
+
type PostLoadLinksMetadataRequest = Extract<
|
|
15
|
+
drivePaths['/drive/photos/volumes/{volumeID}/links']['post']['requestBody'],
|
|
16
|
+
{ content: object }
|
|
17
|
+
>['content']['application/json'];
|
|
18
|
+
type PostLoadLinksMetadataResponse =
|
|
19
|
+
drivePaths['/drive/photos/volumes/{volumeID}/links']['post']['responses']['200']['content']['application/json'];
|
|
20
|
+
|
|
21
|
+
export class PhotosNodesAPIService extends NodeAPIServiceBase<
|
|
22
|
+
EncryptedPhotoNode,
|
|
23
|
+
PostLoadLinksMetadataResponse['Links'][0]
|
|
24
|
+
> {
|
|
25
|
+
protected async fetchNodeMetadata(volumeId: string, linkIds: string[], signal?: AbortSignal) {
|
|
26
|
+
const response = await this.apiService.post<PostLoadLinksMetadataRequest, PostLoadLinksMetadataResponse>(
|
|
27
|
+
`drive/photos/volumes/${volumeId}/links`,
|
|
28
|
+
{
|
|
29
|
+
LinkIDs: linkIds,
|
|
30
|
+
},
|
|
31
|
+
signal,
|
|
32
|
+
);
|
|
33
|
+
return response.Links;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected linkToEncryptedNode(
|
|
37
|
+
volumeId: string,
|
|
38
|
+
link: PostLoadLinksMetadataResponse['Links'][0],
|
|
39
|
+
isOwnVolumeId: boolean,
|
|
40
|
+
): EncryptedPhotoNode {
|
|
41
|
+
const { baseNodeMetadata, baseCryptoNodeMetadata } = linkToEncryptedNodeBaseMetadata(
|
|
42
|
+
this.logger,
|
|
43
|
+
volumeId,
|
|
44
|
+
link,
|
|
45
|
+
isOwnVolumeId,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (link.Link.Type === 2 && link.Photo && link.Photo.ActiveRevision) {
|
|
49
|
+
const node = linkToEncryptedNode(
|
|
50
|
+
this.logger,
|
|
51
|
+
volumeId,
|
|
52
|
+
{ ...link, File: link.Photo, Folder: null },
|
|
53
|
+
isOwnVolumeId,
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
...node,
|
|
57
|
+
type: NodeType.Photo,
|
|
58
|
+
photo: {
|
|
59
|
+
captureTime: new Date(link.Photo.CaptureTime * 1000),
|
|
60
|
+
mainPhotoNodeUid: link.Photo.MainPhotoLinkID
|
|
61
|
+
? makeNodeUid(volumeId, link.Photo.MainPhotoLinkID)
|
|
62
|
+
: undefined,
|
|
63
|
+
relatedPhotoNodeUids: link.Photo.RelatedPhotosLinkIDs.map((relatedLinkId) =>
|
|
64
|
+
makeNodeUid(volumeId, relatedLinkId),
|
|
65
|
+
),
|
|
66
|
+
contentHash: link.Photo.ContentHash || undefined,
|
|
67
|
+
tags: link.Photo.Tags,
|
|
68
|
+
albums: link.Photo.Albums.map((album) => ({
|
|
69
|
+
nodeUid: makeNodeUid(volumeId, album.AlbumLinkID),
|
|
70
|
+
additionTime: new Date(album.AddedTime * 1000),
|
|
71
|
+
nameHash: album.Hash,
|
|
72
|
+
contentHash: album.ContentHash,
|
|
73
|
+
})),
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (link.Link.Type === 3) {
|
|
79
|
+
return {
|
|
80
|
+
...baseNodeMetadata,
|
|
81
|
+
encryptedCrypto: {
|
|
82
|
+
...baseCryptoNodeMetadata,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const baseLink = {
|
|
88
|
+
Link: link.Link,
|
|
89
|
+
Membership: link.Membership,
|
|
90
|
+
Sharing: link.Sharing,
|
|
91
|
+
// @ts-expect-error The photo link can have a folder type, but not always. If not set, it will use other paths.
|
|
92
|
+
Folder: link.Folder,
|
|
93
|
+
File: null, // The photo link metadata never returns a file type.
|
|
94
|
+
};
|
|
95
|
+
return linkToEncryptedNode(this.logger, volumeId, baseLink, isOwnVolumeId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class PhotosNodesCache extends NodesCacheBase<DecryptedPhotoNode> {
|
|
100
|
+
serialiseNode(node: DecryptedPhotoNode): string {
|
|
101
|
+
return serialiseNode(node);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// TODO: use better deserialisation with validation
|
|
105
|
+
deserialiseNode(nodeData: string): DecryptedPhotoNode {
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
107
|
+
const node = deserialiseNode(nodeData) as any;
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
!node ||
|
|
111
|
+
typeof node !== 'object' ||
|
|
112
|
+
(typeof node.photo !== 'object' && node.photo !== undefined) ||
|
|
113
|
+
(typeof node.photo?.captureTime !== 'string' && node.folder?.captureTime !== undefined) ||
|
|
114
|
+
(typeof node.photo?.albums !== 'object' && node.photo?.albums !== undefined)
|
|
115
|
+
) {
|
|
116
|
+
throw new Error(`Invalid node data: ${nodeData}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
...node,
|
|
121
|
+
photo: !node.photo
|
|
122
|
+
? undefined
|
|
123
|
+
: {
|
|
124
|
+
captureTime: new Date(node.photo.captureTime),
|
|
125
|
+
mainPhotoNodeUid: node.photo.mainPhotoNodeUid,
|
|
126
|
+
relatedPhotoNodeUids: node.photo.relatedPhotoNodeUids,
|
|
127
|
+
contentHash: node.photo.contentHash,
|
|
128
|
+
tags: node.photo.tags,
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
|
+
albums: node.photo.albums?.map((album: any) => ({
|
|
131
|
+
nodeUid: album.nodeUid,
|
|
132
|
+
additionTime: new Date(album.additionTime),
|
|
133
|
+
})),
|
|
134
|
+
},
|
|
135
|
+
} as DecryptedPhotoNode;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export class PhotosNodesAccess extends NodesAccessBase<EncryptedPhotoNode, DecryptedPhotoNode, PhotosNodesCryptoService> {
|
|
140
|
+
async getParentKeys(
|
|
141
|
+
node: Pick<EncryptedPhotoNode, 'uid' | 'parentUid' | 'shareId' | 'photo'>,
|
|
142
|
+
): Promise<Pick<DecryptedNodeKeys, 'key' | 'hashKey'>> {
|
|
143
|
+
if (node.parentUid || node.shareId) {
|
|
144
|
+
return super.getParentKeys(node);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (node.photo?.albums.length) {
|
|
148
|
+
// If photo is in multiple albums, we just need to get keys for one of them.
|
|
149
|
+
// Prefer to find a cached key first.
|
|
150
|
+
for (const album of node.photo.albums) {
|
|
151
|
+
try {
|
|
152
|
+
const keys = await this.cryptoCache.getNodeKeys(album.nodeUid);
|
|
153
|
+
return {
|
|
154
|
+
key: keys.key,
|
|
155
|
+
hashKey: keys.hashKey,
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
// We ignore missing or invalid keys here, its just optimization.
|
|
159
|
+
// If it cannot be fixed, it will bubble up later when requesting
|
|
160
|
+
// the node keys for one of the albums.
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const albumNodeUid = node.photo.albums[0].nodeUid;
|
|
165
|
+
return this.getNodeKeys(albumNodeUid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// This is bug that should not happen.
|
|
169
|
+
// API cannot provide node without parent or share or album.
|
|
170
|
+
throw new Error('Node has neither parent node nor share nor album');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
protected getDegradedUndecryptableNode(
|
|
174
|
+
encryptedNode: EncryptedPhotoNode,
|
|
175
|
+
error: DecryptionError,
|
|
176
|
+
): DecryptedPhotoNode {
|
|
177
|
+
return this.getDegradedUndecryptableNodeBase(encryptedNode, error);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
protected parseNode(unparsedNode: DecryptedUnparsedPhotoNode): DecryptedPhotoNode {
|
|
181
|
+
if (unparsedNode.type === NodeType.Photo) {
|
|
182
|
+
const node = parseNodeBase(this.logger, {
|
|
183
|
+
...unparsedNode,
|
|
184
|
+
type: NodeType.File,
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
...node,
|
|
188
|
+
photo: unparsedNode.photo,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return parseNodeBase(this.logger, unparsedNode);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class PhotosNodesCryptoService extends NodesCryptoService {
|
|
197
|
+
async decryptNode(
|
|
198
|
+
encryptedNode: EncryptedPhotoNode,
|
|
199
|
+
parentKey: PrivateKey,
|
|
200
|
+
): Promise<{ node: DecryptedUnparsedPhotoNode; keys?: DecryptedNodeKeys }> {
|
|
201
|
+
const decryptedNode = await super.decryptNode(encryptedNode, parentKey);
|
|
202
|
+
|
|
203
|
+
if (decryptedNode.node.type === NodeType.Photo) {
|
|
204
|
+
return {
|
|
205
|
+
node: {
|
|
206
|
+
...decryptedNode.node,
|
|
207
|
+
photo: encryptedNode.photo,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return decryptedNode;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export class PhotosNodesManagement extends NodesManagementBase<
|
|
217
|
+
EncryptedPhotoNode,
|
|
218
|
+
DecryptedPhotoNode,
|
|
219
|
+
PhotosNodesCryptoService
|
|
220
|
+
> {
|
|
221
|
+
protected generateNodeFolder(
|
|
222
|
+
nodeUid: string,
|
|
223
|
+
parentNodeUid: string,
|
|
224
|
+
name: string,
|
|
225
|
+
encryptedCrypto: {
|
|
226
|
+
hash: string;
|
|
227
|
+
encryptedName: string;
|
|
228
|
+
signatureEmail: string | null;
|
|
229
|
+
},
|
|
230
|
+
): DecryptedPhotoNode {
|
|
231
|
+
return this.generateNodeFolderBase(nodeUid, parentNodeUid, name, encryptedCrypto);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -2,7 +2,7 @@ import { getMockLogger } from '../../tests/logger';
|
|
|
2
2
|
import { DriveCrypto } from '../../crypto';
|
|
3
3
|
import { makeNodeUid } from '../uids';
|
|
4
4
|
import { PhotosAPIService } from './apiService';
|
|
5
|
-
import {
|
|
5
|
+
import { PhotosNodesAccess } from './nodes';
|
|
6
6
|
import { PhotoSharesManager } from './shares';
|
|
7
7
|
import { PhotosTimeline } from './timeline';
|
|
8
8
|
|
|
@@ -11,7 +11,7 @@ describe('PhotosTimeline', () => {
|
|
|
11
11
|
let apiService: PhotosAPIService;
|
|
12
12
|
let driveCrypto: DriveCrypto;
|
|
13
13
|
let photoShares: PhotoSharesManager;
|
|
14
|
-
let nodesService:
|
|
14
|
+
let nodesService: PhotosNodesAccess;
|
|
15
15
|
let timeline: PhotosTimeline;
|
|
16
16
|
|
|
17
17
|
const volumeId = 'volumeId';
|
|
@@ -2,7 +2,7 @@ import { DriveCrypto } from '../../crypto';
|
|
|
2
2
|
import { Logger } from '../../interface';
|
|
3
3
|
import { makeNodeUid } from '../uids';
|
|
4
4
|
import { PhotosAPIService } from './apiService';
|
|
5
|
-
import {
|
|
5
|
+
import { PhotosNodesAccess } from './nodes';
|
|
6
6
|
import { PhotoSharesManager } from './shares';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -14,7 +14,7 @@ export class PhotosTimeline {
|
|
|
14
14
|
private apiService: PhotosAPIService,
|
|
15
15
|
private driveCrypto: DriveCrypto,
|
|
16
16
|
private photoShares: PhotoSharesManager,
|
|
17
|
-
private nodesService:
|
|
17
|
+
private nodesService: PhotosNodesAccess,
|
|
18
18
|
) {
|
|
19
19
|
this.logger = logger;
|
|
20
20
|
this.apiService = apiService;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DriveCrypto } from '../../crypto';
|
|
2
|
-
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail } from '../../interface';
|
|
2
|
+
import { ProtonDriveTelemetry, UploadMetadata, Thumbnail, AnonymousUser } from '../../interface';
|
|
3
3
|
import { DriveAPIService, drivePaths } from '../apiService';
|
|
4
4
|
import { generateFileExtendedAttributes } from '../nodes';
|
|
5
5
|
import { splitNodeRevisionUid } from '../uids';
|
|
@@ -198,7 +198,7 @@ export class PhotoUploadAPIService extends UploadAPIService {
|
|
|
198
198
|
draftNodeRevisionUid: string,
|
|
199
199
|
options: {
|
|
200
200
|
armoredManifestSignature: string;
|
|
201
|
-
signatureEmail: string;
|
|
201
|
+
signatureEmail: string | AnonymousUser;
|
|
202
202
|
armoredExtendedAttributes?: string;
|
|
203
203
|
},
|
|
204
204
|
photo: {
|
|
@@ -220,7 +220,7 @@ export class PhotoUploadAPIService extends UploadAPIService {
|
|
|
220
220
|
XAttr: options.armoredExtendedAttributes || null,
|
|
221
221
|
Photo: {
|
|
222
222
|
ContentHash: photo.contentHash,
|
|
223
|
-
CaptureTime: photo.captureTime ? Math.floor(photo.captureTime?.getTime() /1000) : 0,
|
|
223
|
+
CaptureTime: photo.captureTime ? Math.floor(photo.captureTime?.getTime() / 1000) : 0,
|
|
224
224
|
MainPhotoLinkID: photo.mainPhotoLinkID || null,
|
|
225
225
|
Tags: photo.tags || [],
|
|
226
226
|
Exif: null, // Deprecated field, not used.
|
|
@@ -10,13 +10,13 @@ import { NodeAPIService } from '../nodes/apiService';
|
|
|
10
10
|
import { NodesCache } from '../nodes/cache';
|
|
11
11
|
import { NodesCryptoCache } from '../nodes/cryptoCache';
|
|
12
12
|
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
13
|
-
import { NodesManagement } from '../nodes/nodesManagement';
|
|
14
13
|
import { NodesRevisons } from '../nodes/nodesRevisions';
|
|
15
14
|
import { SharingPublicCryptoReporter } from './cryptoReporter';
|
|
16
|
-
import { SharingPublicNodesAccess } from './nodes';
|
|
15
|
+
import { SharingPublicNodesAccess, SharingPublicNodesManagement } from './nodes';
|
|
17
16
|
import { SharingPublicSharesManager } from './shares';
|
|
18
17
|
|
|
19
18
|
export { SharingPublicSessionManager } from './session/manager';
|
|
19
|
+
export { UnauthDriveAPIService } from './unauthApiService';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Provides facade for the whole sharing public module.
|
|
@@ -38,6 +38,7 @@ export function initSharingPublicModule(
|
|
|
38
38
|
token: string,
|
|
39
39
|
publicShareKey: PrivateKey,
|
|
40
40
|
publicRootNodeUid: string,
|
|
41
|
+
isAnonymousContext: boolean,
|
|
41
42
|
) {
|
|
42
43
|
const shares = new SharingPublicSharesManager(account, publicShareKey, publicRootNodeUid);
|
|
43
44
|
const nodes = initSharingPublicNodesModule(
|
|
@@ -52,6 +53,7 @@ export function initSharingPublicModule(
|
|
|
52
53
|
token,
|
|
53
54
|
publicShareKey,
|
|
54
55
|
publicRootNodeUid,
|
|
56
|
+
isAnonymousContext,
|
|
55
57
|
);
|
|
56
58
|
|
|
57
59
|
return {
|
|
@@ -78,6 +80,7 @@ export function initSharingPublicNodesModule(
|
|
|
78
80
|
token: string,
|
|
79
81
|
publicShareKey: PrivateKey,
|
|
80
82
|
publicRootNodeUid: string,
|
|
83
|
+
isAnonymousContext: boolean,
|
|
81
84
|
) {
|
|
82
85
|
const clientUid = undefined; // No client UID for public context yet.
|
|
83
86
|
const api = new NodeAPIService(telemetry.getLogger('nodes-api'), apiService, clientUid);
|
|
@@ -96,8 +99,9 @@ export function initSharingPublicNodesModule(
|
|
|
96
99
|
token,
|
|
97
100
|
publicShareKey,
|
|
98
101
|
publicRootNodeUid,
|
|
102
|
+
isAnonymousContext,
|
|
99
103
|
);
|
|
100
|
-
const nodesManagement = new
|
|
104
|
+
const nodesManagement = new SharingPublicNodesManagement(api, cryptoCache, cryptoService, nodesAccess);
|
|
101
105
|
const nodesRevisions = new NodesRevisons(telemetry.getLogger('nodes'), api, cryptoService, nodesAccess);
|
|
102
106
|
|
|
103
107
|
return {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { ProtonDriveTelemetry } from '../../interface';
|
|
1
|
+
import { NodeResult, ProtonDriveTelemetry } from '../../interface';
|
|
2
2
|
import { NodeAPIService } from '../nodes/apiService';
|
|
3
3
|
import { NodesCache } from '../nodes/cache';
|
|
4
4
|
import { NodesCryptoCache } from '../nodes/cryptoCache';
|
|
5
5
|
import { NodesCryptoService } from '../nodes/cryptoService';
|
|
6
6
|
import { NodesAccess } from '../nodes/nodesAccess';
|
|
7
|
+
import { NodesManagement } from '../nodes/nodesManagement';
|
|
7
8
|
import { isProtonDocument, isProtonSheet } from '../nodes/mediaTypes';
|
|
8
9
|
import { splitNodeUid } from '../uids';
|
|
9
10
|
import { SharingPublicSharesManager } from './shares';
|
|
10
|
-
import { DecryptedNode, DecryptedNodeKeys } from '../nodes/interface';
|
|
11
|
+
import { DecryptedNode, DecryptedNodeKeys, NodeSigningKeys } from '../nodes/interface';
|
|
11
12
|
import { PrivateKey } from '../../crypto';
|
|
12
13
|
|
|
13
14
|
export class SharingPublicNodesAccess extends NodesAccess {
|
|
@@ -22,11 +23,13 @@ export class SharingPublicNodesAccess extends NodesAccess {
|
|
|
22
23
|
private token: string,
|
|
23
24
|
private publicShareKey: PrivateKey,
|
|
24
25
|
private publicRootNodeUid: string,
|
|
26
|
+
private isAnonymousContext: boolean,
|
|
25
27
|
) {
|
|
26
28
|
super(telemetry, apiService, cache, cryptoCache, cryptoService, sharesService);
|
|
27
29
|
this.token = token;
|
|
28
30
|
this.publicShareKey = publicShareKey;
|
|
29
31
|
this.publicRootNodeUid = publicRootNodeUid;
|
|
32
|
+
this.isAnonymousContext = isAnonymousContext;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
async getParentKeys(
|
|
@@ -56,4 +59,42 @@ export class SharingPublicNodesAccess extends NodesAccess {
|
|
|
56
59
|
// Public link doesn't support specific node URLs.
|
|
57
60
|
return this.url;
|
|
58
61
|
}
|
|
62
|
+
|
|
63
|
+
async getNodeSigningKeys(
|
|
64
|
+
uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
|
|
65
|
+
): Promise<NodeSigningKeys> {
|
|
66
|
+
if (this.isAnonymousContext) {
|
|
67
|
+
const nodeKeys = uids.nodeUid ? await this.getNodeKeys(uids.nodeUid) : { key: undefined };
|
|
68
|
+
const parentNodeKeys = uids.parentNodeUid ? await this.getNodeKeys(uids.parentNodeUid) : { key: undefined };
|
|
69
|
+
return {
|
|
70
|
+
type: 'nodeKey',
|
|
71
|
+
nodeKey: nodeKeys.key,
|
|
72
|
+
parentNodeKey: parentNodeKeys.key,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return super.getNodeSigningKeys(uids);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export class SharingPublicNodesManagement extends NodesManagement {
|
|
81
|
+
constructor(
|
|
82
|
+
apiService: NodeAPIService,
|
|
83
|
+
cryptoCache: NodesCryptoCache,
|
|
84
|
+
cryptoService: NodesCryptoService,
|
|
85
|
+
nodesAccess: SharingPublicNodesAccess,
|
|
86
|
+
) {
|
|
87
|
+
super(apiService, cryptoCache, cryptoService, nodesAccess);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async *deleteNodes(nodeUids: string[], signal?: AbortSignal): AsyncGenerator<NodeResult> {
|
|
91
|
+
// Public link does not support trashing and deleting trashed nodes.
|
|
92
|
+
// Instead, if user is owner, API allows directly deleting existing nodes.
|
|
93
|
+
for await (const result of this.apiService.deleteExistingNodes(nodeUids, signal)) {
|
|
94
|
+
if (result.ok) {
|
|
95
|
+
await this.nodesAccess.notifyNodeDeleted(result.uid);
|
|
96
|
+
}
|
|
97
|
+
yield result;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
59
100
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getUnauthEndpoint } from './unauthApiService';
|
|
2
|
+
|
|
3
|
+
describe('getUnauthEndpoint', () => {
|
|
4
|
+
it('should not change urls endpoints', () => {
|
|
5
|
+
expect(getUnauthEndpoint('drive/urls/anything')).toBe('drive/urls/anything');
|
|
6
|
+
expect(getUnauthEndpoint('drive/urls/drive/anything')).toBe('drive/urls/drive/anything');
|
|
7
|
+
expect(getUnauthEndpoint('drive/urls/drive/v2/anything')).toBe('drive/urls/drive/v2/anything');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('should not change v2/urls endpoints', () => {
|
|
11
|
+
expect(getUnauthEndpoint('drive/v2/urls/anything')).toBe('drive/v2/urls/anything');
|
|
12
|
+
expect(getUnauthEndpoint('drive/v2/urls/drive/anything')).toBe('drive/v2/urls/drive/anything');
|
|
13
|
+
expect(getUnauthEndpoint('drive/v2/urls/drive/v2/anything')).toBe('drive/v2/urls/drive/v2/anything');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should put unauth prefix for v2 endpoints', () => {
|
|
17
|
+
expect(getUnauthEndpoint('drive/v2/anything')).toBe('drive/unauth/v2/anything');
|
|
18
|
+
expect(getUnauthEndpoint('drive/v2/drive/anything')).toBe('drive/unauth/v2/drive/anything');
|
|
19
|
+
expect(getUnauthEndpoint('drive/v2/drive/v2/anything')).toBe('drive/unauth/v2/drive/v2/anything');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should put unauth prefix for non-v2 endpoints', () => {
|
|
23
|
+
expect(getUnauthEndpoint('drive/anything')).toBe('drive/unauth/anything');
|
|
24
|
+
expect(getUnauthEndpoint('drive/anything/v2/anything')).toBe('drive/unauth/anything/v2/anything');
|
|
25
|
+
expect(getUnauthEndpoint('drive/anything/drive/anything')).toBe('drive/unauth/anything/drive/anything');
|
|
26
|
+
expect(getUnauthEndpoint('drive/anything/drive/v2/anything')).toBe('drive/unauth/anything/drive/v2/anything');
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DriveAPIService } from '../apiService';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Drive API Service for public links.
|
|
5
|
+
*
|
|
6
|
+
* This service is used to make requests to the Drive API without
|
|
7
|
+
* authentication. The unauth context uses the same endpoint, but
|
|
8
|
+
* with an `unauth` prefix. The goal is to avoid the need to use
|
|
9
|
+
* different path and use the exact endpoint for both contexts.
|
|
10
|
+
* However, API has global logic for handling expired sessions that
|
|
11
|
+
* is not compatible with the unauth context. For this reason, this
|
|
12
|
+
* service is used to make requests to the Drive API for public
|
|
13
|
+
* link context in the mean time.
|
|
14
|
+
*/
|
|
15
|
+
export class UnauthDriveAPIService extends DriveAPIService {
|
|
16
|
+
protected async makeRequest<RequestPayload, ResponsePayload>(
|
|
17
|
+
url: string,
|
|
18
|
+
method = 'GET',
|
|
19
|
+
data?: RequestPayload,
|
|
20
|
+
signal?: AbortSignal,
|
|
21
|
+
): Promise<ResponsePayload> {
|
|
22
|
+
const unauthUrl = getUnauthEndpoint(url);
|
|
23
|
+
return super.makeRequest(unauthUrl, method, data, signal);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getUnauthEndpoint(url: string): string {
|
|
28
|
+
if (url.startsWith('drive/urls/') || url.startsWith('drive/v2/urls/')) {
|
|
29
|
+
return url;
|
|
30
|
+
}
|
|
31
|
+
return url.replace(/^drive\//, 'drive/unauth/');
|
|
32
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { c } from 'ttag';
|
|
2
2
|
|
|
3
3
|
import { base64StringToUint8Array, uint8ArrayToBase64String } from '../../crypto';
|
|
4
|
+
import { AnonymousUser } from '../../interface';
|
|
4
5
|
import { APICodeError, DriveAPIService, drivePaths, isCodeOk } from '../apiService';
|
|
5
6
|
import { splitNodeUid, makeNodeUid, splitNodeRevisionUid, makeNodeRevisionUid } from '../uids';
|
|
6
7
|
import { UploadTokens } from './interface';
|
|
@@ -65,7 +66,7 @@ export class UploadAPIService {
|
|
|
65
66
|
armoredNodePassphraseSignature: string;
|
|
66
67
|
base64ContentKeyPacket: string;
|
|
67
68
|
armoredContentKeyPacketSignature: string;
|
|
68
|
-
signatureEmail: string;
|
|
69
|
+
signatureEmail: string | AnonymousUser;
|
|
69
70
|
},
|
|
70
71
|
): Promise<{
|
|
71
72
|
nodeUid: string;
|
|
@@ -150,7 +151,7 @@ export class UploadAPIService {
|
|
|
150
151
|
|
|
151
152
|
async requestBlockUpload(
|
|
152
153
|
draftNodeRevisionUid: string,
|
|
153
|
-
addressId: string,
|
|
154
|
+
addressId: string | AnonymousUser,
|
|
154
155
|
blocks: {
|
|
155
156
|
contentBlocks: {
|
|
156
157
|
index: number;
|
|
@@ -211,7 +212,7 @@ export class UploadAPIService {
|
|
|
211
212
|
draftNodeRevisionUid: string,
|
|
212
213
|
options: {
|
|
213
214
|
armoredManifestSignature: string;
|
|
214
|
-
signatureEmail: string;
|
|
215
|
+
signatureEmail: string | AnonymousUser;
|
|
215
216
|
armoredExtendedAttributes?: string;
|
|
216
217
|
},
|
|
217
218
|
): Promise<void> {
|
|
@@ -2,8 +2,15 @@ import { c } from 'ttag';
|
|
|
2
2
|
|
|
3
3
|
import { DriveCrypto, PrivateKey, SessionKey } from '../../crypto';
|
|
4
4
|
import { IntegrityError } from '../../errors';
|
|
5
|
-
import { Thumbnail } from '../../interface';
|
|
6
|
-
import {
|
|
5
|
+
import { Thumbnail, AnonymousUser } from '../../interface';
|
|
6
|
+
import {
|
|
7
|
+
EncryptedBlock,
|
|
8
|
+
EncryptedThumbnail,
|
|
9
|
+
NodeCrypto,
|
|
10
|
+
NodeCryptoSigningKeys,
|
|
11
|
+
NodeRevisionDraftKeys,
|
|
12
|
+
NodesService,
|
|
13
|
+
} from './interface';
|
|
7
14
|
|
|
8
15
|
export class UploadCryptoService {
|
|
9
16
|
constructor(
|
|
@@ -19,11 +26,15 @@ export class UploadCryptoService {
|
|
|
19
26
|
parentKeys: { key: PrivateKey; hashKey: Uint8Array },
|
|
20
27
|
name: string,
|
|
21
28
|
): Promise<NodeCrypto> {
|
|
22
|
-
const
|
|
29
|
+
const signingKeys = await this.getSigningKeys({ parentNodeUid: parentUid });
|
|
30
|
+
|
|
31
|
+
if (!signingKeys.nameAndPassphraseSigningKey) {
|
|
32
|
+
throw new Error('Cannot create new node without a name and passphrase signing key');
|
|
33
|
+
}
|
|
23
34
|
|
|
24
35
|
const [nodeKeys, { armoredNodeName }, hash] = await Promise.all([
|
|
25
|
-
this.driveCrypto.generateKey([parentKeys.key],
|
|
26
|
-
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key,
|
|
36
|
+
this.driveCrypto.generateKey([parentKeys.key], signingKeys.nameAndPassphraseSigningKey),
|
|
37
|
+
this.driveCrypto.encryptNodeName(name, undefined, parentKeys.key, signingKeys.nameAndPassphraseSigningKey),
|
|
27
38
|
this.driveCrypto.generateLookupHash(name, parentKeys.hashKey),
|
|
28
39
|
]);
|
|
29
40
|
|
|
@@ -36,7 +47,57 @@ export class UploadCryptoService {
|
|
|
36
47
|
encryptedName: armoredNodeName,
|
|
37
48
|
hash,
|
|
38
49
|
},
|
|
39
|
-
|
|
50
|
+
signingKeys: {
|
|
51
|
+
email: signingKeys.email,
|
|
52
|
+
addressId: signingKeys.addressId,
|
|
53
|
+
nameAndPassphraseSigningKey: signingKeys.nameAndPassphraseSigningKey,
|
|
54
|
+
contentSigningKey: signingKeys.contentSigningKey || nodeKeys.decrypted.key,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getSigningKeysForExistingNode(uids: {
|
|
60
|
+
nodeUid: string;
|
|
61
|
+
parentNodeUid?: string;
|
|
62
|
+
}): Promise<NodeCryptoSigningKeys> {
|
|
63
|
+
const signingKeys = await this.getSigningKeys(uids);
|
|
64
|
+
|
|
65
|
+
if (!signingKeys.nameAndPassphraseSigningKey) {
|
|
66
|
+
throw new Error('Cannot get name and passphrase signing key for existing node');
|
|
67
|
+
}
|
|
68
|
+
if (!signingKeys.contentSigningKey) {
|
|
69
|
+
throw new Error('Cannot get content signing key for existing node');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
email: signingKeys.email,
|
|
74
|
+
addressId: signingKeys.addressId,
|
|
75
|
+
nameAndPassphraseSigningKey: signingKeys.nameAndPassphraseSigningKey,
|
|
76
|
+
contentSigningKey: signingKeys.contentSigningKey,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private async getSigningKeys(
|
|
81
|
+
uids: { nodeUid: string; parentNodeUid?: string } | { nodeUid?: string; parentNodeUid: string },
|
|
82
|
+
): Promise<
|
|
83
|
+
Omit<NodeCryptoSigningKeys, 'nameAndPassphraseSigningKey' | 'contentSigningKey'> & {
|
|
84
|
+
nameAndPassphraseSigningKey?: PrivateKey;
|
|
85
|
+
contentSigningKey?: PrivateKey;
|
|
86
|
+
}
|
|
87
|
+
> {
|
|
88
|
+
const signingKeys = await this.nodesService.getNodeSigningKeys(uids);
|
|
89
|
+
|
|
90
|
+
const email = signingKeys.type === 'userAddress' ? signingKeys.email : null;
|
|
91
|
+
const addressId = signingKeys.type === 'userAddress' ? signingKeys.addressId : null;
|
|
92
|
+
const nameAndPassphraseSigningKey =
|
|
93
|
+
signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.parentNodeKey;
|
|
94
|
+
const contentSigningKey = signingKeys.type === 'userAddress' ? signingKeys.key : signingKeys.nodeKey;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
email,
|
|
98
|
+
addressId,
|
|
99
|
+
nameAndPassphraseSigningKey,
|
|
100
|
+
contentSigningKey,
|
|
40
101
|
};
|
|
41
102
|
}
|
|
42
103
|
|
|
@@ -47,7 +108,7 @@ export class UploadCryptoService {
|
|
|
47
108
|
const { encryptedData } = await this.driveCrypto.encryptThumbnailBlock(
|
|
48
109
|
thumbnail.thumbnail,
|
|
49
110
|
nodeRevisionDraftKeys.contentKeyPacketSessionKey,
|
|
50
|
-
nodeRevisionDraftKeys.
|
|
111
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
51
112
|
);
|
|
52
113
|
|
|
53
114
|
const digest = await crypto.subtle.digest('SHA-256', encryptedData);
|
|
@@ -71,7 +132,7 @@ export class UploadCryptoService {
|
|
|
71
132
|
block,
|
|
72
133
|
nodeRevisionDraftKeys.key,
|
|
73
134
|
nodeRevisionDraftKeys.contentKeyPacketSessionKey,
|
|
74
|
-
nodeRevisionDraftKeys.
|
|
135
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
75
136
|
);
|
|
76
137
|
|
|
77
138
|
const digest = await crypto.subtle.digest('SHA-256', encryptedData);
|
|
@@ -94,25 +155,25 @@ export class UploadCryptoService {
|
|
|
94
155
|
extendedAttributes?: string,
|
|
95
156
|
): Promise<{
|
|
96
157
|
armoredManifestSignature: string;
|
|
97
|
-
signatureEmail: string;
|
|
158
|
+
signatureEmail: string | AnonymousUser;
|
|
98
159
|
armoredExtendedAttributes?: string;
|
|
99
160
|
}> {
|
|
100
161
|
const { armoredManifestSignature } = await this.driveCrypto.signManifest(
|
|
101
162
|
manifest,
|
|
102
|
-
nodeRevisionDraftKeys.
|
|
163
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
103
164
|
);
|
|
104
165
|
|
|
105
166
|
const { armoredExtendedAttributes } = extendedAttributes
|
|
106
167
|
? await this.driveCrypto.encryptExtendedAttributes(
|
|
107
168
|
extendedAttributes,
|
|
108
169
|
nodeRevisionDraftKeys.key,
|
|
109
|
-
nodeRevisionDraftKeys.
|
|
170
|
+
nodeRevisionDraftKeys.signingKeys.contentSigningKey,
|
|
110
171
|
)
|
|
111
172
|
: { armoredExtendedAttributes: undefined };
|
|
112
173
|
|
|
113
174
|
return {
|
|
114
175
|
armoredManifestSignature,
|
|
115
|
-
signatureEmail: nodeRevisionDraftKeys.
|
|
176
|
+
signatureEmail: nodeRevisionDraftKeys.signingKeys.email,
|
|
116
177
|
armoredExtendedAttributes,
|
|
117
178
|
};
|
|
118
179
|
}
|