Repository: auth0/auth0.js
Pull request #261 (2016-12-07 14:32:22)

Add standard fields to parseHash and normalize responses to camelCase

codecov-io: ## [Current coverage](https://codecov.io/gh/auth0/auth0.js/pull/261?src=pr) is 100% (diff: 100%)
> Merging [#261](https://codecov.io/gh/auth0/auth0.js/pull/261?src=pr) into [v8](https://codecov.io/gh/auth0/auth0.js/branch/v8?src=pr) will increase coverage by **0.36%**

```diff
@@                 v8   #261   diff @@
======================================
  Files            26     26          
  Lines           813    826    +13   
  Methods         143    147     +4   
  Messages          0      0          
  Branches        113    119     +6   
======================================
+ Hits            810    826    +16   
+ Misses            3      0     -3   
  Partials          0      0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [a57abde...544230c](https://codecov.io/gh/auth0/auth0.js/compare/a57abdef1192e38125a776f83620e76bf3903127...544230c0e5d86d265a9550c951a592900673c895?src=pr)

Patch:
diff --git a/src/helper/error.js b/src/helper/error.js
index 0c96b61..c146e4c 100644
--- a/src/helper/error.js
+++ b/src/helper/error.js
@@ -1,7 +1,7 @@
 function buildResponse(error, description) {
   return {
     error: error,
-    error_description: description
+    errorDescription: description
   };
 }
 
diff --git a/src/helper/object.js b/src/helper/object.js
index d8d1ad7..8b1d857 100644
--- a/src/helper/object.js
+++ b/src/helper/object.js
@@ -57,18 +57,36 @@ function camelToSnake(str) {
   return newKey;
 }
 
+function snakeToCamel(str) {
+  var parts = str.split('_');
+  return parts.reduce(function (p, c) {
+    return p + c.charAt(0).toUpperCase() + c.slice(1);
+  }, parts.shift());
+}
+
 function toSnakeCase(object, exceptions) {
   exceptions = exceptions || [];
 
   return Object.keys(object).reduce(function (p, key) {
     var newKey = exceptions.indexOf(key) === -1 ? camelToSnake(key) : key;
-    p[newKey] = object[key];
+    p[newKey] = typeof(object[key]) === 'object' ? toSnakeCase(object[key]) : object[key];
+    return p;
+  }, {});
+}
+
+function toCamelCase(object, exceptions) {
+  exceptions = exceptions || [];
+
+  return Object.keys(object).reduce(function (p, key) {
+    var newKey = exceptions.indexOf(key) === -1 ? snakeToCamel(key) : key;
+    p[newKey] = typeof(object[key]) === 'object' ? toCamelCase(object[key]) : object[key];
     return p;
   }, {});
 }
 
 module.exports = {
   toSnakeCase: toSnakeCase,
+  toCamelCase: toCamelCase,
   blacklist: blacklist,
   merge: merge,
   pick: pick,
diff --git a/src/helper/response-handler.js b/src/helper/response-handler.js
index ef184fb..936d00e 100644
--- a/src/helper/response-handler.js
+++ b/src/helper/response-handler.js
@@ -1,4 +1,5 @@
 var error = require('./error');
+var objectHelper = require('./object');
 
 function wrapCallback(cb) {
   return function (err, data) {
@@ -14,11 +15,11 @@ function wrapCallback(cb) {
       };
 
       if (err.response && err.response.statusCode) {
-        errObj.status_code = err.response.statusCode;
+        errObj.statusCode = err.response.statusCode;
       }
 
       if (err.response && err.response.statusText) {
-        errObj.status_text = err.response.statusText;
+        errObj.statusText = err.response.statusText;
       }
 
       if (err.response && err.response.body) {
@@ -39,7 +40,7 @@ function wrapCallback(cb) {
       return cb(errObj);
     }
 
-    return cb(null, data.body || data.text || data);
+    return cb(null, objectHelper.toCamelCase(data.body || data.text || data));
   };
 }
 
diff --git a/src/web-auth/index.js b/src/web-auth/index.js
index eee3e3f..a776e3c 100644
--- a/src/web-auth/index.js
+++ b/src/web-auth/index.js
@@ -79,12 +79,14 @@ WebAuth.prototype.parseHash = function (hash, options) {
   }
 
   return {
-    accessToken: parsedQs.access_token,
+    accessToken: parsedQs.access_token || null,
     idToken: parsedQs.id_token || null,
     idTokenPayload: token && token.payload ? token.payload : null,
     appStatus: token ? token.appStatus || null : null,
-    refreshToken: parsedQs.refresh_token,
-    state: parsedQs.state
+    refreshToken: parsedQs.refresh_token || null,
+    state: parsedQs.state || null,
+    expiresIn: parsedQs.expires_in || null,
+    tokenType: parsedQs.token_type || null
   };
 };
 


Test patch:
diff --git a/test/authentication/authentication.test.js b/test/authentication/authentication.test.js
index da263b6..f23d267 100644
--- a/test/authentication/authentication.test.js
+++ b/test/authentication/authentication.test.js
@@ -204,7 +204,7 @@ describe('auth0.authentication', function () {
       this.auth0.userInfo('abcd1234', function(err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          user_id: '...',
+          userId: '...',
           provider: 'auth0',
           connection: 'Username-Password-Authentication',
           isSocial: false
@@ -261,9 +261,9 @@ describe('auth0.authentication', function () {
       }, function(err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          'token_type': 'Bearer',
-          'expires_in': 36000,
-          'id_token': 'eyJ...'
+          'tokenType': 'Bearer',
+          'expiresIn': 36000,
+          'idToken': 'eyJ...'
         });
         done();
       })
@@ -351,9 +351,9 @@ describe('auth0.authentication', function () {
       }, function(err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          'token_type': 'Bearer',
-          'expires_in': 36000,
-          'id_token': 'eyJ...'
+          'tokenType': 'Bearer',
+          'expiresIn': 36000,
+          'idToken': 'eyJ...'
         });
         done();
       })
diff --git a/test/authentication/ro.test.js b/test/authentication/ro.test.js
index 6b81ada..7d0b22f 100644
--- a/test/authentication/ro.test.js
+++ b/test/authentication/ro.test.js
@@ -61,9 +61,9 @@ describe('auth0.authentication', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          id_token: 'id_token.id_token.id_token',
-          access_token: 'access_token',
-          token_type: 'bearer'
+          idToken: 'id_token.id_token.id_token',
+          accessToken: 'access_token',
+          tokenType: 'bearer'
         });
         done();
       });
@@ -147,9 +147,9 @@ describe('auth0.authentication', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          id_token: 'id_token.id_token.id_token',
-          access_token: 'access_token',
-          token_type: 'bearer'
+          idToken: 'id_token.id_token.id_token',
+          accessToken: 'access_token',
+          tokenType: 'bearer'
         });
         done();
       });
diff --git a/test/helper/iframe-handler.test.js b/test/helper/iframe-handler.test.js
index 9fe55e0..c49a155 100644
--- a/test/helper/iframe-handler.test.js
+++ b/test/helper/iframe-handler.test.js
@@ -131,7 +131,9 @@ describe('helpers iframeHandler', function () {
             },
             appStatus: null,
             refreshToken: 'kajshdgfkasdjhgfas',
-            state: 'theState'
+            state: 'theState',
+            expiresIn: null,
+            tokenType: 'Bearer'
           });
 
           done();
@@ -155,7 +157,7 @@ describe('helpers iframeHandler', function () {
 
           expect(err).to.eql({
             error:'some_error',
-            error_description: 'the+error+description'
+            errorDescription: 'the+error+description'
           });
 
           done();
diff --git a/test/helper/object.test.js b/test/helper/object.test.js
index 8c5d452..4bf176d 100644
--- a/test/helper/object.test.js
+++ b/test/helper/object.test.js
@@ -276,7 +276,11 @@ describe('helpers', function () {
       var object = {
         attrName1: 'attribute_1',
         attrName22: 'attribute_2',
-        attrNAME3: 'attribute_3'
+        attrNAME3: 'attribute_3',
+        someObj: {
+          objAtt1: 'asd',
+          objAtt2: '123'
+        }
       };
 
       var newObject = objectHelper.toSnakeCase(object);
@@ -284,13 +288,21 @@ describe('helpers', function () {
       expect(object).to.eql({
         attrName1: 'attribute_1',
         attrName22: 'attribute_2',
-        attrNAME3: 'attribute_3'
+        attrNAME3: 'attribute_3',
+        someObj: {
+          objAtt1: 'asd',
+          objAtt2: '123'
+        }
       });
 
       expect(newObject).to.eql({
         attr_name_1: 'attribute_1',
         attr_name_22: 'attribute_2',
-        attr_name_3: 'attribute_3'
+        attr_name_3: 'attribute_3',
+        some_obj: {
+          obj_att_1: 'asd',
+          obj_att_2: '123'
+        }
       });
     });
 
@@ -316,4 +328,62 @@ describe('helpers', function () {
       });
     });
   });
+
+  describe('toCamelCase', function () {
+    it('should change the casing to all the attributes', function () {
+      var object = {
+        attr_name_1: 'attribute_1',
+        attr_name_22: 'attribute_2',
+        attr__name_3: 'attribute_3',
+        some_obj: {
+          obj_att_1: 'asdf',
+          obj_att_2: '1234'
+        }
+      };
+
+      var newObject = objectHelper.toCamelCase(object);
+
+      expect(object).to.eql({
+        attr_name_1: 'attribute_1',
+        attr_name_22: 'attribute_2',
+        attr__name_3: 'attribute_3',
+        some_obj: {
+          obj_att_1: 'asdf',
+          obj_att_2: '1234'
+        }
+      });
+
+      expect(newObject).to.eql({
+        attrName1: 'attribute_1',
+        attrName22: 'attribute_2',
+        attrName3: 'attribute_3',
+        someObj: {
+          objAtt1: 'asdf',
+          objAtt2: '1234'
+        }
+      });
+    });
+
+    it('should change the casing to all the attributes that are not blacklisted', function () {
+      var object = {
+        attr_name_1: 'attribute_1',
+        attr_name_22: 'attribute_2',
+        attr__name_3: 'attribute_3'
+      };
+
+      var newObject = objectHelper.toCamelCase(object, ['attr_name_22']);
+
+      expect(object).to.eql({
+        attr_name_1: 'attribute_1',
+        attr_name_22: 'attribute_2',
+        attr__name_3: 'attribute_3'
+      });
+
+      expect(newObject).to.eql({
+        attrName1: 'attribute_1',
+        attr_name_22: 'attribute_2',
+        attrName3: 'attribute_3'
+      });
+    });
+  });
 });
diff --git a/test/helper/response-handler.test.js b/test/helper/response-handler.test.js
index 88c44fd..4e68b50 100644
--- a/test/helper/response-handler.test.js
+++ b/test/helper/response-handler.test.js
@@ -10,7 +10,7 @@ describe('helpers responseHandler', function () {
       expect(data).to.be(undefined);
       expect(err).to.eql({
         error: 'generic_error',
-        error_description: 'Something went wrong'
+        errorDescription: 'Something went wrong'
       });
       done();
     })(null, null);
@@ -31,8 +31,8 @@ describe('helpers responseHandler', function () {
       expect(data).to.be(undefined);
       expect(err).to.eql({
         original: assert_err,
-        status_code: 400,
-        status_text: 'Bad request',
+        statusCode: 400,
+        statusText: 'Bad request',
         code: 'the_error_code',
         description: 'The error description.',
         name: 'SomeName'
diff --git a/test/management/management.test.js b/test/management/management.test.js
index 1700d34..621df7e 100644
--- a/test/management/management.test.js
+++ b/test/management/management.test.js
@@ -99,7 +99,7 @@ describe('auth0.Management', function () {
       this.auth0.getUser('auth0|123', function(err, user) {
         expect(err).to.be(null);
         expect(user).to.eql({
-          'user_id': 'auth0|123',
+          'userId': 'auth0|123',
           'email': 'me@example.com'
         });
         done();
@@ -182,9 +182,9 @@ describe('auth0.Management', function () {
       this.auth0.patchUserMetadata('auth0|123', {role:'admin'}, function(err, user) {
         expect(err).to.be(null);
         expect(user).to.eql({
-          'user_id': 'auth0|123',
+          'userId': 'auth0|123',
           'email': 'me@example.com',
-          'user_metadata': { role: 'admin' }
+          'userMetadata': { role: 'admin' }
         });
         done();
       })
@@ -281,20 +281,20 @@ describe('auth0.Management', function () {
         expect(err).to.be(null);
         expect(user).to.eql([{
             'connection': 'twitter',
-            'user_id': '191919191919191',
+            'userId': '191919191919191',
             'provider': 'twitter',
             'profileData': {
               'email': '',
-              'email_verified': false,
+              'emailVerified': false,
               'name': '',
               'username': 'johndoe',
-              'given_name': '',
+              'givenName': '',
               'phoneNumber': '',
-              'phone_verified': false,
-              'family_name': ''
+              'phoneVerified': false,
+              'familyName': ''
             },
             'isSocial': false,
-            'access_token': ''
+            'accessToken': ''
           }]);
         done();
       })
diff --git a/test/web-auth/popup.test.js b/test/web-auth/popup.test.js
index 254677c..c2d70c4 100644
--- a/test/web-auth/popup.test.js
+++ b/test/web-auth/popup.test.js
@@ -89,7 +89,6 @@ describe('auth0.WebAuth.popup', function () {
         expect(relayUrl).to.be('https://me.auth0.com/relay.html');
         expect(options).to.eql({});
         cb(null, {
-          _id: '...',
           email_verified: false,
           email: 'me@example.com'
         });
@@ -102,8 +101,7 @@ describe('auth0.WebAuth.popup', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          _id: '...',
-          email_verified: false,
+          emailVerified: false,
           email: 'me@example.com'
         });
         done();
@@ -143,8 +141,7 @@ describe('auth0.WebAuth.popup', function () {
         });
 
         cb(null, {
-          _id: '...',
-          email_verified: false,
+          emailVerified: false,
           email: 'me@example.com'
         });
       });
@@ -159,8 +156,7 @@ describe('auth0.WebAuth.popup', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          _id: '...',
-          email_verified: false,
+          emailVerified: false,
           email: 'me@example.com'
         });
         done();
@@ -223,9 +219,9 @@ describe('auth0.WebAuth.popup', function () {
             cb: function (cb) {
               cb(null, {
                 body: {
-                  id_token: 'id_token.id_token.id_token',
-                  access_token: 'access_token',
-                  token_type: 'bearer'
+                  idToken: 'id_token.id_token.id_token',
+                  accessToken: 'access_token',
+                  tokenType: 'bearer'
                 }
               });
             }
@@ -242,9 +238,9 @@ describe('auth0.WebAuth.popup', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          id_token: 'id_token.id_token.id_token',
-          access_token: 'access_token',
-          token_type: 'bearer'
+          idToken: 'id_token.id_token.id_token',
+          accessToken: 'access_token',
+          tokenType: 'bearer'
         });
         done();
       });
@@ -309,9 +305,9 @@ describe('auth0.WebAuth.popup', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          id_token: 'id_token.id_token.id_token',
-          access_token: 'access_token',
-          token_type: 'bearer'
+          idToken: 'id_token.id_token.id_token',
+          accessToken: 'access_token',
+          tokenType: 'bearer'
         });
         done();
       });
@@ -492,7 +488,7 @@ describe('auth0.WebAuth.popup', function () {
           },
           "code":"user_exists",
           "description":"The user already exists.",
-          "status_code":400
+          "statusCode":400
         });
         done();
       });
diff --git a/test/web-auth/redirect.test.js b/test/web-auth/redirect.test.js
index 9dc5840..a87ad46 100644
--- a/test/web-auth/redirect.test.js
+++ b/test/web-auth/redirect.test.js
@@ -43,7 +43,6 @@ describe('auth0.WebAuth.redirect', function () {
           cb: function (cb) {
             cb(null, {
               body: {
-                _id: '...',
                 email_verified: false,
                 email: 'me@example.com'
               }
@@ -59,8 +58,7 @@ describe('auth0.WebAuth.redirect', function () {
       }, function (err, data) {
         expect(err).to.be(null);
         expect(data).to.eql({
-          _id: '...',
-          email_verified: false,
+          emailVerified: false,
           email: 'me@example.com'
         });
         done();
@@ -332,7 +330,7 @@ describe('auth0.WebAuth.redirect', function () {
           'name': 'ValidationError',
           'code': 'invalid_user_password',
           'description': 'Wrong email or password.',
-          'status_code': 400
+          'statusCode': 400
         });
         done();
       });
@@ -386,7 +384,7 @@ describe('auth0.WebAuth.redirect', function () {
           },
           "code":"user_exists",
           "description":"The user already exists.",
-          "status_code":400
+          "statusCode":400
         });
         done();
       });
diff --git a/test/web-auth/web-auth.test.js b/test/web-auth/web-auth.test.js
index bdc7726..5039dca 100644
--- a/test/web-auth/web-auth.test.js
+++ b/test/web-auth/web-auth.test.js
@@ -57,7 +57,7 @@ describe('auth0.WebAuth', function () {
       webAuth.renewAuth(options, function (err, data) {
         expect(err).to.eql({
           error: 'invalid_token',
-          error_description: 'Nonce does not match'
+          errorDescription: 'Nonce does not match'
         });
         expect(data).to.be(undefined);
         done();
@@ -93,7 +93,9 @@ describe('auth0.WebAuth', function () {
         },
         appStatus: null,
         refreshToken: 'kajshdgfkasdjhgfas',
-        state: 'theState'
+        state: 'theState',
+        expiresIn: null,
+        tokenType: 'Bearer'
       });
     });
 
@@ -119,7 +121,9 @@ describe('auth0.WebAuth', function () {
         },
         appStatus: null,
         refreshToken: 'kajshdgfkasdjhgfas',
-        state: 'theState'
+        state: 'theState',
+        expiresIn: null,
+        tokenType: 'Bearer'
       });
     });
 
@@ -139,7 +143,9 @@ describe('auth0.WebAuth', function () {
         idTokenPayload: null,
         appStatus: null,
         refreshToken: 'kajshdgfkasdjhgfas',
-        state: 'theState'
+        state: 'theState',
+        expiresIn: null,
+        tokenType: 'Bearer'
       });
     });
 
@@ -155,7 +161,7 @@ describe('auth0.WebAuth', function () {
 
       expect(data).to.eql({
         error: 'invalid_token',
-        error_description: 'The clientID configured (0HP71GSd6PuoRYJ3p) does not match with the clientID set in the token (0HP71GSd6PuoRYJ3DXKdiXCUUdGmBbup).' // eslint-disable-line
+        errorDescription: 'The clientID configured (0HP71GSd6PuoRYJ3p) does not match with the clientID set in the token (0HP71GSd6PuoRYJ3DXKdiXCUUdGmBbup).' // eslint-disable-line
       });
     });
 
@@ -171,7 +177,7 @@ describe('auth0.WebAuth', function () {
 
       expect(data).to.eql({
         error: 'invalid_token',
-        error_description: 'The domain configured (https://mdocs_2.auth0.com/) does not match with the domain set in the token (https://mdocs.auth0.com/).' // eslint-disable-line
+        errorDescription: 'The domain configured (https://mdocs_2.auth0.com/) does not match with the domain set in the token (https://mdocs.auth0.com/).' // eslint-disable-line
       });
     });
 
@@ -201,7 +207,7 @@ describe('auth0.WebAuth', function () {
 
       expect(data).to.eql({
         error: 'the_error_code',
-        error_description: 'the_error_description',
+        errorDescription: 'the_error_description',
         state: 'some_state'
       });
     });
@@ -218,7 +224,7 @@ describe('auth0.WebAuth', function () {
 
     it('should pass the correct authorize url', function (done) {
       stub(IframeHandler.prototype, 'init', function(message) {
-        expect(this.url).to.be('https://me.auth0.com/authorize?client_id=...&response_type=id_token&redirect_uri=http%3A%2F%2Fpage.com%2Fcallback&audience=urn%3Asite%3Ademo%3Ablog&scope=openid%20name%20read%3Ablog&state=456&nonce=123&prompt=none');
+        expect(this.url).to.be('https://me.auth0.com/authorize?client_id=...&response_type=id_token&response_mode=fragment&redirect_uri=http%3A%2F%2Fpage.com%2Fcallback&audience=urn%3Asite%3Ademo%3Ablog&scope=openid%20name%20read%3Ablog&state=456&nonce=123&prompt=none');
         this.timeoutCallback();
       });
 
@@ -355,7 +361,7 @@ describe('auth0.WebAuth', function () {
         expect(data).to.be(undefined);
         expect(err).to.eql({
           error: 'invalid_token',
-          error_description: 'The clientID configured (...) does not match with the clientID set in the token (0HP71GSd6PuoRYJ3DXKdiXCUUdGmBbup).'
+          errorDescription: 'The clientID configured (...) does not match with the clientID set in the token (0HP71GSd6PuoRYJ3DXKdiXCUUdGmBbup).'
         });
         done();
       });