-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Separate OAuth2 data out of Sessions and into User Identities #5551
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
Conversation
ecde406
to
1383ce9
Compare
76ab919
to
345c70c
Compare
app/controllers/api/users.php
Outdated
@@ -47,6 +47,14 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e | |||
$email = \strtolower($email); | |||
} | |||
|
|||
// Makes sure this email is not already used in another identity | |||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [ | |||
Query::equal('providerEmail', [$email]), |
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.
Are we making sure to attach only verified emails? Otherwise anyone could fake an email in a 3rd party provider and gain access to it on Appwrite.
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 10000 .
We're actually not really checking verified at all today because not all providers verify. Notice how $isVerified
isn't used anywhere:
appwrite/app/controllers/api/account.php
Line 491 in c8689d1
$isVerified = $oauth2->isEmailVerified($accessToken); |
src/Appwrite/Auth/OAuth2.php
Outdated
\curl_close($ch); | ||
|
||
if ($code != 200) { | ||
$dump = function($value) { |
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.
Reminder, remove debugs.
@@ -98,6 +99,14 @@ | |||
} | |||
} | |||
|
|||
// Makes sure this email is not already used in another identity | |||
$identityWithMatchingEmail = $dbForProject->findOne('identities', [ |
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.
Should a user be able to log in with an email associated solely with the provider that is different than the main email?
Are we blocking users to attach OAuth providers with emails associated to other users?
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.
Should a user be able to log in with an email associated solely with the provider that is different than the main email?
Using OAuth2, If the email is tied to an existing user via an identity, they won't be able to log in. That's what this check does:
appwrite/app/controllers/api/account.php
Lines 564 to 570 in 8fe688a
// Makes sure this email is not already used in another identity | |
$identityWithMatchingEmail = $dbForProject->findOne('identities', [ | |
Query::equal('providerEmail', [$email]), | |
]); | |
if ($identityWithMatchingEmail !== false && !$identityWithMatchingEmail->isEmpty()) { | |
$failureRedirect(Exception::USER_EMAIL_ALREADY_EXISTS); | |
} |
Using OAuth2, If the email is not associated with an existing identity or user, a new user will be created.
Using Create Email Session, a user must use their main user email and cannot use an identity email.
Are we blocking users to attach OAuth providers with emails associated to other users?
Yes, here:
appwrite/app/controllers/api/account.php
Lines 500 to 511 in 8fe688a
// Check if this identity is connected to a different user | |
if (!$user->isEmpty()) { | |
$userId = $user->getId(); | |
$identitiesWithMatchingEmail = $dbForProject->find('identities', [ | |
Query::equal('providerEmail', [$email]), | |
Query::notEqual('userId', $userId), | |
]); | |
if (!empty($identitiesWithMatchingEmail)) { | |
throw new Exception(Exception::USER_ALREADY_EXISTS); | |
} | |
} |
app/workers/deletes.php
Outdated
|
||
// Delete identities | ||
$this->deleteByGroup('identities', [ | ||
Query::equal('userId', [$userId]) |
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.
Use internalId for the deletion of child documents.
|
||
public function getError() : string | ||
{ | ||
return $this->error; |
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.
Why aren't we using the native getMessage interface?
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.
This is the error from the oauth2 provider. The message will include error and error_description.
See https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
345c70c
to
66376de
Compare
434da25
to
a906f23
Compare
app/config/collections.php
Outdated
'$id' => ID::custom('_key_provider_providerUid'), | ||
'type' => Database::INDEX_UNIQUE, | ||
'attributes' => ['provider', 'providerUid'], | ||
'lengths' => [100, 100], |
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.
Provider size is 128 , and providerUid is 2048 (does it have to be so long?)
So will do :
'lengths' => [128, 640],
Since both together is the max of 768
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.
and providerUid is 2048 (does it have to be so long?)
providerUid
in the sessions collection is 2048, so I wanted to make it consistent. Is that okay?
$userId = $user->getId(); | ||
|
||
$identitiesWithMatchingEmail = $dbForProject->find('identities', [ | ||
Query::equal('providerEmail', [$email]), |
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 do not see an index for providerEmail
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked | ||
} | ||
|
||
$identity = $dbForProject->findOne('identities', [ |
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.
To the existing userInternalId index , you can add these 2 attributes: provider, providerUid
['userInternalId', 'provider', 'providerUid']
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.
You're suggesting to make sure there's an index on this query (userInternalId, provider, and providerUid)?
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 , since there is a unique index on [provider,providerUid],
I think adding an additional index on the above will be good
|
||
$filterQueries = Query::groupByType($queries)['filters']; | ||
|
||
$results = $dbForProject->find('identities', $queries); |
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.
Since $queries are dynamic, will have to add indexes for the most common attributes, that will be queried for
a906f23
to
5a52151
Compare
5a52151
to
4cdbd65
Compare
This allows us to retain the OAuth2 info even if the session is deleted. This also provides a foundation for allowing multiple emails, phone numbers, etc, not from an OAuth2 provider.
Setting a password can cause problems with other APIs that expect the password to be null. In addition, it doesn't match the implementation for the other APIs that create a user without a password (Create Magic URL Session, Create Phone Session, Create Anonymous Session, etc).
Update the OAuth2 class to throw an exception if an API call to the OAuth2 provider fails and update the endpoint to redirect to the failure url with the information from the OAuth2 provider.
Prevent missing project ID error.
4cdbd65
to
2e07ac1
Compare
What does this PR do?
(Provide a description of what this PR does and why it's needed.)
Test Plan
Manual
New User - New Session - No Matching Identities
New User - New Session - Matching provider identity
No new user or identity created. Only new session.
Before:
After:
New Session - Matching Another User's Email Identity
If there is an identity with the same email already, there should be an error:
Before:
Error:
Existing User - No Identities - New Session
This should create a new session successfully for backwards compatibility on mobile platforms where we still need to make sure an existing session is used.
Before:
After:
Existing (matching) User - No Identies - Existing Session
This should create a new identity:
Before:
After:
Existing (non-matching) User - No Identities - New Session
Trying to connect an identity where providerEmail matches another user should result in error.
Before:
Error:
Deleting a User Deletes the Identities
Delete job deleted 64b1c0422411984aa897:
Which was the identity:
Related PRs and Issues
Checklist