-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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