Skip to content
This repository has been archived by the owner on Jan 19, 2024. It is now read-only.

Commit

Permalink
Refresh token & public web app auth support (#82)
Browse files Browse the repository at this point in the history
* support for refresh token in oauth2

* fix for failing unit test

* add tests for oauth2 public web apps

* Fix failing tests and linting issues

* fix code review concerns in test

* fix failing tests

* address code review concerns

* address code review concerns

* address code review concerns

* address build failure issues

* Removed optional assert message

Removed optional assert message since it is not very clear in the context of the test description

* Fixed GitHub vulnerabilities

Fixed GitHub vulnerabilities using a yarn upgrade. Added package-lock.json to .gitignore in order to not push on remote a yarn.lock file and a package-lock.json

* updated version
  • Loading branch information
manivinesh authored Sep 4, 2019
1 parent 972bfe1 commit 9dc8c4f
Show file tree
Hide file tree
Showing 6 changed files with 777 additions and 207 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ testing.js
coverage/
*.tgz
npm-debug.log
package-lock.json
98 changes: 74 additions & 24 deletions lib/fuel-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,39 @@ const merge = require('lodash.merge');
const version = require('../package.json').version;

module.exports = class FuelAuth {
constructor(options) {
constructor(options) {
var isOauth2Flow = options && options.authOptions ? options.authOptions.authVersion === 2 : false;
if (options) {
if (!options.clientId || !options.clientSecret) {
if (!options.clientId) {
throw new Error('clientId or clientSecret is missing or invalid');
}

if (typeof options.clientId !== 'string' || typeof options.clientSecret !== 'string') {
if (typeof options.clientId !== 'string') {
throw new Error('clientId or clientSecret must be strings');
}

if (options.authOptions && typeof options.authOptions.authVersion === 2 && !options.authUrl) {
throw new Error('Auth URL is mandatory for OAuth2 Authentication');
if(isOauth2Flow) {
if(options.authOptions.applicationType !== 'public'){
if (!options.clientSecret) {
throw new Error('clientId or clientSecret is missing or invalid');
}
if (typeof options.clientSecret !== 'string') {
throw new Error('clientId or clientSecret must be strings');
}
}
if (!options.authUrl) {
throw new Error('Auth URL is mandatory for OAuth2 Authentication');
}
if(options.authOptions.applicationType === 'public' || options.authOptions.applicationType === 'web'){
if(!options.authOptions.redirectURI || !options.authOptions.authorizationCode){
throw new Error('RedirectURI and Authorization Code are required for Public/Web App OAuth2 Authentication');
}
}
} else {
if (!options.clientSecret) {
throw new Error('clientId or clientSecret is missing or invalid');
}
if (typeof options.clientSecret !== 'string') {
throw new Error('clientId or clientSecret must be strings');
}
}
} else {
throw new Error('options are required. see readme.');
Expand All @@ -37,10 +58,14 @@ module.exports = class FuelAuth {
this.refreshToken = options.refreshToken;
this.version = version;
this.globalReqOptions = options.globalReqOptions || {};

if(options.authOptions){
this.accountId = options.authOptions.accountId;
this.authVersion = options.authOptions.authVersion;
this.scope = options.authOptions.scope;
this.applicationType = options.authOptions.applicationType;
this.redirectURI = options.authOptions.redirectURI;
this.authorizationCode = options.authOptions.authorizationCode;
this.soapUrl = null;
this.restUrl = null;
}
Expand Down Expand Up @@ -112,18 +137,38 @@ module.exports = class FuelAuth {
callback(null, response);
}
}
_requestToken(requestOptions) {
createPayloadForOauth2(){
const payload = {};
if(this.authVersion === 2){
payload.client_id = this.clientId;
payload.client_id = this.clientId;
if(this.applicationType !== 'public'){
payload.client_secret = this.clientSecret;
}

if(this.refreshToken){
payload.grant_type = 'refresh_token';
payload.refresh_token = this.refreshToken;
}
else if(this.applicationType === 'public' || this.applicationType === 'web'){
payload.grant_type = 'authorization_code';
payload.code = this.authorizationCode;
payload.redirect_uri = this.redirectURI;
}
else{
payload.grant_type = 'client_credentials';
if(this.accountId){
payload.account_id = this.accountId;
}
if(this.scope){
payload.scope = this.scope;
}
}

if(this.accountId){
payload.account_id = this.accountId;
}
if(this.scope){
payload.scope = this.scope;
}
return payload;
}
_requestToken(requestOptions) {
var payload = {};
if(this.authVersion === 2){
payload = this.createPayloadForOauth2();
} else {
payload.clientId = this.clientId;
payload.clientSecret = this.clientSecret;
Expand All @@ -136,14 +181,16 @@ module.exports = class FuelAuth {
};

const options = merge({}, this.globalReqOptions, baseOptions, requestOptions);

if (this.refreshToken) {
// adding refresh token to json if it's there
options.json.refreshToken = this.refreshToken;
} else if (this.scope) {
// adding scope to json if it's there
// it's not valid to use both scope and a refresh token
options.json.scope = this.scope;

if(this.authVersion === undefined || this.authVersion !== 2){
if (this.refreshToken) {
// adding refresh token to json if it's there
options.json.refreshToken = this.refreshToken;
} else if (this.scope) {
// adding scope to json if it's there
// it's not valid to use both scope and a refresh token
options.json.scope = this.scope;
}
}

return new Promise((resolve, reject) => {
Expand All @@ -170,6 +217,9 @@ module.exports = class FuelAuth {
this.soapUrl = body.soap_instance_url;
this.restUrl = body.rest_instance_url;
this.expiration = body.expires_in ? process.hrtime()[0] + body.expires_in : null;
if (body.refresh_token) {
this.refreshToken = body.refresh_token;
}
} else {
this.accessToken = body.accessToken || null;
this.expiration = body.expiresIn ? process.hrtime()[0] + body.expiresIn : null;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fuel-auth",
"version": "3.2.1",
"version": "3.3.0",
"description": "Node library for authenticating REST and SOAP APIs in the Salesforce Marketing Cloud (formerly ExactTarget).",
"main": "./lib/fuel-auth.js",
"scripts": {
Expand Down
74 changes: 74 additions & 0 deletions test/general-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,100 @@ describe('General Tests', () => {
assert.equal(typeof FuelAuth, 'function');
});

it('clientSecret not needed for OAuth2 public app', () => {
var options = {
clientId:'client_id',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'public',
redirectURI: 'test',
authorizationCode: 'test'
}
};
assert.doesNotThrow(() => new FuelAuth(options));
});


it('AuthorizationCode mandatory for public app', () => {
var options = {
clientId:'client_id',
clientSecret:'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'public',
redirectURI: 'test'
}
};
assert.throws(() => new FuelAuth(options), "RedirectURI and Authorization Code are required for Public App OAuth2 Authentication");
});

it('RedirectURI mandatory for public app', () => {
var options = {
clientId:'client_id',
clientSecret:'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'public',
authorizationCode: 'test'
}
};
assert.throws(() => new FuelAuth(options), "RedirectURI and Authorization Code are required for Public App OAuth2 Authentication");
});

it('AuthorizationCode mandatory for web app', () => {
var options = {
clientId:'client_id',
clientSecret:'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'web',
redirectURI: 'test'
}
};
assert.throws(() => new FuelAuth(options), "RedirectURI and Authorization Code are required for Web App OAuth2 Authentication");
});

it('RedirectURI mandatory for web app', () => {
var options = {
clientId:'client_id',
clientSecret:'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'web',
authorizationCode: 'test'
}
};
assert.throws(() => new FuelAuth(options), "RedirectURI and Authorization Code are required for Web App OAuth2 Authentication");
});

it('should require clientId and clientSecret', () => {
let AuthClient;

// testing with nothing passed into constructor
try {
AuthClient = new FuelAuth();
assert.fail("Should Throw Exception with Error Message options are required. see readme.");
} catch (err) {
assert.equal(err.message, 'options are required. see readme.');
}

// testing with clientId passed into constructor
try {
AuthClient = new FuelAuth({ clientId: 'test' });
assert.fail("Should Throw Exception with Error Message clientId or clientSecret is missing or invalid");
} catch (err) {
assert.equal(err.message, 'clientId or clientSecret is missing or invalid');
}

// testing with clientSecret passed into constructor
try {
AuthClient = new FuelAuth({ clientSecret: 'test' });
assert.fail("Should Throw Exception with Error Message clientId or clientSecret is missing or invalid");
} catch (err) {
assert.equal(err.message, 'clientId or clientSecret is missing or invalid');
}
Expand Down
100 changes: 100 additions & 0 deletions test/oauth2PayloadTests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright (c) 2018, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

'use strict';
const assert = require('assert');
const FuelAuth = require('../lib/fuel-auth');

describe('Oauth2 Payload Tests', () => {
it('auth payload should have public app attributes', () => {

var options = {
clientId:'client_id',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'public',
redirectURI: 'test',
authorizationCode: 'test'
}
};

var client = new FuelAuth(options);
var payload = client.createPayloadForOauth2();

assert.equal(payload.client_id, options.clientId);
assert.equal(payload.redirect_uri, options.authOptions.redirectURI);
assert.equal(payload.code, options.authOptions.authorizationCode);
assert.equal(payload.grant_type, 'authorization_code');
});

it('auth payload should have web app attributes', () => {

var options = {
clientId:'client_id',
clientSecret: 'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2,
applicationType: 'web',
redirectURI: 'test',
authorizationCode: 'test'
}
};

var client = new FuelAuth(options);
var payload = client.createPayloadForOauth2();

assert.equal(payload.client_id, options.clientId);
assert.equal(payload.client_secret, options.clientSecret);
assert.equal(payload.redirect_uri, options.authOptions.redirectURI);
assert.equal(payload.code, options.authOptions.authorizationCode);
assert.equal(payload.grant_type, 'authorization_code');
});

it('auth payload should have server app attributes', () => {

var options = {
clientId:'client_id',
clientSecret: 'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2
}
};

var client = new FuelAuth(options);
var payload = client.createPayloadForOauth2();

assert.equal(payload.client_id, options.clientId);
assert.equal(payload.client_secret, options.clientSecret);
assert.equal(payload.grant_type, 'client_credentials');
});

it('auth payload should have refresh token attributes', () => {

var options = {
clientId:'client_id',
clientSecret: 'client_secret',
authUrl:'test',
authOptions:{
authVersion: 2
}
};

var client = new FuelAuth(options);
client.refreshToken = "test";

var payload = client.createPayloadForOauth2();

assert.equal(payload.client_id, options.clientId);
assert.equal(payload.client_secret, options.clientSecret);
assert.equal(payload.refresh_token, client.refreshToken);
assert.equal(payload.grant_type, 'refresh_token');
});

});
Loading

0 comments on commit 9dc8c4f

Please sign in to comment.