diff --git a/keyserver/src/utils/validation-utils.js b/keyserver/src/utils/validation-utils.js --- a/keyserver/src/utils/validation-utils.js +++ b/keyserver/src/utils/validation-utils.js @@ -1,8 +1,6 @@ // @flow -import _mapKeys from 'lodash/fp/mapKeys.js'; -import _mapValues from 'lodash/fp/mapValues.js'; -import type { TType, TInterface } from 'tcomb'; +import type { TType } from 'tcomb'; import type { PolicyType } from 'lib/facts/policies.js'; import { @@ -17,16 +15,17 @@ tPlatform, tPlatformDetails, assertWithValidator, - tID, + convertToNewIDSchema, + keyserverPrefixID, + convertClientIDsToServerIDs, + convertObject, + convertServerIDsToClientIDs, } from 'lib/utils/validation-utils.js'; import { fetchNotAcknowledgedPolicies } from '../fetchers/policy-acknowledgment-fetchers.js'; import { verifyClientSupported } from '../session/version.js'; import type { Viewer } from '../session/viewer.js'; -const convertToNewIDSchema = false; -const keyserverPrefixID = '256'; - async function validateInput( viewer: Viewer, inputValidator: TType, @@ -42,11 +41,15 @@ !isWebPlatform(viewer.platformDetails?.platform) && convertToNewIDSchema ) { - return convertClientIDsToServerIDs( - keyserverPrefixID, - inputValidator, - convertedInput, - ); + try { + return convertClientIDsToServerIDs( + keyserverPrefixID, + inputValidator, + convertedInput, + ); + } catch (err) { + throw new ServerError(err.message); + } } return convertedInput; @@ -80,39 +83,6 @@ return data; } -function convertServerIDsToClientIDs( - serverPrefixID: string, - outputValidator: TType, - data: T, -): T { - const conversionFunction = id => { - if (id.indexOf('|') !== -1) { - console.warn(`Server id '${id}' already has a prefix`); - return id; - } - return `${serverPrefixID}|${id}`; - }; - - return convertObject(outputValidator, data, [tID], conversionFunction); -} - -function convertClientIDsToServerIDs( - serverPrefixID: string, - outputValidator: TType, - data: T, -): T { - const prefix = serverPrefixID + '|'; - const conversionFunction = id => { - if (id.startsWith(prefix)) { - return id.substr(prefix.length); - } - - throw new ServerError('invalid_client_id_prefix'); - }; - - return convertObject(outputValidator, data, [tID], conversionFunction); -} - function checkInputValidator(inputValidator: TType, input: mixed): T { if (inputValidator.is(input)) { return assertWithValidator(input, inputValidator); @@ -227,87 +197,6 @@ return null; } -function convertObject( - validator: TType, - input: I, - typesToConvert: $ReadOnlyArray>, - conversionFunction: T => T, -): I { - if (input === null || input === undefined) { - return input; - } - - // While they should be the same runtime object, - // `TValidator` is `TType` and `validator` is `TType`. - // Having them have different types allows us to use `assertWithValidator` - // to change `input` flow type - const TValidator = typesToConvert[typesToConvert.indexOf(validator)]; - if (TValidator && TValidator.is(input)) { - const TInput = assertWithValidator(input, TValidator); - const converted = conversionFunction(TInput); - return assertWithValidator(converted, validator); - } - - if (validator.meta.kind === 'maybe' || validator.meta.kind === 'subtype') { - return convertObject( - validator.meta.type, - input, - typesToConvert, - conversionFunction, - ); - } - if (validator.meta.kind === 'interface' && typeof input === 'object') { - const recastValidator: TInterface = (validator: any); - const result = {}; - for (const key in input) { - const innerValidator = recastValidator.meta.props[key]; - result[key] = convertObject( - innerValidator, - input[key], - typesToConvert, - conversionFunction, - ); - } - return assertWithValidator(result, recastValidator); - } - if (validator.meta.kind === 'union') { - for (const innerValidator of validator.meta.types) { - if (innerValidator.is(input)) { - return convertObject( - innerValidator, - input, - typesToConvert, - conversionFunction, - ); - } - } - return input; - } - if (validator.meta.kind === 'list' && Array.isArray(input)) { - const innerValidator = validator.meta.type; - return (input.map(value => - convertObject(innerValidator, value, typesToConvert, conversionFunction), - ): any); - } - if (validator.meta.kind === 'dict' && typeof input === 'object') { - const domainValidator = validator.meta.domain; - const codomainValidator = validator.meta.codomain; - if (typesToConvert.includes(domainValidator)) { - input = _mapKeys(key => conversionFunction(key))(input); - } - return _mapValues(value => - convertObject( - codomainValidator, - value, - typesToConvert, - conversionFunction, - ), - )(input); - } - - return input; -} - async function policiesValidator( viewer: Viewer, policies: $ReadOnlyArray, @@ -339,8 +228,5 @@ sanitizeInput, findFirstInputMatchingValidator, checkClientSupported, - convertServerIDsToClientIDs, - convertClientIDsToServerIDs, - convertObject, policiesValidator, }; diff --git a/keyserver/src/utils/validation-utils.test.js b/keyserver/src/utils/validation-utils.test.js --- a/keyserver/src/utils/validation-utils.test.js +++ b/keyserver/src/utils/validation-utils.test.js @@ -2,14 +2,9 @@ import t from 'tcomb'; -import { tPassword, tShape, tID } from 'lib/utils/validation-utils.js'; +import { tPassword, tShape } from 'lib/utils/validation-utils.js'; -import { - convertServerIDsToClientIDs, - sanitizeInput, - redactedString, - convertClientIDsToServerIDs, -} from './validation-utils.js'; +import { sanitizeInput, redactedString } from './validation-utils.js'; describe('sanitization', () => { it('should redact a string', () => { @@ -77,46 +72,3 @@ expect(sanitizeInput(validator, object)).toStrictEqual(redacted); }); }); - -describe('id conversion', () => { - it('should convert string id', () => { - const validator = tShape({ id: tID }); - const serverData = { id: '1' }; - const clientData = { id: '0|1' }; - - expect( - convertServerIDsToClientIDs('0', validator, serverData), - ).toStrictEqual(clientData); - expect( - convertClientIDsToServerIDs('0', validator, clientData), - ).toStrictEqual(serverData); - }); - - it('should convert a complex type', () => { - const validator = tShape({ ids: t.dict(tID, t.list(tID)) }); - const serverData = { ids: { '1': ['11', '12'], '2': [], '3': ['13'] } }; - const clientData = { - ids: { '0|1': ['0|11', '0|12'], '0|2': [], '0|3': ['0|13'] }, - }; - - expect( - convertServerIDsToClientIDs('0', validator, serverData), - ).toStrictEqual(clientData); - expect( - convertClientIDsToServerIDs('0', validator, clientData), - ).toStrictEqual(serverData); - }); - - it('should convert a refinement', () => { - const validator = t.refinement(tID, () => true); - const serverData = '1'; - const clientData = '0|1'; - - expect( - convertServerIDsToClientIDs('0', validator, serverData), - ).toStrictEqual(clientData); - expect( - convertClientIDsToServerIDs('0', validator, clientData), - ).toStrictEqual(serverData); - }); -}); diff --git a/lib/utils/validation-utils.js b/lib/utils/validation-utils.js --- a/lib/utils/validation-utils.js +++ b/lib/utils/validation-utils.js @@ -1,6 +1,8 @@ // @flow import invariant from 'invariant'; +import _mapKeys from 'lodash/fp/mapKeys.js'; +import _mapValues from 'lodash/fp/mapValues.js'; import t from 'tcomb'; import type { TStructProps, @@ -101,6 +103,123 @@ return (data: any); } +const convertToNewIDSchema = false; +const keyserverPrefixID = '256'; + +function convertServerIDsToClientIDs( + serverPrefixID: string, + outputValidator: TType, + data: T, +): T { + const conversionFunction = id => { + if (id.indexOf('|') !== -1) { + console.warn(`Server id '${id}' already has a prefix`); + return id; + } + return `${serverPrefixID}|${id}`; + }; + + return convertObject(outputValidator, data, [tID], conversionFunction); +} + +function convertClientIDsToServerIDs( + serverPrefixID: string, + outputValidator: TType, + data: T, +): T { + const prefix = serverPrefixID + '|'; + const conversionFunction = id => { + if (id.startsWith(prefix)) { + return id.substr(prefix.length); + } + + throw new Error('invalid_client_id_prefix'); + }; + + return convertObject(outputValidator, data, [tID], conversionFunction); +} + +function convertObject( + validator: TType, + input: I, + typesToConvert: $ReadOnlyArray>, + conversionFunction: T => T, +): I { + if (input === null || input === undefined) { + return input; + } + + // While they should be the same runtime object, + // `TValidator` is `TType` and `validator` is `TType`. + // Having them have different types allows us to use `assertWithValidator` + // to change `input` flow type + const TValidator = typesToConvert[typesToConvert.indexOf(validator)]; + if (TValidator && TValidator.is(input)) { + const TInput = assertWithValidator(input, TValidator); + const converted = conversionFunction(TInput); + return assertWithValidator(converted, validator); + } + + if (validator.meta.kind === 'maybe' || validator.meta.kind === 'subtype') { + return convertObject( + validator.meta.type, + input, + typesToConvert, + conversionFunction, + ); + } + if (validator.meta.kind === 'interface' && typeof input === 'object') { + const recastValidator: TInterface = (validator: any); + const result = {}; + for (const key in input) { + const innerValidator = recastValidator.meta.props[key]; + result[key] = convertObject( + innerValidator, + input[key], + typesToConvert, + conversionFunction, + ); + } + return assertWithValidator(result, recastValidator); + } + if (validator.meta.kind === 'union') { + for (const innerValidator of validator.meta.types) { + if (innerValidator.is(input)) { + return convertObject( + innerValidator, + input, + typesToConvert, + conversionFunction, + ); + } + } + return input; + } + if (validator.meta.kind === 'list' && Array.isArray(input)) { + const innerValidator = validator.meta.type; + return (input.map(value => + convertObject(innerValidator, value, typesToConvert, conversionFunction), + ): any); + } + if (validator.meta.kind === 'dict' && typeof input === 'object') { + const domainValidator = validator.meta.domain; + const codomainValidator = validator.meta.codomain; + if (typesToConvert.includes(domainValidator)) { + input = _mapKeys(key => conversionFunction(key))(input); + } + return _mapValues(value => + convertObject( + codomainValidator, + value, + typesToConvert, + conversionFunction, + ), + )(input); + } + + return input; +} + export { tBool, tString, @@ -123,4 +242,9 @@ tMediaMessageVideo, tMediaMessageMedia, assertWithValidator, + convertToNewIDSchema, + keyserverPrefixID, + convertClientIDsToServerIDs, + convertServerIDsToClientIDs, + convertObject, }; diff --git a/lib/utils/validation-utils.test.js b/lib/utils/validation-utils.test.js --- a/lib/utils/validation-utils.test.js +++ b/lib/utils/validation-utils.test.js @@ -1,9 +1,15 @@ // @flow +import t from 'tcomb'; + import { tMediaMessagePhoto, tMediaMessageVideo, tNumEnum, + tShape, + tID, + convertServerIDsToClientIDs, + convertClientIDsToServerIDs, } from './validation-utils.js'; import { threadTypes } from '../types/thread-types-enum.js'; import { values } from '../utils/objects.js'; @@ -140,3 +146,46 @@ }); }); }); + +describe('id conversion', () => { + it('should convert string id', () => { + const validator = tShape({ id: tID }); + const serverData = { id: '1' }; + const clientData = { id: '0|1' }; + + expect( + convertServerIDsToClientIDs('0', validator, serverData), + ).toStrictEqual(clientData); + expect( + convertClientIDsToServerIDs('0', validator, clientData), + ).toStrictEqual(serverData); + }); + + it('should convert a complex type', () => { + const validator = tShape({ ids: t.dict(tID, t.list(tID)) }); + const serverData = { ids: { '1': ['11', '12'], '2': [], '3': ['13'] } }; + const clientData = { + ids: { '0|1': ['0|11', '0|12'], '0|2': [], '0|3': ['0|13'] }, + }; + + expect( + convertServerIDsToClientIDs('0', validator, serverData), + ).toStrictEqual(clientData); + expect( + convertClientIDsToServerIDs('0', validator, clientData), + ).toStrictEqual(serverData); + }); + + it('should convert a refinement', () => { + const validator = t.refinement(tID, () => true); + const serverData = '1'; + const clientData = '0|1'; + + expect( + convertServerIDsToClientIDs('0', validator, serverData), + ).toStrictEqual(clientData); + expect( + convertClientIDsToServerIDs('0', validator, clientData), + ).toStrictEqual(serverData); + }); +});