Skip to content

Parse Server additional challenge adapter not behaving correctly for validateAuthData #8518

@magnacartatron

Description

@magnacartatron

New Issue Checklist

Issue Description

@Moumouls In the new Parse MFA auth policy there's inconsistencies between the challenge and login in Rest API, challenge adapter works fine for but login does not. In particular for /login route, it looks like the user object becomes transformed and the authData object from the user gets removed. This causes the 2fa secret to be removed in the auth method. This isn't the case in the challenge route.

Steps to reproduce

1) Create a parse-server

const challengeAdapter = {
  policy: 'additional',
  validateAppId: () => { 
    console.log("VALID APP ID");
    Promise.resolve()
  } ,
  validateAuthData: (authData, challangeAdapter, req) => {
    const reqUser = req.object;
    if(!reqUser?.id) throw new Error("Could not find user");
    if(!authData.code) throw new Error("Verification code not provided");
    const verification = twofactor.verifyToken(reqUser.attributes.authData.challengeAdapter.id, authData.code);
    if(verification == null) {
      throw new Error("The verification code provided is not correct");
    }

    const { delta } = verification;

    if(delta == -1) throw new Error("The verification code provided has expired");
    if(delta == 1) throw new Error("The verification code entered was early");
    if(delta == 0) {
      Promise.resolve()
    }
    Promise.resolve()
  },
  challenge: (challengeData, authData, options, req, user) => {
    const reqUser = req.object;
    if (!reqUser) throw new Error('User not found');
    if(!reqUser?.id) throw new Error("Could not find user");
    if(!challengeData.code) throw new Error("Verification code not provided");
    const verification = twofactor.verifyToken(reqUser.attributes.authData.challengeAdapter.id, challengeData.code);
    if(verification == null) {
      throw new Error("The verification code provided is not correct");
    }

    const { delta } = verification;

    if(delta == -1) throw new Error("The verification code provided has expired");
    if(delta == 1) throw new Error("The verification code entered was early");
    if(delta == 0) {
      Promise.resolve()
    }
    Promise.resolve()
  },
  options: {
    anOption: true,
  },
};

const soloAdapter = {
  validateAppId: () => { 
    console.log("SOLO ADAPTER VALIDATE APP ID");
    Promise.resolve() 
  },
  validateAuthData: () =>  { 
    console.log("SOLO ADAPTER VALIDATE AUTH DATA");
    Promise.resolve() 
  },
  policy: 'solo',
};

const parseServer = new ParseServer({
  databaseURI: "mongodb://localhost:27017/parseDatabase",
  cloud: "./cloud/main.js",
  appId: "myAppId",
  fileKey: "myFileKey",
  masterKey: "mySecretMasterKey",
  allowExpiredAuthDataToken: false,
  expireInactiveSession: true,
  enforcePrivateUsers: false,
  directAccess: true,
  allowClientClassCreation: true,
  serverURL: 'http://localhost:1337/parse',
  publicServerURL: 'http://localhost:1337/parse',
  auth: {
    challengeAdapter,
    soloAdapter
  },
});

2) Create a parse user and link it with the authAdapter, we use node-2fa

You can just add it on the afterSave Parse.User trigger

e.g.

Parse.Cloud.afterSave(Parse.User, async (req) => {
    const user = req.object;

    if(!user.existed()) {
        const secret = twofactor.generateSecret({
            name: "APPNAME",
            account: user.getUsername()
          })

          await user.linkWith("challengeAdapter", {
            authData: {
              id: secret.secret,
              code: twofactor.generateToken(secret.secret).token
            }
          }, {useMasterKey: true})
    }
})

3. Attempt to use challenge via REST API, then attempt to login via rest API

a) Challenge -- this works POST /challenge (body bellow)

{
    "username": "onemore",
    "password": "password",
    "challengeData": {
        "challengeAdapter": {
            "code": "866826"
        }
    }
}

b) Login -- this doesn't not work POST /login (body bellow)

{
    "username": "onemore",
    "password": "password",
    "authData": {
        "challengeAdapter": {
            "code": "481373"
        }
    }
}

c) Login -- this works POST /login (body bellow)

{
    "username": "onemore",
    "password": "password",
    "authData": {
        "challengeAdapter": {
            "id": USER_2FA_SECRET,
            "code": "481373"
        }
    }
}

So if I pass in the secret during the login function, it works, but I can't do this as I don't have the secret that's store against the user.

This isn't the case in the /challenge route. So the /challenge route grabs the secret from the authData, but this gets stripped during the /login function.

Actual Outcome

See above

Expected Outcome

Expect the user object to not be stripped of the authData so that secret can be used during validateAuthData

Environment

Server

  • Parse Server version: 6.1.0-alpha.2
  • Operating system: Ubuntu 22
  • Local or remote host (AWS, Azure, Google Cloud, Heroku, Digital Ocean, etc): Local

Database

  • System (MongoDB or Postgres): MongoDB
  • Database version: 5.0
  • Local or remote host (MongoDB Atlas, mLab, AWS, Azure, Google Cloud, etc): local

Client

  • SDK (iOS, Android, JavaScript, PHP, Unity, etc): REST API
  • SDK version: N/A

Logs

Metadata

Metadata

Assignees

No one assigned

    Labels

    type:bugImpaired feature or lacking behavior that is likely assumed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions