From d87aade803970d7f515c0b926f7f034c864e7314 Mon Sep 17 00:00:00 2001 From: coolneng Date: Mon, 28 Sep 2020 18:19:59 +0200 Subject: [PATCH] Implement OTP verification --- app/external_services.py | 6 +++--- app/routes.py | 19 ++++++++++++------- app/schemas.py | 23 ++++++++++++++++++++--- database/crud.py | 31 ++++++++++++++++--------------- tests/requests_test.py | 14 +++++++++++++- 5 files changed, 64 insertions(+), 29 deletions(-) diff --git a/app/external_services.py b/app/external_services.py index fe610c7..510f259 100644 --- a/app/external_services.py +++ b/app/external_services.py @@ -1,7 +1,7 @@ from twilio.rest import Client from constants import ACCOUNT_ID, SMS_SENDER, TOKEN -from database.crud import fetch_otp +from database.crud import fetch_user_by_key def create_twilio_client(account_sid, auth_token): @@ -11,6 +11,6 @@ def create_twilio_client(account_sid, auth_token): def send_otp(data, db): client = create_twilio_client(account_sid=ACCOUNT_ID, auth_token=TOKEN) - code = fetch_otp(access_key=data.access_key, db=db) - message = "Your OTP code is {0}".format(code) + user = fetch_user_by_key(data=data, db=db) + message = "Your OTP code is {0}".format(user.otp) client.messages.create(to=data.mobile, from_=SMS_SENDER, body=message) diff --git a/app/routes.py b/app/routes.py index 2fc1521..24b0a92 100644 --- a/app/routes.py +++ b/app/routes.py @@ -1,4 +1,5 @@ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends +from fastapi.security import OAuth2PasswordBearer from sqlalchemy.orm import Session from app.external_services import send_otp @@ -7,7 +8,10 @@ from database.crud import get_db, insert_data, verify_otp router = APIRouter() +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +# FIXME Password hash @router.post("/register", response_model=UserCreateResponse) def create_user(data: UserCreate, db: Session = Depends(get_db)): user = insert_data(model="Users", data=data, db=db) @@ -15,14 +19,15 @@ def create_user(data: UserCreate, db: Session = Depends(get_db)): return user -# FIXME Use OAuth2 for verification +# TODO Use OAuth2 for verification @router.post("/login", response_model=UserLoginResponse) -def log_in(request: UserLogin, db: Session = Depends(get_db)): +def log_in( + data: UserLogin, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme), +): pass @router.post("/otpVerification", response_model=OTPVerifyResponse) -def validate_otp(request: OTPVerify, db: Session = Depends(get_db)): - if verify_otp(data=request, db=db): - return {"message": "The OTP has been verified successfully"} - raise HTTPException(status_code=400, detail="The OTP is not correct") +def validate_otp(data: OTPVerify, db: Session = Depends(get_db)): + response = verify_otp(data=data, db=db) + return response diff --git a/app/schemas.py b/app/schemas.py index c2fe6d7..a421a9c 100644 --- a/app/schemas.py +++ b/app/schemas.py @@ -22,7 +22,7 @@ class UserCreate(UserBase): device_type: int = Query(None, ge=1, le=2) city_id: int access_key: str = token_hex() - otp: int = randbits(16) + otp: int = randbits(20) otp_valid_time: datetime = datetime.now() + timedelta(minutes=10) class Config: @@ -74,7 +74,7 @@ class SocialLogin(UserBase): orm_mode = True -class OTPVerify(BaseModel): +class OTPBase(BaseModel): access_key: str otp: int @@ -82,13 +82,30 @@ class OTPVerify(BaseModel): orm_mode = True -class OTPVerifyResponse(BaseModel): +class OTPVerify(OTPBase): pass class Config: orm_mode = True +class OTPVerifyResponse(OTPBase): + id: int + full_name: str + email: EmailStr + gender: int = Query(None, ge=1, le=3) + mobile: str = Query(None, min_length=8, max_length=13) + otp_valid_time: datetime + lang_type: int = Query(None, ge=1, le=2) + status: int = Query(None, ge=0, le=1) + device_type: int = Query(None, ge=1, le=2) + created: datetime + updated: datetime = Query(None) + + class Config: + orm_mode = True + + access_key: str otp: int = Query(None, ge=6, le=6) diff --git a/database/crud.py b/database/crud.py index 6a7814f..9c5ea6d 100644 --- a/database/crud.py +++ b/database/crud.py @@ -1,4 +1,5 @@ -from datetime import datetime, timedelta +from datetime import datetime +from fastapi import HTTPException from app.schemas import * from database import SessionLocal @@ -34,29 +35,29 @@ def delete_data(model, data, db): return result -def fetch_user(data, db): - result = db.query(Users).filter(Users.email == data.email).first() - return result +def fetch_user_by_key(data, db): + return db.query(Users).filter(Users.access_key == data.access_key).first() -def fetch_otp(access_key, db): - result = db.query(Users).filter(Users.access_key == access_key).first() - return result.otp +def fetch_user_by_email(data, db): + return db.query(Users).filter(Users.email == data.email).first() def activate_account(data: OTPVerify, db): db.query(Users).filter(Users.access_key == data.access_key).update( {Users.status: 1} ) - db.commit() + user = fetch_user_by_key(data=data, db=db) + return user def verify_otp(data: OTPVerify, db): - user = fetch_otp(data=data, db=db) - same_otp = user.otp == data.otp - valid_time = datetime.now() <= user.otp_valid_time - valid_otp = same_otp and valid_time + user = fetch_user_by_key(data=data, db=db) + matching_otp = user.otp == data.otp + valid_time = datetime.now() < user.otp_valid_time + valid_otp = matching_otp and valid_time if valid_otp: - activate_account(data=data, db=db) - return True - return False + result = activate_account(data=data, db=db) + return result + else: + raise HTTPException(status_code=400, detail="The OTP is not correct") diff --git a/tests/requests_test.py b/tests/requests_test.py index ac6d632..58036c3 100644 --- a/tests/requests_test.py +++ b/tests/requests_test.py @@ -1,8 +1,10 @@ -from pytest import mark +from pytest import mark, fixture from secrets import token_hex from app.schemas import * +from database.models import * from tests import client +from tests.queries_test import get_test_db def test_registration(): @@ -33,3 +35,13 @@ def test_login(): } response = client.post("/login", json=user) assert response.status_code == 200 + + +def test_otp_verification(get_test_db): + user = get_test_db.query(Users).filter(Users.email == "oyvey@hotmail.com").first() + data = { + "access_key": user.access_key, + "otp": user.otp, + } + response = client.post("/otpVerification", json=data) + assert response.status_code == 200