From d8feba44daccdc7ca28f92c40e5fd8c63fcfcbc4 Mon Sep 17 00:00:00 2001 From: Davert Date: Thu, 7 Jul 2022 04:23:39 +0300 Subject: [PATCH] implemented secret for object --- docs/basics.md | 2 ++ docs/secrets.md | 30 ++++++++++++++++++++++++++++++ lib/secret.js | 30 ++++++++++++++++++++++++++++++ lib/step.js | 2 +- test/data/rest/db.json | 14 +++++++++++++- test/rest/REST_test.js | 9 +++++++++ 6 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 docs/secrets.md diff --git a/docs/basics.md b/docs/basics.md index 9fca12d4e..3d8757f88 100644 --- a/docs/basics.md +++ b/docs/basics.md @@ -212,6 +212,8 @@ To fill in sensitive data use the `secret` function, it won't expose actual valu I.fillField('password', secret('123456')); ``` +> ℹ️ Learn more about [masking secret](/secrets/) output + ### Assertions In order to verify the expected behavior of a web application, its content should be checked. diff --git a/docs/secrets.md b/docs/secrets.md new file mode 100644 index 000000000..3efaecec1 --- /dev/null +++ b/docs/secrets.md @@ -0,0 +1,30 @@ +# Secrets + +It is possible to mask out sensitive data when passing it to steps. This is important when filling password fields, or sending secure keys to API endpoint. + +Wrap data in `secret` function to mask sensitive values in output and logs. + +For basic string `secret` just wrap a value into a string: + +```js +I.fillField('password', secret('123456')); +``` + +When executed it will be printed like this: + +``` +I fill field "password" "*****" +``` + +For an object, which can be a payload to POST request, specify which fields should be masked: + +```js +I.sendPostRequest('/login', secret({ + name: 'davert', + password: '123456' +}, 'password')) +``` + +The object created from `secret` is as Proxy to the object passed in. When printed password will be replaced with ****. + +> ⚠️ Currently, properties of nested objects can't be masked via `secret` \ No newline at end of file diff --git a/lib/secret.js b/lib/secret.js index 4567df3e5..9613816a8 100644 --- a/lib/secret.js +++ b/lib/secret.js @@ -1,3 +1,6 @@ +/* eslint-disable max-classes-per-file */ +const { deepClone } = require('./utils'); + /** @param {string} secret */ class Secret { constructor(secret) { @@ -9,13 +12,40 @@ class Secret { return this._secret; } + getMasked() { + return '*****'; + } + /** * @param {*} secret * @returns {Secret} */ static secret(secret) { + if (typeof secret === 'object') { + const fields = Array.from(arguments); + fields.shift(); + return secretObject(secret, fields); + } return new Secret(secret); } } +function secretObject(obj, fieldsToHide = []) { + const handler = { + get(obj, prop) { + if (prop === 'toString') { + return function () { + const maskedObject = deepClone(obj); + fieldsToHide.forEach(f => maskedObject[f] = '****'); + return JSON.stringify(maskedObject); + }; + } + + return obj[prop]; + }, + }; + + return new Proxy(obj, handler); +} + module.exports = Secret; diff --git a/lib/step.js b/lib/step.js index 490a00e35..bec8cabb2 100644 --- a/lib/step.js +++ b/lib/step.js @@ -168,7 +168,7 @@ class Step { } else if (typeof arg === 'undefined') { return `${arg}`; } else if (arg instanceof Secret) { - return '*****'; + return arg.getMasked(); } else if (arg.toString && arg.toString() !== '[object Object]') { return arg.toString(); } else if (typeof arg === 'object') { diff --git a/test/data/rest/db.json b/test/data/rest/db.json index 4930c5ac1..ad6f29c4d 100644 --- a/test/data/rest/db.json +++ b/test/data/rest/db.json @@ -1 +1,13 @@ -{"posts":[{"id":1,"title":"json-server","author":"davert"}],"user":{"name":"davert"}} \ No newline at end of file +{ + "posts": [ + { + "id": 1, + "title": "json-server", + "author": "davert" + } + ], + "user": { + "name": "john", + "password": "123456" + } +} \ No newline at end of file diff --git a/test/rest/REST_test.js b/test/rest/REST_test.js index 0ceca637b..c3724a382 100644 --- a/test/rest/REST_test.js +++ b/test/rest/REST_test.js @@ -1,6 +1,7 @@ const path = require('path'); const fs = require('fs'); const FormData = require('form-data'); +const secret = require('../../lib/secret').secret; const TestHelper = require('../support/TestHelper'); const REST = require('../../lib/helper/REST'); @@ -68,6 +69,14 @@ describe('REST', () => { response.data.name.should.eql('john'); }); + it('should send POST requests with secret', async () => { + const secretData = secret({ name: 'john', password: '123456' }, 'password'); + const response = await I.sendPostRequest('/user', secretData); + response.data.name.should.eql('john'); + response.data.password.should.eql('123456'); + secretData.toString().should.include('"password":"****"'); + }); + it('should send PUT requests: payload format = json', async () => { const putResponse = await I.sendPutRequest('/posts/1', { author: 'john' }); putResponse.data.author.should.eql('john');