Skip to content

Commit 7775cf7

Browse files
added example
1 parent c3601bc commit 7775cf7

File tree

9 files changed

+224
-61
lines changed

9 files changed

+224
-61
lines changed

index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ const {
5353
eg010admin, eg011admin, eg012admin
5454
} = require("./lib/admin/controllers");
5555

56+
const { eg001connect } = require('./lib/connect/controllers');
57+
5658
const PORT = process.env.PORT || 3000;
5759
const HOST = process.env.HOST || 'localhost';
5860
const max_session_min = 180;
@@ -267,6 +269,9 @@ app.get('/eg001', eg001.getController)
267269
.get('/eg044', eg044.getController)
268270
.post('/eg044', eg044.createController)
269271

272+
app.get('/cneg001', eg001connect.getController)
273+
.post('/cneg001', eg001connect.createController)
274+
270275
function dsLoginCB1(req, res, next) { req.dsAuthCodeGrant.oauth_callback1(req, res, next) }
271276
function dsLoginCB2(req, res, next) { req.dsAuthCodeGrant.oauth_callback2(req, res, next) }
272277

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* @file
3+
* Example 001: Validate webhook message using HMAC
4+
* @author DocuSign
5+
*/
6+
7+
const path = require('path');
8+
const validator = require('validator');
9+
const { formatString, API_TYPES } = require('../../utils.js');
10+
const { getExampleByNumber } = require('../../manifestService');
11+
const dsConfig = require('../../../config/index.js').config;
12+
const { computeHash } = require('../examples/validateWebhookMessage');
13+
14+
const eg001ValidateWebhookMessage = exports;
15+
const exampleNumber = 1;
16+
const eg = `eg00${exampleNumber}`; // This example reference.
17+
const api = API_TYPES.CONNECT;
18+
const mustAuthenticate = "/ds/mustAuthenticate";
19+
20+
/**
21+
* Create the envelope
22+
* @param {object} req Request obj
23+
* @param {object} res Response obj
24+
*/
25+
eg001ValidateWebhookMessage.createController = async (req, res) => {
26+
// Call the worker method
27+
const { body } = req;
28+
const args = {
29+
secret: validator.escape(body.secret),
30+
payload: body.payload,
31+
};
32+
let results = null;
33+
34+
try {
35+
results = computeHash(args);
36+
} catch (error) {
37+
const errorCode = error && error.code;
38+
const errorMessage = error && error.message;
39+
// In production, may want to provide customized error messages and
40+
// remediation advice to the user.
41+
res.render('pages/error', { err: error, errorCode, errorMessage });
42+
}
43+
if (results) {
44+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
45+
res.render('pages/example_done', {
46+
title: example.ExampleName,
47+
message: formatString(example.ResultsPageText, results),
48+
});
49+
}
50+
};
51+
52+
/**
53+
* Form page for this application
54+
*/
55+
eg001ValidateWebhookMessage.getController = (req, res) => {
56+
// Check that the authentication token is ok with a long buffer time.
57+
// If needed, now is the best time to ask the user to authenticate
58+
// since they have not yet entered any information into the form.
59+
const isTokenOK = req.dsAuth.checkToken();
60+
if (!isTokenOK) {
61+
// Save the current operation so it will be resumed after authentication
62+
req.dsAuth.setEg(req, eg);
63+
return res.redirect(mustAuthenticate);
64+
}
65+
66+
const example = getExampleByNumber(res.locals.manifest, exampleNumber, api);
67+
const sourceFile =
68+
path.basename(__filename)[5].toLowerCase() +
69+
path.basename(__filename).substr(6);
70+
res.render('pages/connect-examples/eg001ValidateWebhookMessage', {
71+
eg: eg,
72+
csrfToken: req.csrfToken(),
73+
example: example,
74+
sourceFile: sourceFile,
75+
sourceUrl: dsConfig.githubExampleUrl + 'connect/examples/' + sourceFile,
76+
documentation: dsConfig.documentation + eg,
77+
showDoc: dsConfig.documentation,
78+
});
79+
};

lib/connect/controllers/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports.eg001connect = require('./eg001ValidateWebhookMessage');
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @file
3+
* Example 001: Validate webhook message using HMAC
4+
* @author DocuSign
5+
*/
6+
7+
const crypto = require('crypto');
8+
9+
/**
10+
* This function computes the hash.
11+
* @param {object} args parameters for computing the hash.
12+
* @param {string} args.secret hmac secret key.
13+
* @param {string} args.payload plain text payload.
14+
* @return {string} Computed hash.
15+
*/
16+
const computeHash = (args) => {
17+
const hmac = crypto.createHmac('sha256', args.secret);
18+
hmac.write(args.payload);
19+
hmac.end();
20+
return hmac.read().toString('base64');
21+
};
22+
23+
/**
24+
* This function validates a webhook message.
25+
* @param {object} args parameters for validating the message.
26+
* @param {string} args.verify hash value as base64 string.
27+
* @param {string} args.secret hmac secret key.
28+
* @param {string} args.payload plain text payload.
29+
* @return {boolean} Returns true if the provided hash matches the computed hash, otherwise false.
30+
*/
31+
const isHashValid = (args) => {
32+
return crypto.timingSafeEqual(Buffer.from(args.verify, 'base64'), Buffer.from(computeHash(args), 'base64'));
33+
};
34+
35+
module.exports = { computeHash, isHashValid };

lib/utils.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const API_TYPES = {
1616
CLICK: "Click",
1717
ROOMS: "Rooms",
1818
ADMIN: "Admin",
19+
CONNECT: "Connect",
1920
};
2021

2122
async function isCFR(accessToken, accountId, basePath) {

public/assets/search.js

Lines changed: 58 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,7 @@ const DS_SEARCH = (function () {
55
CLICK: "click",
66
ROOMS: "rooms",
77
ADMIN: "admin",
8-
};
9-
10-
function getHash(value) {
11-
var hash = 0,
12-
i, chr;
13-
if (value.length === 0) return hash;
14-
for (i = 0; i < value.length; i++) {
15-
chr = value.charCodeAt(i);
16-
hash = ((hash << 5) - hash) + chr;
17-
hash |= 0; // Convert to 32bit integer
18-
}
19-
return hash;
8+
CONNECT: "connect",
209
}
2110

2211
const processJSONData = function () {
@@ -34,7 +23,7 @@ const DS_SEARCH = (function () {
3423
function checkIfExampleMatches(example, matches) {
3524
const name = example.ExampleName;
3625
const description = example.ExampleDescription;
37-
const pathNames = example.LinksToAPIMethod.map((a) => a.PathName);
26+
const pathNames = example.LinksToAPIMethod?.map((a) => a.PathName);
3827

3928
for (let i = 0; i < matches.length; i++) {
4029
if (
@@ -85,9 +74,10 @@ const DS_SEARCH = (function () {
8574
],
8675
};
8776

88-
const fuse = new Fuse(json, options);
77+
const clearJSON = JSON.stringify(json).replace(/<\/?[^>]+(>|$)/g, "");
78+
const fuse = new Fuse(JSON.parse(clearJSON), options);
8979

90-
var searchResults = fuse.search(JSON.stringify(pattern));
80+
const searchResults = fuse.search(JSON.stringify(pattern));
9181

9282
searchResults.forEach((searchResult) =>
9383
clearResultsAfterMatching(searchResult.item, searchResult.matches)
@@ -137,17 +127,19 @@ const DS_SEARCH = (function () {
137127
return "meg";
138128
case API_TYPES.ESIGNATURE:
139129
return "eg";
130+
case API_TYPES.CONNECT:
131+
return "cneg";
140132
}
141133
}
142134

143135
const addCodeExampleToHomepage = function (codeExamples) {
144136
var cfrPart11 = processCFR11Value();
145-
137+
146138
codeExamples.forEach((element) => {
147139
let linkToCodeExample = getLinkForApiType(element.Name.toLowerCase());
148140

149141
element.Groups.forEach((group) => {
150-
$("#filtered_code_examples").append(`<h2 id="${getHash(group.Name)}"></h2>`);
142+
$("#filtered_code_examples").append("<h2>" + group.Name + "</h2>");
151143

152144
group.Examples.forEach((example) => {
153145
if (
@@ -159,7 +151,6 @@ const DS_SEARCH = (function () {
159151
((cfrPart11 == "enabled") && (example.CFREnabled == "CFROnly")) ||
160152
((cfrPart11 != "enabled") && (example.CFREnabled == "NonCFR"))))
161153
{
162-
$(`#${getHash(group.Name)}`).html(group.Name);
163154
$("#filtered_code_examples").append(
164155
"<h4 id=" +
165156
"example"
@@ -184,36 +175,37 @@ const DS_SEARCH = (function () {
184175
);
185176

186177
$("#filtered_code_examples").append("<p>");
187-
188-
if (example.LinksToAPIMethod.length == 1) {
189-
$("#filtered_code_examples").append(
190-
processJSONData().SupportingTexts.APIMethodUsed
191-
);
192-
} else {
193-
$("#filtered_code_examples").append(
194-
processJSONData().SupportingTexts.APIMethodUsedPlural
195-
);
196-
}
197-
198-
for (
199-
let index = 0;
200-
index < example.LinksToAPIMethod.length;
201-
index++
202-
) {
203-
$("#filtered_code_examples").append(
204-
" <a target='_blank' href='" +
205-
example.LinksToAPIMethod[index].Path +
206-
"'>" +
207-
example.LinksToAPIMethod[index].PathName +
208-
"</a>"
209-
);
210-
211-
if (index + 1 === example.LinksToAPIMethod.length) {
212-
$("#filtered_code_examples").append("<span></span>");
213-
} else if (index + 1 === example.LinksToAPIMethod.length - 1) {
214-
$("#filtered_code_examples").append("<span> and </span>");
178+
if (example.LinksToAPIMethod && example.LinksToAPIMethod.length !== 0) {
179+
if (example.LinksToAPIMethod.length == 1) {
180+
$("#filtered_code_examples").append(
181+
processJSONData().SupportingTexts.APIMethodUsed
182+
);
215183
} else {
216-
$("#filtered_code_examples").append("<span>, </span>");
184+
$("#filtered_code_examples").append(
185+
processJSONData().SupportingTexts.APIMethodUsedPlural
186+
);
187+
}
188+
189+
for (
190+
let index = 0;
191+
index < example.LinksToAPIMethod.length;
192+
index++
193+
) {
194+
$("#filtered_code_examples").append(
195+
" <a target='_blank' href='" +
196+
example.LinksToAPIMethod[index].Path +
197+
"'>" +
198+
example.LinksToAPIMethod[index].PathName +
199+
"</a>"
200+
);
201+
202+
if (index + 1 === example.LinksToAPIMethod.length) {
203+
$("#filtered_code_examples").append("<span></span>");
204+
} else if (index + 1 === example.LinksToAPIMethod.length - 1) {
205+
$("#filtered_code_examples").append("<span> and </span>");
206+
} else {
207+
$("#filtered_code_examples").append("<span>, </span>");
208+
}
217209
}
218210
}
219211

@@ -266,6 +258,25 @@ function updateValue(esearchPattern) {
266258
DS_SEARCH.textCouldNotBeFound();
267259
} else {
268260
result.forEach((x) => {
261+
const api = json.filter((api) => {
262+
return api.Name === x.item.Name;
263+
})[0];
264+
265+
x.item.Groups.forEach((group, groupIndex) => {
266+
const unfilteredGroup = api.Groups.filter((apiGroup) => {
267+
return apiGroup.Name === group.Name;
268+
})[0];
269+
270+
group.Examples.forEach((example, index) => {
271+
const clearedExample = unfilteredGroup.Examples.filter(
272+
(apiExample) => {
273+
return apiExample.ExampleNumber === example.ExampleNumber;
274+
}
275+
)[0];
276+
x.item.Groups[groupIndex].Examples[index] = clearedExample;
277+
});
278+
});
279+
269280
DS_SEARCH.addCodeExampleToHomepage([x.item]);
270281
});
271282
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<%- include("../../partials/examplesHead") %>
2+
3+
<%- include("../../partials/exampleInfo") %>
4+
5+
<p>
6+
<b>Prerequisites:</b> See <a href="https://developers.docusign.com/platform/webhooks/connect/validate/">How to validate a webhook message using HMAC</a>.
7+
</p>
8+
9+
<form class="eg" action="" method="post" data-busy="form">
10+
<% if(example.Forms && example.Forms[0].FormName) { %>
11+
<%- example.Forms[0].FormName %>
12+
<% } %>
13+
<div class="form-group" style="display: flex;">
14+
<label for="secret" style="width: 50%;"><%= example.Forms[0].Inputs[0].InputName %></label>
15+
<input type="text" class="form-control" id="secret" placeholder="<%= example.Forms[0].Inputs[0].InputPlaceholder %>"
16+
name="secret" required>
17+
</div>
18+
<div class="form-group">
19+
<label for="payload"><%= example.Forms[0].Inputs[1].InputName %></label>
20+
<textarea class="form-control" id="payload" placeholder="<%= example.Forms[0].Inputs[1].InputPlaceholder %>"
21+
name="payload" rows="6" required></textarea>
22+
</div>
23+
24+
<input type="hidden" name="_csrf" value="<%- csrfToken %>">
25+
<%- include("../../partials/submitButton") %>
26+
</form>
27+
28+
<%- include("../../partials/examplesFoot") %>

views/pages/index_examples_list.ejs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
: api.Name == "Click" ? "c"
1818
: api.Name == "Monitor" ? "m"
1919
: api.Name == "Rooms" ? "r"
20+
: api.Name == "Connect" ? "cn"
2021
: ""; %>
2122
2223
<h4
@@ -31,13 +32,13 @@
3132
<p><%- example.ExampleDescription %></p>
3233
3334
<p>
34-
<% if (example.LinksToAPIMethod.length > 1) { %>
35+
<% if (example.LinksToAPIMethod?.length > 1) { %>
3536
API methods used:
3637
<% } else { %>
3738
API method used:
3839
<% } %>
3940
40-
<% example.LinksToAPIMethod.forEach(function(link) { %>
41+
<% example.LinksToAPIMethod?.forEach(function(link) { %>
4142
<a target='_blank' href="<%= link.Path %>"><%= link.PathName %></a>
4243
<% }); %>
4344
</p>

views/partials/exampleInfo.ejs

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@
99
<% } %>
1010

1111
<p>
12-
<% if (example.LinksToAPIMethod.length > 1) { %>
13-
<%= locals.manifest.SupportingTexts.APIMethodUsedPlural %>
14-
<% } else { %>
15-
<%= locals.manifest.SupportingTexts.APIMethodUsed %>
16-
<% } %>
17-
18-
<% for (var i = 0; i < example.LinksToAPIMethod.length; i++) {%>
19-
<% if (i + 1 == example.LinksToAPIMethod.length - 1) { %>
20-
<a target='_blank' href="<%= example.LinksToAPIMethod[i].Path %>"><%= example.LinksToAPIMethod[i].PathName %></a>, and
21-
<% } else if (i == example.LinksToAPIMethod.length - 1) { %>
22-
<a target='_blank' href="<%= example.LinksToAPIMethod[i].Path %>"><%= example.LinksToAPIMethod[i].PathName %></a>
12+
<% if (example.LinksToAPIMethod && example.LinksToAPIMethod.length !== 0) { %>
13+
<% if (example.LinksToAPIMethod.length > 1) { %>
14+
<%= locals.manifest.SupportingTexts.APIMethodUsedPlural %>
2315
<% } else { %>
24-
<a target='_blank' href="<%= example.LinksToAPIMethod[i].Path %>"><%= example.LinksToAPIMethod[i].PathName %></a>,
16+
<%= locals.manifest.SupportingTexts.APIMethodUsed %>
17+
<% } %>
18+
19+
<% for (var i = 0; i < example.LinksToAPIMethod.length; i++) {%>
20+
<% if (i + 1 == example.LinksToAPIMethod.length - 1) { %>
21+
<a target='_blank' href="<%= example.LinksToAPIMethod[i].Path %>"><%= example.LinksToAPIMethod[i].PathName %></a>, and
22+
<% } else if (i == example.LinksToAPIMethod.length - 1) { %>
23+
<a target='_blank' href="<%= example.LinksToAPIMethod[i].Path %>"><%= example.LinksToAPIMethod[i].PathName %></a>
24+
<% } else { %>
25+
<a target='_blank' href="<%= example.LinksToAPIMethod[i].Path %>"><%= example.LinksToAPIMethod[i].PathName %></a>,
26+
<% } %>
2527
<% } %>
2628
<% } %>
2729
</p>

0 commit comments

Comments
 (0)