Okay I did some tests and here are the results. As a side note, it seems Moqui is inherently session based, and by this I mean that even for REST calls that are technically stateless, Moqui will create a session with a corresponding file in the sessions folder and return a JSESSIONID for each call. You could of course store the JSESSIONID and send it in a cookie with subsequent requests so then new sessions would not be created, and also you don’t need to authenticate again.
Also there are some other security problems, not related to this PR, respectively:
- there is no validation after adding a 2FA method, this means you can lock yourself out of your account by: adding Email Factor with no email set on the UserAccount, or an invalid email, or no EmailServer configured; adding an authenticator app and not verifiying it(aka not adding the secret in the authenticator app)
- if the user is added to a group that has
requireAuthcFactor='Y' and the user doesn’t have a valid email address or no EmailServer is configured, the user will again be locked out of his account
In my opinion, after these test results we should merge Taher’s PR and focus on testing and implementing the scenario of setting up a 2FA method for a user that doesn’t have one when 2FA is enforced.
Stock Moqui
UI Tests
Request: login non-existent user
Result: No account found for username notauser
Request: login john.doe:moqui
Response: success
Activate 2FA with single use code
Request: login john.doe:moqui
Response: An authentication code is required for your account, you have these options: Single Use Code
Request: fill wrong 2FA code
Response: Authentication code is not valid
Request: fill correct 2FA code
Response: success
Request: logout and login again
Response: success, 2FA is not required anymore
REST Login Tests
- POST rest/login
Response code 500
{
"errors": "No username specified\n",
"loggedIn": false
}
- POST rest/login
{
"username": "john.doe"
}
Response 500
{
"errors": "No password specified\n",
"loggedIn": false
}
- POST rest/login
{
"username": "notauser",
"password": "moqui"
}
Response 500
{
"errors": "No account found for username notauser\n",
"loggedIn": false
}
- POST rest/login
{
"username": "john.doe",
"password": "moqui"
}
Response 200
{
"loggedIn": true
}
- Activate 2FA with single use code
- POST rest/login
{
"username": "john.doe",
"password": "moqui"
}
Response 200
{
"sendableFactors": [],
"secondFactorRequired": true,
"factorTypeEnumIds": [
"UafSingleUse"
],
"factorTypeDescriptions": [
"Single Use Code"
]
}
- POST rest/login
{
"username": "john.doe",
"password": "moqui",
"code": "wrongcode"
}
Response 200
{
"secondFactorRequired": true,
"factorTypeEnumIds": [
"UafSingleUse"
],
"messages": "Authentication code is not valid\n",
"sendableFactors": [],
"factorTypeDescriptions": [
"Single Use Code"
]
}
- POST rest/login
{
"username": "john.doe",
"password": "moqui",
"code": {{singleUseCode}}
}
Response 200
No response body
After applying Taher’s above PR #226 to runtime
UI Tests
Request: login non-existent user
Result: No account found for username notauser
Request: login john.doe:moqui
Response: success
Activate 2FA with single use code
Request: login john.doe:moqui
Response: An authentication code is required for your account, you have these options: Single Use Code
Request: fill wrong 2FA code
Response: [different] the error Authentication code is not valid doesn’t appear anymore on the UI, the code field is just cleared
Request: fill correct 2FA code
Response: success
Request: logout and login again
Response: success, 2FA is not required anymore
REST Login Tests
- POST rest/login
Response code [different] 200
{
[different] "messages": "No username specified\n",
"loggedIn": false
}
- POST rest/login
{
"username": "john.doe"
}
Response [different] 200
{
[different] "messages": "No password specified\n",
"loggedIn": false
}
- POST rest/login
{
"username": "notauser",
"password": "moqui"
}
Response [different] 200
{
[different] "messages": "No account found for username notauser\n",
"loggedIn": false
}
- POST rest/login
{
"username": "john.doe",
"password": "moqui"
}
Response 200
{
"loggedIn": true
}
- Activate 2FA with single use code
- POST rest/login
{
"username": "john.doe",
"password": "moqui"
}
Response 200
{
"sendableFactors": [],
[different] "loggedIn": false,
"secondFactorRequired": true,
"factorTypeEnumIds": [
"UafSingleUse"
],
"factorTypeDescriptions": [
"Single Use Code"
]
}
- POST rest/login
{
"username": "john.doe",
"password": "moqui",
"code": "wrongcode"
}
Response 200
{ [different]
"messages": "Authentication code is not valid\n",
"loggedIn": false
} [different]
- POST rest/login
{
"username": "john.doe",
"password": "moqui",
"code": {{singleUseCode}}
}
Response 200
{
[different] "loggedIn": true
}
Some other REST tests that have the same functionality after applying the PR
- GET rest/s1/moqui/users
Response 403
{
"errorCode": 403,
"errors": "User [No User] is not authorized for View on REST Path /moqui/users"
}
- GET rest/s1/moqui/users
BASIC wronguser:moqui
Response 401
{
"errorCode": 401,
"errors": "No account found for username wronguser\n"
}
- GET rest/s1/moqui/users
BASIC john.doe:notMyPassword
Response 401
{
"errorCode": 401,
"errors": "Password incorrect for username john.doe\n"
}
- GET rest/s1/moqui/users
BASIC john.doe:moqui
Response 200 OK
5. POST /rest/login and save JSESSIONID and csrf token.
6. Enable 2FA with single use code.
7. GET rest/s1/moqui/users
BASIC john.doe:moqui
Response 403
{
"errorCode": 403,
"errors": "User [No User] is not authorized for View on REST Path /moqui/users"
}
- GET rest/s1/moqui/users
COOKIE JSESSIONID="someJID"
Response 403
{
"errorCode": 403,
"errors": "User [No User] is not authorized for View on REST Path /moqui/users"
}
- GET rest/s1/moqui/users
COOKIE JSESSIONID={{JSESSIONID}}
Response 200 OK
10. PATCH rest/s1/moqui/users/EX_JOHN_DOE
COOKIE JSESSIONID={{JSESSIONID}}
Response 401
401 Session token required (in X-CSRF-Token) for URL http://localhost:8085/rest/s1/moqui/users/EX_JOHN_DOE /rest/s1/moqui/users/EX_JOHN_DOE
- PATCH rest/s1/moqui/users/EX_JOHN_DOE
COOKIE JSESSIONID={{JSESSIONID}}
HEADER x-csrf-token={{csrf}}
HEADER moquisessiontoken={{csrf}}
Response 200
{}