Repository: parse-community/Parse-SDK-JS
Pull request #1875 (2023-04-28 03:08:13)

feat: Add `Parse.User.loginAs`
## Pull Request

- Report security issues [confidentially](https://github.com/parse-community/Parse-SDK-JS/security/policy).
- Any contribution is under this [license](https://github.com/parse-community/Parse-SDK-JS/blob/alpha/LICENSE).
- Link this pull request to an [issue](https://github.com/parse-community/Parse-SDK-JS/issues?q=is%3Aissue).

## Issue
<!-- Add the link to the issue that this PR closes. -->
Later versions of Parse Server https://github.com/parse-community/parse-server/pull/7406. Given a userId and the master key, one can become logged in as the user associated to the userId provided. This PR adds the PHP Parse SDK method to call the Parse Server endpoint.

- [x] Add tests
- [x] Add changes to documentation (guides, repository pages, code comments)


parse-github-assistant[bot]: 
### Thanks for opening this pull request!

- ❌ Please link an issue that describes the reason for this pull request, otherwise your pull request will be closed. Make sure to write it as `Closes: #123` in the PR description, so I can recognize it.

<!-- parse-issue-bot-meta-tag-id {"requireLinkedIssue":true} -->


codecov[bot]: ## [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1875?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) Report
Patch coverage: **`100.00`**% and no project coverage change.
> Comparison is base [(`24c03dd`)](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/24c03dd3234c2387920cb002f55a3d91bf9763b8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) 99.88% compared to head [(`470b967`)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1875?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) 99.88%.

> :exclamation: Current head 470b967 differs from pull request most recent head 5c13ad6. Consider uploading reports for the commit 5c13ad6 to get more accurate results

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##            alpha    #1875   +/-   ##
=======================================
  Coverage   99.88%   99.88%           
=======================================
  Files          61       61           
  Lines        6117     6131   +14     
  Branches     1485     1487    +2     
=======================================
+ Hits         6110     6124   +14     
  Misses          7        7           
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1875?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community) | Coverage Δ | |
|---|---|---|
| [src/ParseUser.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1875?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community#diff-c3JjL1BhcnNlVXNlci5qcw==) | `99.60% <100.00%> (+0.01%)` | :arrow_up: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/1875?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=parse-community).


Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index 058fd6f..dc4a0b0 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -42,6 +42,7 @@
     "jsdoc/require-param-description": 0,
     "jsdoc/require-property-description": 0,
     "jsdoc/require-param-type": 0,
+    "jsdoc/tag-lines": 0,
     "jsdoc/check-param-names": [
       "error",
       {
diff --git a/src/ParseUser.js b/src/ParseUser.js
index 1bd9c37..d2014f1 100644
--- a/src/ParseUser.js
+++ b/src/ParseUser.js
@@ -653,6 +653,25 @@ class ParseUser extends ParseObject {
     return user.logIn(options);
   }
 
+  /**
+   * Logs in a user with an objectId. On success, this saves the session
+   * to disk, so you can retrieve the currently logged in user using
+   * <code>current</code>.
+   *
+   * @param {string} userId The objectId for the user.
+   * @static
+   * @returns {Promise} A promise that is fulfilled with the user when
+   *     the login completes.
+   */
+  static loginAs(userId: string) {
+    if (!userId) {
+      throw new ParseError(ParseError.USERNAME_MISSING, 'Cannot log in as user with an empty user id');
+    }
+    const controller = CoreManager.getUserController();
+    const user = new this();
+    return controller.loginAs(user, userId);
+  }
+
   /**
    * Logs in a user with a session token. On success, this saves the session
    * to disk, so you can retrieve the currently logged in user using
@@ -1097,6 +1116,18 @@ const DefaultController = {
     );
   },
 
+  loginAs(user: ParseUser, userId: string): Promise<ParseUser> {
+    const RESTController = CoreManager.getRESTController();
+    return RESTController.request('POST', 'loginAs', { userId }, { useMasterKey: true }).then(response => {
+      user._finishFetch(response);
+      user._setExisted(true);
+      if (!canUseCurrentUser) {
+        return Promise.resolve(user);
+      }
+      return DefaultController.setCurrentUser(user);
+    });
+  },
+
   become(user: ParseUser, options: RequestOptions): Promise<ParseUser> {
     const RESTController = CoreManager.getRESTController();
     return RESTController.request('GET', 'users/me', {}, options).then(response => {


Test patch:
diff --git a/integration/test/ParseUserTest.js b/integration/test/ParseUserTest.js
index 64b6f2b..2704c34 100644
--- a/integration/test/ParseUserTest.js
+++ b/integration/test/ParseUserTest.js
@@ -148,6 +148,27 @@ describe('Parse User', () => {
     expect(sessions[1].get('sessionToken')).toBe(installationUser.getSessionToken());
   });
 
+  it('can login with userId', async () => {
+    Parse.User.enableUnsafeCurrentUser();
+
+    const user = await Parse.User.signUp('parsetest', 'parse', { code: 'red' });
+    assert.equal(Parse.User.current(), user);
+    await Parse.User.logOut();
+    assert(!Parse.User.current());
+
+    const newUser = await Parse.User.loginAs(user.id);
+    assert.equal(Parse.User.current(), newUser);
+    assert(newUser);
+    assert.equal(user.id, newUser.id);
+    assert.equal(user.get('code'), 'red');
+
+    await Parse.User.logOut();
+    assert(!Parse.User.current());
+    await expectAsync(Parse.User.loginAs('garbage')).toBeRejectedWithError(
+      'user not found'
+    );
+  });
+
   it('can become a user', done => {
     Parse.User.enableUnsafeCurrentUser();
     let session = null;
@@ -782,6 +803,16 @@ describe('Parse User', () => {
     expect(user.doSomething()).toBe(5);
   });
 
+  it('can loginAs user with subclass static', async () => {
+    Parse.User.enableUnsafeCurrentUser();
+
+    let user = await CustomUser.signUp('username', 'password');
+
+    user = await CustomUser.loginAs(user.id);
+    expect(user instanceof CustomUser).toBe(true);
+    expect(user.doSomething()).toBe(5);
+  });
+
   it('can get user (me) with subclass static', async () => {
     Parse.User.enableUnsafeCurrentUser();
 
diff --git a/src/__tests__/ParseUser-test.js b/src/__tests__/ParseUser-test.js
index 4f03012..f671149 100644
--- a/src/__tests__/ParseUser-test.js
+++ b/src/__tests__/ParseUser-test.js
@@ -360,6 +360,73 @@ describe('ParseUser', () => {
     });
   });
 
+  it('does not allow loginAs without id', (done) => {
+    try {
+      ParseUser.loginAs(null, null);
+    } catch (e) {
+      expect(e.message).toBe('Cannot log in as user with an empty user id')
+      done();
+    }
+  });
+
+  it('can login as a user with an objectId', async () => {
+    ParseUser.disableUnsafeCurrentUser();
+    ParseUser._clearCache();
+    CoreManager.setRESTController({
+      request(method, path, body, options) {
+        expect(method).toBe('POST');
+        expect(path).toBe('loginAs');
+        expect(body.userId).toBe('uid4');
+        expect(options.useMasterKey).toBe(true);
+
+        return Promise.resolve(
+          {
+            objectId: 'uid4',
+            username: 'username',
+            sessionToken: '123abc',
+          },
+          200
+        );
+      },
+      ajax() {},
+    });
+
+    const user = await ParseUser.loginAs('uid4');
+    expect(user.id).toBe('uid4');
+    expect(user.isCurrent()).toBe(false);
+    expect(user.existed()).toBe(true);
+  });
+
+  it('can loginAs a user with async storage', async () => {
+    const currentStorage = CoreManager.getStorageController();
+    CoreManager.setStorageController(mockAsyncStorage);
+    ParseUser.enableUnsafeCurrentUser();
+    ParseUser._clearCache();
+    CoreManager.setRESTController({
+      request(method, path, body, options) {
+        expect(method).toBe('POST');
+        expect(path).toBe('loginAs');
+        expect(body.userId).toBe('uid5');
+        expect(options.useMasterKey).toBe(true);
+        return Promise.resolve(
+          {
+            objectId: 'uid5',
+            username: 'username',
+            sessionToken: '123abc',
+          },
+          200
+        );
+      },
+      ajax() {},
+    });
+
+    const user = await ParseUser.loginAs('uid5');
+    expect(user.id).toBe('uid5');
+    expect(user.isCurrent()).toBe(true);
+    expect(user.existed()).toBe(true);
+    CoreManager.setStorageController(currentStorage);
+  });
+
   it('can become a user with a session token', done => {
     ParseUser.enableUnsafeCurrentUser();
     ParseUser._clearCache();