Skip to content

Commit 17c10c0

Browse files
author
Majid Mallis
committed
Added service integration OAuth support
1 parent 09c6c00 commit 17c10c0

File tree

8 files changed

+154
-36
lines changed

8 files changed

+154
-36
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ See [DocuSign Support Center](https://support.docusign.com/en/releasenotes/) for
66
## [Unreleased]
77
More information later on.
88

9+
## [3.2.0] - 2017-08-01
10+
### Added
11+
- Support for DocuSign JWT OAuth for service integration (2-legged authentication)
12+
913
## [3.1.0] - 2017-06-17
1014
### Added
11-
- Added support for DocuSign OAuth
15+
- Support for DocuSign OAuth
1216

1317
## [3.0.0] - 2017-03-10
1418
### BREAKING

README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ Run this script using node command
146146
```javascript
147147
var docusign = require('docusign-esign');
148148
var async = require('async');
149+
var path = require('path');
149150
150151
var integratorKey = '***'; // Integrator Key associated with your DocuSign Integration
151152
var email = 'YOUR_EMAIL'; // Email for your DocuSign Account
@@ -157,23 +158,31 @@ var templateId = '***'; // ID of the Template you want to
157158
var templateRoleName = '***'; // Role Name of the Template
158159
159160
var baseUrl = 'https://' + docusignEnv + '.docusign.net/restapi';
161+
var userId = 'YOUR_USER_ID';
162+
var oAuthBaseUrl = 'account-d.docusign.com'; // use account.docusign.com for Live/Production
163+
var redirectURI = 'https://www.docusign.com/api';
164+
var privateKeyFilename = 'keys/docusign_private_key.txt';
160165
161-
// initialize the api client
162166
var apiClient = new docusign.ApiClient();
163-
apiClient.setBasePath(baseUrl);
164-
165-
// create JSON formatted auth header
166-
var creds = JSON.stringify({
167-
Username: email,
168-
Password: password,
169-
IntegratorKey: integratorKey
170-
});
171-
apiClient.addDefaultHeader('X-DocuSign-Authentication', creds);
172-
173-
// assign api client to the Configuration object
174-
docusign.Configuration.default.setDefaultApiClient(apiClient);
175167
176168
async.waterfall([
169+
function initApiClient (next) {
170+
apiClient.setBasePath(baseUrl);
171+
// assign the api client to the Configuration object
172+
docusign.Configuration.default.setDefaultApiClient(apiClient);
173+
174+
// IMPORTANT NOTE:
175+
// the first time you ask for a JWT access token, you should grant access by making the following call
176+
// get DocuSign OAuth authorization url:
177+
var oauthLoginUrl = apiClient.getJWTUri(integratorKey, redirectURI, oAuthBaseUrl);
178+
// open DocuSign OAuth authorization url in the browser, login and grant access
179+
console.log(oauthLoginUrl);
180+
// END OF NOTE
181+
182+
// configure the ApiClient to asynchronously get an access to token and store it
183+
apiClient.configureJWTAuthorizationFlow(path.resolve(__dirname, privateKeyFilename), oAuthBaseUrl, integratorKey, userId, 3600, next);
184+
},
185+
177186
function login (next) {
178187
// login call available off the AuthenticationApi
179188
var authApi = new docusign.AuthenticationApi();

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "docusign-esign",
3-
"version": "3.1.0",
3+
"version": "3.2.0",
44
"description": "DocuSign Node.js API client.",
55
"license": "MIT",
66
"main": "src/index.js",
@@ -40,6 +40,7 @@
4040
},
4141
"semistandard": {
4242
"globals": [
43+
"before",
4344
"describe",
4445
"it"
4546
],
@@ -50,6 +51,7 @@
5051
]
5152
},
5253
"dependencies": {
54+
"jsonwebtoken": "7.4.1",
5355
"passport-oauth2": "1.4.0",
5456
"superagent": "1.7.1"
5557
},

src/ApiClient.js

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,17 @@
8484
*/
8585
this.cache = true;
8686
};
87-
87+
8888
/**
8989
* Sets the API endpoint base URL.
90-
*/
90+
*/
9191
exports.prototype.setBasePath = function setBasePath(basePath) {
9292
this.basePath = basePath;
9393
};
9494

9595
/**
9696
* Adds request headers to the API client. Useful for Authentication.
97-
*/
97+
*/
9898
exports.prototype.addDefaultHeader = function addDefaultHeader(header, value) {
9999
this.defaultHeaders[header] = value;
100100
};
@@ -426,7 +426,7 @@
426426
if (accept) {
427427
request.accept(accept);
428428
}
429-
429+
430430
var data;
431431
if (request.header['Accept'] === 'application/pdf') {
432432
request.buffer();
@@ -556,6 +556,68 @@
556556
}
557557
};
558558

559+
/**
560+
* Helper method to build the OAuth JWT grant uri (used once to get a user consent for impersonation)
561+
* @param clientId OAuth2 client ID
562+
* @param redirectURI OAuth2 redirect uri
563+
* @param oAuthBasePath DocuSign OAuth base path (account-d.docusign.com for the developer sandbox
564+
* and account.docusign.com for the production platform)
565+
* @returns {string} the OAuth JWT grant uri as a String
566+
*/
567+
exports.prototype.getJWTUri = function(clientId, redirectURI, oAuthBasePath) {
568+
return "https://" + oAuthBasePath + "/oauth/auth" + "?" +
569+
"response_type=code&" +
570+
"client_id=" + encodeURIComponent(clientId) + "&" +
571+
"scope=" + encodeURIComponent("signature+impersonation") + "&" +
572+
"redirect_uri=" + encodeURIComponent(redirectURI);
573+
};
574+
575+
/**
576+
* Configures the current instance of ApiClient with a fresh OAuth JWT access token from DocuSign
577+
* @param privateKeyFilename the filename of the RSA private key
578+
* @param oAuthBasePath DocuSign OAuth base path (account-d.docusign.com for the developer sandbox
579+
* and account.docusign.com for the production platform)
580+
* @param clientId DocuSign OAuth Client Id (AKA Integrator Key)
581+
* @param userId DocuSign user Id to be impersonated (This is a UUID)
582+
* @param expiresIn in seconds for the token time-to-live
583+
* @param callback the callback function.
584+
*/
585+
exports.prototype.configureJWTAuthorizationFlow = function(privateKeyFilename, oAuthBasePath, clientId, userId, expiresIn, callback) {
586+
var _this = this;
587+
var jwt = require('jsonwebtoken')
588+
, fs = require('fs')
589+
, private_key = fs.readFileSync(privateKeyFilename)
590+
, now = Math.floor(Date.now() / 1000)
591+
, later = now + expiresIn;
592+
593+
var jwt_payload = {
594+
iss: clientId,
595+
sub: userId,
596+
aud: oAuthBasePath,
597+
iat: now,
598+
exp: later,
599+
scope: "signature"
600+
};
601+
602+
var assertion = jwt.sign(jwt_payload, private_key, {algorithm: 'RS256'});
603+
604+
superagent('post', 'https://' + oAuthBasePath + '/oauth/token')
605+
.timeout(this.timeout)
606+
.set('Content-Type', 'application/x-www-form-urlencoded')
607+
.send({
608+
'assertion': assertion,
609+
'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer'
610+
})
611+
.end(function(err, res) {
612+
if (callback) {
613+
if (!err && res.body && res.body.access_token) {
614+
_this.addDefaultHeader('Authorization', 'Bearer ' + res.body.access_token);
615+
}
616+
callback(err, res);
617+
}
618+
});
619+
};
620+
559621
/**
560622
* The default API client implementation.
561623
* @type {module:ApiClient}

test-config.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
{
2-
"integratorKey": "SAND-7c8746af-XXXX-XXXX-XXXX-fc10861ef45b",
2+
"integratorKey": "ae30ea4e-xxxx-xxxx-xxxx-fcb57d2dc4df",
33
"apiEnv": "demo",
44
"debug": false,
55
"email": "[email protected]",
66
"noSigEmail": "[email protected]",
77
"userId": "fcc5726c-xxxx-xxxx-xxxx-40bbbe6ca126",
88
"noSigUserId": "538241a0-xxxx-xxxx-xxxx-48aef23da8c4",
9-
"password": "{PASSWORD}",
109
"templateId": "cf2a46c2-xxxx-xxxx-xxxx-752547b1a419",
1110
"templateRole": "bob"
1211
}

test/SdkUnitTests.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
var assert = require('assert');
22
var docusign = require('../src/index');
33
var config = require('../test-config.json');
4+
var path = require('path');
45

56
var UserName = config.email;
6-
var Password = config.password;
77
var IntegratorKey = config.integratorKey;
88
var TemplateId = config.templateId;
99

@@ -12,13 +12,27 @@ var BaseUrl = 'https://demo.docusign.net/restapi';
1212
var SignTest1File = 'docs/SignTest1.pdf';
1313
var accountId = '';
1414
var envelopeId = '';
15-
var creds = '{"Username":"' + UserName + '","Password":"' + Password + '","IntegratorKey":"' + IntegratorKey + '"}';
16-
17-
var apiClient = new docusign.ApiClient();
18-
apiClient.setBasePath(BaseUrl);
19-
apiClient.addDefaultHeader('X-DocuSign-Authentication', creds);
15+
var UserId = config.userId;
16+
var OAuthBaseUrl = 'account-d.docusign.com';
17+
var RedirectURI = 'https://www.docusign.com/api';
18+
var privateKeyFilename = 'keys/docusign_private_key.txt';
19+
20+
describe('SDK Unit Tests:', function (done) {
21+
var apiClient = new docusign.ApiClient();
22+
before(function (done) {
23+
apiClient.setBasePath(BaseUrl);
24+
// IMPORTANT NOTE:
25+
// the first time you ask for a JWT access token, you should grant access by making the following call
26+
// get DocuSign OAuth authorization url:
27+
var oauthLoginUrl = apiClient.getJWTUri(IntegratorKey, RedirectURI, OAuthBaseUrl);
28+
// open DocuSign OAuth authorization url in the browser, login and grant access
29+
console.log(oauthLoginUrl);
30+
// END OF NOTE
31+
32+
// configure the ApiClient to asynchronously get an access to token and store it
33+
apiClient.configureJWTAuthorizationFlow(path.resolve(__dirname, privateKeyFilename), OAuthBaseUrl, IntegratorKey, UserId, 3600, done);
34+
});
2035

21-
describe('SDK Unit Tests:', function () {
2236
it('login', function (done) {
2337
var authApi = new docusign.AuthenticationApi(apiClient);
2438
var loginOps = {};
@@ -53,7 +67,6 @@ describe('SDK Unit Tests:', function () {
5367
var fileBytes = null;
5468
try {
5569
var fs = require('fs');
56-
var path = require('path');
5770
// read file from a local directory
5871
fileBytes = fs.readFileSync(path.resolve(__dirname, SignTest1File));
5972
} catch (ex) {
@@ -167,7 +180,6 @@ describe('SDK Unit Tests:', function () {
167180
var fileBytes = null;
168181
try {
169182
var fs = require('fs');
170-
var path = require('path');
171183
// read file from a local directory
172184
fileBytes = fs.readFileSync(path.resolve(__dirname, SignTest1File));
173185
} catch (ex) {
@@ -260,7 +272,6 @@ describe('SDK Unit Tests:', function () {
260272
var fileBytes = null;
261273
try {
262274
var fs = require('fs');
263-
var path = require('path');
264275
// read file from a local directory
265276
fileBytes = fs.readFileSync(path.resolve(__dirname, SignTest1File));
266277
} catch (ex) {
@@ -331,7 +342,6 @@ describe('SDK Unit Tests:', function () {
331342
var fileBytes = null;
332343
try {
333344
var fs = require('fs');
334-
var path = require('path');
335345
// read file from a local directory
336346
fileBytes = fs.readFileSync(path.resolve(__dirname, SignTest1File));
337347
} catch (ex) {
@@ -407,7 +417,6 @@ describe('SDK Unit Tests:', function () {
407417
if (pdfBytes) {
408418
try {
409419
var fs = require('fs');
410-
var path = require('path');
411420
// download the document pdf
412421
var filename = accountId + '_' + envelopeSummary.envelopeId + '_combined.pdf';
413422
var tempFile = path.resolve(__dirname, filename);
@@ -445,7 +454,6 @@ describe('SDK Unit Tests:', function () {
445454
var fileBytes = null;
446455
try {
447456
var fs = require('fs');
448-
var path = require('path');
449457
// read file from a local directory
450458
fileBytes = fs.readFileSync(path.resolve(__dirname, SignTest1File));
451459
} catch (ex) {
@@ -533,7 +541,6 @@ describe('SDK Unit Tests:', function () {
533541
if (pdfBytes) {
534542
try {
535543
var fs = require('fs');
536-
var path = require('path');
537544
// download the document pdf
538545
var filename = accountId + '_' + envelopeSummary.envelopeId + '_combined.pdf';
539546
var tempFile = path.resolve(__dirname, filename);
@@ -560,7 +567,6 @@ describe('SDK Unit Tests:', function () {
560567
if (diagBytes) {
561568
try {
562569
var fs = require('fs');
563-
var path = require('path');
564570
// download the document pdf
565571
var filename = requestLogId + '.txt';
566572
var tempFile = path.resolve(__dirname, filename);

test/keys/docusign_private_key.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIIEogIBAAKCAQEAh0VLAZe6m36tbthAom/IRzxvm/i66mEpow9AsrFP3EAybtVw
3+
2s13RJ1f0ZJA4sB7SbOtRymIxLlVUWkZLW4NqtECrObqOuke+G2ROWfOhcAyMuiB
4+
lW44mvl8Gil+n6Lwif2+OtUQ88iwPuE6I7jKnBV82lNGDx3H1hqHl7wpQZRyRPCd
5+
zPCrh6TRHj1D40mvrUwqAptj5m71QKA9tWVAQQ1DGKJZVZI4mdz3+I1XCinF8zrx
6+
IUJyEtaJfCHiS086XF3Sw7DkU2o/QIIYbpxlf9Zdjr1QT6/ow7pwJbX2/GSQW2mV
7+
GSY0KSiPFVjbpjKBpVpd4vgnQqn10BzLvLNySwIDAQABAoIBAA2opAm9oeSYlneW
8+
U3RzeBQlWJm1tF39QKCL5jsE52z0eIMzfylAzPW7NFUrgOzEhc5r26fPXFWM5z4I
9+
sDejoLKqVyxRRr57EpsAKUVUI4ji3s7AJnGJxyJy5aKYpQYGhGZSnlY/dG5BSfaX
10+
dHDt9FttWgWLmgvltGt8k0txfvL1nYEQxXxE5gMKNKKoBXE8QF05BmpajApd+hhL
11+
rGLicYpqsrliXr818HvFEKS8ODYfN856osLsvBJDANEW9+OiRQ9QQs5qdStrA5IX
12+
35DktfSs5Tvr8ZkJksQyfIO4WmEVSJIkm00BL1oSWHAM0Zqv23u6H7HtmCBnvbpw
13+
DY33t+ECgYEA4r9rjODMNhuGgrktDsBE+IQcvoWiYiV00OipZzx0l1q3HmRB51Jo
14+
C/pQ+X6YHT3qZN263VksmlbzpvRQTeL97narhpW8ZdW/L0e6g9sOBchewItftfAJ
15+
CdXmzaaPHEzVu56YGwtS1QC6IVdomxgmXNNuUYgyHt6KgDpVuHBi3usCgYEAmLi/
16+
cLSnIHPT42WFf2IgQWxT7vcBxe4pL0zjzC4NSi8m//g/F/MpQfgsSjreEx5hE6+4
17+
tAh6J2MZwcejbdCgV6eES84fEGDDYGT3GV9qwCTp6Z374hjP+0ZeGcFnzZLsrPPc
18+
T5K/uaIH7n2dPzNe3aLCslnpStVIUz60mnusoiECgYAUk2A0EXYWdtr248zV6NaZ
19+
YoulMkUw+Msn5eTxbEf8MAwr4tckIZM1ewp8CWPOS38IliJN0bi9bKSBguwClVWL
20+
nRMljFLjPskxhiXDr04PckY+3KbbwKNhVBq0kKet3r8KXnLZCWcD0yQQwHjKkh9x
21+
DvKUzXIW4QTaa/C5YuFl7wKBgHDF68fD/q1+GncOXnfj88GbxpbtGwgXh53//y6k
22+
yvd+viPCIoUC7/Jg2gOuWJJxmmm5FoEKyXkQOtLXIp1SszRG5PA9Mr8bVOp3Y+f+
23+
h4t/NqNmH7ujauE34wDNymMJHW/RW1v/F0hyl7zKUTV8L48mQvMEZbr2p8OgyChT
24+
LvVBAoGAFPjp50Q3vaZ1aghIjCfSoi0eU5tVbMCAXpwrjLkCGkw8r6U1ILQyMvqw
25+
E57cqoj2hsZ3P7qgQvZox1L7THnAKNYIdM/tj444QmsYudC5wp5hZBEzmmwbIeq8
26+
3h4V9F7T/1PamwfRhZC3t4wf327HEMu0pZlwARWpuxh4eaXumYM=
27+
-----END RSA PRIVATE KEY-----

test/keys/docusign_public_key.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAh0VLAZe6m36tbthAom/I
3+
Rzxvm/i66mEpow9AsrFP3EAybtVw2s13RJ1f0ZJA4sB7SbOtRymIxLlVUWkZLW4N
4+
qtECrObqOuke+G2ROWfOhcAyMuiBlW44mvl8Gil+n6Lwif2+OtUQ88iwPuE6I7jK
5+
nBV82lNGDx3H1hqHl7wpQZRyRPCdzPCrh6TRHj1D40mvrUwqAptj5m71QKA9tWVA
6+
QQ1DGKJZVZI4mdz3+I1XCinF8zrxIUJyEtaJfCHiS086XF3Sw7DkU2o/QIIYbpxl
7+
f9Zdjr1QT6/ow7pwJbX2/GSQW2mVGSY0KSiPFVjbpjKBpVpd4vgnQqn10BzLvLNy
8+
SwIDAQAB
9+
-----END PUBLIC KEY-----

0 commit comments

Comments
 (0)