Skip to content

Commit 81f2280

Browse files
authored
implemented secret for object (#3355)
1 parent a7749df commit 81f2280

File tree

6 files changed

+85
-2
lines changed

6 files changed

+85
-2
lines changed

docs/basics.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ To fill in sensitive data use the `secret` function, it won't expose actual valu
212212
I.fillField('password', secret('123456'));
213213
```
214214

215+
> ℹ️ Learn more about [masking secret](/secrets/) output
216+
215217
### Assertions
216218

217219
In order to verify the expected behavior of a web application, its content should be checked.

docs/secrets.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Secrets
2+
3+
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.
4+
5+
Wrap data in `secret` function to mask sensitive values in output and logs.
6+
7+
For basic string `secret` just wrap a value into a string:
8+
9+
```js
10+
I.fillField('password', secret('123456'));
11+
```
12+
13+
When executed it will be printed like this:
14+
15+
```
16+
I fill field "password" "*****"
17+
```
18+
19+
For an object, which can be a payload to POST request, specify which fields should be masked:
20+
21+
```js
22+
I.sendPostRequest('/login', secret({
23+
name: 'davert',
24+
password: '123456'
25+
}, 'password'))
26+
```
27+
28+
The object created from `secret` is as Proxy to the object passed in. When printed password will be replaced with ****.
29+
30+
> ⚠️ Currently, properties of nested objects can't be masked via `secret`

lib/secret.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
/* eslint-disable max-classes-per-file */
2+
const { deepClone } = require('./utils');
3+
14
/** @param {string} secret */
25
class Secret {
36
constructor(secret) {
@@ -9,13 +12,40 @@ class Secret {
912
return this._secret;
1013
}
1114

15+
getMasked() {
16+
return '*****';
17+
}
18+
1219
/**
1320
* @param {*} secret
1421
* @returns {Secret}
1522
*/
1623
static secret(secret) {
24+
if (typeof secret === 'object') {
25+
const fields = Array.from(arguments);
26+
fields.shift();
27+
return secretObject(secret, fields);
28+
}
1729
return new Secret(secret);
1830
}
1931
}
2032

33+
function secretObject(obj, fieldsToHide = []) {
34+
const handler = {
35+
get(obj, prop) {
36+
if (prop === 'toString') {
37+
return function () {
38+
const maskedObject = deepClone(obj);
39+
fieldsToHide.forEach(f => maskedObject[f] = '****');
40+
return JSON.stringify(maskedObject);
41+
};
42+
}
43+
44+
return obj[prop];
45+
},
46+
};
47+
48+
return new Proxy(obj, handler);
49+
}
50+
2151
module.exports = Secret;

lib/step.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class Step {
168168
} else if (typeof arg === 'undefined') {
169169
return `${arg}`;
170170
} else if (arg instanceof Secret) {
171-
return '*****';
171+
return arg.getMasked();
172172
} else if (arg.toString && arg.toString() !== '[object Object]') {
173173
return arg.toString();
174174
} else if (typeof arg === 'object') {

test/data/rest/db.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,13 @@
1-
{"posts":[{"id":1,"title":"json-server","author":"davert"}],"user":{"name":"davert"}}
1+
{
2+
"posts": [
3+
{
4+
"id": 1,
5+
"title": "json-server",
6+
"author": "davert"
7+
}
8+
],
9+
"user": {
10+
"name": "john",
11+
"password": "123456"
12+
}
13+
}

test/rest/REST_test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const path = require('path');
22
const fs = require('fs');
33
const FormData = require('form-data');
4+
const secret = require('../../lib/secret').secret;
45

56
const TestHelper = require('../support/TestHelper');
67
const REST = require('../../lib/helper/REST');
@@ -68,6 +69,14 @@ describe('REST', () => {
6869
response.data.name.should.eql('john');
6970
});
7071

72+
it('should send POST requests with secret', async () => {
73+
const secretData = secret({ name: 'john', password: '123456' }, 'password');
74+
const response = await I.sendPostRequest('/user', secretData);
75+
response.data.name.should.eql('john');
76+
response.data.password.should.eql('123456');
77+
secretData.toString().should.include('"password":"****"');
78+
});
79+
7180
it('should send PUT requests: payload format = json', async () => {
7281
const putResponse = await I.sendPutRequest('/posts/1', { author: 'john' });
7382
putResponse.data.author.should.eql('john');

0 commit comments

Comments
 (0)