parse-community / parse-community/parse-server
Parse Server additional challenge adapter not behaving correctly for validateAuthData
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 21.4k
- Forks
- 4.8k
- Avg merge
- 7h 45m
- Merged PRs (30d)
- 11
Description
New Issue Checklist
- I am not disclosing a vulnerability.
- I am not just asking a question.
- I have searched through existing issues.
- I can reproduce the issue with the latest version of Parse Server.
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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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