Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
30 changes: 30 additions & 0 deletions docs/secrets.md
Original file line number Diff line number Diff line change
@@ -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`
30 changes: 30 additions & 0 deletions lib/secret.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/* eslint-disable max-classes-per-file */
const { deepClone } = require('./utils');

/** @param {string} secret */
class Secret {
constructor(secret) {
Expand All @@ -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;
2 changes: 1 addition & 1 deletion lib/step.js
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down
14 changes: 13 additions & 1 deletion test/data/rest/db.json
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
{"posts":[{"id":1,"title":"json-server","author":"davert"}],"user":{"name":"davert"}}
{
"posts": [
{
"id": 1,
"title": "json-server",
"author": "davert"
}
],
"user": {
"name": "john",
"password": "123456"
}
}
9 changes: 9 additions & 0 deletions test/rest/REST_test.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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');
Expand Down