parse-community / parse-community/parse-server

Parse Server additional challenge adapter not behaving correctly for validateAuthData

Open
#8,518 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type:bug
Dominant language
JavaScript
Stars
21.4k
Forks
4.8k
Avg merge
7h 45m
Merged PRs (30d)
11

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Compare the REST API /challenge and /login entry points, focusing on how the user authData reaches validateAuthData for the additional challenge adapter. Reproduce the differing requests from the issue and trace where the stored secret is removed during login; done means login can validate using the user's stored authData without requiring the secret in the request.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, node.js
Domain
api, authentication, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.