-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved Auth Adapter Interface #7052
Improved Auth Adapter Interface #7052
Conversation
Codecov Report
@@ Coverage Diff @@
## master #7052 +/- ##
==========================================
+ Coverage 93.63% 93.74% +0.11%
==========================================
Files 169 169
Lines 12500 12620 +120
==========================================
+ Hits 11704 11831 +127
+ Misses 796 789 -7
Continue to review full report at Codecov.
|
@dblythy i just finished implementation and fixed regressions, if you want to take a look, i hope i can finish tests on friday then we will be able to implement easly webauthn, TOTP (you named mfa) and combined auth (ex OAuth + Webauthn + MFA ) |
Looks good. I wish you did this before I built mfa it would’ve saved me heaps of time 😂. I am happy to build MFA and passwordless adapters based off this PR, I will be eagerly awaiting. |
I see that |
@mtrezza, this PR is currently draft, i'm on tests to cover new features ! Note here, common SDKs will need an update to support the new combined auth feature (username , password + TOTP). We need to support the new To avoid a breaking change i can suggest Parse.User.logIn({
username: 'test',
password: 'test',
authData: {
aProvider: {
someData: true
}
}
}
,options) Note: i request reviews if some are interested in giving feedback on what I pushed |
Okay i'm close to the end, @mtrezza if you want to take a look, i'm think this pr will be finished on monday 😄 PS: It seems that i got a package lock change due to my node version and the new npm version |
# Conflicts: # package.json # src/GraphQL/loaders/parseClassTypes.js
Okay @mtrezza @dplewis @davimacedo this PR is ready for review ! Normally the spec should allow easy integration of 2-step authentication systems such as SMS OTP, WebAuthn and many others. i would like to apologize in advance to reviewers since the PR is pretty huge, i tried to write as much as i can easy to read code, i hope it will facilitate your review ! 😃 |
I'm checking why postgres has started to fail ... Postgres error is super weird:
Okay i found an issue on how Postgres adapter save the authData |
So i will begin the webauthn implementation based on this branch |
SDK PR Ready: parse-community/Parse-SDK-JS#1276 |
If somebody have some time to give a quick review I'll be happy 😁 Then it will unlock the mfa and webauth adapter 😊 @parse-community/server |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great job, @Moumouls ! I've left two questions to better understand the options. Also, how would that work in the sign up process? I mean... maybe we want mfa to be forced in log in, but not in sign up. We also only want to force mfa in the case that the user has previously set it up. I see that also in most of tests you are saving new users. Maybe it would be good to have more tests for the log in process.
src/Adapters/Auth/AuthAdapter.js
Outdated
* Set to true if you want to force to validateAuthData | ||
* even if authData do not change | ||
*/ | ||
this.alwaysValidate = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When should the validation happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently validation only occur if authData is detected as changed (a loadash equality check on the DB authData and provided authData)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The option allow to force the validation even if authData are detected as same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But what always mean? Every time that the user logs in, every time that the session token is used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
every time when authData is touched by the client ! (login/save/signup)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe that the token should be always validated at login regardless of this option. Otherwise we can have a security issue since someone would be able to use the current token, even if expired, to log in. What is the use case that you imagined for always validate false?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Otherwise we can have a security issue since someone would be able to use the current token,
Parse Server has always handled authData this way. i think also like you, it's a security issue ! So i will remove the alwaysValidate options, remove the loadash equality check, and the hasMutatedAuthData
behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davimacedo @TomWFox may be we should open a security advisory about this (i don't know why but i can't open one)
* additional: could be only used with a default policy auth provider | ||
* solo: Will ignore ALL additional providers if additional configured on user | ||
*/ | ||
this.policy = 'default'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't get it well. We can't have multilpe adapters anymore, let's say for google, facebook, twitter?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we need to improve this doc part. Policy allow parse server to know how to handle 2FA. default is the policy that always existed in parse server, a user can login/signup directly with just facebook
or twitter
etc... adapters with default policy always have an ID to identify a user
Then we will have the MFA adapter with additional
policy, additional policy tell to parse server that if this kind of auth is configured on the user, it need to be provided as a second factor authentication method like facebook + MFA
, twitter + MFA
, username + MFA
, facebook + SMS OTP
; Auth Adapters with additional policy DO NOT HAVE ids , since there will be used as a validation auth system (OTP, MFA, etc...) and are not intended to be used as an identifier auth system.
Finally the policy solo
tell to the parse server that the auth adapter (like webauthn) DO NOT NEED additionnal validation if an additional auth adapter (like MFA) was configured on the User.
This way we have:
default
: Intended to signup/login/identify the user and can be combined with an additional auth system if the user has one configured
additional
: Intended to be used as a 2FA to validate an initial login via a default
adapter
solo
: Can be used to signup/login/identify the user and CAN'T be combined with an additional auth system
I am quite open if you have an idea to clarify as much as possible these behaviors :)
Note the system that i designed only support 2FA (not 3FA,4FA since it's useless), so if a user has for example: username/password (considered as default), he has SMS OTP (additional) and also MFA (additional), parse server will require the user to provide ONE OF the additional auth system, in this use case username/password/OTP
OR username/password/MFA
the client currently has the choice about which configured additional auth system he wants to use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Now I understand. Maybe this explanation should go in the code or docs :)
Tests fully cover logins use cases, and for sure the system ONLY REQUIRE an adapter on login if it was configured before by the user (presence of data into authData object). In other hand the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davimacedo I just added some comments for a better understanding 👍
Maybe by reading the tests carefully, it might be easier to understand my main intention with this PR :)
Sorry again for this massive changes, i know that's not easy to review correctly !
src/Auth.js
Outdated
|
||
// In case of signup we need to only check | ||
// required providers | ||
if (!userAuthData) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here logic of required auth adapters @davimacedo
|
||
// Solo providers can be considered as safe | ||
// so we do not have to check if the user need | ||
// to provide an additional provider to login | ||
if (hasProvidedASoloProvider) return; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Solo provider handling (like webauthn)
const additionProvidersNotFound = []; | ||
const hasProvidedAtLeastOneAdditionalProvider = savedUserProviders.some(provider => { | ||
if (config.auth[provider] && config.auth[provider].policy === 'additional') { | ||
if (authData[provider]) { | ||
return true; | ||
} else { | ||
// Push missing provider for plausible error return | ||
additionProvidersNotFound.push(provider); | ||
} | ||
} | ||
}); | ||
if (hasProvidedAtLeastOneAdditionalProvider || !additionProvidersNotFound.length) return; | ||
|
||
throw new Parse.Error( | ||
Parse.Error.OTHER_CAUSE, | ||
`Missing additional authData ${additionProvidersNotFound.join(',')}` | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Additional adpater handling (like mfa), here we are searching if an additional adpater was configured on the user
src/Auth.js
Outdated
const hasMutatedAuthData = (authData, userAuthData, config) => { | ||
if (!userAuthData) return { hasMutatedAuthData: true, mutatedAuthData: authData }; | ||
const mutatedAuthData = {}; | ||
Object.keys(authData).forEach(provider => { | ||
// Anonymous provider is not handled this way | ||
if (provider === 'anonymous') return; | ||
const providerData = authData[provider]; | ||
const userProviderAuthData = userAuthData[provider]; | ||
if (!_.isEqual(providerData, userProviderAuthData) || config.auth[provider].alwaysValidate) { | ||
mutatedAuthData[provider] = providerData; | ||
} | ||
}); | ||
const hasMutatedAuthData = Object.keys(mutatedAuthData).length !== 0; | ||
return { hasMutatedAuthData, mutatedAuthData }; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
old code used !_.isEqual(providerData, userProviderAuthData)
, here i just added the alwaysValidate
option to force authData validation
spec/AuthenticationAdapters.spec.js
Outdated
|
||
it('should pass user to auth adapter on update by matching session', async () => { | ||
spyOn(alwaysValidateAdapter, 'validateAuthData').and.resolveTo({}); | ||
await reconfigureServer({ auth: { alwaysValidateAdapter } }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@davimacedo here use case about updating a user authData already logged in
spec/AuthenticationAdapters.spec.js
Outdated
|
||
it('should return authData response and save some info on username login', async () => { | ||
spyOn(requiredAdapter, 'validateAuthData').and.resolveTo({ | ||
response: { someData: true }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here a login via username and authData use case
spec/AuthenticationAdapters.spec.js
Outdated
}); | ||
it('should return authData response and save some info on non username login', async () => { | ||
spyOn(requiredAdapter, 'validateAuthData').and.resolveTo({ | ||
response: { someData: true }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here loginWith user case
So, if I add a mfa adapter to my app, all users will be obligated to setup the mfa? I don't have the option to make it optional for my users? |
if you add MFA adapter to your app, User will have the choice to configure the auth system on their account. Once a user has configured the MFA on his account (authData will contain something like this In other hand an authAdapter with |
After more thinking @davimacedo here, i will add new methods |
src/Adapters/Auth/AuthAdapter.js
Outdated
} | ||
|
||
/* | ||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dblythy does the new following validate functions seems more clear to you to implement MFA ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not entirely yet, I haven't looked through it completely. I'll probably wait until this is merged and then create a PR for a MFA adapter based on the final interface and the test code that you've written for it. Is that ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah no problem !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you legend! 😊
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might have another look 😊
Okay working on removing mutatedAuthData a Issue related: #3867 and #3872 Ok, I found out why they validated this, it's for some SDKs and dashboards that back up authentication data every time I will clarify the code |
Okay @davimacedo , auth spec is now better and easier to understand and implement, i wait you feedbacks ! 😃 It seems that we have some flaky tests on wiredTigger/4.0.4/replica , I had to re-run the tests twice in order for them to pass. |
Seems to have a strange cod cov issue on src/Options/parsers.js |
@davimacedo @dplewis if you want to give a look to this one ! 🙂 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@@ -1789,7 +1789,7 @@ describe('Parse.User testing', () => { | |||
}); | |||
}); | |||
|
|||
it('should allow login with old authData token', done => { | |||
it('should not allow login with old authData token', async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it should be mentioned in the breaking changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[redacted by @mtrezza]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Moumouls, again I had to redact, just stumbled upon it by chance.
Also if you want to take a look at: #7079 |
#7050
This PR can then allow to implement MFA, Webauthn through the Auth Adapter Interface.
The new system can also support multi auth pipeline, ex (Super strong auth): login user by username + password + OTP + MFA + Webauthn + other auth system