From 43b3f1b4539e6eb0f2d4cd03280a70892fccfcfe Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Tue, 19 Jan 2021 23:08:17 +0200 Subject: [PATCH 01/56] WIP register - not finished --- .env | 1 + app/crud.py | 40 +++++++++++++++++++++++++ app/database/database.py | 4 +++ app/database/models.py | 4 ++- app/database/schemas.py | 57 +++++++++++++++++++++++++++++++++++ app/main.py | 60 ++++++++++++++++++++++++++++++++++++- app/templates/base.html | 11 +++++++ app/templates/register.html | 24 +++++++++++++++ requirements.txt | 9 ++++++ run.txt | 1 + 10 files changed, 209 insertions(+), 2 deletions(-) create mode 100644 .env create mode 100644 app/crud.py create mode 100644 app/templates/base.html create mode 100644 app/templates/register.html create mode 100644 run.txt diff --git a/.env b/.env new file mode 100644 index 00000000..5388fd5c --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_CONNECTION_STRING = "sqlite:///calendar.db" \ No newline at end of file diff --git a/app/crud.py b/app/crud.py new file mode 100644 index 00000000..3c944e68 --- /dev/null +++ b/app/crud.py @@ -0,0 +1,40 @@ +import bcrypt +from database import models, schemas +from sqlalchemy.orm import Session + + +def get_user(db: Session, user_id: int): + return db.query(models.User).filter(models.User.id == user_id).first() + + +def get_user_by_username(db: Session, username: str): + return db.query(models.User).filter(models.User.username == username).first() + + +def get_user_by_email(db: Session, email: str): + return db.query(models.User).filter(models.User.email == email).first() + + +def create_user(db: Session, user: schemas.UserCreate): + salt = bcrypt.gensalt(prefix=b'2b', rounds=10) + unhashed_password = user.password.encode('utf-8') + hashed_password = bcrypt.hashpw(unhashed_password, salt) + fake_hashed_password = user.password + "notreallyhashed" + user_details = { + 'username': user.username, + 'first_name': user.first_name, + 'last_name': user.last_name, + 'email': user.email, + 'password': hashed_password + } + db_user = models.User(**user_details) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + + +def delete_user_by_mail(db: Session, email: str): + db_user = get_user_by_email(db=db, email=email) + db.delete(db_user) + db.commit() \ No newline at end of file diff --git a/app/database/database.py b/app/database/database.py index 43626378..fe08b1d8 100644 --- a/app/database/database.py +++ b/app/database/database.py @@ -1,10 +1,14 @@ import os +from dotenv import load_dotenv from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker + +load_dotenv() + SQLALCHEMY_DATABASE_URL = os.getenv("DATABASE_CONNECTION_STRING") engine = create_engine( diff --git a/app/database/models.py b/app/database/models.py index 1b3bf771..b64000c7 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -9,8 +9,10 @@ class User(Base): id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True) + first_name = Column(String, nullable=False) + last_name = Column(String, nullable=False) email = Column(String, unique=True) - password = Column(String) + password = Column(String, nullable=False) is_active = Column(Boolean, default=True) events = relationship( diff --git a/app/database/schemas.py b/app/database/schemas.py index e69de29b..e61b3163 100644 --- a/app/database/schemas.py +++ b/app/database/schemas.py @@ -0,0 +1,57 @@ +import re +from typing import List +from fastapi import Depends, Form, Query +from pydantic import BaseModel, validator, EmailStr, EmailError + + +def form_body(cls): + cls.__signature__ = cls.__signature__.replace( + parameters=[ + arg.replace(default=Form(...)) + for arg in cls.__signature__.parameters.values() + ] + ) + return cls + +@form_body +class UserBase(BaseModel): + username: str + email: str + first_name: str + last_name: str + +@form_body +class UserCreate(UserBase): + password: str + confirm_password: str + + @validator('confirm_password') + def passwords_match(cls, confirm_password, values, **kwargs): + if 'password' in values and confirm_password != values['password']: + return {"message": "Passwords don't match"} + return confirm_password + + @validator('username') + def username_length(cls, username): + if username == "": + return {"message": "Username field can not be empty"} + if len(username) < 3: + return {"message": "Username must contain minimum of 3 characters"} + return username + + @validator('email') + def confirm_mail(cls, email): + try: + EmailStr.validate(email) + return email + except EmailError: + return {"message": "Email address is not valid"} + + +class User(UserBase): + id: int + is_active: bool + events: List[int] = [] + + class Config: + orm_mode = True diff --git a/app/main.py b/app/main.py index dfbf4ca2..5e9272cf 100644 --- a/app/main.py +++ b/app/main.py @@ -1,12 +1,70 @@ -from fastapi import FastAPI, Request +from database import models, schemas +from database.database import engine, SessionLocal +from fastapi import Depends, FastAPI, Request from fastapi.templating import Jinja2Templates +from sqlalchemy.orm import Session +import crud +models.Base.metadata.create_all(bind=engine) app = FastAPI() templates = Jinja2Templates(directory="templates") +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + +@app.get("/register", response_model=schemas.User) +def register_user_form(request: Request): + return templates.TemplateResponse("register.html", { + "request": request, + "message": "register page" + }) + + +@app.post("/register", response_model=schemas.UserBase) +def register_user(request: Request, user: schemas.UserBase = Depends(schemas.UserCreate), db: Session = Depends(get_db)): + messages = [] + if type(user.confirm_password) is dict or type(user.email) is dict or type(user.username) is dict: + if type(user.confirm_password) is dict: + messages.append(user.confirm_password['message']) + if type(user.email) is dict: + messages.append(user.email['message']) + if type(user.username) is dict: + messages.append(user.username['message']) + else: + db_user_email = crud.get_user_by_email(db, email=user.email) + db_user_username = crud.get_user_by_username(db, username=user.username) + if db_user_username: + messages.append("That username is already taken") + if db_user_email: + messages.append("Email already registered") + if len(messages) > 0: + return templates.TemplateResponse("register.html", { + "request": request, + "messages": messages, + "status_code": 400 + }) + crud.create_user(db=db, user=user) + return templates.TemplateResponse("home.html", { + "request": request, + "message": "User created", + "status_code": 201 + }) + + +@app.get("/delete") +def delete_user(db: Session = Depends(get_db)): + crud.delete_user_by_mail(db=db, email="kobyfogel@gmail.com") + + @app.get("/") def home(request: Request): return templates.TemplateResponse("home.html", { diff --git a/app/templates/base.html b/app/templates/base.html new file mode 100644 index 00000000..656240ef --- /dev/null +++ b/app/templates/base.html @@ -0,0 +1,11 @@ + + + + + + {{ title }} + + + {% block content %}{% endblock %} + + \ No newline at end of file diff --git a/app/templates/register.html b/app/templates/register.html new file mode 100644 index 00000000..ebeb8d75 --- /dev/null +++ b/app/templates/register.html @@ -0,0 +1,24 @@ +{% extends "base.html" %} +{% block content %} +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + {% if messages %} + {% for message in messages %} +

{{message}}

+ {% endfor %} + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index de91534a..32b6ee69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,17 @@ atomicwrites==1.4.0 attrs==20.3.0 +bcrypt==3.2.0 +cffi==1.14.4 click==7.1.2 colorama==0.4.4 +dnspython==2.1.0 +email-validator==1.1.2 fastapi==0.63.0 h11==0.12.0 h2==4.0.0 hpack==4.0.0 hyperframe==6.0.0 +idna==3.1 importlib-metadata==3.3.0 iniconfig==1.1.1 Jinja2==2.11.2 @@ -15,9 +20,13 @@ packaging==20.8 pluggy==0.13.1 priority==1.3.0 py==1.10.0 +pycparser==2.20 pydantic==1.7.3 pyparsing==2.4.7 pytest==6.2.1 +python-dotenv==0.15.0 +python-multipart==0.0.5 +six==1.15.0 SQLAlchemy==1.3.22 starlette==0.13.6 toml==0.10.2 diff --git a/run.txt b/run.txt new file mode 100644 index 00000000..76ea2dfa --- /dev/null +++ b/run.txt @@ -0,0 +1 @@ +uvicorn main:app --reload \ No newline at end of file From a5a1a04765e20a904da4be14250c35707d6a675f Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Sat, 23 Jan 2021 14:53:55 +0200 Subject: [PATCH 02/56] register fixed errors --- app/database/models.py | 4 +-- app/database/schemas.py | 35 +++++++++++----------- app/internal/crud.py | 6 ++-- app/routers/register.py | 31 +++++++++++--------- tests/test_agenda_route.py | 1 - tests/test_crud.py | 31 +++++++++++--------- tests/test_register.py | 59 +++++++++++++++++++++++++------------- 7 files changed, 97 insertions(+), 70 deletions(-) diff --git a/app/database/models.py b/app/database/models.py index 3ca874a2..665b27f5 100644 --- a/app/database/models.py +++ b/app/database/models.py @@ -9,14 +9,12 @@ class User(Base): id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, nullable=False) - # first_name = Column(String, nullable=False) - # last_name = Column(String, nullable=False) email = Column(String, unique=True, nullable=False) password = Column(String, nullable=False) full_name = Column(String, nullable=False) description = Column(String, default="Happy new user!") avatar = Column(String, default="profile.png") - + is_active = Column(Boolean, default=True) events = relationship( diff --git a/app/database/schemas.py b/app/database/schemas.py index 697b1033..5f8e7b5d 100644 --- a/app/database/schemas.py +++ b/app/database/schemas.py @@ -1,7 +1,5 @@ -import re -from typing import List, Optional, Union -from fastapi import Depends, Form, Query -from pydantic import BaseModel, validator, EmailStr, EmailError, root_validator +from typing import Optional, Union +from pydantic import BaseModel, validator, EmailStr, EmailError EMPTY_FIELD_STRING = 'field is required' @@ -29,6 +27,7 @@ class UserBase(BaseModel): class Config: orm_mode = True + class UserCreate(UserBase): ''' Validating fields types @@ -36,19 +35,23 @@ class UserCreate(UserBase): 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) - + _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]: + def passwords_match(cls, confirm_password: str, + values: UserBase) -> Union[ValueError, str]: ''' Validating passwords fields identical. ''' @@ -61,19 +64,19 @@ def username_length(cls, username: str) -> Union[ValueError, str]: ''' Validating username length is legal ''' - if len(username) < 3 or len(username) > 20: + if len(username) < 3 or len(username) > 20: 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 len(password) < 3 or len(password) > 20: + if len(password) < 3 or len(password) > 20: raise ValueError("must contain between 3 to 20 charactars") return password - + @validator('email') def confirm_mail(cls, email: str) -> Union[ValueError, str]: ''' diff --git a/app/internal/crud.py b/app/internal/crud.py index 2b2d6bd4..ae66e656 100644 --- a/app/internal/crud.py +++ b/app/internal/crud.py @@ -14,7 +14,8 @@ def get_user_by_username(db: Session, username: str) -> models.User: ''' query database for a user by unique username ''' - return db.query(models.User).filter(models.User.username == username).first() + return db.query(models.User).filter( + models.User.username == username).first() def get_user_by_email(db: Session, email: str) -> models.User: @@ -51,4 +52,5 @@ def delete_user_by_mail(db: Session, email: str) -> None: ''' db_user = get_user_by_email(db=db, email=email) db.delete(db_user) - db.commit() \ No newline at end of file + db.commit() + \ No newline at end of file diff --git a/app/routers/register.py b/app/routers/register.py index dac2d36e..4578e161 100644 --- a/app/routers/register.py +++ b/app/routers/register.py @@ -26,7 +26,8 @@ async def register_user_form(request: Request) -> templates: @router.post("/register") -async def register(request:Request, db: Session = Depends(get_db)) -> templates: +async def register(request: Request, + db: Session = Depends(get_db)) -> templates: ''' rendering register route post method. ''' @@ -39,11 +40,12 @@ async def register(request:Request, db: Session = Depends(get_db)) -> templates: except ValidationError as e: # if pydantic validations fails, rendering errors to register.html - errors = {error['loc'][0]: " ".join((error['loc'][0].capitalize(), error['msg'])) for error in e.errors()} + errors = {error['loc'][0]: " ".join((error['loc'][0].capitalize(), + error['msg'])) for error in e.errors()} return templates.TemplateResponse("register.html", { - "request": request, - "errors": errors, - "form_values": form_dict}) + "request": request, + "errors": errors, + "form_values": form_dict}) try: # attempt creating User Model object, and saving to database @@ -57,18 +59,19 @@ async def register(request:Request, db: Session = Depends(get_db)) -> templates: db.rollback() errors = {} db_user_email = crud.get_user_by_email(db, email=user.email) - db_user_username = crud.get_user_by_username(db, username=user.username) + db_user_username = crud.get_user_by_username(db, + username=user.username) if db_user_username: errors['username'] = "That username is already taken" if db_user_email: - errors['email'] = "Email already registered" + errors['email'] = "Email already registered" return templates.TemplateResponse("register.html", { - "request": request, - "errors": errors, - "form_values": form_dict}) + "request": request, + "errors": errors, + "form_values": form_dict}) return templates.TemplateResponse("home.html", { - "request": request, - "message": "User created", - "status_code": 201 -}) \ No newline at end of file + "request": request, + "message": "User created", + "status_code": 201}) + \ No newline at end of file diff --git a/tests/test_agenda_route.py b/tests/test_agenda_route.py index 9e4111c7..707c23fc 100644 --- a/tests/test_agenda_route.py +++ b/tests/test_agenda_route.py @@ -3,7 +3,6 @@ from fastapi import status from app.database.models import User, Event -import pytest class TestAgenda: diff --git a/tests/test_crud.py b/tests/test_crud.py index ffd0db2d..6e5b9b6e 100644 --- a/tests/test_crud.py +++ b/tests/test_crud.py @@ -1,15 +1,16 @@ from app.internal import crud -from app.database import models, schemas +from app.database import schemas user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} +'password': 'password', 'confirm_password': 'password', +'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) + def test_create_user(session): user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) assert user.username == 'username' @@ -17,8 +18,8 @@ def test_create_user(session): def test_get_user_by_id(session): user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) user = crud.get_user_by_id(session, 1) @@ -27,8 +28,8 @@ def test_get_user_by_id(session): def test_get_user_by_username(session): user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) user = crud.get_user_by_username(session, 'username') @@ -37,19 +38,21 @@ def test_get_user_by_username(session): def test_get_user_by_email(session): user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) user = crud.get_user_by_email(session, 'example@email.com') assert user.full_name == 'full_name' + def test_delete_user_by_email(session): user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) crud.create_user(db=session, user=user) crud.delete_user_by_mail(session, 'example@email.com') user = crud.get_user_by_email(session, 'example@email.com') - assert user == None \ No newline at end of file + assert user is None + \ No newline at end of file diff --git a/tests/test_register.py b/tests/test_register.py index c897ec3d..f1108297 100644 --- a/tests/test_register.py +++ b/tests/test_register.py @@ -5,23 +5,37 @@ def test_register_route_ok(client): response = client.get("/register") assert response.status_code == 200 + REGISTER_FORM_VALIDATORS = [ - ('ad', 'admin_user', 'password', 'password', 'example@mail.com', 'description', b'Username must contain'), - ('admin', 'admin_user', 'pa', 'pa', 'example@mail.com', 'description', b'Password must contain'), - ('admin', 'admin_user', 'password', 'wrong_password', 'example@mail.com', 'description', b"match"), - ('admin', 'admin_user', 'password', 'password', 'invalid_mail', 'description', b"Email address is not valid"), - ('', 'admin_user', 'password', 'password', 'example@mail.com', 'description', b'Username field is required'), - ('admin', '', 'password', 'password', 'example@mail.com', 'description', b'Full_name field is required'), - ('admin', 'admin_user', '', 'password', 'example@mail.com', 'description', b'Password field is required'), - ('admin', 'admin_user', 'password', '', 'example@mail.com', 'description', b'Confirm_password field is required'), - ('admin', 'admin_user', 'password', 'password', '', 'description', b'Email field is required'), + ('ad', 'admin_user', 'password', 'password', 'example@mail.com', + 'description', b'Username must contain'), + ('admin', 'admin_user', 'pa', 'pa', 'example@mail.com', + 'description', b'Password must contain'), + ('admin', 'admin_user', 'password', 'wrong_password', 'example@mail.com', + 'description', b"match"), + ('admin', 'admin_user', 'password', 'password', 'invalid_mail', + 'description', b"Email address is not valid"), + ('', 'admin_user', 'password', 'password', 'example@mail.com', + 'description', b'Username field is required'), + ('admin', '', 'password', 'password', 'example@mail.com', + 'description', b'Full_name field is required'), + ('admin', 'admin_user', '', 'password', 'example@mail.com', + 'description', b'Password field is required'), + ('admin', 'admin_user', 'password', '', 'example@mail.com', + 'description', b'Confirm_password field is required'), + ('admin', 'admin_user', 'password', 'password', '', + 'description', b'Email field is required'), ] + ''' Test all active pydantic validators ''' -@pytest.mark.parametrize("username, full_name, password, confirm_password, email, description, expected_response", REGISTER_FORM_VALIDATORS) -def test_register_form_validators(client, username, full_name, password, confirm_password, email, description, expected_response): +@pytest.mark.parametrize( + "username, full_name, password, confirm_password, email, description," + + "expected_response", REGISTER_FORM_VALIDATORS) +def test_register_form_validators(client, username, full_name, password, + confirm_password, email, description, expected_response): data = {'username': username, 'full_name': full_name, 'password': password, 'confirm_password': confirm_password, 'email': email, 'description': description} @@ -34,27 +48,32 @@ def test_register_form_validators(client, username, full_name, password, confirm ''' def test_register_successfull(session, client): data = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} data = client.post('/register', data=data).content assert b'User created' in data UNIQUE_FIELDS_ARE_TAKEN = [ - ('admin', 'admin_user', 'password', 'password', 'example_new@mail.com', 'description', b'That username is already taken'), - ('admin_new', 'admin_user', 'password', 'password', 'example@mail.com', 'description', b"Email already registered") + ('admin', 'admin_user', 'password', 'password', 'example_new@mail.com', + 'description', b'That username is already taken'), + ('admin_new', 'admin_user', 'password', 'password', 'example@mail.com', + 'description', b"Email already registered") ] ''' Test register a user fails due to unique database fields already in use ''' -@pytest.mark.parametrize("username, full_name, password, confirm_password, email, description, expected_response", UNIQUE_FIELDS_ARE_TAKEN) -def test_register_form_validators(session, client, username, full_name, password, confirm_password, email, description, expected_response): +@pytest.mark.parametrize("username, full_name, password, confirm_password, ++ "email, description, expected_response", UNIQUE_FIELDS_ARE_TAKEN) +def test_register_form_validators(session, client, username, full_name, password, confirm_password, + email, description, expected_response): user_data ={'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} client.post('/register', data=user_data) data = client.post('/register', data=user_data).content print(data) - assert expected_response in data \ No newline at end of file + assert expected_response in data + \ No newline at end of file From 5d2c52ecd04bbaec254a41b10f0de0964db6b83b Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Sat, 23 Jan 2021 17:49:31 +0200 Subject: [PATCH 03/56] register fixed flake-8 --- app/database/schemas.py | 25 +++++++++++++------------ app/internal/crud.py | 1 - app/routers/register.py | 15 ++++++++------- run.txt | 2 ++ 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/database/schemas.py b/app/database/schemas.py index 5f8e7b5d..b9de56bf 100644 --- a/app/database/schemas.py +++ b/app/database/schemas.py @@ -38,20 +38,21 @@ class UserCreate(UserBase): ''' 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) + _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]: + def passwords_match( + cls, confirm_password: str, + values: UserBase) -> Union[ValueError, str]: ''' Validating passwords fields identical. ''' diff --git a/app/internal/crud.py b/app/internal/crud.py index ae66e656..f3188e4a 100644 --- a/app/internal/crud.py +++ b/app/internal/crud.py @@ -53,4 +53,3 @@ def delete_user_by_mail(db: Session, email: str) -> None: db_user = get_user_by_email(db=db, email=email) db.delete(db_user) db.commit() - \ No newline at end of file diff --git a/app/routers/register.py b/app/routers/register.py index 4578e161..557caf05 100644 --- a/app/routers/register.py +++ b/app/routers/register.py @@ -14,6 +14,7 @@ responses={404: {"description": "Not found"}}, ) + @router.get("/register") async def register_user_form(request: Request) -> templates: ''' @@ -26,8 +27,8 @@ async def register_user_form(request: Request) -> templates: @router.post("/register") -async def register(request: Request, - db: Session = Depends(get_db)) -> templates: +async def register( + request: Request, db: Session = Depends(get_db)) -> templates: ''' rendering register route post method. ''' @@ -40,7 +41,8 @@ async def register(request: Request, except ValidationError as e: # if pydantic validations fails, rendering errors to register.html - errors = {error['loc'][0]: " ".join((error['loc'][0].capitalize(), + errors = {error['loc'][0]: " ".join(( + error['loc'][0].capitalize(), error['msg'])) for error in e.errors()} return templates.TemplateResponse("register.html", { "request": request, @@ -59,8 +61,8 @@ async def register(request: Request, db.rollback() errors = {} db_user_email = crud.get_user_by_email(db, email=user.email) - db_user_username = crud.get_user_by_username(db, - username=user.username) + db_user_username = crud.get_user_by_username( + db, username=user.username) if db_user_username: errors['username'] = "That username is already taken" if db_user_email: @@ -69,9 +71,8 @@ async def register(request: Request, "request": request, "errors": errors, "form_values": form_dict}) - + return templates.TemplateResponse("home.html", { "request": request, "message": "User created", "status_code": 201}) - \ No newline at end of file diff --git a/run.txt b/run.txt index 54de08c2..51dc3341 100644 --- a/run.txt +++ b/run.txt @@ -2,6 +2,8 @@ uvicorn main:app --reload uvicorn app.main:app --reload +flake8 .\app + # @router.get("/delete") # def delete_user(db: Session = Depends(get_db)): From eafbe3753f02775072602b66b5506c91b9f78c8b Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Sat, 23 Jan 2021 18:12:50 +0200 Subject: [PATCH 04/56] register fixed tests flake8 --- tests/test_crud.py | 29 ++++++++-------------- tests/test_register.py | 56 +++++++++++++++++++++++++----------------- 2 files changed, 43 insertions(+), 42 deletions(-) diff --git a/tests/test_crud.py b/tests/test_crud.py index 6e5b9b6e..ee33a98c 100644 --- a/tests/test_crud.py +++ b/tests/test_crud.py @@ -1,25 +1,26 @@ from app.internal import crud from app.database import schemas -user_details = {'username': 'username', 'full_name': 'full_name', -'password': 'password', 'confirm_password': 'password', -'email': 'example@email.com', 'description': ""} +user_details = { + 'username': 'username', 'full_name': 'full_name', + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) +user_details = { + 'username': 'username', 'full_name': 'full_name', + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} + + def test_create_user(session): - user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) assert user.username == 'username' def test_get_user_by_id(session): - user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) user = crud.get_user_by_id(session, 1) @@ -27,9 +28,6 @@ def test_get_user_by_id(session): def test_get_user_by_username(session): - user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) user = crud.get_user_by_username(session, 'username') @@ -37,9 +35,6 @@ def test_get_user_by_username(session): def test_get_user_by_email(session): - user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) user = crud.create_user(db=session, user=user) user = crud.get_user_by_email(session, 'example@email.com') @@ -47,12 +42,8 @@ def test_get_user_by_email(session): def test_delete_user_by_email(session): - user_details = {'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} user = schemas.UserCreate(**user_details) crud.create_user(db=session, user=user) crud.delete_user_by_mail(session, 'example@email.com') user = crud.get_user_by_email(session, 'example@email.com') assert user is None - \ No newline at end of file diff --git a/tests/test_register.py b/tests/test_register.py index f1108297..2560aeb0 100644 --- a/tests/test_register.py +++ b/tests/test_register.py @@ -8,35 +8,39 @@ def test_register_route_ok(client): REGISTER_FORM_VALIDATORS = [ ('ad', 'admin_user', 'password', 'password', 'example@mail.com', - 'description', b'Username must contain'), + 'description', b'Username must contain'), ('admin', 'admin_user', 'pa', 'pa', 'example@mail.com', - 'description', b'Password must contain'), + 'description', b'Password must contain'), ('admin', 'admin_user', 'password', 'wrong_password', 'example@mail.com', - 'description', b"match"), + 'description', b"match"), ('admin', 'admin_user', 'password', 'password', 'invalid_mail', - 'description', b"Email address is not valid"), + 'description', b"Email address is not valid"), ('', 'admin_user', 'password', 'password', 'example@mail.com', - 'description', b'Username field is required'), + 'description', b'Username field is required'), ('admin', '', 'password', 'password', 'example@mail.com', - 'description', b'Full_name field is required'), + 'description', b'Full_name field is required'), ('admin', 'admin_user', '', 'password', 'example@mail.com', - 'description', b'Password field is required'), + 'description', b'Password field is required'), ('admin', 'admin_user', 'password', '', 'example@mail.com', - 'description', b'Confirm_password field is required'), + 'description', b'Confirm_password field is required'), ('admin', 'admin_user', 'password', 'password', '', - 'description', b'Email field is required'), + 'description', b'Email field is required'), ] ''' Test all active pydantic validators ''' + + @pytest.mark.parametrize( - "username, full_name, password, confirm_password, email, description," + "username, full_name, password, confirm_password, email, description," + "expected_response", REGISTER_FORM_VALIDATORS) -def test_register_form_validators(client, username, full_name, password, - confirm_password, email, description, expected_response): - data = {'username': username, 'full_name': full_name, +def test_register_form_validators( + client, username, full_name, password, confirm_password, + email, description, expected_response): + data = { + 'username': username, 'full_name': full_name, 'password': password, 'confirm_password': confirm_password, 'email': email, 'description': description} data = client.post('/register', data=data).content @@ -46,6 +50,8 @@ def test_register_form_validators(client, username, full_name, password, ''' Test successfully register user to database, after passing all validators ''' + + def test_register_successfull(session, client): data = {'username': 'username', 'full_name': 'full_name', 'password': 'password', 'confirm_password': 'password', @@ -56,24 +62,28 @@ def test_register_successfull(session, client): UNIQUE_FIELDS_ARE_TAKEN = [ ('admin', 'admin_user', 'password', 'password', 'example_new@mail.com', - 'description', b'That username is already taken'), + 'description', b'That username is already taken'), ('admin_new', 'admin_user', 'password', 'password', 'example@mail.com', - 'description', b"Email already registered") + 'description', b"Email already registered") ] ''' Test register a user fails due to unique database fields already in use ''' -@pytest.mark.parametrize("username, full_name, password, confirm_password, -+ "email, description, expected_response", UNIQUE_FIELDS_ARE_TAKEN) -def test_register_form_validators(session, client, username, full_name, password, confirm_password, - email, description, expected_response): - user_data ={'username': 'username', 'full_name': 'full_name', - 'password': 'password', 'confirm_password': 'password', - 'email': 'example@email.com', 'description': ""} + + +@pytest.mark.parametrize( + "username, full_name, password, confirm_password" + + "email, description, expected_response", UNIQUE_FIELDS_ARE_TAKEN) +def test_unique_fields_are_taken( + session, client, username, full_name, password, confirm_password, + email, description, expected_response): + user_data = { + 'username': 'username', 'full_name': 'full_name', + 'password': 'password', 'confirm_password': 'password', + 'email': 'example@email.com', 'description': ""} client.post('/register', data=user_data) data = client.post('/register', data=user_data).content print(data) assert expected_response in data - \ No newline at end of file From 56d0ca3156611af8bb3a8b64167eeceecab57bf5 Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Sat, 23 Jan 2021 18:21:55 +0200 Subject: [PATCH 05/56] register fixed tests issues --- tests/calendar.db | Bin 28672 -> 28672 bytes tests/test_register.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/calendar.db b/tests/calendar.db index 200255ca07785287b2d9866a8589c86af8a29183..2becbffaf0d9d930380eb9227472859c575a6596 100644 GIT binary patch delta 35 lcmZp8z}WDBae}nq2L=WPb|_|JU|^XsQOB6^!^VUK`2eBd2_OIf delta 35 lcmZp8z}WDBae}m9F9QPuI~21qFtFH7)G=o4-I%Z-9{`eH2mk;8 diff --git a/tests/test_register.py b/tests/test_register.py index 2560aeb0..c784dbba 100644 --- a/tests/test_register.py +++ b/tests/test_register.py @@ -74,7 +74,7 @@ def test_register_successfull(session, client): @pytest.mark.parametrize( - "username, full_name, password, confirm_password" + "username, full_name, password, confirm_password," + "email, description, expected_response", UNIQUE_FIELDS_ARE_TAKEN) def test_unique_fields_are_taken( session, client, username, full_name, password, confirm_password, From c9c777ebb9af07c6db43d7df43887374d7b160c2 Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Sat, 23 Jan 2021 23:52:44 +0200 Subject: [PATCH 06/56] register flake8 fix --- app/database/database.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/database/database.py b/app/database/database.py index 05272638..082df117 100644 --- a/app/database/database.py +++ b/app/database/database.py @@ -8,8 +8,6 @@ from app import config -# load_dotenv() - SQLALCHEMY_DATABASE_URL = os.getenv( "DATABASE_CONNECTION_STRING", config.DEVELOPMENT_DATABASE_STRING) From bb2b52dca9413524e6721bd7674c121fd45f32d8 Mon Sep 17 00:00:00 2001 From: Koby Fogel Date: Sat, 23 Jan 2021 23:58:45 +0200 Subject: [PATCH 07/56] register flake8 more fixes --- app/database/database.py | 1 - calendar.db | Bin 28672 -> 0 bytes 2 files changed, 1 deletion(-) delete mode 100644 calendar.db diff --git a/app/database/database.py b/app/database/database.py index 082df117..b89bf6d1 100644 --- a/app/database/database.py +++ b/app/database/database.py @@ -1,5 +1,4 @@ import os -from dotenv import load_dotenv from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base diff --git a/calendar.db b/calendar.db deleted file mode 100644 index 08e060bcf1514fb75d8db5ed24371e30ab1463d8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI(O>fgM7{Kvl>snUPbtj_UtRbXEnh=9C5^d*o)w+~r9ij&mg(Z_BwdrWmc2{0L z2`9b`d>)R(?lM-i!GT-#x0E+Owv*@Qebw4T(=cTpUKSJ9Oh&cZlNyeL?llv=KA^rh%H`|_>09Tz=EEZT^A6xbhU8zrk& zGe5Ue|NSsrL>Bwv^%(s|vny}b?`k!Oq>G@jMPES!(%h)!?t)&T+#Xh$A`Bd8oRB zo+~?TN7dEyKDKb>fpn$QlBZ&tgp))!FWb#R(Ryr|gB(!DvYY7R#-Dt7RO$U5=3K!4_E6Lh;KN<9BOjb@=}_Ih2}aPG4|b2=wyQq=QYY}GKY)H&#y)}d@2 zWixOyY{wDxXS-t0w#&A?UbJ3XrlF@K`7q3u>z+SN$NJjSir#a5DA;TNZn~$Nu4kXw z1NEs@)NWhtC;F}qUh+Z!0R#|0009ILKmY** z5I{f+u>R*oKmY**5I_I{1Q0*~0R#|0VD$x9|F8ZWlOX~KAb Date: Sun, 24 Jan 2021 16:35:11 +0200 Subject: [PATCH 08/56] fixed CR suggestions --- app/config.py.example | 3 ++ app/database/schemas.py | 36 ++++++++-------------- app/internal/{crud.py => user.py} | 28 ++++++----------- app/routers/register.py | 12 ++++---- tests/calendar.db | Bin 28672 -> 0 bytes tests/{test_crud.py => test_user_crud.py} | 22 ++++++------- 6 files changed, 42 insertions(+), 59 deletions(-) rename app/internal/{crud.py => user.py} (60%) delete mode 100644 tests/calendar.db rename tests/{test_crud.py => test_user_crud.py} (65%) diff --git a/app/config.py.example b/app/config.py.example index 9063b9b7..dce90c26 100644 --- a/app/config.py.example +++ b/app/config.py.example @@ -29,3 +29,6 @@ email_conf = ConnectionConfig( MAIL_SSL=False, USE_CREDENTIALS=True, ) + +# register +DATABASE_CONNECTION_STRING = "sqlite:///calendar.db" \ No newline at end of file diff --git a/app/database/schemas.py b/app/database/schemas.py index b9de56bf..a7c68864 100644 --- a/app/database/schemas.py +++ b/app/database/schemas.py @@ -3,13 +3,13 @@ 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 field == "": + '''Global function to validate fields are not empty.''' + if not field: raise ValueError(EMPTY_FIELD_STRING) return field @@ -29,15 +29,11 @@ class Config: class UserCreate(UserBase): - ''' - Validating fields types - ''' + '''Validating fields types''' password: str confirm_password: str - ''' - Calling to field_not_empty validaion function for each required field. - ''' + '''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( @@ -53,36 +49,28 @@ class UserCreate(UserBase): def passwords_match( cls, confirm_password: str, values: UserBase) -> Union[ValueError, str]: - ''' - Validating passwords fields identical. - ''' + '''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 len(username) < 3 or len(username) > 20: + '''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 len(password) < 3 or len(password) > 20: + '''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. - ''' + '''Validating email is valid mail address.''' try: EmailStr.validate(email) return email diff --git a/app/internal/crud.py b/app/internal/user.py similarity index 60% rename from app/internal/crud.py rename to app/internal/user.py index f3188e4a..00341298 100644 --- a/app/internal/crud.py +++ b/app/internal/user.py @@ -3,29 +3,23 @@ from sqlalchemy.orm import Session -def get_user_by_id(db: Session, user_id: int) -> models.User: - ''' - query database for a user by unique id - ''' +def get_by_id(db: Session, user_id: int) -> models.User: + '''query database for a user by unique id''' return db.query(models.User).filter(models.User.id == user_id).first() -def get_user_by_username(db: Session, username: str) -> models.User: - ''' - query database for a user by unique username - ''' +def get_by_username(db: Session, username: str) -> models.User: + '''query database for a user by unique username''' return db.query(models.User).filter( models.User.username == username).first() -def get_user_by_email(db: Session, email: str) -> models.User: - ''' - query database for a user by unique email - ''' +def get_by_mail(db: Session, email: str) -> models.User: + '''query database for a user by unique email''' return db.query(models.User).filter(models.User.email == email).first() -def create_user(db: Session, user: schemas.UserCreate) -> models.User: +def create(db: Session, user: schemas.UserCreate) -> models.User: ''' creating a new User object in the database, with hashed password ''' @@ -46,10 +40,8 @@ def create_user(db: Session, user: schemas.UserCreate) -> models.User: return db_user -def delete_user_by_mail(db: Session, email: str) -> None: - ''' - deletes a user from database by unique email - ''' - db_user = get_user_by_email(db=db, email=email) +def delete_by_mail(db: Session, email: str) -> None: + '''deletes a user from database by unique email''' + db_user = get_by_mail(db=db, email=email) db.delete(db_user) db.commit() diff --git a/app/routers/register.py b/app/routers/register.py index 557caf05..c2d987ed 100644 --- a/app/routers/register.py +++ b/app/routers/register.py @@ -1,4 +1,4 @@ -from app.internal import crud +from app.internal import user from app.database import schemas from app.database.database import get_db from app.dependencies import templates @@ -37,7 +37,7 @@ async def register( try: # creating pydantic schema object out of form data - user = schemas.UserCreate(**form_dict) + new_user = schemas.UserCreate(**form_dict) except ValidationError as e: # if pydantic validations fails, rendering errors to register.html @@ -51,7 +51,7 @@ async def register( try: # attempt creating User Model object, and saving to database - crud.create_user(db=db, user=user) + user.create(db=db, user=new_user) ''' if creating User Model objects fails due to registered unique details - @@ -60,9 +60,9 @@ async def register( except IntegrityError: db.rollback() errors = {} - db_user_email = crud.get_user_by_email(db, email=user.email) - db_user_username = crud.get_user_by_username( - db, username=user.username) + db_user_email = user.get_by_mail(db, email=new_user.email) + db_user_username = user.get_by_username( + db, username=new_user.username) if db_user_username: errors['username'] = "That username is already taken" if db_user_email: diff --git a/tests/calendar.db b/tests/calendar.db deleted file mode 100644 index 2becbffaf0d9d930380eb9227472859c575a6596..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28672 zcmeI)F%CgN5CG6wJ{o*S_$Sb~0F_1~PM{!Bh(sL55uC Date: Sun, 24 Jan 2021 22:39:45 +0200 Subject: [PATCH 09/56] first commit --- app/config.py.example | 6 ++- app/internal/security/__init__.py | 0 app/internal/security/ouath2.py | 78 +++++++++++++++++++++++++++++++ app/internal/security/schema.py | 16 +++++++ app/internal/user.py | 8 ++-- app/main.py | 4 +- app/routers/login.py | 47 +++++++++++++++++++ app/routers/register.py | 7 +++ app/templates/base.html | 2 +- app/templates/login.html | 22 +++++++++ 10 files changed, 183 insertions(+), 7 deletions(-) create mode 100644 app/internal/security/__init__.py create mode 100644 app/internal/security/ouath2.py create mode 100644 app/internal/security/schema.py create mode 100644 app/routers/login.py create mode 100644 app/templates/login.html diff --git a/app/config.py.example b/app/config.py.example index dce90c26..81578bdd 100644 --- a/app/config.py.example +++ b/app/config.py.example @@ -31,4 +31,8 @@ email_conf = ConnectionConfig( ) # register -DATABASE_CONNECTION_STRING = "sqlite:///calendar.db" \ No newline at end of file +DATABASE_CONNECTION_STRING = "sqlite:///calendar.db" + +# security +JWT_SECRET_KEY = "c06857128217c661de43d746ecdbe9f1dbc3b3685957fab64512456f639b1881" +JWT_ALGORITHM = "HS256" \ No newline at end of file diff --git a/app/internal/security/__init__.py b/app/internal/security/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/internal/security/ouath2.py b/app/internal/security/ouath2.py new file mode 100644 index 00000000..c96652aa --- /dev/null +++ b/app/internal/security/ouath2.py @@ -0,0 +1,78 @@ +from passlib.context import CryptContext +# from app.database.schemas import Jwt_User +# from app.internal.crud import get_user_by_username +from .schema import LoginUser +from app.database.models import User +from sqlalchemy.orm.session import Session +from fastapi import APIRouter, Depends, Request, HTTPException +from app.dependencies import templates +from app.database.database import get_db, SessionLocal +from fastapi.security import OAuth2PasswordBearer +from datetime import datetime, timedelta +import jwt +from starlette.status import HTTP_401_UNAUTHORIZED +import time +from app.config import JWT_ALGORITHM, JWT_SECRET_KEY + + +JWT_MIN_EXP = 120 + +pwd_context = CryptContext(schemes=["bcrypt"]) +oauth_schema = OAuth2PasswordBearer(tokenUrl="/login") + +def get_db_user_by_username(username: str): + session = SessionLocal() + return session.query(User).filter_by(username = username).first() + + +def get_hashed_password(password): + return pwd_context.hash(password) + + +def verify_password(plain_password, hashed_password): + try: + return pwd_context.verify(plain_password, hashed_password) + except Exception as e: + return False + +# Authenticate username and password +def authenticate_user(user: LoginUser): + db_user = get_db_user_by_username(username = user.username) + if db_user: + if verify_password(user.hashed_password, db_user.password, ): + return user + return False + + +# Create access JWT token +def create_jwt_token(user: LoginUser): + expiration = datetime.utcnow() + timedelta(minutes=JWT_MIN_EXP ) + jwt_payload = {"sub": user.username, "hashed_password": user.hashed_password, "exp": expiration} + jwt_token = jwt.encode(jwt_payload, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM) + return jwt_token + + +# Check whether JWT token is correct +async def check_jwt_token(token: str = Depends(oauth_schema)): + try: + jwt_payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=JWT_ALGORITHM) + jwt_username = jwt_payload.get("sub") + jwt_hashed_password = jwt_payload.get("hashed_password") + jwt_expiration = jwt_payload.get("exp") + is_valid = False + if time.time() < jwt_expiration: + db_user = await get_db_user_by_username(username=jwt_username) + # is_valid = await get_db_user_by_username(username=jwt_username) + if db_user and db_user.password == jwt_hashed_password: + is_valid == True + # is_valid = await db_check_jwt_username(username) + if is_valid: + return True + else: + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED) + else: + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED) + except Exception as e: + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED) + + diff --git a/app/internal/security/schema.py b/app/internal/security/schema.py new file mode 100644 index 00000000..ec60efa7 --- /dev/null +++ b/app/internal/security/schema.py @@ -0,0 +1,16 @@ +from typing import List, Optional, Union +from fastapi import Depends, Form, Query +from pydantic import BaseModel, validator + + +class LoginUser(BaseModel): + username: str + hashed_password: str + + class Config: + orm_mode = True + + + +# class Jwt_User(Login_User): +# is_active: Optional[bool] = False \ No newline at end of file diff --git a/app/internal/user.py b/app/internal/user.py index 00341298..bf04862f 100644 --- a/app/internal/user.py +++ b/app/internal/user.py @@ -1,6 +1,9 @@ -import bcrypt from app.database import models, schemas +from passlib.context import CryptContext from sqlalchemy.orm import Session +from app.internal.security.ouath2 import pwd_context + +# pwd_context = CryptContext(schemes=["bcrypt"]) def get_by_id(db: Session, user_id: int) -> models.User: @@ -23,9 +26,8 @@ def create(db: Session, user: schemas.UserCreate) -> models.User: ''' creating a new User object in the database, with hashed password ''' - salt = bcrypt.gensalt(prefix=b'2b', rounds=10) unhashed_password = user.password.encode('utf-8') - hashed_password = bcrypt.hashpw(unhashed_password, salt) + hashed_password = pwd_context.hash(unhashed_password) user_details = { 'username': user.username, 'full_name': user.full_name, diff --git a/app/main.py b/app/main.py index 29b02660..a42b2a13 100644 --- a/app/main.py +++ b/app/main.py @@ -2,7 +2,7 @@ from app.database.database import engine from app.dependencies import ( MEDIA_PATH, STATIC_PATH, templates) -from app.routers import agenda, event, profile, email, invitation, register +from app.routers import agenda, event, profile, email, invitation, register, login from fastapi import FastAPI, Request from fastapi.staticfiles import StaticFiles @@ -18,7 +18,7 @@ app.include_router(register.router) app.include_router(email.router) app.include_router(invitation.router) - +app.include_router(login.router) @app.get("/") async def home(request: Request): diff --git a/app/routers/login.py b/app/routers/login.py new file mode 100644 index 00000000..382c3d06 --- /dev/null +++ b/app/routers/login.py @@ -0,0 +1,47 @@ +from fastapi import APIRouter, Depends, Request, HTTPException +from app.dependencies import templates +from app.database.database import get_db +from fastapi.security import OAuth2PasswordRequestForm +from app.internal.security.ouath2 import authenticate_user +from app.internal.security.ouath2 import LoginUser, create_jwt_token, check_jwt_token +from starlette.status import HTTP_401_UNAUTHORIZED + + +router = APIRouter( + prefix="", + tags=["/login"], + responses={404: {"description": "Not found"}}, +) + +@router.get("/login") +async def login_user_form(request: Request) -> templates: + ''' + rendering register route get method + ''' + return templates.TemplateResponse("login.html", { + "request": request, + "errors": None + }) + +@router.post('/login') +async def login(form: OAuth2PasswordRequestForm = Depends()): + # form = await request.form() + # form_username = form.username + # form_password = form.password + form_dict = {'username': form.username, 'hashed_password': form.password} + # user = LoginUser(form_username, form_password) + user = LoginUser(**form_dict) + # print(user) + if not user or not authenticate_user(user): + raise HTTPException(status_code=HTTP_401_UNAUTHORIZED) + jwt_token = create_jwt_token(user) + # user = authenticate_user() + return {'token': jwt_token} + + +@router.get('/protected') +async def protected_route(request: Request, jwt: bool = Depends(check_jwt_token)): + return templates.TemplateResponse("home.html", { + "request": request, + "message": "protected" + }) \ No newline at end of file diff --git a/app/routers/register.py b/app/routers/register.py index c2d987ed..60f75c63 100644 --- a/app/routers/register.py +++ b/app/routers/register.py @@ -7,6 +7,8 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session +from app.internal import user + router = APIRouter( prefix="", @@ -76,3 +78,8 @@ async def register( "request": request, "message": "User created", "status_code": 201}) + +### NOT for production +@router.get("/delete") +def delete_user(db: Session = Depends(get_db)): + user.delete_by_mail(db=db, email="") \ No newline at end of file diff --git a/app/templates/base.html b/app/templates/base.html index 675fadc8..ea964873 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -29,7 +29,7 @@ Profile