Skip to content

Commit 9d2c0f5

Browse files
authored
feat(sdk): Decrypt error is StreamrClientError (#2895)
Removed custom `DecryptError` class. Using `StreamrClientError` with code `DECRYPT_ERROR` instead. Changed some wording of decrypt errors: encryption keys are no longer referred as group key, which is an internal term for encryption key (see e.g. `exports.ts#36`). ## Cause parameter Considered adding `cause` parameter for `StreamrClientError` (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause). Decided not to include it as it we've earlier had compatibility issues with it when using Firefox. Maybe browser support is nowadays better. We can add that later if needed. The `cause` parameter would link root cause `Error` instances to StreamrClientError and therefore provide very detailed stack traces. For client errors such stack traces are maybe not needed in practice. I.e. if `decryptWithAES()` throws an error, we'll create a `StreamrClientError` with message of "`AES decryption failed`" and type of `DECRYPT_ERROR`. In most cases these bits of information are sufficient for analyzing application level issues. ## Future improvements - convert all other custom errors to `StreamrClientError` - add `toEqualStreamrError()` test utility
1 parent 01c5cad commit 9d2c0f5

File tree

9 files changed

+39
-24
lines changed

9 files changed

+39
-24
lines changed

packages/sdk/src/StreamrClientError.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { MessageID } from './protocol/MessageID'
2+
13
export type StreamrClientErrorCode =
24
'STREAM_NOT_FOUND' |
35
'NODE_NOT_FOUND' |
@@ -8,6 +10,7 @@ export type StreamrClientErrorCode =
810
'PIPELINE_ERROR' |
911
'UNSUPPORTED_OPERATION' |
1012
'INVALID_STREAM_METADATA' |
13+
'DECRYPT_ERROR' |
1114
'STORAGE_NODE_ERROR' |
1215
'UNKNOWN_ERROR'
1316

@@ -21,3 +24,7 @@ export class StreamrClientError extends Error {
2124
this.name = this.constructor.name
2225
}
2326
}
27+
28+
export const formMessageIdDescription = (messageId: MessageID): string => {
29+
return JSON.stringify(messageId)
30+
}

packages/sdk/src/encryption/EncryptionUtil.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import crypto, { CipherKey } from 'crypto'
22
import { StreamMessage, StreamMessageAESEncrypted } from '../protocol/StreamMessage'
3-
import { StreamMessageError } from '../protocol/StreamMessageError'
43
import { GroupKey } from './GroupKey'
4+
import { formMessageIdDescription, StreamrClientError } from '../StreamrClientError'
55

6-
export class DecryptError extends StreamMessageError {
7-
constructor(streamMessage: StreamMessage, message = '') {
8-
super(`Decrypt error: ${message}`, streamMessage)
9-
}
6+
export const createDecryptError = (message: string, streamMessage: StreamMessage): StreamrClientError => {
7+
return new StreamrClientError(`${message} (messageId=${formMessageIdDescription(streamMessage.messageId)})`, 'DECRYPT_ERROR')
108
}
119

1210
export const INITIALIZATION_VECTOR_LENGTH = 16
@@ -53,17 +51,16 @@ export class EncryptionUtil {
5351
let content: Uint8Array
5452
try {
5553
content = this.decryptWithAES(streamMessage.content, groupKey.data)
56-
} catch (err) {
57-
throw new DecryptError(streamMessage, err.stack)
54+
} catch {
55+
throw createDecryptError('AES decryption failed', streamMessage)
5856
}
5957

6058
let newGroupKey: GroupKey | undefined = undefined
6159
if (streamMessage.newGroupKey) {
6260
try {
6361
newGroupKey = groupKey.decryptNextGroupKey(streamMessage.newGroupKey)
64-
} catch (err) {
65-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
66-
throw new DecryptError(streamMessage, `Could not decrypt new group key: ${err.stack}`)
62+
} catch {
63+
throw createDecryptError('Could not decrypt new encryption key', streamMessage)
6764
}
6865
}
6966

packages/sdk/src/encryption/decrypt.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DestroySignal } from '../DestroySignal'
2-
import { DecryptError, EncryptionUtil } from '../encryption/EncryptionUtil'
2+
import { createDecryptError, EncryptionUtil } from '../encryption/EncryptionUtil'
33
import { GroupKey } from '../encryption/GroupKey'
44
import { GroupKeyManager } from '../encryption/GroupKeyManager'
55
import { EncryptionType, StreamMessage, StreamMessageAESEncrypted } from '../protocol/StreamMessage'
@@ -22,12 +22,11 @@ export const decrypt = async (
2222
streamMessage.groupKeyId,
2323
streamMessage.getPublisherId()
2424
)
25-
} catch (e: any) {
25+
} catch {
2626
if (destroySignal.isDestroyed()) {
2727
return streamMessage
2828
}
29-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
30-
throw new DecryptError(streamMessage, `Could not get GroupKey ${streamMessage.groupKeyId}: ${e.message}`)
29+
throw createDecryptError(`Could not get encryption key ${streamMessage.groupKeyId}`, streamMessage)
3130
}
3231
if (destroySignal.isDestroyed()) {
3332
return streamMessage

packages/sdk/test/integration/resend-with-existing-key.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { fastWallet } from '@streamr/test-utils'
44
import { collect, toEthereumAddress, toStreamID, toUserId } from '@streamr/utils'
55
import { Stream } from '../../src/Stream'
66
import { StreamrClient } from '../../src/StreamrClient'
7-
import { DecryptError } from '../../src/encryption/EncryptionUtil'
87
import { GroupKey } from '../../src/encryption/GroupKey'
98
import { StreamPermission } from '../../src/permission'
109
import { FakeEnvironment } from '../test-utils/fake/FakeEnvironment'
1110
import { FakeStorageNode } from '../test-utils/fake/FakeStorageNode'
1211
import { createMockMessage, createRelativeTestStreamId, getLocalGroupKeyStore } from '../test-utils/utils'
12+
import { StreamrClientError } from '../../src/StreamrClientError'
1313

1414
/*
1515
* A subscriber has some GroupKeys in the local store and reads historical data
@@ -68,7 +68,8 @@ describe('resend with existing key', () => {
6868
await collect(messageStream)
6969
expect(onError).toHaveBeenCalled()
7070
const error = onError.mock.calls[0][0]
71-
expect(error).toBeInstanceOf(DecryptError)
71+
expect(error).toBeInstanceOf(StreamrClientError)
72+
expect(error.code).toBe('DECRYPT_ERROR')
7273
}
7374

7475
beforeEach(async () => {

packages/sdk/test/integration/revoke-permissions.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ describe('revoke permissions', () => {
159159
break
160160
}
161161
}
162-
}).rejects.toThrow(/not a subscriber|Could not get GroupKey/)
162+
}).rejects.toThrow(/not a subscriber|Could not get encryption key/)
163163
} finally {
164164
clearTimeout(t)
165165
// run in finally to ensure publish promise finishes before

packages/sdk/test/integration/update-encryption-key.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import 'reflect-metadata'
22

33
import { StreamPartID, StreamPartIDUtils, until } from '@streamr/utils'
44
import { Message } from '../../src/Message'
5-
import { DecryptError } from '../../src/encryption/EncryptionUtil'
65
import { GroupKey } from '../../src/encryption/GroupKey'
76
import { StreamPermission } from '../../src/permission'
87
import { nextValue } from '../../src/utils/iterators'
98
import { StreamrClient } from './../../src/StreamrClient'
109
import { FakeEnvironment } from './../test-utils/fake/FakeEnvironment'
10+
import { StreamrClientError } from '../../src/StreamrClientError'
1111

1212
/*
1313
* Subscriber has subscribed to a stream, and the publisher updates the encryption key for that stream.
@@ -161,7 +161,8 @@ describe('update encryption key', () => {
161161
mockId: 2
162162
})
163163
await until(() => onError.mock.calls.length > 0, 10 * 1000)
164-
expect(onError.mock.calls[0][0]).toBeInstanceOf(DecryptError)
164+
expect(onError.mock.calls[0][0]).toBeInstanceOf(StreamrClientError)
165+
expect(onError.mock.calls[0][0].code).toBe('DECRYPT_ERROR')
165166
}, 10 * 1000)
166167
})
167168
})

packages/sdk/test/unit/Decrypt.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { GroupKeyManager } from '../../src/encryption/GroupKeyManager'
1010
import { decrypt } from '../../src/encryption/decrypt'
1111
import { createGroupKeyManager, createMockMessage } from '../test-utils/utils'
1212
import { EncryptionType, StreamMessage, StreamMessageAESEncrypted } from './../../src/protocol/StreamMessage'
13+
import { formMessageIdDescription } from '../../src/StreamrClientError'
1314

1415
describe('Decrypt', () => {
1516

@@ -54,6 +55,9 @@ describe('Decrypt', () => {
5455
msg as StreamMessageAESEncrypted,
5556
groupKeyManager,
5657
destroySignal)
57-
}).rejects.toThrow(`Decrypt error: Could not get GroupKey ${groupKey.id}`)
58+
}).rejects.toThrowStreamrError({
59+
code: 'DECRYPT_ERROR',
60+
message: `Could not get encryption key ${groupKey.id} (messageId=${formMessageIdDescription(msg.messageId)})`
61+
})
5862
})
5963
})

packages/sdk/test/unit/EncryptionUtil.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { GroupKey } from '../../src/encryption/GroupKey'
55
import { createMockMessage } from '../test-utils/utils'
66
import { EncryptedGroupKey } from './../../src/protocol/EncryptedGroupKey'
77
import { StreamMessage, StreamMessageAESEncrypted } from './../../src/protocol/StreamMessage'
8+
import { formMessageIdDescription } from '../../src/StreamrClientError'
89

910
const STREAM_ID = toStreamID('streamId')
1011

@@ -61,6 +62,9 @@ describe('EncryptionUtil', () => {
6162
...msg,
6263
newGroupKey: new EncryptedGroupKey('mockId', hexToBinary('0x1234'))
6364
}) as StreamMessageAESEncrypted
64-
expect(() => EncryptionUtil.decryptStreamMessage(msg2, key)).toThrow('Could not decrypt new group key')
65+
expect(() => EncryptionUtil.decryptStreamMessage(msg2, key)).toThrowStreamrError({
66+
code: 'DECRYPT_ERROR',
67+
message: `Could not decrypt new encryption key (messageId=${formMessageIdDescription(msg2.messageId)})`
68+
})
6569
})
6670
})

packages/sdk/test/unit/messagePipeline.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { StrictStreamrClientConfig } from '../../src/Config'
99
import { DestroySignal } from '../../src/DestroySignal'
1010
import { ERC1271ContractFacade } from '../../src/contracts/ERC1271ContractFacade'
1111
import { StreamRegistry } from '../../src/contracts/StreamRegistry'
12-
import { DecryptError, EncryptionUtil } from '../../src/encryption/EncryptionUtil'
12+
import { EncryptionUtil } from '../../src/encryption/EncryptionUtil'
1313
import { GroupKey } from '../../src/encryption/GroupKey'
1414
import { GroupKeyManager } from '../../src/encryption/GroupKeyManager'
1515
import { LitProtocolFacade } from '../../src/encryption/LitProtocolFacade'
@@ -22,6 +22,7 @@ import { PushPipeline } from '../../src/utils/PushPipeline'
2222
import { mockLoggerFactory } from '../test-utils/utils'
2323
import { MessageID } from './../../src/protocol/MessageID'
2424
import { ContentType, EncryptionType, SignatureType, StreamMessage, StreamMessageType } from './../../src/protocol/StreamMessage'
25+
import { StreamrClientError } from '../../src/StreamrClientError'
2526

2627
const CONTENT = {
2728
foo: 'bar'
@@ -168,8 +169,9 @@ describe('messagePipeline', () => {
168169
const output = await collect(pipeline)
169170
expect(onError).toHaveBeenCalledTimes(1)
170171
const error = onError.mock.calls[0][0]
171-
expect(error).toBeInstanceOf(DecryptError)
172-
expect(error.message).toMatch(/timed out/)
172+
expect(error).toBeInstanceOf(StreamrClientError)
173+
expect(error.code).toBe('DECRYPT_ERROR')
174+
expect(error.message).toMatch(/Could not get encryption key/)
173175
expect(output).toEqual([])
174176
expect(streamRegistry.invalidatePermissionCaches).toHaveBeenCalledTimes(1)
175177
expect(streamRegistry.invalidatePermissionCaches).toHaveBeenCalledWith(StreamPartIDUtils.getStreamID(streamPartId))

0 commit comments

Comments
 (0)