diff --git a/src/vault-api/Controller.ts b/src/vault-api/Controller.ts index b3da1d4..41668a9 100644 --- a/src/vault-api/Controller.ts +++ b/src/vault-api/Controller.ts @@ -12,6 +12,7 @@ import { IDeleteInput, IDeleteOptions, IGetInput, + IGetOptions, IInsertOptions, IUpdateInput, IUpdateOptions, @@ -151,12 +152,12 @@ class Controller { }); } - get(getInput: IGetInput) { + get(getInput: IGetInput,options:IGetOptions={}) { return new Promise((resolve, reject) => { try { validateInitConfig(this.#client.config) printLog(logs.infoLogs.VALIDATE_GET_INPUT, MessageType.LOG); - validateGetInput(getInput); + validateGetInput(getInput,options); printLog(parameterizedString(logs.infoLogs.EMIT_REQUEST, TYPES.GET), MessageType.LOG); @@ -164,7 +165,8 @@ class Controller { fetchRecordsBySkyflowID( getInput.records, this.#client, - res + res, + options ).then( (resolvedResult) => { printLog(logs.infoLogs.GET_BY_SKYFLOWID_RESOLVED, MessageType.LOG); diff --git a/src/vault-api/Skyflow.ts b/src/vault-api/Skyflow.ts index 10ab44c..2fef186 100644 --- a/src/vault-api/Skyflow.ts +++ b/src/vault-api/Skyflow.ts @@ -19,6 +19,7 @@ import { IUpdateInput, IUpdateOptions, IGetInput, + IGetOptions, IDeleteInput, IDeleteOptions, } from './utils/common'; @@ -77,10 +78,10 @@ class Skyflow { return this.#Controller.getById(getByIdInput); } - get(getInput: IGetInput) { + get(getInput: IGetInput,options?:IGetOptions) { printLog(logs.infoLogs.GET_CALL_TRIGGERED, MessageType.LOG); - return this.#Controller.get(getInput); + return this.#Controller.get(getInput,options); } invokeConnection(config: IConnectionConfig) { diff --git a/src/vault-api/core/Reveal.ts b/src/vault-api/core/Reveal.ts index 86a40b2..dba6b72 100644 --- a/src/vault-api/core/Reveal.ts +++ b/src/vault-api/core/Reveal.ts @@ -4,7 +4,7 @@ import Client from '../client'; import SkyflowError from '../libs/SkyflowError'; import { - ISkyflowIdRecord, IRevealRecord, IRevealResponseType, IUpdateRecord, IUpdateOptions,RedactionType + ISkyflowIdRecord, IRevealRecord, IRevealResponseType, IUpdateRecord, IUpdateOptions,RedactionType, IGetOptions } from '../utils/common'; import 'core-js/modules/es.promise.all-settled'; interface IApiSuccessResponse { @@ -56,6 +56,7 @@ const getSkyflowIdRecordsFromVault = ( skyflowIdRecord: ISkyflowIdRecord, client: Client, authToken: string, + options?: IGetOptions ) => { let paramList: string = ''; @@ -64,11 +65,26 @@ const getSkyflowIdRecordsFromVault = ( paramList += `skyflow_ids=${skyflowId}&`; }); - skyflowIdRecord.columnValues?.forEach((column) => { - paramList += `column_name=${skyflowIdRecord.columnName}&column_values=${column}&`; - }) + if(options && Object.prototype.hasOwnProperty.call(options, 'encodeURI') && options?.encodeURI === true) { + skyflowIdRecord.columnValues?.forEach((column) => { + var encode_column_value = encodeURIComponent(column) + paramList += `column_name=${skyflowIdRecord.columnName}&column_values=${encode_column_value}&`; + }); + } else { + skyflowIdRecord.columnValues?.forEach((column) => { + paramList += `column_name=${skyflowIdRecord.columnName}&column_values=${column}&`; + }); + } - const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}?${paramList}redaction=${skyflowIdRecord.redaction}`; + if(options && Object.prototype.hasOwnProperty.call(options,'tokens')){ + paramList += `tokenization=${options.tokens}&` + } + + if(skyflowIdRecord?.redaction){ + paramList += `redaction=${skyflowIdRecord.redaction}` + } + + const vaultEndPointurl: string = `${client.config.vaultURL}/v1/vaults/${client.config.vaultID}/${skyflowIdRecord.table}?${paramList}`; return client.request({ requestMethod: 'GET', @@ -158,12 +174,13 @@ export const fetchRecordsByTokenId = ( export const fetchRecordsBySkyflowID = async ( skyflowIdRecords: ISkyflowIdRecord[], client: Client, - authToken: string + authToken: string, + options?: IGetOptions ) => new Promise((rootResolve, rootReject) => { let vaultResponseSet: Promise[]; vaultResponseSet = skyflowIdRecords.map( (skyflowIdRecord) => new Promise((resolve, reject) => { - getSkyflowIdRecordsFromVault(skyflowIdRecord, client, authToken as string) + getSkyflowIdRecordsFromVault(skyflowIdRecord, client, authToken as string,options) .then( (resolvedResult: any) => { const response: any[] = []; diff --git a/src/vault-api/utils/common/index.ts b/src/vault-api/utils/common/index.ts index ffcda46..c5cf834 100644 --- a/src/vault-api/utils/common/index.ts +++ b/src/vault-api/utils/common/index.ts @@ -56,7 +56,7 @@ export interface IDetokenizeInput { export interface ISkyflowIdRecord { ids?: string[]; - redaction: RedactionType; + redaction?: RedactionType; table: string; columnName?: string; columnValues?: string[]; @@ -128,6 +128,10 @@ export interface IUpdateOptions{ tokens: boolean } +export interface IGetOptions{ + tokens?: boolean + encodeURI?: boolean +} export interface IDeleteRecord { id: string; table: string; @@ -141,4 +145,4 @@ export interface IDeleteOptions { } -export const SDK_METRICS_HEADER_KEY = "sky-metadata"; \ No newline at end of file +export const SDK_METRICS_HEADER_KEY = "sky-metadata"; diff --git a/src/vault-api/utils/constants.ts b/src/vault-api/utils/constants.ts index eea05a2..d6f3a13 100644 --- a/src/vault-api/utils/constants.ts +++ b/src/vault-api/utils/constants.ts @@ -127,6 +127,22 @@ const SKYFLOW_ERROR_CODE = { code: 400, description: logs.errorLogs.INVALID_GET_BY_ID_INPUT, }, + INVALID_TOKENS_IN_GET: { + code: 400, + description: logs.errorLogs.INVALID_TOKENS_IN_GET, + }, + TOKENS_GET_COLUMN_NOT_SUPPORTED: { + code: 400, + description: logs.errorLogs.TOKENS_GET_COLUMN_NOT_SUPPORTED, + }, + REDACTION_WITH_TOKENS_NOT_SUPPORTED: { + code: 400, + description: logs.errorLogs.REDACTION_WITH_TOKENS_NOT_SUPPORTED, + }, + INVALID_ENCODE_URI_IN_GET: { + code: 400, + description: logs.errorLogs.INVALID_ENCODE_URI_IN_GET, + }, MISSING_ID_IN_DELETE: { code: 400, description: logs.errorLogs.MISSING_ID_IN_DELETE, @@ -149,13 +165,12 @@ const SKYFLOW_ERROR_CODE = { }, INVLAID_DELETE_RECORDS_INPUT: { code: 400, - description: logs.errorLogs.INVLAID_DELETE_RECORDS_INPUT + description: logs.errorLogs.INVLAID_DELETE_RECORDS_INPUT, }, DETOKENIZE_INVALID_REDACTION_TYPE:{ code: 400, description: logs.errorLogs.DETOKENIZE_INVALID_REDACTION_TYPE, } - }; export default SKYFLOW_ERROR_CODE; diff --git a/src/vault-api/utils/logs.ts b/src/vault-api/utils/logs.ts index 34b25e8..81f16a5 100644 --- a/src/vault-api/utils/logs.ts +++ b/src/vault-api/utils/logs.ts @@ -49,6 +49,7 @@ const logs = { INVALID_VAULTURL_IN_INIT: 'Interface: init - Invalid client credentials. Expecting https://XYZ for vaultURL', GET_BEARER_TOKEN_IS_REQUIRED: 'Interface: init - Invalid client credentials. getBearerToken is required.', BEARER_TOKEN_REJECTED: 'Interface: init - GetBearerToken promise got rejected.', + INVALID_ENCODE_URI_IN_GET: 'Interface: get method - Invalid encodeURI type in get.', INVALID_BEARER_TOKEN: 'Bearer token is invalid or expired.', INVALID_VAULT_ID: 'Vault Id is invalid or cannot be found.', EMPTY_VAULT_ID: 'VaultID is empty.', @@ -128,6 +129,9 @@ const logs = { INVALID_UPDATE_INPUT: 'Interface: update method - Invalid argument , object with records key is required.', INVALID_RECORDS_UPDATE_INPUT: 'Interface: update method - Invalid records type, records should be an array of objects.', INVALID_GET_BY_ID_INPUT: 'Interface: getById method - columnName or columnValues cannot be passed, use get method instead.', + INVALID_TOKENS_IN_GET: 'Interface: get method - Invalid tokens in options. tokens of type boolean is required.', + TOKENS_GET_COLUMN_NOT_SUPPORTED: 'Interface: get method - column_name or column_values cannot be used with tokens in options.', + REDACTION_WITH_TOKENS_NOT_SUPPORTED: 'Interface: get method - redaction cannot be used when tokens are true in options.', INVALID_DELETE_INPUT: 'Interface: delete method - Invalid argument , object with records key is required.', INVLAID_DELETE_RECORDS_INPUT: 'Interface: delete method - Invalid records type, records should be an array of objects.', MISSING_ID_IN_DELETE: 'Interface: delete method - id key is required in records object at index %s1', diff --git a/src/vault-api/utils/validators/index.ts b/src/vault-api/utils/validators/index.ts index 3b2591b..6b85bb6 100644 --- a/src/vault-api/utils/validators/index.ts +++ b/src/vault-api/utils/validators/index.ts @@ -4,7 +4,7 @@ import SkyflowError from '../../libs/SkyflowError'; import { ISkyflow } from '../../Skyflow'; import { - IInsertRecordInput, IDetokenizeInput, RedactionType, IGetByIdInput, IConnectionConfig, RequestMethod, IUpdateInput, IGetInput, IDeleteInput, IDeleteOptions, + IInsertRecordInput, IDetokenizeInput, RedactionType, IGetByIdInput, IConnectionConfig, RequestMethod, IUpdateInput, IGetInput, IGetOptions, IDeleteInput, IDeleteOptions, } from '../common'; import SKYFLOW_ERROR_CODE from '../constants'; @@ -93,7 +93,7 @@ export const validateGetByIdInput = (getByIdInput: IGetByIdInput) => { }); }; -export const validateGetInput = (getInput: IGetInput) => { +export const validateGetInput = (getInput: IGetInput,options?:IGetOptions) => { if (!Object.prototype.hasOwnProperty.call(getInput, 'records')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_RECORDS); } @@ -102,6 +102,14 @@ export const validateGetInput = (getInput: IGetInput) => { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_RECORDS); } + if (Object.prototype.hasOwnProperty.call(options, 'tokens') && (typeof options?.tokens !== 'boolean')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET); + } + + if (Object.prototype.hasOwnProperty.call(options, 'encodeURI') && (typeof options?.encodeURI !== 'boolean')) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET); + } + records.forEach((record) => { if (Object.keys(record).length === 0) { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_RECORDS); @@ -114,11 +122,14 @@ export const validateGetInput = (getInput: IGetInput) => { }); const recordRedaction = record.redaction; - if (!recordRedaction) throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_REDACTION); - if (!Object.values(RedactionType).includes(recordRedaction)) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REDACTION_TYPE); + if(!(options && Object.prototype.hasOwnProperty.call(options, 'tokens') && options?.tokens === true)){ + if (!recordRedaction) throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_REDACTION); + if (!Object.values(RedactionType).includes(recordRedaction)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_REDACTION_TYPE); + } } + const recordTable = record.table; if (!Object.prototype.hasOwnProperty.call(record, 'table')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TABLE); } @@ -143,6 +154,13 @@ export const validateGetInput = (getInput: IGetInput) => { if (eachColumnValue === '') throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_COLUMN_VALUE); }); } + if(options && Object.prototype.hasOwnProperty.call(options, 'tokens') && options?.tokens === true){ + if(columnName || columnValues) + throw new SkyflowError(SKYFLOW_ERROR_CODE.TOKENS_GET_COLUMN_NOT_SUPPORTED) + + if(recordRedaction) + throw new SkyflowError(SKYFLOW_ERROR_CODE.REDACTION_WITH_TOKENS_NOT_SUPPORTED) + } }); }; diff --git a/test/vault-api/Skyflow.test.js b/test/vault-api/Skyflow.test.js index c45928e..b7b5b6c 100644 --- a/test/vault-api/Skyflow.test.js +++ b/test/vault-api/Skyflow.test.js @@ -524,6 +524,13 @@ const getByIdInput = { }], }; +const getByIdInputWithoutRedaction = { + records: [{ + ids: ['id'], + table: 'cards', + }], +}; + const getByIdInputMissingIds = { records: [{ table: 'cards', @@ -1789,6 +1796,246 @@ describe('skyflow detokenize with redaction', () => { }); }); +describe('get method with options', () => { + let skyflow; + beforeEach(() => { + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: jest.fn(), + }); + }); + + + test('get method should send request url with tokenization true when tokens are true', (done) => { + + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes) + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {} + } + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdInputWithoutRedaction, { tokens: true }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=true')).toBe(true); + done(); + }).catch((er) => { + done(er) + }); + + + }); + + + test('get method should send request url with tokenization false and redaction when tokens are false', (done) => { + + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes) + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {} + } + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdInput, { tokens: false }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=false')).toBe(true); + expect((reqArg.url).includes('redaction=PLAIN_TEXT')).toBe(true); + done(); + }).catch((er) => { + done(er) + }); + + + }); + + test('get method should not send redaction along with url if redaction is not passed.', (done) => { + + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes) + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {} + } + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdInputWithoutRedaction, { tokens: true }); + response.then((res) => { + console.log(reqArg.url); + expect((reqArg.url).includes('redaction=PLAIN_TEXT')).toBe(false); + done(); + }).catch((er) => { + done(er) + }); + + + }); + + test('get method should throw error when tokens options is invalid type value', (done) => { + + skyflow.get(getByIdInput, { tokens: '12343' }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options is invalid type value', (done) => { + + skyflow.get(getByIdInput, { tokens: "true" }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options is null', (done) => { + skyflow.get(getByIdInput, { tokens: null }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options is undefined', (done) => { + skyflow.get(getByIdInput, { tokens: undefined }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_TOKENS_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when tokens options along with column values', (done) => { + skyflow.get(getByIdWithValidUniqColumnOptions, { tokens: true }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.TOKENS_GET_COLUMN_NOT_SUPPORTED.description); + done(); + }) + }); + + test('get method should throw error when tokens options used along with redaction', (done) => { + skyflow.get(getByIdInput, { tokens: true }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.REDACTION_WITH_TOKENS_NOT_SUPPORTED.description); + done(); + }) + }); + + test('get method should encode column values when encodeURI option is true', (done) => { + let reqArg; + const clientReq = jest.fn((arg) => { + reqArg = arg; + return Promise.resolve(getByIdRes); + }); + + const mockClient = { + config: skyflowConfig, + request: clientReq, + metadata: {}, + }; + + clientModule.mockImplementation(() => { return mockClient }); + skyflow = Skyflow.init({ + vaultID: '', + vaultURL: 'https://www.vaulturl.com', + getBearerToken: () => { + return new Promise((resolve, _) => { + resolve("token") + }) + } + }); + + const response = skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: true }); + response.then((res) => { + expect((reqArg.url).includes('tokenization=false')).toBe(false); + done(); + }).catch((er) => { + done(er) + }); + }); + + test('get method should throw error when encodeURI options is invalid type value', (done) => { + + skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: '12343' }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when encodeURI options is null', (done) => { + skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: null }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET.description); + done(); + }) + }); + + test('get method should throw error when encodeURI options is undefined', (done) => { + skyflow.get(getByIdWithValidUniqColumnOptions, { encodeURI: undefined }).then((res) => { + done('Should throw error.') + }).catch((err) => { + expect(err.errors[0].description).toEqual(SKYFLOW_ERROR_CODE.INVALID_ENCODE_URI_IN_GET.description); + done(); + }) + }); +}); + const deleteInput = { records: [ {