-
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
Merged
yammesicka
merged 54 commits into
PythonFreeCourse:develop
from
kobyfogel:feature/login
Feb 15, 2021
Merged
Feature/login #195
Changes from all commits
Commits
Show all changes
54 commits
Select commit
Hold shift + click to select a range
43b3f1b
WIP register - not finished
kobyfogel d211377
register before updated pull
kobyfogel 2f0501a
register for review
kobyfogel a5a1a04
register fixed errors
kobyfogel 5d2c52e
register fixed flake-8
kobyfogel eafbe37
register fixed tests flake8
kobyfogel 56d0ca3
register fixed tests issues
kobyfogel 00d9e7d
register placeholders fix
kobyfogel c9c777e
register flake8 fix
kobyfogel bb2b52d
register flake8 more fixes
kobyfogel a32c9c4
fixed CR suggestions
kobyfogel deee638
first commit
kobyfogel 7ca40e1
fixed check_jwt_token
kobyfogel 1bdbc54
Not Finshed
kobyfogel eed52e2
minor fix
kobyfogel b793e2c
starting dependancy
kobyfogel 46820e1
dependancies working
kobyfogel 68e40fe
redirectin and user messages
kobyfogel b7e7e60
async fixing
kobyfogel e3c620a
exception handler
kobyfogel b6b7441
quary parameters
kobyfogel d1849bb
Documentation added
kobyfogel 8d7f135
after pull and fixing conflicts
kobyfogel 84db349
fixed pep8
kobyfogel d9ae477
pep8 more fixes
kobyfogel d7465b5
pep8 config fix
kobyfogel 9d735b1
pep8 jwt fix
kobyfogel 4f5a127
pep8 jwt final fix
kobyfogel 4392974
pep8 final fix
kobyfogel b9ee9d3
CR fixes, tests added
kobyfogel 94eb29a
after merge conflicts
kobyfogel 97d8aff
flake8 fixes
kobyfogel 8b54f67
flake8 fixes2
kobyfogel b46d679
updating requirements
kobyfogel 73f53c1
test added
kobyfogel c15a451
conflicts fixed
kobyfogel c3cad9d
flake8 fix
kobyfogel befd5f9
CR fixing
kobyfogel 063489f
conflicts fixed
kobyfogel da12351
flake8 fixes
kobyfogel cc4fdae
flake8 fix2
kobyfogel 0ae794a
flake8 fixes3
kobyfogel 7a47985
flake8 fixes4
kobyfogel e7bc55a
flake8 fixes5
kobyfogel 8fbe36a
CR fix
kobyfogel f203c38
Revert "CR fix"
kobyfogel bbb241b
CR fixes
kobyfogel b00439d
conflicts fix
kobyfogel 84a6fa7
Merge branch 'develop' of https://github.com/PythonFreeCourse/calenda…
kobyfogel 5f3e3e1
many CR fixes
kobyfogel 5d47833
conflicts fixed
kobyfogel 2488679
CR fixes
kobyfogel 7d5395c
changed dependencies according to CR
kobyfogel 8f59086
fix conflicts
kobyfogel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -145,6 +145,9 @@ dmypy.json | |
| # Pyre type checker | ||
| .pyre/ | ||
|
|
||
|
|
||
| # register stuff | ||
| run.txt | ||
| # VScode | ||
| .vscode/ | ||
| app/.vscode/ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| from typing import Optional, Union | ||
| from pydantic import BaseModel, validator, EmailStr, EmailError | ||
|
|
||
|
|
||
| EMPTY_FIELD_STRING = 'field is required' | ||
| MIN_FIELD_LENGTH = 3 | ||
| MAX_FIELD_LENGTH = 20 | ||
|
|
||
|
|
||
| def fields_not_empty(field: Optional[str]) -> Union[ValueError, str]: | ||
| """Global function to validate fields are not empty.""" | ||
| if not field: | ||
| raise ValueError(EMPTY_FIELD_STRING) | ||
| return field | ||
|
|
||
|
|
||
| class UserBase(BaseModel): | ||
| """ | ||
| Validating fields types | ||
| Returns a User object without sensitive information | ||
| """ | ||
| username: str | ||
| email: str | ||
| full_name: str | ||
| description: Optional[str] = None | ||
|
|
||
| class Config: | ||
| orm_mode = True | ||
|
|
||
|
|
||
| class UserCreate(UserBase): | ||
| """Validating fields types""" | ||
| password: str | ||
| confirm_password: str | ||
|
|
||
| """ | ||
| Calling to field_not_empty validaion function, | ||
| for each required field. | ||
| """ | ||
| _fields_not_empty_username = validator( | ||
| 'username', allow_reuse=True)(fields_not_empty) | ||
| _fields_not_empty_full_name = validator( | ||
| 'full_name', allow_reuse=True)(fields_not_empty) | ||
| _fields_not_empty_password = validator( | ||
| 'password', allow_reuse=True)(fields_not_empty) | ||
| _fields_not_empty_confirm_password = validator( | ||
| 'confirm_password', allow_reuse=True)(fields_not_empty) | ||
| _fields_not_empty_email = validator( | ||
| 'email', allow_reuse=True)(fields_not_empty) | ||
|
|
||
| @validator('confirm_password') | ||
| def passwords_match( | ||
| cls, confirm_password: str, | ||
| values: UserBase) -> Union[ValueError, str]: | ||
| """Validating passwords fields identical.""" | ||
| if 'password' in values and confirm_password != values['password']: | ||
| raise ValueError("doesn't match to password") | ||
| return confirm_password | ||
|
|
||
| @validator('username') | ||
| def username_length(cls, username: str) -> Union[ValueError, str]: | ||
| """Validating username length is legal""" | ||
| if not (MIN_FIELD_LENGTH < len(username) < MAX_FIELD_LENGTH): | ||
| raise ValueError("must contain between 3 to 20 charactars") | ||
| return username | ||
|
|
||
| @validator('password') | ||
| def password_length(cls, password: str) -> Union[ValueError, str]: | ||
| """Validating username length is legal""" | ||
| if not (MIN_FIELD_LENGTH < len(password) < MAX_FIELD_LENGTH): | ||
| raise ValueError("must contain between 3 to 20 charactars") | ||
| return password | ||
|
|
||
| @validator('email') | ||
| def confirm_mail(cls, email: str) -> Union[ValueError, str]: | ||
| """Validating email is valid mail address.""" | ||
| try: | ||
| EmailStr.validate(email) | ||
| return email | ||
| except EmailError: | ||
| raise ValueError("address is not valid") | ||
|
|
||
|
|
||
| class User(UserBase): | ||
| """ | ||
| Validating fields types | ||
| Returns a User object without sensitive information | ||
| """ | ||
| id: int | ||
| is_active: bool |
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| from starlette.requests import Request | ||
|
|
||
| from app.dependencies import get_db | ||
| from app.internal.security.ouath2 import ( | ||
| Depends, Session, check_jwt_token, get_authorization_cookie) | ||
|
|
||
|
|
||
| async def is_logged_in( | ||
| request: Request, db: Session = Depends(get_db), | ||
| jwt: str = Depends(get_authorization_cookie)) -> bool: | ||
| """ | ||
| A dependency function protecting routes for only logged in user | ||
| """ | ||
| await check_jwt_token(db, jwt) | ||
| return True | ||
|
|
||
|
|
||
| async def is_manager( | ||
| request: Request, db: Session = Depends(get_db), | ||
| jwt: str = Depends(get_authorization_cookie)) -> bool: | ||
| """ | ||
| A dependency function protecting routes for only logged in manager | ||
| """ | ||
| await check_jwt_token(db, jwt, manager=True) | ||
| return True | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| from datetime import datetime, timedelta | ||
| from typing import Union | ||
|
|
||
| from passlib.context import CryptContext | ||
| from fastapi import Depends, HTTPException | ||
| from fastapi.security import OAuth2PasswordBearer | ||
| import jwt | ||
| from jwt.exceptions import InvalidSignatureError | ||
| from sqlalchemy.orm import Session | ||
| from starlette.requests import Request | ||
| from starlette.responses import RedirectResponse | ||
| from starlette.status import HTTP_401_UNAUTHORIZED | ||
| from . import schema | ||
|
|
||
| from app.config import JWT_ALGORITHM, JWT_KEY, JWT_MIN_EXP | ||
| from app.database.models import User | ||
|
|
||
|
|
||
| pwd_context = CryptContext(schemes=["bcrypt"]) | ||
| oauth_schema = OAuth2PasswordBearer(tokenUrl="/login") | ||
|
|
||
|
|
||
| def get_hashed_password(password: str) -> str: | ||
| """Hashing user password""" | ||
| return pwd_context.hash(password) | ||
|
|
||
|
|
||
| def verify_password(plain_password: str, hashed_password: str) -> bool: | ||
| """Verifying password and hashed password are equal""" | ||
| return pwd_context.verify(plain_password, hashed_password) | ||
yammesicka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| async def authenticate_user( | ||
| db: Session, new_user: schema.LoginUser, | ||
| ) -> Union[schema.LoginUser, bool]: | ||
| """Verifying user is in database and password is correct""" | ||
| db_user = await User.get_by_username(db=db, username=new_user.username) | ||
| if db_user and verify_password(new_user.password, db_user.password): | ||
| return schema.LoginUser( | ||
| user_id=db_user.id, is_manager=db_user.is_manager, | ||
| username=new_user.username, password=db_user.password) | ||
| return False | ||
|
|
||
|
|
||
| def create_jwt_token( | ||
| user: schema.LoginUser, jwt_min_exp: int = JWT_MIN_EXP, | ||
| jwt_key: str = JWT_KEY) -> str: | ||
| """Creating jwt-token out of user unique data""" | ||
| expiration = datetime.utcnow() + timedelta(minutes=jwt_min_exp) | ||
| jwt_payload = { | ||
| "sub": user.username, | ||
| "user_id": user.user_id, | ||
| "is_manager": user.is_manager, | ||
| "exp": expiration} | ||
| jwt_token = jwt.encode( | ||
| jwt_payload, jwt_key, algorithm=JWT_ALGORITHM) | ||
| return jwt_token | ||
|
|
||
|
|
||
| async def check_jwt_token( | ||
| db: Session, | ||
| token: str = Depends(oauth_schema), path: bool = None, | ||
| manager: bool = False) -> User: | ||
| """ | ||
| Check whether JWT token is correct. | ||
| Returns jwt payloads if correct. | ||
| Raises HTTPException if fails to decode. | ||
| """ | ||
| try: | ||
| jwt_payload = jwt.decode( | ||
| token, JWT_KEY, algorithms=JWT_ALGORITHM) | ||
| if not manager: | ||
| return True | ||
| if jwt_payload.get("is_manager"): | ||
| return True | ||
| raise HTTPException( | ||
| status_code=HTTP_401_UNAUTHORIZED, | ||
| headers=path, | ||
| detail="You don't have a permition to enter this page") | ||
| except InvalidSignatureError: | ||
| raise HTTPException( | ||
| status_code=HTTP_401_UNAUTHORIZED, | ||
| headers=path, | ||
| detail="Your token is incorrect. Please log in again") | ||
| except jwt.ExpiredSignatureError: | ||
| raise HTTPException( | ||
| status_code=HTTP_401_UNAUTHORIZED, | ||
| headers=path, | ||
| detail="Your token has expired. Please log in again") | ||
| except jwt.DecodeError: | ||
| raise HTTPException( | ||
| status_code=HTTP_401_UNAUTHORIZED, | ||
| headers=path, | ||
| detail="Your token is incorrect. Please log in again") | ||
|
|
||
|
|
||
| async def get_authorization_cookie(request: Request) -> str: | ||
| """ | ||
| Extracts jwt from HTTPONLY cookie, if exists. | ||
| Raises HTTPException if not. | ||
| """ | ||
| if 'Authorization' in request.cookies: | ||
| return request.cookies['Authorization'] | ||
| raise HTTPException( | ||
| status_code=HTTP_401_UNAUTHORIZED, | ||
| headers=request.url.path, | ||
| detail="Please log in to enter this page") | ||
|
|
||
|
|
||
| async def auth_exception_handler( | ||
| request: Request, | ||
| exc: HTTP_401_UNAUTHORIZED) -> RedirectResponse: | ||
| """ | ||
| Whenever HTTP_401_UNAUTHORIZED is raised, | ||
| redirecting to login route, with original requested url, | ||
| and details for why original request failed. | ||
| """ | ||
| paramas = f"?next={exc.headers}&message={exc.detail}" | ||
| url = f"/login{paramas}" | ||
yammesicka marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| response = RedirectResponse(url=url) | ||
| response.delete_cookie('Authorization') | ||
| return response | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from typing import Optional | ||
|
|
||
| from pydantic import BaseModel | ||
|
|
||
|
|
||
| class LoginUser(BaseModel): | ||
| """ | ||
| Validating fields types | ||
| Returns a User object for signing in. | ||
| """ | ||
| user_id: Optional[int] | ||
| is_manager: Optional[bool] | ||
| username: str | ||
| password: str | ||
|
|
||
| class Config: | ||
| orm_mode = True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 getTrue/False:)