parse-community / parse-server

Parse Server for Node.js / Express
https://parseplatform.org
Apache License 2.0
20.84k stars 4.77k forks source link

Parse.FacebookUtils.logIn is creating new users when Facebook ID already exists. #4897

Closed tai-x closed 6 years ago

tai-x commented 6 years ago

We have been using a very simple flow to log in users using Facebook that has been working for almost a year and a half, and now it has stopped working.

We use Parse.FacebookUtils.logIn to log in an existing user, or creating a new one.

var facebookAuthData = {
    "id": <fb_id>,
    "access_token": <fb_access_token>,
    "expiration_date": <fb_expiration_date>
};
Parse.FacebookUtils.logIn(facebookAuthData, {
    success: function(user) {
        if (user.isNew()) {
            // New user....
        } else {
            // Old user...
        }
    },
    error: ...
});

But it's happening that although there is an existing user in the database with the exact that we pass to Parse.FacebookUtils.logIn(), it is creating a new user instead of login the existing user. Furthermore, this "new" user is created with a weird/random "username" in the database.

Some more details:

I've tried for days trying to understand why this is happening with no luck. Any help appreciated, especially since this one reached our Production environment and previously FB logged users are being locked out.

Thanks in advance for the help.

cbaker6 commented 6 years ago

I have this same problem with FacebookUtils and TwitterUtils whenever I use Parse Server 2.8 or higher. On ParseServer 2.74 and below, I don't have this issue. As a note, I use the Parse SDK for iOS, version 1.17.1. To further clarify, this issue occurs with users who had created or linked their account using an older Parse Server version. I haven't verified if it for new facebook or twitter users.

I haven't looked at the code, but it seems like FacebookUtils and TwitterUtils doesn't query authData correctly for facebookId and twitterId for duplicates anymore and instead just adds another facebookId and twitterId, even if it's already in the _User table.

tai-x commented 6 years ago

As per @cbaker6 comment, I re-tested using Parse 2.7.2, and the issue does not happen. Confirmed in 2.8.2.

flovilmart commented 6 years ago

Are you able to provide some verbose logs for the issue, as well as potentially a way to consistently reproduce it (including database etc...) is uou’re a le to reproduce the issue in a failing test this would greatly help as well. Otherwise we’ll still look into it.

One last thing, can you check/ share with us which indexes exist on the _User collection?

tai-x commented 6 years ago

Extract of our package.json:

{
  "dependencies": {
    "@parse/simple-mailgun-adapter": "^1.0.2",
    "axios": "^0.18.0",
    "bcryptjs": "^2.4.3",
    "express": "~4.16.x",
    "kerberos": "^0.0.24",
    "lodash": "^4.17.10",
    "moment": "^2.22.2",
    "nan": "^2.10.0",
    "node-gyp": ">=3.7.0",
    "node-pre-gyp": "^0.10.2",
    "npm": ">=6.1.0",
    "parse": "~1.11.1",
    "parse-dashboard": "~1.2.0",
    "parse-server": "~2.8.2",
    "pre-commit": "^1.2.2",
    "request": "^2.87.0",
    "request-promise": "^4.2.2",
    "semver": "^5.5.0",
    "socks": "~2.2.0",
    "underscore": "~1.9.1"
  },
  "scripts": {
    "start": "node index.js",
    ...
  },
  "engines": {
    "node": ">=10.0.0"
  }
}

Here is the log when Parse is deployed (kerberos compiles with warnings):

$ git push heroku v1.8.2:master -f
Total 0 (delta 0), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Node.js app detected
remote:
remote: -----> Creating runtime environment
remote:
remote:        NPM_CONFIG_LOGLEVEL=error
remote:        NODE_VERBOSE=false
remote:        NODE_ENV=production
remote:        NODE_MODULES_CACHE=false
remote:
remote: -----> Installing binaries
remote:        engines.node (package.json):  >=10.0.0
remote:        engines.npm (package.json):   unspecified (use default)
remote:
remote:        Resolving node version >=10.0.0...
remote:        Downloading and installing node 10.6.0...
remote:        Using default npm version: 6.1.0
remote:
remote: -----> Restoring cache
remote:        Skipping cache restore (disabled)
remote:
remote: -----> Building dependencies
remote:        Installing node modules (package.json + package-lock)
remote:
remote:        > bcrypt@2.0.1 install /tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/bcrypt
remote:        > node-pre-gyp install --fallback-to-build
remote:
remote:        [bcrypt] Success: "/tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/bcrypt/lib/binding/bcrypt_lib.node" is installed via remote
remote:
remote:        > kerberos@0.0.24 install /tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/kerberos
remote:        > (node-gyp rebuild) || (exit 0)
remote:
remote:        make: Entering directory '/tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/kerberos/build'
remote:        CXX(target) Release/obj.target/kerberos/lib/kerberos.o
remote:        ../lib/kerberos.cc: In static member function ‘static void Kerberos::After(uv_work_t*)’:
remote:        ../lib/kerberos.cc:948:50: warning: ‘v8::Local<v8::Value> Nan::Callback::Call(int, v8::Local<v8::Value>*) const’ is deprecated [-Wdeprecated-declarations]
remote:        worker->callback->Call(ARRAY_SIZE(info), info);
remote:        ^
remote:        In file included from ../lib/kerberos.h:9:0,
remote:        from ../lib/kerberos.cc:1:
remote:        ../../nan/nan.h:1618:3: note: declared here
remote:        Call(int argc, v8::Local<v8::Value> argv[]) const {
remote:        ^
remote:        ../lib/kerberos.cc:972:50: warning: ‘v8::Local<v8::Value> Nan::Callback::Call(int, v8::Local<v8::Value>*) const’ is deprecated [-Wdeprecated-declarations]
remote:        worker->callback->Call(ARRAY_SIZE(info), info);
remote:        ^
remote:        In file included from ../lib/kerberos.h:9:0,
remote:        from ../lib/kerberos.cc:1:
remote:        ../../nan/nan.h:1618:3: note: declared here
remote:        Call(int argc, v8::Local<v8::Value> argv[]) const {
remote:        ^
remote:        CXX(target) Release/obj.target/kerberos/lib/worker.o
remote:        CC(target) Release/obj.target/kerberos/lib/kerberosgss.o
remote:        ../lib/kerberosgss.c:36:0: warning: ignoring #pragma clang diagnostic [-Wunknown-pragmas]
remote:        #pragma clang diagnostic push
remote:        ^
remote:        ../lib/kerberosgss.c:37:0: warning: ignoring #pragma clang diagnostic [-Wunknown-pragmas]
remote:        #pragma clang diagnostic ignored "-Wdeprecated-declarations"
remote:        ^
remote:        ../lib/kerberosgss.c: In function ‘authenticate_gss_client_wrap’:
remote:        ../lib/kerberosgss.c:446:19: warning: variable ‘server_conf_flags’ set but not used [-Wunused-but-set-variable]
remote:        char buf[4096], server_conf_flags;
remote:        ^
remote:        ../lib/kerberosgss.c: At top level:
remote:        ../lib/kerberosgss.c:1236:0: warning: ignoring #pragma clang diagnostic [-Wunknown-pragmas]
remote:        #pragma clang diagnostic pop
remote:        ^
remote:        CC(target) Release/obj.target/kerberos/lib/base64.o
remote:        CXX(target) Release/obj.target/kerberos/lib/kerberos_context.o
remote:        SOLINK_MODULE(target) Release/obj.target/kerberos.node
remote:        COPY Release/kerberos.node
remote:        make: Leaving directory '/tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/kerberos/build'
remote:
remote:        > pre-commit@1.2.2 install /tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/pre-commit
remote:        > node install.js
remote:
remote:
remote:        > spawn-sync@1.0.15 postinstall /tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/spawn-sync
remote:        > node postinstall
remote:
remote:
remote:        > nodemon@1.17.5 postinstall /tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/nodemon
remote:        > node bin/postinstall || exit 0
remote:
remote:        Love nodemon? You can now support the project via the open collective:
remote:        > https://opencollective.com/nodemon/donate
remote:
remote:
remote:        > parse-server@2.8.2 postinstall /tmp/build_bd81344b7effebfd0db2762cdf22abea/node_modules/parse-server
remote:        > node -p 'require("./postinstall.js")()'
remote:
remote:
remote:        1111111111
remote:        1111111111111111
remote:        1111111111111111111111
remote:        11111111111111111111111111
remote:        111111111111111       11111111
remote:        1111111111111             111111
remote:        1111111111111   111111111   111111
remote:        111111111111   11111111111   111111
remote:        1111111111111   11111111111   111111
remote:        1111111111111   1111111111    111111
remote:        1111111111111111111111111    1111111
remote:        11111111                    11111111
remote:        111111         1111111111111111111
remote:        11111   11111  111111111111111111
remote:        11111         11111111111111111
remote:        111111     111111111111111111
remote:        11111111111111111111111111
remote:        1111111111111111111111
remote:        111111111111111111
remote:        11111111111
remote:
remote:
remote:        Thanks for installing parse 🙏
remote:        Please consider donating to our open collective
remote:        to help us maintain this package.
remote:
remote:        👉 https://opencollective.com/parse-server
remote:
remote:        added 1423 packages from 1491 contributors and audited 8063 packages in 36.955s
remote:        found 8 vulnerabilities (4 low, 4 moderate)
remote:        run `npm audit fix` to fix them, or `npm audit` for details
remote:
remote: -----> Caching build
remote:        Clearing previous node cache
remote:        Skipping cache save (disabled by config)
remote:
remote: -----> Pruning devDependencies
remote:        removed 220 packages and audited 6620 packages in 8.974s
remote:        found 7 vulnerabilities (3 low, 4 moderate)
remote:        run `npm audit fix` to fix them, or `npm audit` for details
remote:
remote: -----> Build succeeded!
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote:        Done: 45.1M
remote: -----> Launching...
remote:        Released v143
remote:        <my heroku app> deployed to Heroku
remote:
remote: Verifying deploy... done.
To <my heroku remote repo>
 + 7905cfe...9915b97 v1.8.2 -> master (forced update)

When the existing user tries to logging to facebook (a user that already has a FB auth data well defined), the Parse.FacebookUtils.logIn() utility will return a new user with random username (expressed in the log below: Q2NGibf9AGcl3W7s6Ud881TiX), instead of returning the existing one which has the same authData.facebook.id. Note that the "Account already exists for this email address." happens after I have received the undesired new user and assign it an email. This is what it logged during the FB login attempt:

2018-07-17T06:29:25.742808+00:00 app[web.1]: verbose: REQUEST for [POST] /users: {
2018-07-17T06:29:25.742822+00:00 app[web.1]:   "authData": {
2018-07-17T06:29:25.742824+00:00 app[web.1]:     "facebook": {
2018-07-17T06:29:25.742826+00:00 app[web.1]:       "id": "<fb_id>",
2018-07-17T06:29:25.742831+00:00 app[web.1]:       "access_token": "<fb_token>",
2018-07-17T06:29:25.742833+00:00 app[web.1]:       "expiration_date": "2026-08-17T17:35:02.659Z"
2018-07-17T06:29:25.742835+00:00 app[web.1]:     }
2018-07-17T06:29:25.742836+00:00 app[web.1]:   }
2018-07-17T06:29:25.742838+00:00 app[web.1]: } method=POST, url=/users, host=<my_heroku_app>, connection=close, content-type=text/plain, origin=file://, if-none-match=W/"3b7-v0XpDL5Ht2PFXiJZ4QnlSoxPaOI", accept=*/*, user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79, accept-language=es-es, accept-encoding=br, gzip, deflate, x-request-id=81938098-5cdc-4be0-960e-0b9a2f486dbb, x-forwarded-for=<ip>, x-forwarded-proto=https, x-forwarded-port=443, via=1.1 vegur, connect-time=1, x-request-start=1531808965738, total-route-time=0, content-length=406, id=<fb_id>, access_token=<fb_token>, expiration_date=2026-08-17T17:35:02.659Z
2018-07-17T06:29:25.837790+00:00 app[web.1]: verbose: RESPONSE from [POST] /users: {
2018-07-17T06:29:25.837795+00:00 app[web.1]:   "status": 201,
2018-07-17T06:29:25.837797+00:00 app[web.1]:   "response": {
2018-07-17T06:29:25.837799+00:00 app[web.1]:     "objectId": "9Rrk8Xn8HJ",
2018-07-17T06:29:25.837804+00:00 app[web.1]:     "createdAt": "2018-07-17T06:29:25.745Z",
2018-07-17T06:29:25.837806+00:00 app[web.1]:     "username": "Q2NGibf9AGcl3W7s6Ud881TiX",
2018-07-17T06:29:25.837808+00:00 app[web.1]:     "sessionToken": "r:<some_session_token>"
2018-07-17T06:29:25.837809+00:00 app[web.1]:   },
2018-07-17T06:29:25.837811+00:00 app[web.1]:   "location": "https://<my_heroku_app>/users/9Rrk8Xn8HJ"
2018-07-17T06:29:25.837814+00:00 app[web.1]: } status=201, objectId=9Rrk8Xn8HJ, createdAt=2018-07-17T06:29:25.745Z, username=Q2NGibf9AGcl3W7s6Ud881TiX, sessionToken=r:<some_session_token>, location=https://<my_heroku_app>/users/9Rrk8Xn8HJ
2018-07-17T06:29:25.839443+00:00 heroku[router]: at=info method=POST path="/users" host=<my_heroku_app> request_id=81938098-5cdc-4be0-960e-0b9a2f486dbb fwd="<ip>" dyno=web.1 connect=1ms service=99ms status=201 bytes=751 protocol=https
2018-07-17T06:29:26.023821+00:00 app[web.1]: verbose: REQUEST for [PUT] /classes/_User/9Rrk8Xn8HJ: {
2018-07-17T06:29:26.023839+00:00 app[web.1]:   "nombre": "<some_name>",
2018-07-17T06:29:26.023841+00:00 app[web.1]:   "email": "<some_email>",
2018-07-17T06:29:26.023843+00:00 app[web.1]:   "imageUrl": "<some_img_url>",
2018-07-17T06:29:26.023845+00:00 app[web.1]:   "genero": "F"
2018-07-17T06:29:26.023847+00:00 app[web.1]: } method=PUT, url=/classes/_User/9Rrk8Xn8HJ, host=<my_heroku_app>, connection=close, content-type=text/plain, origin=file://, accept=*/*, user-agent=Mozilla/5.0 (iPhone; CPU iPhone OS 11_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15F79, accept-language=es-es, accept-encoding=br, gzip, deflate, x-request-id=1301cc6b-158f-4a4c-82e1-0f1343c83ac2, x-forwarded-for=186.15.145.21, x-forwarded-proto=https, x-forwarded-port=443, via=1.1 vegur, connect-time=1, x-request-start=1531808966003, total-route-time=0, content-length=410, nombre=<some_name>, email=<some_email>, imageUrl=<some_img_url>, genero=F
2018-07-17T06:29:26.052244+00:00 app[web.1]: error: Error generating response. ParseError {
2018-07-17T06:29:26.052247+00:00 app[web.1]:   code: 203,
2018-07-17T06:29:26.052252+00:00 app[web.1]:   message: 'Account already exists for this email address.' } code=203, message=Account already exists for this email address.
2018-07-17T06:29:26.053969+00:00 app[web.1]: error: Account already exists for this email address. code=203, message=Account already exists for this email address.
2018-07-17T06:29:26.054682+00:00 heroku[router]: at=info method=POST path="/classes/_User/9Rrk8Xn8HJ" host=<my_heroku_app> request_id=1301cc6b-158f-4a4c-82e1-0f1343c83ac2 fwd="<ip>" dyno=web.1 connect=1ms service=49ms status=400 bytes=602 protocol=https

I cannot share my environment, but I can confirm that the issue can be reproduced consistently with Parse v2.8.2, just let me know how I can help. Right now I've deployed 2.7.4 to avoid the issue in Production.

Some notes about the environment:

{
  "_id": "<some_id>",
  "version": 3,
  "members": [
    {
      "_id": 0,
      "host": "<some_host>",
      "arbiterOnly": false,
      "buildIndexes": true,
      "hidden": false,
      "priority": 1,
      "tags": {},
      "slaveDelay": 0,
      "votes": 1
    },
    {
      "_id": 1,
      "host": "<some_host>",
      "arbiterOnly": false,
      "buildIndexes": true,
      "hidden": false,
      "priority": 1,
      "tags": {},
      "slaveDelay": 0,
      "votes": 1
    },
    {
      "_id": 10,
      "host": "<some_host>",
      "arbiterOnly": true,
      "buildIndexes": true,
      "hidden": false,
      "priority": 1,
      "tags": {},
      "slaveDelay": 0,
      "votes": 1
    }
  ],
  "settings": {
    "chainingAllowed": true,
    "heartbeatTimeoutSecs": 10,
    "getLastErrorModes": {},
    "getLastErrorDefaults": {
      "w": 1,
      "wtimeout": 0
    }
  }
}
{
  "set": "rs-<some_id>",
  "date": {
    "$date": "2018-07-17T06:49:04.723Z"
  },
  "myState": 1,
  "members": [
    {
      "_id": 0,
      "name": "<some_name>",
      "health": 1,
      "state": 1,
      "stateStr": "PRIMARY",
      "uptime": 75758,
      "optime": {
        "$ts": 1531809010,
        "$inc": 1
      },
      "optimeDate": {
        "$date": "2018-07-17T06:30:10.000Z"
      },
      "electionTime": {
        "$ts": 1531734394,
        "$inc": 1
      },
      "electionDate": {
        "$date": "2018-07-16T09:46:34.000Z"
      },
      "configVersion": 3,
      "self": true,
      "serverId": "<some_server_id>"
    },
    {
      "_id": 1,
      "name": "<some_name>",
      "health": 1,
      "state": 2,
      "stateStr": "SECONDARY",
      "uptime": 75757,
      "optime": {
        "$ts": 1531809010,
        "$inc": 1
      },
      "optimeDate": {
        "$date": "2018-07-17T06:30:10.000Z"
      },
      "lastHeartbeat": {
        "$date": "2018-07-17T06:49:03.133Z"
      },
      "lastHeartbeatRecv": {
        "$date": "2018-07-17T06:49:03.448Z"
      },
      "pingMs": 0,
      "syncingTo": "<some_name>",
      "configVersion": 3,
      "serverId": "<some_name>"
    },
    {
      "_id": 10,
      "name": "<some_name>,
      "health": 1,
      "state": 7,
      "stateStr": "ARBITER",
      "uptime": 75757,
      "lastHeartbeat": {
        "$date": "2018-07-17T06:49:04.078Z"
      },
      "lastHeartbeatRecv": {
        "$date": "2018-07-17T06:49:03.854Z"
      },
      "pingMs": 16,
      "configVersion": 3,
      "serverId": "<some_name>"
    }
  ],
  "ok": 1
}

The _User collection has 1 index:

db['system.indexes'].find() Raw Output
{
  "v": 1,
  "key": {
    "_id": 1
  },
  "name": "_id_",
  "ns": "<some_name>._User"
}
flovilmart commented 6 years ago

That’s : odd that only one index on the_ User table is there (on id) as on startup, the server should create more indexes on the DB. Is it possible for you pull out the first logs after the server starts?

tai-x commented 6 years ago

Here are the logs from the server when deploying/starting.


[api]: Build started by user <my_user>
2018-07-17T07:19:59.979141+00:00 app[api]: Release v145 created by user <my_user>
2018-07-17T07:19:59.979141+00:00 app[api]: Deploy <commit> by user <my_user>
2018-07-17T07:20:00.467469+00:00 heroku[web.1]: Restarting
2018-07-17T07:20:00.468014+00:00 heroku[web.1]: State changed from up to starting
2018-07-17T07:20:01.498843+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2018-07-17T07:20:01.692041+00:00 heroku[web.1]: Process exited with status 143
2018-07-17T07:20:00.000000+00:00 app[api]: Build succeeded
2018-07-17T07:20:06.101319+00:00 heroku[web.1]: Starting process with command `npm start`
2018-07-17T07:20:08.662033+00:00 app[web.1]: [heroku-exec] Starting
2018-07-17T07:20:08.983022+00:00 app[web.1]: 
2018-07-17T07:20:08.983125+00:00 app[web.1]: > <parse-server>@1.8.2 start /app
2018-07-17T07:20:08.983136+00:00 app[web.1]: > node index.js
2018-07-17T07:20:08.983257+00:00 app[web.1]: 
2018-07-17T07:20:10.872877+00:00 app[web.1]: verbose: Support key pairs Map {}
2018-07-17T07:20:10.879253+00:00 app[web.1]: (node:49) ExperimentalWarning: The http2 module is an experimental API.
2018-07-17T07:20:10.881139+00:00 app[web.1]: parse-server-example running on port 37266.
2018-07-17T07:20:11.088284+00:00 app[web.1]: warn: Unable to ensure uniqueness for usernames:  code=137, message=Tried to ensure field uniqueness for a class that already has duplicates.
2018-07-17T07:20:11.092748+00:00 app[web.1]: (node:49) UnhandledPromiseRejectionWarning: [object Object]
2018-07-17T07:20:11.092821+00:00 app[web.1]: (node:49) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
2018-07-17T07:20:11.092881+00:00 app[web.1]: (node:49) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
2018-07-17T07:20:11.096851+00:00 app[web.1]: warn: Unable to ensure uniqueness for user email addresses:  code=137, message=Tried to ensure field uniqueness for a class that already has duplicates.
2018-07-17T07:20:11.230664+00:00 heroku[web.1]: State changed from starting to up
2018-07-17T07:20:11.929446+00:00 heroku[router]: at=info method=GET path="/health" host=<my_heroku_app> request_id=b75307c5-d36b-41bd-8bb0-2c6fdf4dc811 fwd="<ip>" dyno=web.1 connect=1ms service=9ms status=200 bytes=538 protocol=https```
flovilmart commented 6 years ago

Yeah, so that means there’s already duplicated entries with the same username in the _User table.

You may want to remove those duplicates and restart the server to ensure all indexes are properly created on startup. While this may not exactly be the root cause, this will help

tai-x commented 6 years ago

I will look up for duplicate usernames. According to our logic there shouldn't be any, but well if it happened it may be from the earlier development times.

Would the "email" duplicate entries also be a problem? This may be related to "anonymous" users (FB log in's without email permissions), and in such case I am not able to fix that.

flovilmart commented 6 years ago

Yes, emails should be unique too, and perhaps the initialization will fail at a later point. Do you have the same log when starting a server against your production DB, with an earlier version of the server?

tai-x commented 6 years ago

Server log deploying version 2.7.4:

2018-07-17T07:59:04.000000+00:00 app[api]: Build started by user <my_user>
2018-07-17T08:00:15.303994+00:00 app[api]: Deploy d2d16e6f by user <my_user>
2018-07-17T08:00:15.303994+00:00 app[api]: Release v150 created by user <my_user>
2018-07-17T08:00:15.000000+00:00 app[api]: Build succeeded
2018-07-17T08:00:17.162696+00:00 heroku[web.1]: Restarting
2018-07-17T08:00:17.163201+00:00 heroku[web.1]: State changed from up to starting
2018-07-17T08:00:18.394267+00:00 heroku[web.1]: Stopping all processes with SIGTERM
2018-07-17T08:00:18.496795+00:00 heroku[web.1]: Process exited with status 143
2018-07-17T08:00:26.253317+00:00 heroku[web.1]: Starting process with command `npm start`
2018-07-17T08:00:29.814129+00:00 app[web.1]: [heroku-exec] Starting
2018-07-17T08:00:30.468972+00:00 app[web.1]: 
2018-07-17T08:00:30.469256+00:00 app[web.1]: > <parse_server>@1.8.3 start /app
2018-07-17T08:00:30.469259+00:00 app[web.1]: > node index.js
2018-07-17T08:00:30.469261+00:00 app[web.1]: 
2018-07-17T08:00:33.855064+00:00 app[web.1]: verbose: Support key pairs Map {}
2018-07-17T08:00:33.885778+00:00 app[web.1]: parse-server-example running on port 38682.
2018-07-17T08:00:33.886590+00:00 app[web.1]: info: Parse LiveQuery Server starts running
2018-07-17T08:00:34.275030+00:00 heroku[web.1]: State changed from starting to up
2018-07-17T08:00:34.598629+00:00 app[web.1]: warn: Unable to ensure uniqueness for usernames:  code=137, message=Tried to ensure field uniqueness for a class that already has duplicates.
2018-07-17T08:00:34.601176+00:00 app[web.1]: (node:49) UnhandledPromiseRejectionWarning: [object Object]
2018-07-17T08:00:34.601351+00:00 app[web.1]: (node:49) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
2018-07-17T08:00:34.601427+00:00 app[web.1]: (node:49) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
2018-07-17T08:00:34.601998+00:00 app[web.1]: warn: Unable to ensure uniqueness for user email addresses:  code=137, message=Tried to ensure field uniqueness for a class that already has duplicates.
2018-07-17T08:00:35.158012+00:00 heroku[router]: at=info method=GET path="/health" host=<my_heroku_app> request_id=613bd60b-84b4-4eb8-b632-4db53e2e4e7d fwd="<ip>" dyno=web.1 connect=1ms service=17ms status=200 bytes=538 protocol=https

When you say that emails should be unique, does it mean that they cannot be left blank? I really don't see an easy way to change that, as for quite a long time we've supported users using FB login and declining email permissions - actually, many people decline this permission. Even if we change the login flow to request an email address in such a case, we already have a lot of users in the database without email. If it should be, probably is better to express that clearly in documentation, or possibly enforce it somehow.

flovilmart commented 6 years ago

They can be left blank. Usernames can’t/ should not.

As I can see in this log, this issue is there as well with 2.7.4, so that’s not it, fixing it should still be done. I’ll check why the original Facebook id can’t be found. Perhaps the unique sparse index can’t be built either on those.

flovilmart commented 6 years ago

@tai-x Another question, can you pull from the DB the raw document for a user that fails to login and another that succeeds in logging in?

CIO-COSINE commented 6 years ago

Hi @tai-x, I have noticed your environment is completely different from the one described in issue #4407 (where you left your initial comment). The issue seems to be the related, perhaps parse-server should not look for duplicate email addresses if the email field provided is null or empty? (since the email field is not required?)

To answer your initial question left in issue #4407, the way I solved my issue was by dropping the "User" Class through the parse dashboard, then creating a new "User" Class by selecting the "User" Class option in the dialog window that appears. Have in mind you would of course need to backup any data and structural changes made to your "User" Class before dropping it.

flovilmart commented 6 years ago

Dropping the user table will destroy the data...

d not look for duplicate email addresses if the email field provided is null or empty

This is mongodb index creation, the email index is sparse so null /undefined is indeed allowed.

cbaker6 commented 6 years ago

Here is the log from Parse-server 2.7.4, this one works correctly by connecting to the old user who has logged in via Facebook using previous versions of Parse:

verbose: REQUEST for [POST] /parse/users: {
  "authData": {
    "facebook": {
      "id": "8379****",
      "access_token": "EAAKn2SZA5kRkBALZAoSglQ2VNrDwhbJZAi9Ehpw4hXdz0YZAsHjASZC3W8PpJ6eoMqSCrz0k8o9OrogdDrbhCYzBTCgiBgMMeAhiO0Hhz5wkGt62t9Dmgj836Km7kiEZATkKub4Q3ZBxxQeQlmKwZB8gfN3rAPNNToTwfZAZA5DoY6TIym32XUBFH1ZA1q3lKzOsZC2d0dGI8yqZBjfZArFASSw*******",
      "expiration_date": "2018-09-15T14:55:14.785Z"
    }
  }
} method=POST, url=/parse/users, host=n*****edu:1337, content-type=application/json; charset=utf-8, x-parse-revocable-session=1, accept=*/*, x-parse-application-id=SBC0DswFA7RdbIQJ8Z6JWkchaZsRbNfvIgg8C2W2, x-parse-installation-id=27b6dfdb-9878-488e-be2e-8849fbae1074, x-parse-os-version=11.4 (15F79), accept-language=en-us, accept-encoding=br, gzip, deflate, x-parse-client-version=i1.17.1, content-length=347, user-agent=AlleyOop/3 CFNetwork/901.1 Darwin/17.6.0, connection=keep-alive, x-parse-app-build-version=3, x-parse-app-display-version=2.04, id=8379****, access_token=EAAKn2SZA5kRkBALZAoSglQ2VNrDwhbJZAi9Ehpw4hXdz0YZAsHjASZC3W8PpJ6eoMqSCrz0k8o9OrogdDrbhCYzBTCgiBgMMeAhiO0Hhz5wkGt62t9Dmgj836Km7kiEZATkKub4Q3ZBxxQeQlmKwZB8gfN3rAPNNToTwfZAZA5DoY6TIym32XUBFH1ZA1q3lKzOsZC2d0dGI8yqZBjfZArFASSw*******, expiration_date=2018-09-15T14:55:14.785Z
verbose: RESPONSE from [POST] /parse/users: {
  "response": {
    "objectId": "YrCVyDcejf",
    "createdAt": "2015-01-02T19:09:59.788Z",
    "updatedAt": "2018-07-17T18:53:50.175Z",
    "email": "c***@ufl.edu",
    "username": "coreyiphone6",
    "usernameLiteral": "CoreyIphone6",
    "displayName": "Corey E Baker",
    "authData": {
      "twitter": {
        "auth_token": "***",
        "id": "***",
        "consumer_key": "** kvKGRA5D59dT*****",
        "auth_token_secret": "*****",
        "consumer_secret": "*****",
        "screen_name": "iKilledChivalry"
      },
      "facebook": {
        "id": "8379****",
        "access_token": "EAAKn2SZA5kRkBALZAoSglQ2VNrDwhbJZAi9Ehpw4hXdz0YZAsHjASZC3W8PpJ6eoMqSCrz0k8o9OrogdDrbhCYzBTCgiBgMMeAhiO0Hhz5wkGt62t9Dmgj836Km7kiEZATkKub4Q3ZBxxQeQlmKwZB8gfN3rAPNNToTwfZAZA5DoY6TIym32XUBFH1ZA1q3lKzOsZC2d0dGI8yqZBjfZArFASSw*******",
        "expiration_date": "2018-09-15T14:55:14.785Z"
      }
    },
    "twitterId": "***",
    "twitterFriends": [
      ""
    ],
    "_failed_login_count": 5,
    "_account_lockout_expires_at": "2017-02-08T23:30:19.899Z",
    "facebookId": "8379****",
    "facebookFriends": [

    ],
    "sessionToken": "r:bbb57dd2954da772a8b9ff09b37f419b"
  },
  "location": "https://n*****edu:1337/parse/users/YrCVyDcejf"
} objectId=YrCVyDcejf, createdAt=2015-01-02T19:09:59.788Z, updatedAt=2018-07-17T18:53:50.175Z, email=c***@ufl.edu, username=coreyiphone6, usernameLiteral=CoreyIphone6, displayName=Corey E Baker, auth_token=***, id=***, consumer_key=** kvKGRA5D59dT*****, auth_token_secret=*****, consumer_secret=*****, screen_name=iKilledChivalry, id=8379****, access_token=EAAKn2SZA5kRkBALZAoSglQ2VNrDwhbJZAi9Ehpw4hXdz0YZAsHjASZC3W8PpJ6eoMqSCrz0k8o9OrogdDrbhCYzBTCgiBgMMeAhiO0Hhz5wkGt62t9Dmgj836Km7kiEZATkKub4Q3ZBxxQeQlmKwZB8gfN3rAPNNToTwfZAZA5DoY6TIym32XUBFH1ZA1q3lKzOsZC2d0dGI8yqZBjfZArFASSw*******, expiration_date=2018-09-15T14:55:14.785Z, twitterId=***, twitterFriends=[2207036778], _failed_login_count=5, _account_lockout_expires_at=Wed Feb 08 2017 18:30:19 GMT-0500 (EST), facebookId=8379****, facebookFriends=[], sessionToken=r:bbb57dd2954da772a8b9ff09b37f419b, location=https://n*****edu:1337/parse/users/YrCVyDcejf

Log from 2.8.2, which creates a new account instead of linking to the old user:

verbose: REQUEST for [POST] /parse/users: {
  "authData": {
    "facebook": {
      "id": "8379****",
      "access_token": "EAAKn2SZA5kRkBADpd9Kwd0JeXWkLVeswsZC5fk5DdST3DfnoOP6Xxiqk1hiTNSXZAhZCcDVxCEeQZCYO6EZCjktZAuxcfkXpDnZB8MqZBxDat7ZA1CqBm3FY1GWUY6PnXP8QDr0bVMCpjf4vZAftOI2I7iOZB9sTsvZBRmQIC3Lgi9Bm6VurrFTeoFQbmzCArEPAZBkXBb7Xq81Smz5aC9XcN0YFh41NZBqtR6**************",
      "expiration_date": "2018-09-15T14:55:15.064Z"
    }
  }
} method=POST, url=/parse/users, host=n*****edu:1337, content-type=application/json; charset=utf-8, x-parse-revocable-session=1, accept=*/*, x-parse-application-id=SBC0DswFA7RdbIQJ8Z6JWkchaZsRbNfvIgg8C2W2, x-parse-installation-id=b5ff282d-a2bc-4566-ac03-31730d80798f, x-parse-os-version=11.4 (15F79), accept-language=en-us, accept-encoding=br, gzip, deflate, x-parse-client-version=i1.17.1, content-length=347, user-agent=AlleyOop/3 CFNetwork/901.1 Darwin/17.6.0, connection=keep-alive, x-parse-app-build-version=3, x-parse-app-display-version=2.04, id=8379****, access_token=EAAKn2SZA5kRkBADpd9Kwd0JeXWkLVeswsZC5fk5DdST3DfnoOP6Xxiqk1hiTNSXZAhZCcDVxCEeQZCYO6EZCjktZAuxcfkXpDnZB8MqZBxDat7ZA1CqBm3FY1GWUY6PnXP8QDr0bVMCpjf4vZAftOI2I7iOZB9sTsvZBRmQIC3Lgi9Bm6VurrFTeoFQbmzCArEPAZBkXBb7Xq81Smz5aC9XcN0YFh41NZBqtR6**************, expiration_date=2018-09-15T14:55:15.064Z
verbose: RESPONSE from [POST] /parse/users: {
  "status": 201,
  "response": {
    "objectId": "XWPlckYLhs",
    "createdAt": "2018-07-17T18:48:41.263Z",
    "username": "81b2frJTm5tl73fdXNmDDjX8q",
    "sessionToken": "r:9320edf4dd9f7b996bcaff9854b83dad"
  },
  "location": "https://n*****edu:1337/parse/users/XWPlckYLhs"
} status=201, objectId=XWPlckYLhs, createdAt=2018-07-17T18:48:41.263Z, username=81b2frJTm5tl73fdXNmDDjX8q, sessionToken=r:9320edf4dd9f7b996bcaff9854b83dad, location=https://n*****edu:1337/parse/users/XWPlckYLhs
verbose: REQUEST for [PUT] /parse/classes/_User/XWPlckYLhs: {
  "email": "c***@ufl.edu",
  "username": "81b2frjtm5tl73fdxnmddjx8q",
  "usernameLiteral": "81b2frJTm5tl73fdXNmDDjX8q",
  "displayName": "Corey E Baker",
  "authData": {
    "facebook": {
      "id": "8379****",
      "access_token": "EAAKn2SZA5kRkBADpd9Kwd0JeXWkLVeswsZC5fk5DdST3DfnoOP6Xxiqk1hiTNSXZAhZCcDVxCEeQZCYO6EZCjktZAuxcfkXpDnZB8MqZBxDat7ZA1CqBm3FY1GWUY6PnXP8QDr0bVMCpjf4vZAftOI2I7iOZB9sTsvZBRmQIC3Lgi9Bm6VurrFTeoFQbmzCArEPAZBkXBb7Xq81Smz5aC9XcN0YFh41NZBqtR6**************",
      "expiration_date": "2018-09-15T14:55:15.064Z"
    }
  },
  "facebookId": "8379****"
} method=PUT, url=/parse/classes/_User/XWPlckYLhs, host=n*****edu:1337, content-type=application/json; charset=utf-8, accept=*/*, x-parse-session-token=r:9320edf4dd9f7b996bcaff9854b83dad, x-parse-application-id=SBC0DswFA7RdbIQJ8Z6JWkchaZsRbNfvIgg8C2W2, x-parse-installation-id=b5ff282d-a2bc-4566-ac03-31730d80798f, x-parse-os-version=11.4 (15F79), accept-language=en-us, accept-encoding=br, gzip, deflate, x-parse-client-version=i1.17.1, content-length=516, user-agent=AlleyOop/3 CFNetwork/901.1 Darwin/17.6.0, connection=keep-alive, x-parse-app-build-version=3, x-parse-app-display-version=2.04, email=c***@ufl.edu, username=81b2frjtm5tl73fdxnmddjx8q, usernameLiteral=81b2frJTm5tl73fdXNmDDjX8q, displayName=Corey E Baker, id=8379****, access_token=EAAKn2SZA5kRkBADpd9Kwd0JeXWkLVeswsZC5fk5DdST3DfnoOP6Xxiqk1hiTNSXZAhZCcDVxCEeQZCYO6EZCjktZAuxcfkXpDnZB8MqZBxDat7ZA1CqBm3FY1GWUY6PnXP8QDr0bVMCpjf4vZAftOI2I7iOZB9sTsvZBRmQIC3Lgi9Bm6VurrFTeoFQbmzCArEPAZBkXBb7Xq81Smz5aC9XcN0YFh41NZBqtR6**************, expiration_date=2018-09-15T14:55:15.064Z, facebookId=8379****
verbose: RESPONSE from [PUT] /parse/classes/_User/XWPlckYLhs: {
  "response": {
    "updatedAt": "2018-07-17T18:50:57.761Z"
  }
} updatedAt=2018-07-17T18:50:57.761Z

Note: as stated by @tai-x, on 2.8.2, if I login via Facebook and let it create a new account. Sign-out, then sign-in again, it logs back into the new account it created.

In addition, I don't allow duplicate emails or duplicate usernames in my mongodb. I also don't have any blank email addresses.

Update: here is the log after "install" and "start" just incase it's needed. I don't get any uniqueness warnings on start-up:

alleyoopsocial-parse-server@2.0.0 /Users/netreconlab/Documents/Server/alleyoop_parse_server
└─┬ parse-server@2.7.4 
  ├─┬ @parse/push-adapter@2.0.2 
  │ ├─┬ apn@2.2.0 
  │ │ └── http2@3.3.6 
  │ └─┬ node-gcm@0.14.10 
  │   ├── lodash@3.10.1 
  │   └─┬ request@2.81.0 
  │     ├── aws-sign2@0.6.0 
  │     ├── form-data@2.1.4 
  │     ├─┬ har-validator@4.2.1 
  │     │ ├─┬ ajv@4.11.8 
  │     │ │ └─┬ json-stable-stringify@1.0.1 
  │     │ │   └── jsonify@0.0.0 
  │     │ └── har-schema@1.0.5 
  │     ├─┬ hawk@3.1.3 
  │     │ ├── boom@2.10.1 
  │     │ ├── cryptiles@2.0.5 
  │     │ ├── hoek@2.16.3 
  │     │ └── sntp@1.0.9 
  │     ├─┬ http-signature@1.1.1 
  │     │ └── assert-plus@0.2.0 
  │     ├── performance-now@0.2.0 
  │     └── qs@6.4.0 
  ├─┬ @parse/simple-mailgun-adapter@1.0.1 
  │ └─┬ mailgun-js@0.13.1 
  │   ├── async@2.5.0 
  │   ├── form-data@2.2.0 
  │   └─┬ proxy-agent@2.1.0 
  │     └── lru-cache@2.6.5 
  ├─┬ bcrypt@1.0.3 
  │ ├── nan@2.6.2 
  │ └─┬ node-pre-gyp@0.6.36 
  │   ├─┬ rc@1.2.8 
  │   │ └── deep-extend@0.6.0 
  │   ├─┬ tar@2.2.1 
  │   │ ├── block-stream@0.0.9 
  │   │ └─┬ fstream@1.0.11 
  │   │   └── graceful-fs@4.1.11 
  │   └─┬ tar-pack@3.4.1 
  │     ├── fstream-ignore@1.0.5 
  │     └── uid-number@0.0.6 
  ├─┬ body-parser@1.18.2 
  │ └── qs@6.5.1 
  ├── commander@2.15.0 
  ├── deepcopy@0.6.3 
  ├── mime@2.2.0 
  ├─┬ mongodb@3.0.4 
  │ └── mongodb-core@3.0.4 
  ├─┬ pg-promise@8.2.1 
  │ └─┬ pg@7.4.1
  │   └─┬ pg-types@1.12.1
  │     └── postgres-interval@1.1.2 
  ├─┬ request@2.83.0 
  │ ├── form-data@2.3.2 
  │ └─┬ hawk@6.0.2 
  │   ├── boom@4.3.1 
  │   ├── cryptiles@3.1.2 
  │   ├── hoek@4.2.1 
  │   └── sntp@2.1.0 
  ├── uws@9.148.0 
  └── ws@5.0.0 

netrecon:alleyoop_parse_server netreconlab$ npm start

> alleyoopsocial-parse-server@2.0.0 start /Users/netreconlab/Documents/Server/alleyoop_parse_server
> node index.js

verbose: Support key pairs Map {}
alleyoop-parse-server running on port 1337.
verbose: REQUEST for [GET] /parse/classes/_Installation: {
  "where": {},
  "limit": 0,
  "count": 1
}
flovilmart commented 6 years ago

@cbaker6 are you able to share here the user object, right from mongodb, without all the transformations applied to it from parse server?

tai-x commented 6 years ago

@flovilmart

Here is the user document that existed previously (removing some of the non-important columns that we added to our User class).

{
    "_id": "DN5ix4I0m9",
    "updatedAt": "2016-07-10T02:50:10.580Z",
    "createdAt": "2016-04-25T06:26:51.313Z",
    "name": "<some_name>",
    "email": "<some_email>",
    "username": "<some_username>",
    "_hashed_password": "<some_password>",
    "_auth_data_facebook": {
        "id": "70754445267000855",
        "access_token": "<some_access_token>",
        "expiration_date": "2028-03-18T10:28:17.124Z"
    },
    "sessionToken": "<some_session_token>",
    "_updated_at": {
        "$date": "2018-06-25T01:16:43.254Z"
    }
}

Here is the malformed "new" user document that shouldn't have been created, untouched. It has some fields in the document that are not present in the old user above (_wperm, _rperm, _acl), as well as missing some apparently default ones (_hashed_password, sessionToken) and the rest of custom columns we added to the User class.

{
    "_id": "kVG2JtpyhE",
    "username": "syhCbAjJoTq7jAuPRn9HMmBAn",
    "_wperm": [
        "kVG2JtpyhE"
    ],
    "_rperm": [
        "*",
        "kVG2JtpyhE"
    ],
    "_auth_data_facebook": {
        "id": "70754445267000855",
        "access_token": "<some_access_token>",
        "expiration_date": "2028-03-18T10:28:16.633Z"
    },
    "_acl": {
        "kVG2JtpyhE": {
            "w": true,
            "r": true
        },
        "*": {
            "r": true
        }
    },
    "_created_at": {
        "$date": "2018-07-17T19:55:30.713Z"
    },
    "_updated_at": {
        "$date": "2018-07-17T19:55:30.713Z"
    }
}

Regards,

flovilmart commented 6 years ago

@tai-x this is exactly the conclusion I was reaching now. In recent versions the user object follow ACL’s and it is possible that legacy users without any ACL are getting locked out.

The new feature was introduced so a developer can close a user’s account without deleting it by marking the ACL completely private.

There are 2 ways to fix it for 2.8.2:

  1. Add read/write ACL to the user himself so he can access and therefore login.
  2. Fix on the server for this case that was overlooked.

For now you can stick to 2.7.4 or update your old user’s ACL’s so they can read themselves.

I’ll be working on a patch for this

tai-x commented 6 years ago

Thanks for your help @flovilmart. Let me know when you have a patch to test it or if you need more info.

flovilmart commented 6 years ago

Thanks for providing the data, i’ll Keep you posted here. I need to think about the possible side effects. As this issue also shows that a locked out account would let the user create a new account anyway.

flovilmart commented 6 years ago

@tai-x you can try the branch fix/issue-4897 it should fix the issue. If you're using npm >= 6, you can use the branch directly by setting the parse-server dependency to https://github.com/parse-community/parse-server#fix/issue-4897

tai-x commented 6 years ago

Fixed. Nice!

flovilmart commented 6 years ago

Glad to hear!

cbaker6 commented 6 years ago

@flovilmart my data looked like @tai-x's, missing the ACL in mongodb for legacy users, but showing all ACLs in Parse Dashboard:

User created with legacy version:

{
    "_id" : "OldjvvMVaG",
    "_created_at" : ISODate("2015-07-13T18:05:21.502Z"),
    "_updated_at" : ISODate("2018-06-27T17:35:42.215Z"),
    "_perishable_token" : "***",
    "_auth_data_facebook" : {
        "access_token" : "***",
        "expiration_date" : ISODate("2015-09-11T18:05:19.475Z"),
        "id" : "1010***"
    },
    "username" : "jerbear323",
    "_session_token" : "LzYBc7QEElr85m3kGJFvHu3AH",
    "_hashed_password" : "",
    "facebookFriends" : [
        ***
    ],
    "email" : "jam323@ufl.edu",
    "usernameLiteral" : "JerBear323",
    "displayName" : "Jeremy",
    "facebookId" : "1010***"
}

User created with newer version of Parse server:

{
    "_id" : "5aDx6moNEU",
    "username" : "bhaskark2",
    "usernameLiteral" : "bhaskark2",
    "displayName" : "bhaskark2",
    "email" : "b**@gmail.com",
    "_hashed_password" : "",
    "_wperm" : [
        "5aDx6moNEU"
    ],
    "_rperm" : [
        "*",
        "5aDx6moNEU"
    ],
    "_acl" : {
        "5aDx6moNEU" : {
            "w" : true,
            "r" : true
        },
        "*" : {
            "r" : true
        }
    },
    "_created_at" : ISODate("2017-03-22T17:16:00.908Z"),
    "_updated_at" : ISODate("2017-03-22T17:16:00.908Z")
}

@flovilmart I wasn't able to test fix-issue-4897, but I did write a CloudCode job that fixed the ACL's for all users as you suggested. After running, ParseServer 2.82 properly logged in Facebook and Twitter users using FacebookUtils and TwitterUtils. The job is below if anyone needs it:

Parse.Cloud.job("fixLegacyUserPermissions", function(request, status) {

    // the params passed through the start request
    var params = request.params;
    // Headers from the request that triggered the job
    var headers = request.headers;

    // get the parse-server logger
    var log = request.log;

    // Update the Job status message
    status.message("Starting to fixLegacyUserPermissions for all activities in DB");

    var query = new Parse.Query('_User');
    query.notEqualTo('username', null);

    query.find({useMasterKey: true,
      success: function(results) {

        if (results == null){
            status.error("Warning no results were found in query");
        }else{

          var counter = 0;
          var counterForTotalObjects = 0;
          var fixingActivity = false;

          console.log("Found "+ results.length.toString() + " items that need to be fixed")

          for (var i = 0; i < results.length; i++) {
              var object = results[i];

              var newACL = new Parse.ACL();
              newACL.setWriteAccess(object, true);
              newACL.setReadAccess(object, true);
              newACL.setPublicReadAccess(true);
              object.setACL(newACL);

              object.save(null, {useMasterKey: true,
                success: function(){
                    counter = counter + 1;
                    status.message("Updated permissions for " + counter.toString() + " Users " + object.id);
                    counterForTotalObjects = counterForTotalObjects + 1;
                    if(counterForTotalObjects == (results.length)){
                        status.success("Fixed " + counter + " items");
                    }
                }, error: function(returnedError) {
                    status.error("Error in fixLegacyUserPermissions: " + returnedError);
                    counterForTotalObjects = counterForTotalObjects + 1;
                    if(counterForTotalObjects == (results.length)){
                        status.success();
                    }
                }
              });

           }

        }

        if (!fixingActivity){
          status.message("No users needed to be fixed");
            status.success();
        }
      },
      error: function(error){
          status.error('Error saving User.'+error.toString());
      }

  });
});
flovilmart commented 6 years ago

Awesome! If that’s resolved for you then that’s awesome!