Skip to content

Commit 92bee86

Browse files
Merge pull request #87 from adobe/fix/validate-key-characters
fix: validate key characters, throw actionable error
2 parents 3b6d45f + ba1ea80 commit 92bee86

File tree

4 files changed

+29
-3
lines changed

4 files changed

+29
-3
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Apply when init with OW credentials (and not own cloud DB credentials):
7070
- Max state key size: `1024 bytes`
7171
- Max total state size: `10 GB`
7272
- Token expiry (need to re-init after expiry): `1 hour`
73-
- Non supported characters for state keys are: `'/', '\\', '?', '#'`
73+
- Non supported characters for state keys are: `'/', '\', '?', '#'`
7474

7575
## Adobe I/O State Store Consistency Guarantees
7676

lib/StateStore.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ function validateInput (input, schema, details) {
5555

5656
// eslint-disable-next-line jsdoc/require-jsdoc
5757
function validateKey (key, details, label = 'key') {
58-
validateInput(key, joi.string().label(label).required(), details)
58+
validateInput(key, joi.string().label(label).required().regex(/[?#/\\]/, { invert: true }).messages({
59+
'string.pattern.invert.base': 'Key cannot contain ?, #, /, or \\'
60+
}), details)
5961
}
6062

6163
// eslint-disable-next-line jsdoc/require-jsdoc

lib/StateStoreError.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const logger = require('@adobe/aio-lib-core-logging')('@adobe/aio-lib-state', {
2727
*
2828
* @typedef StateLibErrors
2929
* @type {object}
30-
* @property {StateLibError} ERROR_BAD_ARGUMENT this error is thrown when an argument is missing or has invalid type
30+
* @property {StateLibError} ERROR_BAD_ARGUMENT this error is thrown when an argument is missing, has invalid type, or includes invalid characters.
3131
* @property {StateLibError} ERROR_BAD_REQUEST this error is thrown when an argument has an illegal value.
3232
* @property {StateLibError} ERROR_NOT_IMPLEMENTED this error is thrown when a method is not implemented or when calling
3333
* methods directly on the abstract class (StateStore).

test/StateStore.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ describe('get', () => {
3838
const state = new StateStore(true)
3939
await global.expectToThrowBadArg(state.get.bind(state, 123), ['string', 'key'], { key: 123 })
4040
})
41+
// eslint-disable-next-line jest/expect-expect
42+
test('bad key characters', async () => {
43+
const state = new StateStore(true)
44+
await global.expectToThrowBadArg(state.put.bind(state, '?test', 'value', {}), ['Key', 'cannot', 'contain'], { key: '?test', value: 'value', options: {} })
45+
await global.expectToThrowBadArg(state.put.bind(state, 't#est', 'value', {}), ['Key', 'cannot', 'contain'], { key: 't#est', value: 'value', options: {} })
46+
await global.expectToThrowBadArg(state.put.bind(state, 't\\est', 'value', {}), ['Key', 'cannot', 'contain'], { key: 't\\est', value: 'value', options: {} })
47+
await global.expectToThrowBadArg(state.put.bind(state, 'test/', 'value', {}), ['Key', 'cannot', 'contain'], { key: 'test/', value: 'value', options: {} })
48+
})
4149
test('calls _get (part of interface)', async () => {
4250
const state = new StateStore(true)
4351
state._get = jest.fn()
@@ -60,6 +68,14 @@ describe('put', () => {
6068
await global.expectToThrowBadArg(state.put.bind(state, 123, 'value', {}), ['string', 'key'], { key: 123, value: 'value', options: {} })
6169
})
6270
// eslint-disable-next-line jest/expect-expect
71+
test('bad key characters', async () => {
72+
const state = new StateStore(true)
73+
await global.expectToThrowBadArg(state.put.bind(state, '?test', 'value', {}), ['Key', 'cannot', 'contain'], { key: '?test', value: 'value', options: {} })
74+
await global.expectToThrowBadArg(state.put.bind(state, 't#est', 'value', {}), ['Key', 'cannot', 'contain'], { key: 't#est', value: 'value', options: {} })
75+
await global.expectToThrowBadArg(state.put.bind(state, 't\\est', 'value', {}), ['Key', 'cannot', 'contain'], { key: 't\\est', value: 'value', options: {} })
76+
await global.expectToThrowBadArg(state.put.bind(state, 'test/', 'value', {}), ['Key', 'cannot', 'contain'], { key: 'test/', value: 'value', options: {} })
77+
})
78+
// eslint-disable-next-line jest/expect-expect
6379
test('bad options', async () => {
6480
const state = new StateStore(true)
6581
const expectedDetails = { key: 'key', value: 'value' }
@@ -102,6 +118,14 @@ describe('delete', () => {
102118
const state = new StateStore(true)
103119
await global.expectToThrowBadArg(state.delete.bind(state, 123), ['string', 'key'], { key: 123 })
104120
})
121+
// eslint-disable-next-line jest/expect-expect
122+
test('bad key characters', async () => {
123+
const state = new StateStore(true)
124+
await global.expectToThrowBadArg(state.put.bind(state, '?test', 'value', {}), ['Key', 'cannot', 'contain'], { key: '?test', value: 'value', options: {} })
125+
await global.expectToThrowBadArg(state.put.bind(state, 't#est', 'value', {}), ['Key', 'cannot', 'contain'], { key: 't#est', value: 'value', options: {} })
126+
await global.expectToThrowBadArg(state.put.bind(state, 't\\est', 'value', {}), ['Key', 'cannot', 'contain'], { key: 't\\est', value: 'value', options: {} })
127+
await global.expectToThrowBadArg(state.put.bind(state, 'test/', 'value', {}), ['Key', 'cannot', 'contain'], { key: 'test/', value: 'value', options: {} })
128+
})
105129
test('calls _delete (part of interface)', async () => {
106130
const state = new StateStore(true)
107131
state._delete = jest.fn()

0 commit comments

Comments
 (0)