Repository: auth0/auth0.js
Pull request #949 (2019-06-12 19:13:50)

Add method to patch root user attriutes
closes https://github.com/auth0/auth0.js/issues/946

### Changes

Add method to patch root user attributes

### Testing
[x] This change adds unit test coverage



Patch:
diff --git a/README.md b/README.md
index f7ea3d2..404211a 100644
--- a/README.md
+++ b/README.md
@@ -50,12 +50,13 @@ Provides support for all the authentication flows.
 
 ```js
 var auth0 = new auth0.WebAuth({
-  domain: "{YOUR_AUTH0_DOMAIN}",
-  clientID: "{YOUR_AUTH0_CLIENT_ID}"
+  domain: '{YOUR_AUTH0_DOMAIN}',
+  clientID: '{YOUR_AUTH0_CLIENT_ID}'
 });
 ```
 
 Parameters:
+
 - **domain {REQUIRED, string}**: Your Auth0 account domain such as `'example.auth0.com'` or `'example.eu.auth0.com'`.
 - **clientID {REQUIRED, string}**: The Client ID found on your Application settings page.
 - **redirectUri {OPTIONAL, string}**: The URL where Auth0 will call back to with the result of a successful or failed authentication. It must be whitelisted in the "Allowed Callback URLs" in your Auth0 Application's settings.
@@ -63,12 +64,12 @@ Parameters:
 - **audience {OPTIONAL, string}**: The default audience, used if requesting access to an API.
 - **responseType {OPTIONAL, string}**: Response type for all authentication requests. It can be any space separated list of the values `code`, `token`, `id_token`. **If you don't provide a global `responseType`, you will have to provide a `responseType` for each method that you use**.
 - **responseMode {OPTIONAL, string}**: The default responseMode used, defaults to `'fragment'`. The `parseHash` method can be used to parse authentication responses using fragment response mode. Supported values are `query`, `fragment` and `form_post`. The `query` value is only supported when `responseType` is `code`.
-- **_disableDeprecationWarnings {OPTIONAL, boolean}**: Indicates if deprecation warnings should be output to the browser console, defaults to `false`.
+- **\_disableDeprecationWarnings {OPTIONAL, boolean}**: Indicates if deprecation warnings should be output to the browser console, defaults to `false`.
 
 ### API
 
 - **authorize(options)**: Redirects to the `/authorize` endpoint to start an authentication/authorization transaction.
-Auth0 will call back to your application with the results at the specified `redirectUri`. **The default scope for this method is `openid profile email`**.
+  Auth0 will call back to your application with the results at the specified `redirectUri`. **The default scope for this method is `openid profile email`**.
 
 ```js
 auth0.authorize({
@@ -102,15 +103,18 @@ auth0.parseHash({ hash: window.location.hash }, function(err, authResult) {
 ```
 
 - **checkSession(options, callback)**: Allows you to acquire a new token from Auth0 for a user who already has an SSO session established against Auth0 for your domain. If the user is not authenticated, the authentication result will be empty and you'll receive an error like this: `{error: 'login_required'}`.The method accepts any valid OAuth2 parameters that would normally be sent to `/authorize`.
-Everything happens inside an iframe, so it will not reload your application or redirect away from it.
+  Everything happens inside an iframe, so it will not reload your application or redirect away from it.
 
 ```js
-auth0.checkSession({
-  audience: 'https://mystore.com/api/v2',
-  scope: 'read:order write:order'
-  }, function (err, authResult) {
+auth0.checkSession(
+  {
+    audience: 'https://mystore.com/api/v2',
+    scope: 'read:order write:order'
+  },
+  function(err, authResult) {
     // Authentication tokens or error
-});
+  }
+);
 ```
 
 The contents of `authResult` are identical to those returned by `parseHash()`.
@@ -124,15 +128,18 @@ Remember to add the URL where the authorization request originates from to the A
 - **client.login(options, callback)**: Authenticates a user with username and password in a realm using `/oauth/token`. This will not initialize a SSO session at Auth0, hence can not be used along with silent authentication.
 
 ```js
-auth0.client.login({
-  realm: 'Username-Password-Authentication', //connection name or HRD domain
-  username: 'info@auth0.com',
-  password: 'areallystrongpassword',
-  audience: 'https://mystore.com/api/v2',
-  scope: 'read:order write:order',
-  }, function(err, authResult) {
+auth0.client.login(
+  {
+    realm: 'Username-Password-Authentication', //connection name or HRD domain
+    username: 'info@auth0.com',
+    password: 'areallystrongpassword',
+    audience: 'https://mystore.com/api/v2',
+    scope: 'read:order write:order'
+  },
+  function(err, authResult) {
     // Auth tokens in the result or an error
-});
+  }
+);
 ```
 
 The contents of `authResult` are identical to those returned by `parseHash()`.
@@ -145,8 +152,8 @@ Provides an API client for the Auth0 Authentication API.
 
 ```js
 var auth0 = new auth0.Authentication({
-  domain: "{YOUR_AUTH0_DOMAIN}",
-  clientID: "{YOUR_AUTH0_CLIENT_ID}"
+  domain: '{YOUR_AUTH0_DOMAIN}',
+  clientID: '{YOUR_AUTH0_CLIENT_ID}'
 });
 ```
 
@@ -167,8 +174,8 @@ Provides an API Client for the Auth0 Management API (only methods meant to be us
 
 ```js
 var auth0 = new auth0.Management({
-  domain: "{YOUR_AUTH0_DOMAIN}",
-  token: "{ACCESS_TOKEN_FROM_THE_USER}"
+  domain: '{YOUR_AUTH0_DOMAIN}',
+  token: '{ACCESS_TOKEN_FROM_THE_USER}'
 });
 ```
 
@@ -176,11 +183,11 @@ var auth0 = new auth0.Management({
 
 - **getUser(userId, cb)**: Returns the user profile. https://auth0.com/docs/api/management/v2#!/Users/get_users_by_id
 - **patchUserMetadata(userId, userMetadata, cb)**: Updates the user metadata. It will patch the user metadata with the attributes sent. https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id
+- **patchUserAttributes(userId, user, cb)**: Updates the user attributes. It will patch the root attributes that the server allows it. To check what attributes can be patched, go to https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id
 - **linkUser(userId, secondaryUserToken, cb)**: Link two users. https://auth0.com/docs/api/management/v2#!/Users/post_identities
 
 ## Documentation
 
-
 For a complete reference and examples please check our [docs](https://auth0.com/docs/libraries/auth0js).
 
 ## Migration
diff --git a/src/management/index.js b/src/management/index.js
index 0b78cca..360d894 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -91,6 +91,30 @@ Management.prototype.patchUserMetadata = function(userId, userMetadata, cb) {
     .end(responseHandler(cb, { ignoreCasing: true }));
 };
 
+/**
+ * Updates the user attributes. It will patch the user attributes that the server allows it.
+ *
+ * @method patchUserAttributes
+ * @param {String} userId
+ * @param {Object} user
+ * @param {userCallback} cb
+ * @see   {@link https://auth0.com/docs/api/management/v2#!/Users/patch_users_by_id}
+ */
+Management.prototype.patchUserAttributes = function(userId, user, cb) {
+  var url;
+
+  assert.check(userId, { type: 'string', message: 'userId parameter is not valid' });
+  assert.check(user, { type: 'object', message: 'user parameter is not valid' });
+  assert.check(cb, { type: 'function', message: 'cb parameter is not valid' });
+
+  url = urljoin(this.baseOptions.rootUrl, 'users', userId);
+
+  return this.request
+    .patch(url)
+    .send(user)
+    .end(responseHandler(cb, { ignoreCasing: true }));
+};
+
 /**
  * Link two users
  *


Test patch:
diff --git a/test/management/management.test.js b/test/management/management.test.js
index 89a7cbc..033f624 100644
--- a/test/management/management.test.js
+++ b/test/management/management.test.js
@@ -186,6 +186,88 @@ describe('auth0.Management', function() {
     });
   });
 
+  context('patchUserAttributes options', function() {
+    before(function() {
+      this.auth0 = new Management({
+        domain: 'me.auth0.com',
+        token: '...',
+        _sendTelemetry: false
+      });
+    });
+
+    it('should check that userId is valid', function() {
+      expect(() => {
+        this.auth0.patchUserAttributes();
+      }).to.throwException(function(e) {
+        expect(e.message).to.be('userId parameter is not valid');
+      });
+    });
+
+    it('should check that user is valid', function() {
+      expect(() => {
+        this.auth0.patchUserAttributes('...');
+      }).to.throwException(function(e) {
+        expect(e.message).to.be('user parameter is not valid');
+      });
+    });
+
+    it('should check that cb is valid', function() {
+      expect(() => {
+        this.auth0.patchUserAttributes('...', {});
+      }).to.throwException(function(e) {
+        expect(e.message).to.be('cb parameter is not valid');
+      });
+    });
+  });
+
+  context('patchUserAttributes', function() {
+    before(function() {
+      this.auth0 = new Management({
+        domain: 'me.auth0.com',
+        token: 'the_token',
+        _sendTelemetry: false
+      });
+    });
+
+    afterEach(function() {
+      request.patch.restore();
+    });
+
+    it('should fetch the user from the api', function(done) {
+      stub(request, 'patch', function(url) {
+        expect(url).to.be('https://me.auth0.com/api/v2/users/auth0|123');
+        return new RequestMock({
+          body: {
+            name: 'test name'
+          },
+          headers: {
+            'Content-Type': 'application/json',
+            Authorization: 'Bearer the_token'
+          },
+          cb: function(cb) {
+            cb(null, {
+              body: {
+                user_id: 'auth0|123',
+                email: 'me@example.com',
+                name: 'test name'
+              }
+            });
+          }
+        });
+      });
+
+      this.auth0.patchUserAttributes('auth0|123', { name: 'test name' }, function(err, user) {
+        expect(err).to.be(null);
+        expect(user).to.eql({
+          user_id: 'auth0|123',
+          email: 'me@example.com',
+          name: 'test name'
+        });
+        done();
+      });
+    });
+  });
+
   context('linkUsers options', function() {
     before(function() {
       this.auth0 = new Management({