-
Notifications
You must be signed in to change notification settings - Fork 52
Feature/login #195
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
Feature/login #195
Conversation
|
If i clicked on 'resoved' on an issue, without addressing it, it was just by mistake. In few of the cases, i just thought that the CR is for another line of code, and i thought i did resolve it. |
yammesicka
left a 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.
Awesome progress - keep up the good job :)
app/internal/security/ouath2.py
Outdated
| token, JWT_KEY, algorithms=JWT_ALGORITHM) | ||
| jwt_username = jwt_payload.get("sub") | ||
| jwt_hashed_password = jwt_payload.get("hashed_password") | ||
| db_user = await get_db_user_by_username(db, username=jwt_username) |
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.
Sorry, but I still unable to understand why.
Why not having one method to check the username and the password against the database (which happens only if the user asks to log-in), and another method to check if the user is authenticated (have a valid JWT token)?
app/internal/security/ouath2.py
Outdated
| jwt_user_id = jwt_payload.get("user_id") | ||
| db_user = await get_db_user_by_username(db, username=jwt_username) | ||
| if db_user and db_user.id == jwt_user_id: | ||
| return CurrentUser(**db_user.__dict__) |
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.
What is the difference between CurrentUser and LoginUser?
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.
LoginUser is a pydantic object for internal actions management of the security system.
CurrentUser is the Object that will be returned to the routes from the dependencies functions, after verifying JWT token. It's purpose is to return a User Base Model object to the routed, minus the password column, and keep the workflow with pydantic models.
I'm thinking maybe to re-change it to a Base Model object, as the code is keep on changing by other. And this object has to be maintained in order to keep it's functionality.
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.
What are the downsides of having only one of them? Currently having two different classes for two similar objects is a bit confusing from the developer side
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 by 'them' you mean LoginUser and CurrentUser, so they are not the same at all. LoginUser is created from the login form, and verifies that required information was given by the user. It is essential for this action. As CurrentUser supposed to return all the User information to the routes, after jwt verification.
Maybe it will be better to delete CurrentUser, and just return the User Base Object? It's a db object, no confusions there. That will be also much easy to understand just by looking at the type annotations.
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 think the major source of confusion is in the object's name. Maybe we should find a better name which describes it better and which highlights the differences between them.
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 it ok by you, i will just return db_user. otherwise, every change in User base model will have to be updated in CurrentUser, in order for it to return the new column.
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.
Just be sure to name stuff very indicatively. Otherwise it'll be really PITA to maintain :)
app/internal/security/ouath2.py
Outdated
| jwt_user_id = jwt_payload.get("user_id") | ||
| db_user = await get_db_user_by_username(db, username=jwt_username) | ||
| if db_user and db_user.id == jwt_user_id: | ||
| return CurrentUser(**db_user.__dict__) |
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.
What are the downsides of having only one of them? Currently having two different classes for two similar objects is a bit confusing from the developer side
yammesicka
left a 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.
Great job! Another review to go :)
| A dependency function protecting routes for only logged in user | ||
| """ | ||
| await check_jwt_token(db, jwt) | ||
| return 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.
Will this function always return 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.
this function activates check_jwt_token function. That function will raise an exception if validation fails. so yes..
should i add that to annotations?
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.
So catch the exception and return False.
The function called is_logged_in, the developer who sees it surely expect to get True/False :)
| from starlette.requests import Request | ||
|
|
||
|
|
||
| async def validate_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.
Hasn't this function should have been called get_user?
| @@ -0,0 +1,66 @@ | |||
| from typing import Union | |||
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 some problems in this CR could be resolved with better naming.
We should also rethink a bit about the design: there are some functions that it's hard to tell why they're different one from another.
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 and great improvement! Here are some things to improve further :)
| A dependency function protecting routes for only logged in user | ||
| """ | ||
| await check_jwt_token(db, jwt) | ||
| return 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.
So catch the exception and return False.
The function called is_logged_in, the developer who sees it surely expect to get True/False :)
|
|
||
| async def optional_logged_in_user( | ||
| request: Request, | ||
| db: Session = Depends(get_db)) -> Union[User, type(None)]: |
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.
Optional[User](instead ofUnionwithNoneType)- Remove the indentation before the
db: - Prefix
)in a new line, with comma after the previous line.
Look how the code become much more readable:
async def current_user(
request: Request, db: Session = Depends(get_db),
) -> Optional[User]:| """ | ||
| A dependency function. | ||
| Returns logged in User object if exists. | ||
| None if not. | ||
| """ |
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.
Prefer a docstring that matches the guidelines in PEP 257:
"""Return the currently logged in user.
Return logged in User object if exists, None if not.
A dependency function.
"""
app/routers/login.py
Outdated
| @router.get("/login") | ||
| async def login_user_form( | ||
| request: Request, message: Optional[str] = "", | ||
| current_user: Union[User, None] = Depends(optional_logged_in_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.
Optional
| @router.post('/login') | ||
| async def login( | ||
| request: Request, | ||
| next: Optional[str] = "/", |
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 next can be http://attacker.com, for example.
Please read about open redirection attack.
| return await get_user(db, user_id, username, request.url.path) | ||
|
|
||
|
|
||
| async def optional_logged_in_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.
Is that another instance of get_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.
No it's not.
It is designed to be a route dependency.
get_user is a function that helps other dependencies, after the jwt token was extracted from cookie, and was checked.
The way it is done varies with the different dependencies.
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.
Unfortunately I can't understand what is the difference even after your explanation.
Please try to look at the issue from the perspective of the programmer who uses your system.
He probably wants to have:
is_logged_inis_managerget_user_by_username/_id/_emaillogin/logout
That's it.
|
|
||
|
|
||
| @router.get('/logout') | ||
| async def logout(request: Request): |
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.
Can we somehow check the logout request using some CSRF cookie? Currently it will disconnect you if you'll click a link that I've sent you.
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.
(Don't overthink it if you find it too hard - I do want to finish this ticket ASAP)
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 started to read about it. Seems like it's a whole topic of security, which is new to me.
I am always eager to learn more but that will take some time to understand in a proper depth.
If we can leave it as it is for now, I promise to try to improve it after i finish my tickets.
If you have any good suggestions for me regarding what on this subject to put my focus on, i will be more then happy to know.
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.
Sababa :)
app/routers/register.py
Outdated
|
|
||
|
|
||
| def get_error_messages_by_fields(errors: [list]) -> Dict[str, str]: | ||
| '''Getting validation errors by fields from pydantic ValidationError''' |
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.
Decide on whether to use ''' or """ (you mixed them inside the file)
app/routers/register.py
Outdated
| ) | ||
|
|
||
|
|
||
| async def user_create( |
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.
Prefer create_user
|
I tried to answer the best i can, hopefully it clarifies everything that was not understood. |
yammesicka
left a 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.
Hope we're almost done :)
| return True | ||
|
|
||
|
|
||
| async def logged_in_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.
So how is it different from get_current_user?
You must understand you're building an API for other programmers.
All the functions must be easily understood from their names only.
Currently it's really hard to tell what this function (or any other function in this file) do from only looking at it's name.
We must find a better naming that will describe it in a way the other programmers would be able to understand what is does.
| return await get_user(db, user_id, username, request.url.path) | ||
|
|
||
|
|
||
| async def optional_logged_in_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.
Unfortunately I can't understand what is the difference even after your explanation.
Please try to look at the issue from the perspective of the programmer who uses your system.
He probably wants to have:
is_logged_inis_managerget_user_by_username/_id/_emaillogin/logout
That's it.
app/routers/register.py
Outdated
| @@ -0,0 +1,101 @@ | |||
| from typing import Dict | |||
|
|
|||
| from app.internal.security.ouath2 import get_hashed_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.
This paragraph should be the 3rd one
This is my version for users management system: register, login, and current logged-in user.
This version doesn't use fast-api_users library.
My comment to 1 CR was not answered, so it's not 'resolved' yet.
Instructions for using security dependencies are given in README.md file.