-
Notifications
You must be signed in to change notification settings - Fork 447
feat: Social Logins #719
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
feat: Social Logins #719
Conversation
Codecov Report
@@ Coverage Diff @@
## develop #719 +/- ##
===========================================
+ Coverage 95.84% 95.93% +0.08%
===========================================
Files 95 98 +3
Lines 5200 5361 +161
===========================================
+ Hits 4984 5143 +159
- Misses 216 218 +2
|
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.
@yugantarjain good work with this PR :) This is the first time that I see code related with social login so this is a bit new to me. Here are my suggestions:
- I suggested some improvements to the code to follow some conventions of this app.
- Unit tests, where you may have to use mock functionalities, I am not sure
- Can you explain in the PR description, and perhaps also in a markdown file under
/docs
folder, how the social login works, how to use this here (for me and others to learn)
app/api/resources/user.py
Outdated
# create tokens and expiry timestamps | ||
access_token = create_access_token(identity=user.id) | ||
refresh_token = create_refresh_token(identity=user.id) | ||
|
||
from run import application | ||
access_expiry = datetime.utcnow() + application.config.get( | ||
"JWT_ACCESS_TOKEN_EXPIRES" | ||
) | ||
refresh_expiry = datetime.utcnow() + application.config.get( | ||
"JWT_REFRESH_TOKEN_EXPIRES" | ||
) | ||
|
||
# return data | ||
return ( | ||
{ | ||
"access_token": access_token, | ||
"access_expiry": access_expiry.timestamp(), | ||
"refresh_token": refresh_token, | ||
"refresh_expiry": refresh_expiry.timestamp(), | ||
}, | ||
HTTPStatus.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.
This can go to a private method in this file or somewhere else, as long as you reuse this logic since you use it in at least 2 different places.
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.
Done
app/api/resources/user.py
Outdated
|
||
token = request.json.get("id_token") | ||
email = request.json.get("email") | ||
client_id = "992237180107-lsibe891591qcubpbd8qom4fts74i5in.apps.googleusercontent.com" |
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.
is this a secret variable? can you please put this in a constant, either here or probably better in Config.py
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.
Okay. I checked the config.py file and am not sure how exactly to add this constant there... Should I just do it the way it is done in messages.py?
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.
@yugantarjain Read the 4th Point here in Setup and Run part of Docs https://github.com/anitab-org/mentorship-backend. Config.py reads the os.env variables for config. As part of setup of this app, someone sets the env variables. So you just need to add the variable and read from there.
I am not sure how you can get the exact value of the config into production instance. @isabelcosta you can help clarify this?
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.
Done. Made a constant in config.py.
app/api/resources/user.py
Outdated
user = DAO.get_user_for_social_login(email) | ||
|
||
if not user: | ||
# create a new user | ||
data = request.json | ||
user = DAO.create_user_using_social_login(data) | ||
|
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.
will the logic here work fine if this is not a gmail email? I never did anything like this so I am really curious
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, it will work fine. In-fact, when I tested with Sign in with Apple, I hid my email (the service then returns a private relay email) and it worked perfectly.
app/database/models/user.py
Outdated
@@ -29,6 +29,7 @@ class UserModel(db.Model): | |||
|
|||
# security | |||
password_hash = db.Column(db.String(100)) | |||
apple_auth_id = db.Column(db.String(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.
for this to work I think you need to create a migration script to add this field to dev database. Can you do this?
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, I did create a migration script and ran it locally. Do I need to perform any additional steps?
"username": fields.String(required=False, description="User username"), | ||
"password": fields.String(required=False, description="User password"), |
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.
Does this need to change? as far as I see you did not change anything to the POST /register API, you created new endpoints that follow a different request model.
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. Actually, when a user signs up using social login, we first try to find them in our database. If a pre-existing user is not found, we create a new one. To facilitate the creation of a new user, these fields were made optional. I did some testing and it works fine (because we have validation checks for them in any case) and all the unit tests passed.
If there is a better solution to this, please let me know. We also have an ongoing discussion about this on zulip mentorship ios channel.
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.
Sounds good @yugantarjain thank you for your explanation!
This PR is aimed to solve #730 Issue |
app/api/resources/user.py
Outdated
user = DAO.create_user_using_social_login(data, provider) | ||
# If any error occured, return error | ||
if not isinstance(user, UserModel): | ||
return user |
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 returning a user instance here? Is this expected? in case of error, shouldn't you format it or does the user variable have the error response in which case you should also return error type (and also can you make that clear using comments?)
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.
If user is not an instance of UserModel, then that means it contains the error message and http code.
Comment added to clarify the code.
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 we unwrap the error and http code from user
variable here to make the code more readable without comment?
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.
Done
app/api/resources/user.py
Outdated
|
||
token = request.json.get("id_token") | ||
email = request.json.get("email") | ||
client_id = "992237180107-lsibe891591qcubpbd8qom4fts74i5in.apps.googleusercontent.com" |
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.
@yugantarjain Read the 4th Point here in Setup and Run part of Docs https://github.com/anitab-org/mentorship-backend. Config.py reads the os.env variables for config. As part of setup of this app, someone sets the env variables. So you just need to add the variable and read from there.
I am not sure how you can get the exact value of the config into production instance. @isabelcosta you can help clarify this?
…e constant in config.py
@vatsalkul just realized, the id token for apple is always the same, but google gives a new one. However, the logic remains unaffected. |
config.py
Outdated
@@ -1,6 +1,7 @@ | |||
import os | |||
from datetime import timedelta | |||
|
|||
GOOGLE_AUTH_CLIENT_ID = "992237180107-lsibe891591qcubpbd8qom4fts74i5in.apps.googleusercontent.com" |
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 are not supposed to commit this, instead keep it configurable using an environment variable.
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.
Please also make sure you revoke the oAuth application associated with this, if you don't want misuse.
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.
Fixed. Thanks!
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 are not supposed to commit this, instead keep it configurable using an environment variable.
I actually thought about that, but then ... not sure why I forgot to review it here. Thank you so much @SanketDG for your review!
app/database/models/user.py
Outdated
|
||
# default values | ||
self.is_admin = True if self.is_empty() else False # first user is admin | ||
self.is_email_verified = False | ||
if social_login: | ||
self.is_email_verified = 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.
Why not verify here? Any special usecase?
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. For normal login, the email is not verified and the user specifically has to do that from a link they receive.
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.
Got you, I was thinking something different, not applicable here though
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 have updated this to - self.is_email_verified = social_login
That removes unnecessary if statements.
@isabelcosta I guess all the requested changes have been addressed. |
associated_email = db.Column(db.String(50)) | ||
full_name = db.Column(db.String(50)) | ||
|
||
def __init__(self, user_id, sign_in_type, id_token, email, name): |
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 you are using type hints in the other functions of this file, why not use here :)
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'm not clear about this point.
"JWT_REFRESH_TOKEN_EXPIRES" | ||
) | ||
|
||
# return data |
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.
# return data |
Is this line for debugging purposes?
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.
No, this is just a comment, shall I remove it?
Co-authored-by: Isabel Costa <[email protected]>
Co-authored-by: Isabel Costa <[email protected]>
Co-authored-by: Isabel Costa <[email protected]>
Co-authored-by: Isabel Costa <[email protected]>
@isabelcosta @SanketDG Should I raise new Issue under Outreach/Research for OSH: Such that someone might propose a probable uniform solution integrating more social logins like Slack, Facebook, Twitter, etc. If, yes. Should I break it into two or more parts? And how many hours and people should be assigned? |
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.
Looks good to merge!
Sounds good to me @paritoshsinghrahar
Great! |
@paritoshsinghrahar I +1 this :) |
@anitab-org/mentorship-backend-maintainers any update on this? |
@yugantarjain please resolve the merge conflicts |
@vj-codes i think we can close this for now, since its inactive for a whillllle. Also the main issue should have its scope reduced, to split issues. So social login for one service (e.g.: google) and another for another service (e.g.: github). Can you help refine the issue please? |
Description
Currently a work in progress, this PR implements social Login options (Google and Apple). This has been done by creating callback APIs for both Google auth and Apple auth.
Flow:
Fixes #730
Type of Change:
Code/Quality Assurance Only
How Has This Been Tested?
Tested on local host. Google sign in done using iOS app to get id token. Then id token used in backend api and tested.
Checklist:
Code/Quality Assurance Only