Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add test for email verification not applying to auth sign-up #8740

Open
wants to merge 25 commits into
base: alpha
Choose a base branch
from

Conversation

ashish-naik
Copy link

Pull Request

This PR is to test Facebook signup error 206 describe in #6511

Closes: #6511

Approach

Added test case for Facebook signup and login.
Calling from iOS Objective-C SDK, saving new PFUser created from Facebook signup fails with error 206.
Was first reported in PS version 5.x.x and observed in v6.2.1 also.

Tasks

[x ] Add tests

@parse-github-assistant
Copy link

Thanks for opening this pull request!

@codecov
Copy link

codecov bot commented Sep 6, 2023

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 94.32%. Comparing base (f1469c6) to head (7f73eb3).
Report is 8 commits behind head on alpha.

❗ Current head 7f73eb3 differs from pull request most recent head 93485f2. Consider uploading reports for the commit 93485f2 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##            alpha    #8740      +/-   ##
==========================================
+ Coverage   94.13%   94.32%   +0.18%     
==========================================
  Files         186      186              
  Lines       14687    14773      +86     
==========================================
+ Hits        13826    13934     +108     
+ Misses        861      839      -22     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Member

@mtrezza mtrezza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between your new test and this existing test?

it('test facebook signup and login', done => {
const data = {
authData: {
facebook: {
id: '8675309',
access_token: 'jenny',
},
},
};

@ashish-naik
Copy link
Author

I guess i was supposed to add test to ParseUser.spec.js.

Can you please check whether this is correct?

Copy link
Member

@mtrezza mtrezza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can using the newer jasmine syntax; I didn't test this, just wrote it as a comment, so it may not be fully correct.
The test still seems to contain some unnecessary code, like

signedUpUser.set('social_id', provider.authData.id);
signedUpUser.set('account_status', 'active');

In any case, the test passes, so we would need to get it to fail. Since you are using the JS SDK methods, there may be a difference to what the Parse iOS SDK sends. To know what the iOS SDK sends to the server you could:

  • look at the incoming request on the server side (may be easiest)
  • look at the source code of the iOS SDK

Once you know the request that is sent to the server is the same, and you cannot get the test to fail, I suggest you look into your custom Parse Server code as a possible source of the issue.

I ran the CI here, but it failed in all environments, so it's not necessary to run the CI for now. You can execute the test in your local development environment and see whether it fails. Once it fails, it can help to run it on the CI to get more insight.

spec/ParseUser.spec.js Outdated Show resolved Hide resolved
ashish-naik and others added 2 commits September 7, 2023 22:31
Co-authored-by: Manuel <[email protected]>
Signed-off-by: Ashish Naik <[email protected]>
@ashish-naik
Copy link
Author

The test you corrected didn't generate error so i tried to add email adaptor parameters that i have in my index.js.
The test failed with error 206.

Test log is as below

> [email protected] pretest
> cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start

  ◠ Starting a MongoDB deployment to test against...
> [email protected] test
> npm run testonly "spec/ParseUser.spec.js"


> [email protected] testonly
> cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine "spec/ParseUser.spec.js"

Randomized with seed 60989
Started
Jasmine started
(node:16648) ExperimentalWarning: stream/web is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
warn: DeprecationWarning: The Parse Server option 'allowClientClassCreation' default will change to 'false' in a future version.
warn: DeprecationWarning: The Parse Server option 'allowExpiredAuthDataToken' default will change to 'false' in a future version.
warn: DeprecationWarning: The Parse Server option 'encodeParseObjectInCloudFunction' default will change to 'true' in a future version.
F
  Parse.User testing
    ✗ log in with Facebook and update email for signed up User
      - Unhandled promise rejection: ParseError: 206 Cannot modify user aV67EHAYgx.



Failures:
1) Parse.User testing log in with Facebook and update email for signed up User
  Message:
    Unhandled promise rejection: ParseError: 206 Cannot modify user aV67EHAYgx.
  Stack:
    error properties: Object({ code: 206 })
    Error: Cannot modify user aV67EHAYgx.
        at handleError (/Users/ashishn/Developer/GitHub Projects/Parse-Server-Fix-Facebook-Signup-Error206/node_modules/parse/lib/node/RESTController.js:274:17)
        at processTicksAndRejections (node:internal/process/task_queues:96:5)

Ran 1 of 161 specs
1 spec, 1 failure
Finished in 0.594 seconds
Randomized with seed 60989 (jasmine --random=true --seed=60989)
**************************************************
*                    Failures                    *
**************************************************

1) Parse.User testing log in with Facebook and update email for signed up User
  - Unhandled promise rejection: ParseError: 206 Cannot modify user aV67EHAYgx.

Executed 1 of 161 specs (1 FAILED) (160 SKIPPED) in 0.594 sec.
Randomized with seed 60989.

@mtrezza
Copy link
Member

mtrezza commented Sep 7, 2023

Great, as a next step, remove all custom configuration that is not needed for the test to fail. For example you have resetTokenValidityDuration: 24 * 60 * 60, which likely can be removed, among others. The test still looks too verbose. Are all these lines needed for the test to fail?

user.set('email', '[email protected]');
user.set('loginProvider', 'facebook');

@ashish-naik
Copy link
Author

Are all these lines needed for the test to fail?

user.set('email', '[email protected]');
user.set('loginProvider', 'facebook');

I wanted to replicate my actual code. Removed now.

I was able to identify that combination of these two set to true causes the error. Doesn't fail for any other combination.
have modified the best and test description.

      verifyUserEmails: true,
      preventLoginWithUnverifiedEmail: true,

spec/ParseUser.spec.js Show resolved Hide resolved
spec/ParseUser.spec.js Outdated Show resolved Hide resolved
spec/ParseUser.spec.js Outdated Show resolved Hide resolved
@mtrezza
Copy link
Member

mtrezza commented Sep 8, 2023

Yes, that's good. We don't see the error logged anymore, but we know what it is anyway. The next step would be to analyze whether this is expected behavior.

spec/ParseUser.spec.js Outdated Show resolved Hide resolved
spec/ParseUser.spec.js Outdated Show resolved Hide resolved
spec/ParseUser.spec.js Outdated Show resolved Hide resolved
spec/ParseUser.spec.js Outdated Show resolved Hide resolved
@ashish-naik
Copy link
Author

I think i also committed the change and created issue in branch. I am not able to pull.
Would be corrected if i reset my commit to position shown by arrow?

image

@mtrezza
Copy link
Member

mtrezza commented Sep 8, 2023

You can just reset your local branch to the head of the remote branch. Or delete your local branch and pull the branch from the remote.

@mtrezza
Copy link
Member

mtrezza commented Sep 9, 2023

Let's recap: you are signing up a user with Facebook auth, but saving the user afterwards fails if preventLoginWithUnverifiedEmail is set to true, it succeeds if preventLoginWithUnverifiedEmail is set to false.

Error 206 means:

SessionMissing | 206 | A user object without a valid session could not be altered.

So it's possible that a user sign-up is not completed until the email address is verified, even if the user signed up with auth. That could be expected behavior, even though I didn't find anything in the docs. However, it would be logical to assume that if a user is authenticated via a third-party provider, the email verification requirement would be bypassed, since the third-party provider has already done its own verification.

As a next step you could look at the Parse Server code. Since we know it's related to preventLoginWithUnverifiedEmail you could just search for that and set breakpoints. Then run the test and dive deeper into why the test fails.

@ashish-naik
Copy link
Author

i tried to install v3.0.0 to v3.8.0 to 4.2.0 but kept getting errors abt dependencies so couldn't do testing.

I tried to debug more today. Please see if this provides any clue.
Auth.prototype.isUnauthenticated function return false when request is for updating _User after login. The row was created using PFFacebookUtils.logInInBackground(withReadPermissions: facebookPermissions) { called from iOS client.

Variable user in auth is undefined at the time of calling function update(config, auth, className, restWhere, restObject, clientSDK, context) { from Rest
Would this cause of Auth.prototype.isUnauthenticated returning false?

and req.userFromJWT in middleware.js is undefined and sessionToken is blank so Auth seems to be created without user.

 if (req.userFromJWT) {
    req.auth = new _Auth.default.Auth({
      config: req.config,
      installationId: info.installationId,
      isMaster: false,
      user: req.userFromJWT
    });
    return handleRateLimit(req, res, next);
  }

 if (!info.sessionToken) {
    req.auth = new _Auth.default.Auth({
      config: req.config,
      installationId: info.installationId,
      isMaster: false
    });
  }

I debug client code and found that loginWithFacebook returns new PFUser without sessionToken and reason is this in createSessionTokenIfNeeded.
so my update call fails because Auth.prototype.isUnauthenticated returns false when doing database operation.

if (!this.storage.authProvider &&
  // signup call, with
  this.config.preventLoginWithUnverifiedEmail === true &&
  // no login without verification
  this.config.verifyUserEmails) {
    // verification is on
    this.storage.rejectSignup = true;
    return;
  }

Please see this screenshot. Right side popup shows hat current state is to update _User using initialisation parameters post user signup.

auth error

@ashish-naik
Copy link
Author

ashish-naik commented Sep 17, 2023

Tried to fix the issue.

i updated handleAuthData function with this.storage.authProvider = Object.keys(authData).join(','); placed just before

// No user found with provided authData we need to validate
  if (!results.length) {

This resolved the issue and also ran the test with success.

  Parse.User testing
    ✓ log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true

I found that version 2.6.1 introduced this check.
https://github.com/parse-community/parse-server/blob/2.6.1/src/RestWrite.js#L544

RestWrite.prototype.createSessionTokenIfNeeded = function() {
  if (this.className !== '_User') {
    return;
  }
  if (this.query) {
    return;
  }
  if (!this.storage['authProvider'] // signup call, with
      && this.config.preventLoginWithUnverifiedEmail // no login without verification
      && this.config.verifyUserEmails) { // verification is on
    return; // do not create the session token in that case!
  }
  return this.createSessionToken();
}

In latest version this check is bit different

if (
    !this.storage.authProvider && // signup call, with
    this.config.preventLoginWithUnverifiedEmail === true && // no login without verification
    this.config.verifyUserEmails
  ) {
    // verification is on
    this.storage.rejectSignup = true;
    return;
  }

@ashish-naik
Copy link
Author

Previous discussions on this issue

#4155

#4142

ashish-naik and others added 7 commits September 19, 2023 18:18
* commit 'b1e1bf6708f5d32b2846e66de40f48fb0ec1dc86':
  chore(release): 6.4.0-beta.1 [skip ci]
  release
  chore(release): 6.3.0 [skip ci]
  release
  chore(release): 6.3.0-alpha.9 [skip ci]
  perf: Improve performance of recursive pointer iterations (parse-community#8741)
  refactor: Parse Pointer allows to access internal Parse Server classes and circumvent `beforeFind` query trigger (parse-community#8734)
  chore(release): 6.2.2 [skip ci]
  fix: Parse Pointer allows to access internal Parse Server classes and circumvent `beforeFind` query trigger; fixes security vulnerability [GHSA-fcv6-fg5r-jm9q](GHSA-fcv6-fg5r-jm9q)
  refactor: Remote code execution via MongoDB BSON parser through prototype pollution; fixes security vulnerability [GHSA-462x-c3jw-7vr6](GHSA-462x-c3jw-7vr6) (parse-community#8677)
  chore(release): 6.2.1 [skip ci]
  fix: Remote code execution via MongoDB BSON parser through prototype pollution; fixes security vulnerability [GHSA-462x-c3jw-7vr6](GHSA-462x-c3jw-7vr6) (parse-community#8674)
  refactor: Add option to convert `Parse.Object` to instance in Cloud Function payload (parse-community#8656)
Reproducing error using test 

it('log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true'

Under ParseUser.spec.js.

Signed-off-by: Ashish Naik <[email protected]>
@ashish-naik
Copy link
Author

@mtrezza i have again tried to reproduce with latest version and it is still failing. Below is the test log.

This solution still works.

In meantime, i have added a workaround in beforeLogin trigger to handle this issue.

Parse.Cloud.beforeLogin(async request => {
  const { object: user }  = request;
  if (user.get('login_provider') == "email" && user.get('emailVerified') != true) {
    logger.error(`beforeLogin: User ${user.id} is not verified`)
    throw new Parse.Error(302,'Email is unverified')
   }
});

Test result

% npm test spec/ParseUser.spec.js

> [email protected] pretest
> cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017

Server started and running at mongodb://127.0.0.1:27017/
Run the following command to stop the instance:
mongodb-runner stop --id=3c53f8e2-946a-4012-a356-ce497958e619

> [email protected] test
> npm run testonly "spec/ParseUser.spec.js"


> [email protected] testonly
> cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine "spec/ParseUser.spec.js"

Randomized with seed 34178
Started
Jasmine started
warn: DeprecationWarning: The Parse Server option 'encodeParseObjectInCloudFunction' default will change to 'true' in a future version.
.
  allowExpiredAuthDataToken option
    ✓ should default false
.    ✓ should enforce boolean values
.    ✓ should accept true value
.    ✓ should accept false value

.  Parse.User testing
    ✓ user login with non-string username with REST API
.    ✓ user login with context
.    ✓ user login wrong password
.    ✓ login with provider should be blockable by beforeLogin
.    ✓ user with missing username
.    ✓ login with provider should not call beforeSave trigger
.    ✓ user login wrong username
.    ✓ should allow PUT request with stale auth Data
.    ✓ saving user after browser refresh
.    ✓ become
.    ✓ signup with provider should not call beforeLogin trigger
.    ✓ user sign up instance method
.    ✓ querying for users only gets the expected fields
.    ✓ should fail linking with existing
.    ✓ user save should fail with invalid email
.    ✓ log in with provider failed
.    ✓ link multiple providers
.    ✓ unset user email
.    ✓ link with provider
.    ✓ cannot delete session if no sessionToken
.    ✓ should not fail querying non existing relations
.    ✓ user sign up class method
.    ✓ should be able to update user with authData passed
.    ✓ log in with provider cancelled
.    ✓ can login when both email and username are passed
.    ✓ should not allow login with expired authData token since allowExpiredAuthDataToken is set to false by default
.    ✓ should not retrieve hidden fields on GET users/me (#3432)
.    ✓ password format matches hosted parse
.    ✓ should not revoke session tokens if the server is configures to not revoke session tokens
.    ✓ logout with provider should call afterLogout trigger
.    ✓ should throw OBJECT_NOT_FOUND instead of SESSION_MISSING when using masterKey
.    ✓ fails to login when email and username are not provided
.    ✓ can not set authdata to null
.    ✓ user cannot update email to existing user
.    ✓ link with provider for already linked user
.    ✓ should revoke sessions when converting anonymous user to "normal" user
.    ✓ user modified while saving with unsaved child
.    ✓ user associations
.    ✓ should revoke sessions when setting paswword with masterKey (#3289)
.    ✓ should not create extraneous session tokens
.    ✓ should let legacy users without ACL login
.    ✓ fails to login when email doesn't match username
.    ✓ should not overwrite username when unlinking facebook user (regression test for #1532)
.    ✓ link with provider failed
.    ✓ can login with email
.    ✓ should return current session with expired expiration date
.    ✓ link with provider via sessionToken should not create new sessionToken (Regression #5799)
.    ✓ become sends token back
.    ✓ allows login when providing email as username
.    ✓ log in with explicit facebook auth data
.    ✓ should fail linking with existing through REST
.    ✓ should not retrieve hidden fields on GET users/id (#3432)
.    ✓ login with provider should be blockable by beforeLogin even when the user has a attached file
.    ✓ fails to login when password is not provided
.    ✓ should send email when upgrading from anon
.    ✓ should allow updates to fields with maintenanceKey
.    ✓ create session from user
.    ✓ authenticated check
.    ✓ user queries
.    ✓ user loaded from localStorage from signup
.    ✓ user login with files
.    ✓ log in with provider and update token
.    ✓ should not allow updates to emailVerified
.    ✓ link async with explicit facebook auth data
.    ✓ cannot saveAll with non-authed user
.    ✓ should properly error when password is missing
    ✗ log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true
      - Expected [object Promise] to be resolved.
.    ✓ should be let masterKey lock user out with authData
.    ✓ user updates
.    ✓ handles properly when 2 users share username / email pairs, counterpart
.    ✓ user modified while saving
.    ✓ should not retrieve hidden fields on login (#3432)
.    ✓ async methods
.    ✓ unlink with provider
.    ✓ log in with provider
.    ✓ user.isCurrent
.    ✓ user get session from token on signup
.    ✓ should have authData in beforeSave and afterSave
.    ✓ fails to login when username doesn't match email
.    ✓ should cleanup null authData keys (regression test for #935)
.    ✓ user loaded from localStorage from login
.
    issue #4897
      ✓ should be able to login with a legacy user (no ACL)

.    ✓ contained in user array queries
.    ✓ user get session from token on anonymous login
.    ✓ ignore setting authdata to undefined
.    ✓ login with provider should call beforeLogin trigger
.    ✓ should let masterKey lockout user
.    ✓ link with provider cancelled
.    ✓ never locks himself up
.    ✓ cannot save non-authed user
.    ✓ set password then change password
.    ✓ should aftersave with full object
.    ✓ link with provider should return sessionToken
.    ✓ Invalid session tokens are rejected
.    ✓ user signup class method uses subclassing
.    ✓ user with missing password
.    ✓ unlink and link
.    ✓ incorrect login with provider should not call beforeLogin trigger
.    ✓ should not call beforeLogin with become
.    ✓ count users
.    ✓ should cleanup null authData keys ParseUser update (regression test for #1198, #2252)
.    ✓ should respect ACL without locking user out
.    ✓ user authData should be available in cloudcode (#2342)
.    ✓ log in with provider with files
.
    case insensitive signup not allowed
      ✓ signup should fail with duplicate case insensitive username with field specific setter
.      ✓ edit should fail with duplicate case insensitive email
.      ✓ signup should fail with duplicate case insensitive username with basic setter
.      ✓ signup should fail with duplicate case insensitive email
.
      anonymous users
        ✓ should not fail on case insensitive matches

.    ✓ user stupid subclassing
.    ✓ user get session from token on username/password login
.    ✓ should not allow updates to hidden fields
.    ✓ user login
.    ✓ handles properly when 2 users share username / email pairs
.    ✓ should not serve null authData keys
.    ✓ cannot delete non-authed user
.    ✓ log in with provider twice
.    ✓ user login using POST with REST API
.    ✓ saving a user signs them up but doesn't log them in
.    ✓ link multiple providers and updates token
.    ✓ retrieve user data from fetch, make sure the session token hasn't changed
.    ✓ current user
.    ✓ current user isn't dirty
.    ✓ returns authData when authed and logged in with provider (regression test for #1498)
.    ✓ changes to a user should update the cache
.    ✓ ensure logout works
.    ✓ querying for users doesn't get session tokens
.    ✓ log in async with explicit facebook auth data
.    ✓ can login with email through query string
.    ✓ user sign up with container class
.    ✓ user login with non-string username with REST API (again)
.    ✓ changing password clears sessions
.    ✓ user signup should error if email taken
.    ✓ user update session with other field
.    ✓ link multiple providers and update token
.    ✓ only creates a single session for an installation / user pair (#2885)
.    ✓ user on disk gets updated after save
.    ✓ should strip out authdata in LiveQuery
.    ✓ cannot login with email and invalid password
*.    ✓ link with explicit facebook auth data
.    ✓ cannot update session if invalid or no session token
.    ✓ should not send email when email is not a string
.    ✓ session expiresAt correct format
.    ✓ does not duplicate session when logging in multiple times #3451
.    ✓ user login with enforcePrivateUsers
.    ✓ get session only for current user
.    ✓ should fail to become user with expired token
.    ✓ support user/password signup with empty authData block
.    ✓ test parse user become
.    ✓ should throw when enforcePrivateUsers is invalid
.    ✓ delete session by object

.  Security Advisory GHSA-8w3j-g983-8jh5
    ✓ should ignore authData field
.    ✓ should validate credentials first and check if account already linked afterwards ()

.  login as other user
    ✓ rejects creating a session for another user with invalid parameters
.    ✓ rejects creating a session for another user without the master key
.    ✓ rejects creating a session for another user if the user does not exist
.    ✓ allows creating a session for another user with the master key

.  allowClientClassCreation option
    ✓ should accept false value
.    ✓ should default false
.    ✓ should enforce boolean values
.    ✓ should accept true value



Failures:
1) Parse.User testing log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true
  Message:
    Expected [object Promise] to be resolved.
  Stack:
    Error
        at <Jasmine>
        at UserContext.<anonymous> (/Users/ashishn/Developer/GitHub Projects/Parse-Server-Fix-Facebook-Signup-Error206/spec/ParseUser.spec.js:1290:36)
        at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Pending:

1) Parse.User testing should not send a verification email if the user signed up using oauth
  this test fails.  See: https://github.com/parse-community/parse-server/issues/5097

166 specs, 1 failure, 1 pending spec
Finished in 20.468 seconds
Randomized with seed 34178 (jasmine --random=true --seed=34178)
**************************************************
*                    Failures                    *
**************************************************

1) Parse.User testing log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true
  - Expected [object Promise] to be resolved.

**************************************************
*                    Pending                     *
**************************************************

1) Parse.User testing should not send a verification email if the user signed up using oauth
  this test fails.  See: https://github.com/parse-community/parse-server/issues/5097

Executed 165 of 166 specs (1 FAILED) (1 PENDING) in 20 secs.
Randomized with seed 34178.

I feel i have made some mistake merging my branch.

@mtrezza mtrezza changed the title test: Add test for email verification not applying to auth sign-up test: add test for email verification not applying to auth sign-up May 5, 2024
@mtrezza mtrezza changed the title test: add test for email verification not applying to auth sign-up test: add test for email verification not applying to auth sign-up May 5, 2024
Copy link

I will reformat the title to use the proper commit message syntax.

@parse-github-assistant parse-github-assistant bot changed the title test: add test for email verification not applying to auth sign-up test: Add test for email verification not applying to auth sign-up May 5, 2024
spec/ParseUser.spec.js Outdated Show resolved Hide resolved
src/RestWrite.js Outdated Show resolved Hide resolved
src/RestWrite.js Outdated Show resolved Hide resolved
Signed-off-by: Manuel <[email protected]>
@mtrezza
Copy link
Member

mtrezza commented May 6, 2024

Now that we have a test case, it should be easy to debug and find out want's going on inside Parse Server.

@ashish-naik
Copy link
Author

ashish-naik commented May 11, 2024

Hi,

i tried debugging but could not reproduce. Test however still fails.
I tested with my current branch.
I have since changed my code to align with latest Parse iOS SDK in which FacebookUtils was removed, i added below cloud function to handle authentication.

Parse.Cloud.define('signInWithAuth', async ({ params: { loginProvider, accessToken, expirationDate, email, userId, displayName } }) => {
  
  var authDataToSave = {"id":userId,"expiration_date":expirationDate,"access_token":accessToken}

  try {

      const payload = await verifyFacebookAccessToken(accessToken, authDataToSave)
    var userQuery = new Parse.Query('_User');
    userQuery.equalTo('email', email);

    var users = await userQuery.find({ useMasterKey: true });

    try {
          if (users.length == 0) {

            var user = new Parse.User();
            user.set('email', email);
            user.set('username', email);
            user.set('login_provider', loginProvider);
            user.set('social_id', userId);
            user.set('name:', displayName)
            await user.save(null, { useMasterKey: true })

            try {
                await user.linkWith(loginProvider, { authData: authDataToSave }, { useMasterKey: true });

                //login the linked user and retutn to client
                try {
                  var loggedInUser = await Parse.User.logInWith(loginProvider, { authData: authDataToSave }, { useMasterKey: true });
                  //return to client to created logged in user  using Parse.become method
                  return {
                      token:loggedInUser.getSessionToken(),
                      isNew:true,
                      objId: loggedInUser.id
                    }
                } catch (loginError) {
                  throw loginError;
                } //eof of try of loginUser

            } catch (linkError) {
              throw linkError;
            } //end of try of linkUser
          }         
    } catch (queryError) {
      throw queryError
    };
  } catch (error) {
    throw error

  };
});

Client close calling the cloud code is as below.

  func performSignInFlow()  {
       
       let facebookPermissions = ["public_profile","email"]

       LoginManager().logIn(permissions: facebookPermissions, from: self) { result, error in
           
           if let error = error {

               print("Error on login \(error.localizedDescription)")

           } else {
               GraphRequest(graphPath: "/me", parameters: ["fields" : "email, name, id"])
                   .start(completion: { connection, result, error in
                       
                       if error != nil {
                           
                           print("Error while fetching user info from Facebook - \(String(describing: error?.localizedDescription))")
                           
                           
                       } else if let result = result {
                                                          
                               let displayName = (result as AnyObject).object(forKey: "name") as! String
                               let socialId = (result as AnyObject).object(forKey: "id") as! String
                               let accessToken = AccessToken.current!.tokenString
                               let tokenExpirationDate = AccessToken.current!.expirationDate
                                                               
                               PFCloud.callFunction(inBackground: "signInWithAuth", withParameters: ["loginProvider": "facebook",
                                                                                                     "accessToken":accessToken,
                                                                                                     "expirationDate": tokenExpirationDate,
                                                                                                     "email" : userEmail,
                                                                                                       "userId":socialId,
                                                                                                       "displayName":displayName])
                               { response, errorFromCloudCode in
                                   if errorFromCloudCode != nil {
                                       
                                       let nsError = errorFromCloudCode! as NSError
                                                           
                                       print("Error in signInWithAuth cloud code \(errorFromCloudCode!.localizedDescription)")
                                   
                                       return
                                   } else  {

                                       let returnedValues = response as! [String:Any?]

                                       if let sessionToken = returnedValues["token"] as? String {
                                           
                                           PFUser.become(inBackground: sessionToken) { pfUser, error in
                                               
                                               if error == nil {
                                                   print("User linked")
                                               } else {
                                                   
                                                   print("Error when becoming user from session token \(error!.localizedDescription)")
                                               }
                                               
                                           } //end ofPFUser.become(inBackground: sessionToken) {
                                       } else {
                                           
                                           print("Parse login failed.")

                                       } //end of else of let sessionToken = returnedValues["token"] as? String
                                       
                                   } // end of if errorFromCloudCode != nil {
                               } //end of PFCloud.callFunction(inBackground:
                       } //end of if let result = result
                   }) //end of GraphRequest.start
              }
       }//end of loginManager.logIn(permissions: f

   }

latest test log

npm test spec/ParseUser.spec.js

> [email protected] pretest
> cross-env MONGODB_VERSION=${MONGODB_VERSION:=7.0.1} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} mongodb-runner start -t ${MONGODB_TOPOLOGY} --version ${MONGODB_VERSION} -- --port 27017

Server started and running at mongodb://127.0.0.1:27017/
Run the following command to stop the instance:
mongodb-runner stop --id=2c01330c-f1bd-4635-9f8e-9f5a19a2fc81

> [email protected] test
> npm run testonly spec/ParseUser.spec.js


> [email protected] testonly
> cross-env MONGODB_VERSION=${MONGODB_VERSION:=5.3.2} MONGODB_TOPOLOGY=${MONGODB_TOPOLOGY:=standalone} TESTING=1 jasmine "spec/ParseUser.spec.js"

Randomized with seed 73902
Started
Jasmine started
warn: DeprecationWarning: The Parse Server option 'encodeParseObjectInCloudFunction' default will change to 'true' in a future version.
F
  Parse.User testing
    ✗ log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true
      - Expected [object Promise] to be resolved.



Failures:
1) Parse.User testing log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true
  Message:
    Expected [object Promise] to be resolved.
  Stack:
    Error
        at <Jasmine>
        at UserContext.<anonymous> (/Users/ashishn/Developer/GitHub Projects/Parse-Server-Fix-Facebook-Signup-Error206/spec/ParseUser.spec.js:1290:36)
        at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Ran 1 of 166 specs
1 spec, 1 failure
Finished in 0.446 seconds
Randomized with seed 73902 (jasmine --random=true --seed=73902)
**************************************************
*                    Failures                    *
**************************************************

1) Parse.User testing log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true
  - Expected [object Promise] to be resolved.

Executed 1 of 166 specs (1 FAILED) (165 SKIPPED) in 0.445 sec.
Randomized with seed 73902.

@mtrezza
Copy link
Member

mtrezza commented May 11, 2024

i tried debugging but could not reproduce. Test however still fails.

If the test fails as expected, could you just debug to test to find out why it fails? That should give you a hint on how to fix the bug.

Added test "'link and login with provider with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true'" under ParseUser.spec.js.

Signed-off-by: Ashish Naik <[email protected]>
@ashish-naik
Copy link
Author

I don't know how to debug a test. I have to run using jest i guess but couldn't figure it out.

but i realised the test i had written seems incorrect. I tries to save user after linking. I believe session token doesn't get created on linking, it is created on successful login.

So i added this test that links and then logs in.

When I make preventLoginWithUnverifiedEmail= false, it times out even after 10 minutes at expectAsync step.

fit('link and login with provider with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true', async done => {
  const provider = getMockFacebookProvider();
  const emailAdapter = {
    sendPasswordResetEmail: () => Promise.resolve(),
    sendMail: () => Promise.resolve(),
  };
  await reconfigureServer({
    appName: 'ExampleApp',
    verifyUserEmails: true,
    preventLoginWithUnverifiedEmail: false,
    emailAdapter: {
      module: emailAdapter,
    },
    publicServerURL: 'http://localhost:8378/1',
  });

  Parse.User._registerAuthenticationProvider(provider);
  var user = new Parse.User();
  await expectAsync(user.save()).toBeResolved();
  console.log("saved")
  user.set('username', 'testLinkWithProvider');
  user.set('password', 'mypass');
  await user.signUp();
  console.log("signed up")
  user.set('username', 'testLinkWithProvider');
  user.set('password', 'mypass');
  await user.logIn()
  console.log("logged in")
  const model = await user._linkWith('facebook');
  console.log("linked")
  expect(user instanceof Parse.User).toBeTrue();
  expect(user instanceof Parse.User).toBeTrue();
  expect(Parse.User.current()).toEqual(user);
  expect(user.extended()).toBeTrue();
  expect(provider.authData.id).toBe(provider.synchronizedUserId);
  expect(provider.authData.access_token).toBe(provider.synchronizedAuthToken);
  expect(provider.authData.expiration_date).toBe(provider.synchronizedExpiration);
  expect(user._isLinked('facebook')).toBeTrue();
  console.log("Linked");
  const loggedinuser = await Parse.User._logInWith('facebook');
  console.log("logged in " + loggedinuser.getSessionToken());
  await expectAsync(loggedinuser.save()).toBeResolved();

}, 10000);

I faced these issues when i am running tests.
"pretest" runs only if i set MongoDB version 7.0.1.
"posttest": "cross-env mongodb-runner stop --all" doesn't run after i run npm test spec/ParseUser.spec.js

@mtrezza
Copy link
Member

mtrezza commented May 12, 2024

This is how you would sign up using an auth adapter and then get the session token:

const user2 = await Parse.User.logInWith('doNotSaveAdapter', {
      authData: { id: 'doNotSaveAdapter', example: 'example' },
    });
expect(user2.getSessionToken()).toBeDefined();

If you search existing tests for .loginwith( then you find various examples. For example the test should support loginWith with session token and with/without mutated authData.

Test debugging:

  1. Set the test to fit so that it only runs that one test, not all tests.
  2. Set a breakpoint in the test.
  3. In VSC click on "Run and Debug" > select "Node.js" in the drop-down > select script Run Script: test:mongodb:testonly (assuming that a MongoDB instance for testing is already running in the background, otherwise you need to run script test:mongodb:7.0.1 once to launch the instance).

Rewrote test "'log in with Facebook and save signed up User with verifyUserEmails=true and preventLoginWithUnverifiedEmail=true'" .


Signed-off-by: Ashish Naik <[email protected]>
@ashish-naik
Copy link
Author

Thanks for those hints. I was able to debug.

The issue start in handleAuthData where if users with provided authData is not found then function returns from this condition.

if (!results.length) {
    const { authData: validatedAuthData, authDataResponse } = await Auth.handleAuthDataValidation(
      authData,
      this
    );
    this.authDataResponse = authDataResponse;
    // Replace current authData by the new validated one
    this.data.authData = validatedAuthData;
    return;
  }

next is createSessionTokenIfNeeded function in which if (!this.storage.authProvider) condition is evaluated to true because this.storage.authProvider wasn't set to auth provider in handleAuthData.

In if (!this.storage.authProvider), if verifyUserEmails and preventLoginWithUnverifiedEmail are true then function returns without creating session token.

The resolution is placing this.storage.authProvider = Object.keys(authData).join(','); at start of handleAuthData
With this, createSessionTokenIfNeeded will correctly evaluate if (!this.storage.authProvider) to false and will go on to create session token.

Hope i was able to explain. I have made change in RestWrite.js and ParseUser.spec.js.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error 206 cannot modify user for new Facebook user
2 participants