@protontech/drive-sdk 0.1.2 → 0.2.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/crypto/driveCrypto.d.ts +11 -0
- package/dist/crypto/driveCrypto.js +20 -7
- package/dist/crypto/driveCrypto.js.map +1 -1
- package/dist/crypto/interface.d.ts +10 -1
- package/dist/crypto/openPGPCrypto.d.ts +18 -2
- package/dist/crypto/openPGPCrypto.js +25 -6
- package/dist/crypto/openPGPCrypto.js.map +1 -1
- package/dist/diagnostic/telemetry.d.ts +1 -1
- package/dist/diagnostic/telemetry.js +1 -1
- package/dist/diagnostic/telemetry.js.map +1 -1
- package/dist/interface/download.d.ts +46 -0
- package/dist/interface/index.d.ts +2 -2
- package/dist/interface/index.js.map +1 -1
- package/dist/interface/nodes.d.ts +26 -1
- package/dist/interface/nodes.js.map +1 -1
- package/dist/interface/sharing.d.ts +3 -1
- package/dist/interface/telemetry.d.ts +5 -2
- package/dist/interface/telemetry.js.map +1 -1
- package/dist/internal/apiService/apiService.js +1 -1
- package/dist/internal/apiService/apiService.js.map +1 -1
- package/dist/internal/apiService/driveTypes.d.ts +78 -165
- package/dist/internal/apiService/errorCodes.d.ts +1 -0
- package/dist/internal/apiService/errors.js +1 -0
- package/dist/internal/apiService/errors.js.map +1 -1
- package/dist/internal/apiService/index.d.ts +1 -1
- package/dist/internal/apiService/index.js +2 -2
- package/dist/internal/apiService/index.js.map +1 -1
- package/dist/internal/apiService/transformers.d.ts +1 -1
- package/dist/internal/apiService/transformers.js +2 -2
- package/dist/internal/apiService/transformers.js.map +1 -1
- package/dist/internal/download/blockIndex.d.ts +11 -0
- package/dist/internal/download/blockIndex.js +35 -0
- package/dist/internal/download/blockIndex.js.map +1 -0
- package/dist/internal/download/blockIndex.test.d.ts +1 -0
- package/dist/internal/download/blockIndex.test.js +147 -0
- package/dist/internal/download/blockIndex.test.js.map +1 -0
- package/dist/internal/download/fileDownloader.d.ts +6 -2
- package/dist/internal/download/fileDownloader.js +83 -6
- package/dist/internal/download/fileDownloader.js.map +1 -1
- package/dist/internal/download/fileDownloader.test.js +69 -4
- package/dist/internal/download/fileDownloader.test.js.map +1 -1
- package/dist/internal/download/interface.d.ts +4 -4
- package/dist/internal/download/seekableStream.d.ts +80 -0
- package/dist/internal/download/seekableStream.js +163 -0
- package/dist/internal/download/seekableStream.js.map +1 -0
- package/dist/internal/download/seekableStream.test.d.ts +1 -0
- package/dist/internal/download/seekableStream.test.js +149 -0
- package/dist/internal/download/seekableStream.test.js.map +1 -0
- package/dist/internal/download/telemetry.js +1 -1
- package/dist/internal/download/telemetry.js.map +1 -1
- package/dist/internal/download/telemetry.test.js +7 -7
- package/dist/internal/download/telemetry.test.js.map +1 -1
- package/dist/internal/errors.d.ts +1 -1
- package/dist/internal/errors.js +7 -1
- package/dist/internal/errors.js.map +1 -1
- package/dist/internal/errors.test.js +44 -10
- package/dist/internal/errors.test.js.map +1 -1
- package/dist/internal/events/index.js +1 -1
- package/dist/internal/events/index.js.map +1 -1
- package/dist/internal/nodes/apiService.js +16 -3
- package/dist/internal/nodes/apiService.js.map +1 -1
- package/dist/internal/nodes/apiService.test.js +43 -7
- package/dist/internal/nodes/apiService.test.js.map +1 -1
- package/dist/internal/nodes/cache.js +12 -3
- package/dist/internal/nodes/cache.js.map +1 -1
- package/dist/internal/nodes/cache.test.js +31 -1
- package/dist/internal/nodes/cache.test.js.map +1 -1
- package/dist/internal/nodes/cryptoService.d.ts +4 -1
- package/dist/internal/nodes/cryptoService.js +66 -16
- package/dist/internal/nodes/cryptoService.js.map +1 -1
- package/dist/internal/nodes/cryptoService.test.js +129 -46
- package/dist/internal/nodes/cryptoService.test.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.d.ts +2 -1
- package/dist/internal/nodes/extendedAttributes.js +27 -1
- package/dist/internal/nodes/extendedAttributes.js.map +1 -1
- package/dist/internal/nodes/extendedAttributes.test.js +59 -6
- package/dist/internal/nodes/extendedAttributes.test.js.map +1 -1
- package/dist/internal/nodes/index.test.js +1 -1
- package/dist/internal/nodes/index.test.js.map +1 -1
- package/dist/internal/nodes/interface.d.ts +18 -2
- package/dist/internal/nodes/nodesAccess.js +11 -1
- package/dist/internal/nodes/nodesAccess.js.map +1 -1
- package/dist/internal/nodes/nodesManagement.js +1 -1
- package/dist/internal/nodes/nodesManagement.js.map +1 -1
- package/dist/internal/nodes/nodesRevisions.d.ts +4 -3
- package/dist/internal/nodes/nodesRevisions.js +2 -2
- package/dist/internal/nodes/nodesRevisions.js.map +1 -1
- package/dist/internal/shares/cryptoService.js +7 -4
- package/dist/internal/shares/cryptoService.js.map +1 -1
- package/dist/internal/shares/cryptoService.test.js +5 -3
- package/dist/internal/shares/cryptoService.test.js.map +1 -1
- package/dist/internal/sharing/apiService.js +5 -5
- package/dist/internal/sharing/apiService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.d.ts +1 -0
- package/dist/internal/sharing/cryptoService.js +22 -10
- package/dist/internal/sharing/cryptoService.js.map +1 -1
- package/dist/internal/sharing/cryptoService.test.js +7 -4
- package/dist/internal/sharing/cryptoService.test.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.js +4 -2
- package/dist/internal/sharing/sharingAccess.js.map +1 -1
- package/dist/internal/sharing/sharingAccess.test.js +6 -0
- package/dist/internal/sharing/sharingAccess.test.js.map +1 -1
- package/dist/internal/upload/telemetry.js +2 -2
- package/dist/internal/upload/telemetry.js.map +1 -1
- package/dist/internal/upload/telemetry.test.js +7 -7
- package/dist/internal/upload/telemetry.test.js.map +1 -1
- package/dist/telemetry.d.ts +2 -2
- package/dist/telemetry.js +2 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/tests/telemetry.js +1 -1
- package/dist/tests/telemetry.js.map +1 -1
- package/dist/transformers.d.ts +1 -1
- package/dist/transformers.js +2 -1
- package/dist/transformers.js.map +1 -1
- package/package.json +1 -1
- package/src/crypto/driveCrypto.ts +70 -25
- package/src/crypto/interface.ts +15 -0
- package/src/crypto/openPGPCrypto.ts +37 -5
- package/src/diagnostic/telemetry.ts +1 -1
- package/src/interface/download.ts +46 -0
- package/src/interface/index.ts +2 -1
- package/src/interface/nodes.ts +28 -1
- package/src/interface/sharing.ts +3 -1
- package/src/interface/telemetry.ts +6 -1
- package/src/internal/apiService/apiService.ts +1 -1
- package/src/internal/apiService/driveTypes.ts +78 -165
- package/src/internal/apiService/errorCodes.ts +1 -0
- package/src/internal/apiService/errors.ts +1 -0
- package/src/internal/apiService/index.ts +1 -1
- package/src/internal/apiService/transformers.ts +1 -1
- package/src/internal/download/blockIndex.test.ts +158 -0
- package/src/internal/download/blockIndex.ts +36 -0
- package/src/internal/download/fileDownloader.test.ts +100 -7
- package/src/internal/download/fileDownloader.ts +109 -9
- package/src/internal/download/interface.ts +4 -4
- package/src/internal/download/seekableStream.test.ts +187 -0
- package/src/internal/download/seekableStream.ts +182 -0
- package/src/internal/download/telemetry.test.ts +7 -7
- package/src/internal/download/telemetry.ts +1 -1
- package/src/internal/errors.test.ts +45 -11
- package/src/internal/errors.ts +8 -0
- package/src/internal/events/index.ts +1 -1
- package/src/internal/nodes/apiService.test.ts +59 -15
- package/src/internal/nodes/apiService.ts +21 -4
- package/src/internal/nodes/cache.test.ts +34 -1
- package/src/internal/nodes/cache.ts +12 -3
- package/src/internal/nodes/cryptoService.test.ts +139 -47
- package/src/internal/nodes/cryptoService.ts +94 -9
- package/src/internal/nodes/extendedAttributes.test.ts +60 -7
- package/src/internal/nodes/extendedAttributes.ts +37 -1
- package/src/internal/nodes/index.test.ts +1 -1
- package/src/internal/nodes/interface.ts +19 -2
- package/src/internal/nodes/nodesAccess.ts +15 -1
- package/src/internal/nodes/nodesManagement.ts +1 -1
- package/src/internal/nodes/nodesRevisions.ts +14 -5
- package/src/internal/shares/cryptoService.test.ts +5 -3
- package/src/internal/shares/cryptoService.ts +7 -4
- package/src/internal/sharing/apiService.ts +6 -6
- package/src/internal/sharing/cryptoService.test.ts +7 -4
- package/src/internal/sharing/cryptoService.ts +27 -10
- package/src/internal/sharing/sharingAccess.test.ts +6 -0
- package/src/internal/sharing/sharingAccess.ts +4 -2
- package/src/internal/upload/telemetry.test.ts +7 -7
- package/src/internal/upload/telemetry.ts +2 -2
- package/src/telemetry.ts +2 -2
- package/src/tests/telemetry.ts +1 -1
- package/src/transformers.ts +4 -2
|
@@ -83,83 +83,100 @@ describe('extended attrbiutes', () => {
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
describe('should parses file attributes', () => {
|
|
86
|
-
const testCases: [string, FileExtendedAttributesParsed][] = [
|
|
87
|
-
['', {}],
|
|
88
|
-
['{}', {}],
|
|
89
|
-
['a', {}],
|
|
86
|
+
const testCases: [Date, string, FileExtendedAttributesParsed][] = [
|
|
87
|
+
[new Date('2025-01-01'), '', {}],
|
|
88
|
+
[new Date('2025-01-01'), '{}', {}],
|
|
89
|
+
[new Date('2025-01-01'), 'a', {}],
|
|
90
90
|
[
|
|
91
|
+
new Date('2025-01-01'),
|
|
91
92
|
'{"Common": {"ModificationTime": "2009-02-13T23:31:30+0000"}}',
|
|
92
93
|
{
|
|
93
94
|
claimedModificationTime: new Date(1234567890000),
|
|
94
95
|
claimedSize: undefined,
|
|
95
96
|
claimedDigests: undefined,
|
|
96
97
|
claimedAdditionalMetadata: undefined,
|
|
98
|
+
claimedBlockSizes: undefined,
|
|
97
99
|
},
|
|
98
100
|
],
|
|
99
101
|
[
|
|
102
|
+
new Date('2025-01-01'),
|
|
100
103
|
'{"Common": {"Size": 123}}',
|
|
101
104
|
{
|
|
102
105
|
claimedModificationTime: undefined,
|
|
103
106
|
claimedSize: 123,
|
|
104
107
|
claimedDigests: undefined,
|
|
105
108
|
claimedAdditionalMetadata: undefined,
|
|
109
|
+
claimedBlockSizes: undefined,
|
|
106
110
|
},
|
|
107
111
|
],
|
|
108
112
|
[
|
|
109
|
-
'
|
|
113
|
+
new Date('2025-01-01'),
|
|
114
|
+
'{"Common": {"ModificationTime": "2009-02-13T23:31:30+0000", "Size": 123, "BlockSizes": [123]}}',
|
|
110
115
|
{
|
|
111
116
|
claimedModificationTime: new Date(1234567890000),
|
|
112
117
|
claimedSize: 123,
|
|
113
118
|
claimedDigests: undefined,
|
|
114
119
|
claimedAdditionalMetadata: undefined,
|
|
120
|
+
claimedBlockSizes: [123],
|
|
115
121
|
},
|
|
116
122
|
],
|
|
117
123
|
[
|
|
124
|
+
new Date('2025-01-01'),
|
|
118
125
|
'{"Common": {"ModificationTime": "aa", "Size": 123}}',
|
|
119
126
|
{
|
|
120
127
|
claimedModificationTime: undefined,
|
|
121
128
|
claimedSize: 123,
|
|
122
129
|
claimedDigests: undefined,
|
|
123
130
|
claimedAdditionalMetadata: undefined,
|
|
131
|
+
claimedBlockSizes: undefined,
|
|
124
132
|
},
|
|
125
133
|
],
|
|
126
134
|
[
|
|
135
|
+
new Date('2025-01-01'),
|
|
127
136
|
'{"Common": {"ModificationTime": "2009-02-13T23:31:30+0000", "Size": "aaa"}}',
|
|
128
137
|
{
|
|
129
138
|
claimedModificationTime: new Date(1234567890000),
|
|
130
139
|
claimedSize: undefined,
|
|
131
140
|
claimedDigests: undefined,
|
|
132
141
|
claimedAdditionalMetadata: undefined,
|
|
142
|
+
claimedBlockSizes: undefined,
|
|
133
143
|
},
|
|
134
144
|
],
|
|
135
145
|
[
|
|
146
|
+
new Date('2025-01-01'),
|
|
136
147
|
'{"Common": {"Digests": {}}}',
|
|
137
148
|
{
|
|
138
149
|
claimedModificationTime: undefined,
|
|
139
150
|
claimedSize: undefined,
|
|
140
151
|
claimedDigests: undefined,
|
|
141
152
|
claimedAdditionalMetadata: undefined,
|
|
153
|
+
claimedBlockSizes: undefined,
|
|
142
154
|
},
|
|
143
155
|
],
|
|
144
156
|
[
|
|
157
|
+
new Date('2025-01-01'),
|
|
145
158
|
'{"Common": {"Digests": {"SHA1": null}}}',
|
|
146
159
|
{
|
|
147
160
|
claimedModificationTime: undefined,
|
|
148
161
|
claimedSize: undefined,
|
|
149
162
|
claimedDigests: undefined,
|
|
150
163
|
claimedAdditionalMetadata: undefined,
|
|
164
|
+
claimedBlockSizes: undefined,
|
|
151
165
|
},
|
|
152
166
|
],
|
|
153
167
|
[
|
|
168
|
+
new Date('2025-01-01'),
|
|
154
169
|
'{"Common": {"Digests": {"SHA1": "abcdef"}}}',
|
|
155
170
|
{
|
|
156
171
|
claimedModificationTime: undefined,
|
|
157
172
|
claimedSize: undefined,
|
|
158
173
|
claimedDigests: { sha1: 'abcdef' },
|
|
159
174
|
claimedAdditionalMetadata: undefined,
|
|
175
|
+
claimedBlockSizes: undefined,
|
|
160
176
|
},
|
|
161
177
|
],
|
|
162
178
|
[
|
|
179
|
+
new Date('2025-01-01'),
|
|
163
180
|
'{"Common": {}, "Media": {}}',
|
|
164
181
|
{
|
|
165
182
|
claimedModificationTime: undefined,
|
|
@@ -168,12 +185,48 @@ describe('extended attrbiutes', () => {
|
|
|
168
185
|
claimedAdditionalMetadata: {
|
|
169
186
|
Media: {},
|
|
170
187
|
},
|
|
188
|
+
claimedBlockSizes: undefined,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
[
|
|
192
|
+
new Date('2025-01-01'),
|
|
193
|
+
'{"Common": {"BlockSizes": [1024, 1024, 1024, 1024, 123]}}',
|
|
194
|
+
{
|
|
195
|
+
claimedModificationTime: undefined,
|
|
196
|
+
claimedSize: undefined,
|
|
197
|
+
claimedDigests: undefined,
|
|
198
|
+
claimedAdditionalMetadata: undefined,
|
|
199
|
+
claimedBlockSizes: [1024, 1024, 1024, 1024, 123],
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
[
|
|
203
|
+
// Starting from 2025-01-01, block sizes are passed as is.
|
|
204
|
+
new Date('2025-01-01'),
|
|
205
|
+
'{"Common": {"BlockSizes": [1024, 1024, 123, 1024, 1024]}}',
|
|
206
|
+
{
|
|
207
|
+
claimedModificationTime: undefined,
|
|
208
|
+
claimedSize: undefined,
|
|
209
|
+
claimedDigests: undefined,
|
|
210
|
+
claimedAdditionalMetadata: undefined,
|
|
211
|
+
claimedBlockSizes: [1024, 1024, 123, 1024, 1024],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
[
|
|
215
|
+
// Before 2025-01-01, block sizes are sorted in descending order.
|
|
216
|
+
new Date('2024-01-01'),
|
|
217
|
+
'{"Common": {"BlockSizes": [123, 1024, 1024, 1024, 1024]}}',
|
|
218
|
+
{
|
|
219
|
+
claimedModificationTime: undefined,
|
|
220
|
+
claimedSize: undefined,
|
|
221
|
+
claimedDigests: undefined,
|
|
222
|
+
claimedAdditionalMetadata: undefined,
|
|
223
|
+
claimedBlockSizes: [1024, 1024, 1024, 1024, 123],
|
|
171
224
|
},
|
|
172
225
|
],
|
|
173
226
|
];
|
|
174
|
-
testCases.forEach(([input, expectedAttributes]) => {
|
|
227
|
+
testCases.forEach(([creationTime, input, expectedAttributes]) => {
|
|
175
228
|
it(`should parse ${input}`, () => {
|
|
176
|
-
const output = parseFileExtendedAttributes(getMockLogger(), input);
|
|
229
|
+
const output = parseFileExtendedAttributes(getMockLogger(), creationTime, input);
|
|
177
230
|
expect(output).toMatchObject(expectedAttributes);
|
|
178
231
|
});
|
|
179
232
|
});
|
|
@@ -48,6 +48,7 @@ export interface FileExtendedAttributesParsed {
|
|
|
48
48
|
sha1?: string;
|
|
49
49
|
};
|
|
50
50
|
claimedAdditionalMetadata?: object;
|
|
51
|
+
claimedBlockSizes?: number[];
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export function generateFolderExtendedAttributes(claimedModificationTime?: Date): string | undefined {
|
|
@@ -113,7 +114,11 @@ export function generateFileExtendedAttributes(options: {
|
|
|
113
114
|
});
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
export function parseFileExtendedAttributes(
|
|
117
|
+
export function parseFileExtendedAttributes(
|
|
118
|
+
logger: Logger,
|
|
119
|
+
creationTime: Date,
|
|
120
|
+
extendedAttributes?: string,
|
|
121
|
+
): FileExtendedAttributesParsed {
|
|
117
122
|
if (!extendedAttributes) {
|
|
118
123
|
return {};
|
|
119
124
|
}
|
|
@@ -131,6 +136,7 @@ export function parseFileExtendedAttributes(logger: Logger, extendedAttributes?:
|
|
|
131
136
|
claimedAdditionalMetadata: Object.keys(claimedAdditionalMetadata).length
|
|
132
137
|
? claimedAdditionalMetadata
|
|
133
138
|
: undefined,
|
|
139
|
+
claimedBlockSizes: parseBlockSizes(logger, creationTime, parsed),
|
|
134
140
|
};
|
|
135
141
|
} catch (error: unknown) {
|
|
136
142
|
logger.error(`Failed to parse extended attributes`, error);
|
|
@@ -183,3 +189,33 @@ function parseDigests(logger: Logger, xattr?: FileExtendedAttributesSchema): { s
|
|
|
183
189
|
sha1,
|
|
184
190
|
};
|
|
185
191
|
}
|
|
192
|
+
|
|
193
|
+
function parseBlockSizes(
|
|
194
|
+
logger: Logger,
|
|
195
|
+
creationTime: Date,
|
|
196
|
+
xattr?: FileExtendedAttributesSchema,
|
|
197
|
+
): number[] | undefined {
|
|
198
|
+
const blockSizes = xattr?.Common?.BlockSizes;
|
|
199
|
+
if (blockSizes === undefined) {
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
if (!Array.isArray(blockSizes)) {
|
|
203
|
+
logger.warn(`XAttr block sizes "${blockSizes}" is not valid`);
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
if (blockSizes.some((size) => typeof size !== 'number' || size <= 0)) {
|
|
207
|
+
logger.warn(`XAttr block sizes "${blockSizes}" is not valid`);
|
|
208
|
+
return undefined;
|
|
209
|
+
}
|
|
210
|
+
if (blockSizes.length === 0) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
// Before 2025, there was a bug on the Windows client that didn't sort
|
|
214
|
+
// the block sizes in correct order. Because the sizes were all the same
|
|
215
|
+
// except the last one, which was always smaller, the block sizes must be
|
|
216
|
+
// sorted in descending order.
|
|
217
|
+
if (creationTime < new Date('2025-01-01')) {
|
|
218
|
+
return blockSizes.sort((a, b) => b - a);
|
|
219
|
+
}
|
|
220
|
+
return blockSizes;
|
|
221
|
+
}
|
|
@@ -32,7 +32,12 @@ interface BaseNode {
|
|
|
32
32
|
// Share node metadata
|
|
33
33
|
shareId?: string;
|
|
34
34
|
isShared: boolean;
|
|
35
|
-
|
|
35
|
+
directRole: MemberRole;
|
|
36
|
+
membership?: {
|
|
37
|
+
role: MemberRole;
|
|
38
|
+
inviteTime: Date;
|
|
39
|
+
// TODO: acceptedBy: Author;
|
|
40
|
+
};
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
/**
|
|
@@ -50,6 +55,12 @@ export interface EncryptedNodeCrypto {
|
|
|
50
55
|
armoredKey: string;
|
|
51
56
|
armoredNodePassphrase: string;
|
|
52
57
|
armoredNodePassphraseSignature: string;
|
|
58
|
+
membership?: {
|
|
59
|
+
inviterEmail: string;
|
|
60
|
+
base64MemberSharePassphraseKeyPacket: string;
|
|
61
|
+
armoredInviterSharePassphraseKeyPacketSignature: string;
|
|
62
|
+
armoredInviteeSharePassphraseSessionKeySignature: string;
|
|
63
|
+
};
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export interface EncryptedNodeFileCrypto extends EncryptedNodeCrypto {
|
|
@@ -78,9 +89,14 @@ export interface EncryptedNodeAlbumCrypto extends EncryptedNodeCrypto {}
|
|
|
78
89
|
* This interface is holding decrypted node metadata that is not yet parsed,
|
|
79
90
|
* such as extended attributes.
|
|
80
91
|
*/
|
|
81
|
-
export interface DecryptedUnparsedNode extends BaseNode {
|
|
92
|
+
export interface DecryptedUnparsedNode extends Omit<BaseNode, 'membership'> {
|
|
82
93
|
keyAuthor: Author;
|
|
83
94
|
nameAuthor: Author;
|
|
95
|
+
membership?: {
|
|
96
|
+
role: MemberRole;
|
|
97
|
+
inviteTime: Date;
|
|
98
|
+
sharedBy: Author;
|
|
99
|
+
};
|
|
84
100
|
name: Result<string, Error>;
|
|
85
101
|
activeRevision?: Result<DecryptedUnparsedRevision, Error>;
|
|
86
102
|
folder?: {
|
|
@@ -147,6 +163,7 @@ export interface DecryptedUnparsedRevision extends BaseRevision {
|
|
|
147
163
|
|
|
148
164
|
export interface DecryptedRevision extends Revision {
|
|
149
165
|
thumbnails?: Thumbnail[];
|
|
166
|
+
claimedBlockSizes?: number[];
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
/**
|
|
@@ -270,6 +270,16 @@ export class NodesAccess {
|
|
|
270
270
|
claimedAuthor: encryptedNode.encryptedCrypto.nameSignatureEmail,
|
|
271
271
|
error: getErrorMessage(error),
|
|
272
272
|
}),
|
|
273
|
+
membership: encryptedNode.membership
|
|
274
|
+
? {
|
|
275
|
+
role: encryptedNode.membership.role,
|
|
276
|
+
inviteTime: encryptedNode.membership.inviteTime,
|
|
277
|
+
sharedBy: resultError({
|
|
278
|
+
claimedAuthor: encryptedNode.encryptedCrypto.membership?.inviterEmail,
|
|
279
|
+
error: getErrorMessage(error),
|
|
280
|
+
}),
|
|
281
|
+
}
|
|
282
|
+
: undefined,
|
|
273
283
|
errors: [error],
|
|
274
284
|
treeEventScopeId: splitNodeUid(encryptedNode.uid).volumeId,
|
|
275
285
|
},
|
|
@@ -311,7 +321,11 @@ export class NodesAccess {
|
|
|
311
321
|
|
|
312
322
|
if (unparsedNode.type === NodeType.File) {
|
|
313
323
|
const extendedAttributes = unparsedNode.activeRevision?.ok
|
|
314
|
-
? parseFileExtendedAttributes(
|
|
324
|
+
? parseFileExtendedAttributes(
|
|
325
|
+
this.logger,
|
|
326
|
+
unparsedNode.activeRevision.value.creationTime,
|
|
327
|
+
unparsedNode.activeRevision.value.extendedAttributes,
|
|
328
|
+
)
|
|
315
329
|
: undefined;
|
|
316
330
|
|
|
317
331
|
return {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Logger
|
|
1
|
+
import { Logger } from '../../interface';
|
|
2
2
|
import { makeNodeUidFromRevisionUid } from '../uids';
|
|
3
3
|
import { NodeAPIService } from './apiService';
|
|
4
4
|
import { NodesCryptoService } from './cryptoService';
|
|
5
5
|
import { NodesAccess } from './nodesAccess';
|
|
6
6
|
import { parseFileExtendedAttributes } from './extendedAttributes';
|
|
7
|
+
import { DecryptedRevision } from './interface';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Provides access to revisions metadata.
|
|
@@ -21,26 +22,34 @@ export class NodesRevisons {
|
|
|
21
22
|
this.nodesAccess = nodesAccess;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
async getRevision(nodeRevisionUid: string): Promise<
|
|
25
|
+
async getRevision(nodeRevisionUid: string): Promise<DecryptedRevision> {
|
|
25
26
|
const nodeUid = makeNodeUidFromRevisionUid(nodeRevisionUid);
|
|
26
27
|
const { key } = await this.nodesAccess.getNodeKeys(nodeUid);
|
|
27
28
|
|
|
28
29
|
const encryptedRevision = await this.apiService.getRevision(nodeRevisionUid);
|
|
29
30
|
const revision = await this.cryptoService.decryptRevision(nodeUid, encryptedRevision, key);
|
|
30
|
-
const extendedAttributes = parseFileExtendedAttributes(
|
|
31
|
+
const extendedAttributes = parseFileExtendedAttributes(
|
|
32
|
+
this.logger,
|
|
33
|
+
revision.creationTime,
|
|
34
|
+
revision.extendedAttributes,
|
|
35
|
+
);
|
|
31
36
|
return {
|
|
32
37
|
...revision,
|
|
33
38
|
...extendedAttributes,
|
|
34
39
|
};
|
|
35
40
|
}
|
|
36
41
|
|
|
37
|
-
async *iterateRevisions(nodeUid: string, signal?: AbortSignal): AsyncGenerator<
|
|
42
|
+
async *iterateRevisions(nodeUid: string, signal?: AbortSignal): AsyncGenerator<DecryptedRevision> {
|
|
38
43
|
const { key } = await this.nodesAccess.getNodeKeys(nodeUid);
|
|
39
44
|
|
|
40
45
|
const encryptedRevisions = await this.apiService.getRevisions(nodeUid, signal);
|
|
41
46
|
for (const encryptedRevision of encryptedRevisions) {
|
|
42
47
|
const revision = await this.cryptoService.decryptRevision(nodeUid, encryptedRevision, key);
|
|
43
|
-
const extendedAttributes = parseFileExtendedAttributes(
|
|
48
|
+
const extendedAttributes = parseFileExtendedAttributes(
|
|
49
|
+
this.logger,
|
|
50
|
+
revision.creationTime,
|
|
51
|
+
revision.extendedAttributes,
|
|
52
|
+
);
|
|
44
53
|
yield {
|
|
45
54
|
...revision,
|
|
46
55
|
...extendedAttributes,
|
|
@@ -59,7 +59,7 @@ describe('SharesCryptoService', () => {
|
|
|
59
59
|
|
|
60
60
|
expect(account.getOwnAddress).toHaveBeenCalledWith('addressId');
|
|
61
61
|
expect(account.getPublicKeys).toHaveBeenCalledWith('signatureEmail');
|
|
62
|
-
expect(telemetry.
|
|
62
|
+
expect(telemetry.recordMetric).not.toHaveBeenCalled();
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
it('should decrypt root share with signiture verification error', async () => {
|
|
@@ -97,12 +97,13 @@ describe('SharesCryptoService', () => {
|
|
|
97
97
|
|
|
98
98
|
expect(account.getOwnAddress).toHaveBeenCalledWith('addressId');
|
|
99
99
|
expect(account.getPublicKeys).toHaveBeenCalledWith('signatureEmail');
|
|
100
|
-
expect(telemetry.
|
|
100
|
+
expect(telemetry.recordMetric).toHaveBeenCalledWith({
|
|
101
101
|
eventName: 'verificationError',
|
|
102
102
|
volumeType: 'own_volume',
|
|
103
103
|
field: 'shareKey',
|
|
104
104
|
addressMatchingDefaultShare: undefined,
|
|
105
105
|
fromBefore2024: undefined,
|
|
106
|
+
uid: 'shareId',
|
|
106
107
|
});
|
|
107
108
|
});
|
|
108
109
|
|
|
@@ -124,12 +125,13 @@ describe('SharesCryptoService', () => {
|
|
|
124
125
|
|
|
125
126
|
await expect(result).rejects.toThrow(error);
|
|
126
127
|
|
|
127
|
-
expect(telemetry.
|
|
128
|
+
expect(telemetry.recordMetric).toHaveBeenCalledWith({
|
|
128
129
|
eventName: 'decryptionError',
|
|
129
130
|
volumeType: 'own_volume',
|
|
130
131
|
field: 'shareKey',
|
|
131
132
|
fromBefore2024: undefined,
|
|
132
133
|
error,
|
|
134
|
+
uid: 'shareId',
|
|
133
135
|
});
|
|
134
136
|
});
|
|
135
137
|
});
|
|
@@ -76,7 +76,7 @@ export class SharesCryptoService {
|
|
|
76
76
|
const { keys: addressKeys } = await this.account.getOwnAddress(share.addressId);
|
|
77
77
|
const addressPublicKeys = await this.account.getPublicKeys(share.creatorEmail);
|
|
78
78
|
|
|
79
|
-
let key, passphraseSessionKey, verified;
|
|
79
|
+
let key, passphraseSessionKey, verified, verificationErrors;
|
|
80
80
|
try {
|
|
81
81
|
const result = await this.driveCrypto.decryptKey(
|
|
82
82
|
share.encryptedCrypto.armoredKey,
|
|
@@ -88,6 +88,7 @@ export class SharesCryptoService {
|
|
|
88
88
|
key = result.key;
|
|
89
89
|
passphraseSessionKey = result.passphraseSessionKey;
|
|
90
90
|
verified = result.verified;
|
|
91
|
+
verificationErrors = result.verificationErrors;
|
|
91
92
|
} catch (error: unknown) {
|
|
92
93
|
this.reportDecryptionError(share, error);
|
|
93
94
|
throw error;
|
|
@@ -98,7 +99,7 @@ export class SharesCryptoService {
|
|
|
98
99
|
? resultOk(share.creatorEmail)
|
|
99
100
|
: resultError({
|
|
100
101
|
claimedAuthor: share.creatorEmail,
|
|
101
|
-
error: getVerificationMessage(verified),
|
|
102
|
+
error: getVerificationMessage(verified, verificationErrors),
|
|
102
103
|
});
|
|
103
104
|
|
|
104
105
|
if (!author.ok) {
|
|
@@ -125,12 +126,13 @@ export class SharesCryptoService {
|
|
|
125
126
|
const fromBefore2024 = share.creationTime ? share.creationTime < new Date('2024-01-01') : undefined;
|
|
126
127
|
this.logger.error(`Failed to decrypt share ${share.shareId} (from before 2024: ${fromBefore2024})`, error);
|
|
127
128
|
|
|
128
|
-
this.telemetry.
|
|
129
|
+
this.telemetry.recordMetric({
|
|
129
130
|
eventName: 'decryptionError',
|
|
130
131
|
volumeType: shareTypeToMetricContext(share.type),
|
|
131
132
|
field: 'shareKey',
|
|
132
133
|
fromBefore2024,
|
|
133
134
|
error,
|
|
135
|
+
uid: share.shareId,
|
|
134
136
|
});
|
|
135
137
|
this.reportedDecryptionErrors.add(share.shareId);
|
|
136
138
|
}
|
|
@@ -143,11 +145,12 @@ export class SharesCryptoService {
|
|
|
143
145
|
const fromBefore2024 = share.creationTime ? share.creationTime < new Date('2024-01-01') : undefined;
|
|
144
146
|
this.logger.error(`Failed to verify share ${share.shareId} (from before 2024: ${fromBefore2024})`);
|
|
145
147
|
|
|
146
|
-
this.telemetry.
|
|
148
|
+
this.telemetry.recordMetric({
|
|
147
149
|
eventName: 'verificationError',
|
|
148
150
|
volumeType: shareTypeToMetricContext(share.type),
|
|
149
151
|
field: 'shareKey',
|
|
150
152
|
fromBefore2024,
|
|
153
|
+
uid: share.shareId,
|
|
151
154
|
});
|
|
152
155
|
this.reportedVerificationErrors.add(share.shareId);
|
|
153
156
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
DriveAPIService,
|
|
5
5
|
drivePaths,
|
|
6
6
|
nodeTypeNumberToNodeType,
|
|
7
|
-
|
|
7
|
+
permissionsToMemberRole,
|
|
8
8
|
memberRoleToPermission,
|
|
9
9
|
} from '../apiService';
|
|
10
10
|
import {
|
|
@@ -224,7 +224,7 @@ export class SharingAPIService {
|
|
|
224
224
|
base64KeyPacket: response.Invitation.KeyPacket,
|
|
225
225
|
base64KeyPacketSignature: response.Invitation.KeyPacketSignature,
|
|
226
226
|
invitationTime: new Date(response.Invitation.CreateTime * 1000),
|
|
227
|
-
role:
|
|
227
|
+
role: permissionsToMemberRole(this.logger, response.Invitation.Permissions),
|
|
228
228
|
share: {
|
|
229
229
|
armoredKey: response.Share.ShareKey,
|
|
230
230
|
armoredPassphrase: response.Share.Passphrase,
|
|
@@ -312,7 +312,7 @@ export class SharingAPIService {
|
|
|
312
312
|
base64KeyPacket: member.KeyPacket,
|
|
313
313
|
base64KeyPacketSignature: member.KeyPacketSignature,
|
|
314
314
|
invitationTime: new Date(member.CreateTime * 1000),
|
|
315
|
-
role:
|
|
315
|
+
role: permissionsToMemberRole(this.logger, member.Permissions),
|
|
316
316
|
};
|
|
317
317
|
});
|
|
318
318
|
}
|
|
@@ -469,7 +469,7 @@ export class SharingAPIService {
|
|
|
469
469
|
uid: makePublicLinkUid(shareUrl.ShareID, shareUrl.ShareURLID),
|
|
470
470
|
creationTime: new Date(shareUrl.CreateTime * 1000),
|
|
471
471
|
expirationTime: shareUrl.ExpirationTime ? new Date(shareUrl.ExpirationTime * 1000) : undefined,
|
|
472
|
-
role:
|
|
472
|
+
role: permissionsToMemberRole(this.logger, shareUrl.Permissions),
|
|
473
473
|
flags: shareUrl.Flags,
|
|
474
474
|
creatorEmail: shareUrl.CreatorEmail,
|
|
475
475
|
publicUrl: shareUrl.PublicUrl,
|
|
@@ -588,7 +588,7 @@ export class SharingAPIService {
|
|
|
588
588
|
addedByEmail: invitation.InviterEmail,
|
|
589
589
|
inviteeEmail: invitation.InviteeEmail,
|
|
590
590
|
invitationTime: new Date(invitation.CreateTime * 1000),
|
|
591
|
-
role:
|
|
591
|
+
role: permissionsToMemberRole(this.logger, invitation.Permissions),
|
|
592
592
|
base64KeyPacket: invitation.KeyPacket,
|
|
593
593
|
base64KeyPacketSignature: invitation.KeyPacketSignature,
|
|
594
594
|
};
|
|
@@ -605,7 +605,7 @@ export class SharingAPIService {
|
|
|
605
605
|
addedByEmail: invitation.InviterEmail,
|
|
606
606
|
inviteeEmail: invitation.InviteeEmail,
|
|
607
607
|
invitationTime: new Date(invitation.CreateTime * 1000),
|
|
608
|
-
role:
|
|
608
|
+
role: permissionsToMemberRole(this.logger, invitation.Permissions),
|
|
609
609
|
base64Signature: invitation.ExternalInvitationSignature,
|
|
610
610
|
state,
|
|
611
611
|
};
|
|
@@ -84,7 +84,7 @@ describe('SharingCryptoService', () => {
|
|
|
84
84
|
'armoredPassphrase',
|
|
85
85
|
);
|
|
86
86
|
expect(driveCrypto.decryptNodeName).toHaveBeenCalledWith('encryptedName', 'decryptedKey', []);
|
|
87
|
-
expect(telemetry.
|
|
87
|
+
expect(telemetry.recordMetric).not.toHaveBeenCalled();
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
it('should handle undecryptable URL password', async () => {
|
|
@@ -97,11 +97,12 @@ describe('SharingCryptoService', () => {
|
|
|
97
97
|
url: resultError(new Error('Failed to decrypt bookmark password: Failed to decrypt URL password')),
|
|
98
98
|
nodeName: resultError(new Error('Failed to decrypt bookmark password: Failed to decrypt URL password')),
|
|
99
99
|
});
|
|
100
|
-
expect(telemetry.
|
|
100
|
+
expect(telemetry.recordMetric).toHaveBeenCalledWith({
|
|
101
101
|
eventName: 'decryptionError',
|
|
102
102
|
volumeType: MetricVolumeType.SharedPublic,
|
|
103
103
|
field: 'shareUrlPassword',
|
|
104
104
|
error,
|
|
105
|
+
uid: 'tokenId',
|
|
105
106
|
});
|
|
106
107
|
});
|
|
107
108
|
|
|
@@ -115,11 +116,12 @@ describe('SharingCryptoService', () => {
|
|
|
115
116
|
url: resultOk('https://drive.proton.me/urls/tokenId#urlPassword'),
|
|
116
117
|
nodeName: resultError(new Error('Failed to decrypt bookmark key: Failed to decrypt share key')),
|
|
117
118
|
});
|
|
118
|
-
expect(telemetry.
|
|
119
|
+
expect(telemetry.recordMetric).toHaveBeenCalledWith({
|
|
119
120
|
eventName: 'decryptionError',
|
|
120
121
|
volumeType: MetricVolumeType.SharedPublic,
|
|
121
122
|
field: 'shareKey',
|
|
122
123
|
error,
|
|
124
|
+
uid: 'tokenId',
|
|
123
125
|
});
|
|
124
126
|
});
|
|
125
127
|
|
|
@@ -133,11 +135,12 @@ describe('SharingCryptoService', () => {
|
|
|
133
135
|
url: resultOk('https://drive.proton.me/urls/tokenId#urlPassword'),
|
|
134
136
|
nodeName: resultError(new Error('Failed to decrypt bookmark name: Failed to decrypt node name')),
|
|
135
137
|
});
|
|
136
|
-
expect(telemetry.
|
|
138
|
+
expect(telemetry.recordMetric).toHaveBeenCalledWith({
|
|
137
139
|
eventName: 'decryptionError',
|
|
138
140
|
volumeType: MetricVolumeType.SharedPublic,
|
|
139
141
|
field: 'nodeName',
|
|
140
142
|
error,
|
|
143
|
+
uid: 'tokenId',
|
|
141
144
|
});
|
|
142
145
|
});
|
|
143
146
|
|