Commit 3fdc6a3e authored by arun.uday's avatar arun.uday

AssetManager-V1.0- Not reviewed

> Added google login functionality, completed the API for forgot password, reset password and change password functionality.
> Changed the normal_login handler name
parent 8f0c4a48
......@@ -21,4 +21,6 @@ EMAIL_SMTP=smtp.gmail.com
EMAIL_PORT=465
EMAIL_PASSWORD=gpphuiweedqukchf
HTML_LINK=scripts/utils/link_email.html
RESET_ENDPOINT=http://localhost:8671/v1/login/reset?token
\ No newline at end of file
RESET_ENDPOINT=http://localhost:8671/asset_manager_api/v1/login/reset?token
CLIENT_ID=1060631831358-a21djaa3hm165a8976fnmo1lerujs5p6.apps.googleusercontent.com
\ No newline at end of file
uvicorn==0.21.1
python-dotenv~=1.0.0
pydantic~=1.10.6
pydantic==1.10.7
fastapi==0.95.0
passlib~=1.7.4
pymongo~=4.3.3
......@@ -9,4 +9,7 @@ email-validator~=1.3.1
pycryptodomex~=3.17
PyJWT~=2.6.0
validate_email~=1.3
redis~=4.5.2
\ No newline at end of file
redis==4.5.4
google~=3.0.0
google-auth~=2.17.1
requests~=2.28.2
\ No newline at end of file
......@@ -46,6 +46,7 @@ class _Secrets(BaseSettings):
TOKEN_EXPIRE_TIME = 5
leeway_in_minutes: int = 10
KEY_ENCRYPTION: str
CLIENT_ID: str
issuer: str = "iotManager"
SECRET_KEY: str
ALGORITHM = "HS256"
......
class ApiEndPoints:
# Main root
root = "/asset_manager_api"
# version
version = "/v1"
......@@ -32,6 +35,7 @@ class ApiEndPoints:
asset_manager_user_delete: str = asset_manager_user_management + delete
asset_manager_user_search: str = asset_manager_user_management + search
asset_manager_user_logout: str = asset_manager_user_management + logout
asset_manager_user_reset: str = asset_manager_user_management + reset
# dashboard-management
asset_manager_dashboard: str = "/dashboard"
......
......@@ -4,11 +4,17 @@ from datetime import timedelta, datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from jwt.exceptions import ExpiredSignatureError
from google.oauth2 import id_token
from google.auth.transport import requests
from google.auth.exceptions import InvalidValue
from fastapi import status
from fastapi.responses import JSONResponse
from fastapi.responses import JSONResponse, RedirectResponse
from scripts.config import Services, Secrets
from scripts.core.handlers.normal_login import NormalLogin
from scripts.core.handlers.process_login_data import NormalLogin
from scripts.database.mongo.mongo_db import MongoUser
from scripts.errors import ErrorMessages
from scripts.logging.logger import logger
......@@ -64,9 +70,46 @@ class LoginHandlers:
response.set_cookie(key="login-token", value=responses, expires=exp)
return response
# v2
def google_login(self, request):
pass
def google_login(self, user_data, request):
user_data_remove_none = {key: value for key, value in user_data if key != 'login_type' and value is not None}
req = requests.Request()
try:
id_info = id_token.verify_oauth2_token(
user_data_remove_none["id_token"], req, Secrets.CLIENT_ID)
except InvalidValue:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_TOKEN_EXPIRED).dict(),
status_code=status.HTTP_200_OK)
response, message = self.obj_login_handler.db_data_validation(user_data.login_type, id_info["email"])
# if the response is false then an error message is send back
if response is not None:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=message).dict(),
status_code=status.HTTP_200_OK)
message.update({"name": id_info["name"], "pic_url": id_info["picture"]})
responses = self.obj_login_handler.update_pic(obj_mongo_user, id_info)
if responses is None:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_IN_UPDATING).dict(),
status_code=status.HTTP_200_OK)
# generating the access tokens
responses, exp = self.obj_login_handler.generate_cookie_tokens(message, request)
# token generation unsuccessful
if responses is None:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_TOKEN_GENERATION).dict(),
status_code=status.HTTP_200_OK)
# sending successful response to UI
response = JSONResponse(
content=DefaultResponse(status="success", message="Logged In Successfully",
data=message).dict(),
status_code=status.HTTP_200_OK, headers={"Content-Type": "application/json"})
response.set_cookie(key="login-token", value=responses, expires=exp)
return response
# v2
def microsoft_login(self, request):
......@@ -90,7 +133,7 @@ class LoginHandlers:
mail = MIMEMultipart()
mail['From'] = Services.EMAIL_SENDER
mail['To'] = email
mail['Subject'] = "Link TO Reset Password"
mail['Subject'] = "Link To Reset Password"
to_encode = {"email": email}
expire = datetime.utcnow() + timedelta(minutes=Secrets.TOKEN_EXPIRE_TIME)
to_encode.update({"exp": expire})
......@@ -117,3 +160,61 @@ class LoginHandlers:
status_code=status.HTTP_200_OK)
except Exception as e:
logger.exception(e)
@staticmethod
def validate_jwt(request):
try:
jwt_token = request.query_params['token']
jwt_token_encoded = jwt_token.encode('utf-8')
# Verify and decode the JWT token
try:
decoded_token = jwt.decode(jwt_token_encoded)
db_user_data = obj_mongo_user.fetch_one_user_details({"email": decoded_token['email']})
# if the user is not available
if not db_user_data:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_USER_ID_DOESNT_EXIST).dict(),
status_code=status.HTTP_200_OK)
jwt_token_new = jwt.encode({"email": decoded_token['email']})
return RedirectResponse('http://192.168.2.102/iLens_UI/#/l/login?user_id=' + jwt_token_new)
except ExpiredSignatureError:
return RedirectResponse(
'http://192.168.2.102/iLens_UI/#/l/login?error=' + "true")
except Exception as e:
logger.exception(e)
@staticmethod
def reset_user_password(reset_data):
try:
user_id_token = reset_data.user_id
user_id = user_id_token.encode('utf-8')
# Verify and decode the JWT token
try:
decoded_token = jwt.decode(user_id)
db_user_data = obj_mongo_user.fetch_one_user_details({"email": decoded_token['email']})
# if the user is not available
if not db_user_data:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_USER_ID_DOESNT_EXIST).dict(),
status_code=status.HTTP_200_OK)
response = EncryptDecryptPassword().password_encrypt(reset_data.password)
if not response:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_AUTH_FAILED).dict(),
status_code=status.HTTP_200_OK)
response = obj_mongo_user.update_user({"email": decoded_token['email']}, {"password": response})
if not response:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_IN_UPDATING).dict(),
status_code=status.HTTP_200_OK)
return JSONResponse(
content=DefaultSuccessResponse(status="success", message="Reset Successful").dict(),
status_code=status.HTTP_200_OK)
except ExpiredSignatureError:
return "Password Reset Token Expired"
except Exception as e:
logger.exception(e)
......@@ -43,9 +43,17 @@ class NormalLogin:
return False, ErrorMessages.ERROR_UNAUTHORIZED_USER_LOGIN
# if the user is not registered through normal login
if self.db_user_data["login_type"] != login_type:
return False, ErrorMessages.ERROR_LOGIN_TYPE_INVALID
return False, ErrorMessages.ERROR_ACCESS_DENIED
try:
response = {"user_id": self.db_user_data["user_id"], "name": self.db_user_data["name"],
"email": email,
"user_role": self.db_user_data["user_role"]}
except KeyError:
response = {"user_id": self.db_user_data["user_id"],
"email": email,
"user_role": self.db_user_data["user_role"]}
# if the user exist
return None, {"message": True}
return None, response
except Exception as e:
logger.exception(e)
......@@ -61,12 +69,17 @@ class NormalLogin:
if not self.pwd_context.verify(password, self.db_user_data["password"]):
return False, ErrorMessages.ERROR_PASSWORD_MISMATCH
# if the password is correct
return None, {"user_id": self.db_user_data["user_id"], "name": self.db_user_data["name"],
"email": user_data.email,
"user_role": self.db_user_data["user_role"]}
return None, message
except Exception as e:
logger.exception(e)
@staticmethod
def update_pic(obj_mongo_user, info_data):
if not obj_mongo_user.update_user({"email": info_data["email"]},
{"name": info_data["name"], "pic_url": info_data["picture"]}):
return None
return True
# cookie and token creation
@staticmethod
def generate_cookie_tokens(user_data, request):
......
......@@ -221,6 +221,33 @@ class UserManagement:
except Exception as e:
logger.exception(e)
# user change password
@staticmethod
def reset_password(reset_data):
db_user_data = obj_mongo_user.fetch_one_user_details({"user_id": reset_data.user_id})
# if the user is not available
if db_user_data is None:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_USER_ID_DOESNT_EXIST).dict(),
status_code=status.HTTP_404_NOT_FOUND)
response = EncryptDecryptPassword().password_encrypt(reset_data.password)
if not response:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_AUTH_FAILED).dict(),
status_code=status.HTTP_200_OK)
response = obj_mongo_user.update_user({"user_id": reset_data.user_id}, {"password": response})
if not response:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_IN_UPDATING).dict(),
status_code=status.HTTP_200_OK)
return JSONResponse(
content=DefaultSuccessResponse(status="success", message="Updated Successfully").dict(),
status_code=status.HTTP_200_OK)
# user logout
@staticmethod
def logout_user(user_id, request):
......
......@@ -6,7 +6,7 @@ class ErrorMessages:
ERROR_INVALID_LOGIN = "Your are not authorized to view this website."
ERROR_UNAUTHORIZED_USER_LOGIN = "Account is not available"
ERROR_UNAUTHORIZED_ACCESS = "Your are not authorized to view this page"
ERROR_LOGIN_TYPE_INVALID = "Invalid Login Method"
ERROR_ACCESS_DENIED = "Access Denied!"
ERROR_USER_NOT_REGISTERED = "Account is not registered in the portal."
ERROR_PASSWORD_MISMATCH = "Please enter the correct password"
ERROR_TOKEN_GENERATION = "Unsuccessful token generation"
......@@ -16,6 +16,7 @@ class ErrorMessages:
ERROR_IN_UPDATING = "Error in Updating"
ERROR_INVALID_REQUEST = "Invalid Request"
ERROR_USER_SESSION = "Not The Users Session"
ERROR_TOKEN_EXPIRED = "Google Token Expired"
# Data Validation
ERROR_INVALID_PASSWORD = "Invalid Password"
......
This diff is collapsed.
......@@ -6,8 +6,9 @@ from pydantic import BaseModel
# model for login request
class LoginRequest(BaseModel):
login_type: str
email: str
password: str
email: Optional[str] = None
password: Optional[str] = None
id_token: Optional[str] = None
class RegistrationData(BaseModel):
......@@ -36,6 +37,11 @@ class UsersFilter(BaseModel):
user_role: Optional[str] = None
class ResetPassword(BaseModel):
user_id: str
password: str
class UserIDValidation(BaseModel):
user_id: str
......
from fastapi import APIRouter
from scripts.constants.api import ApiEndPoints
from scripts.services import v1
router = APIRouter()
router = APIRouter(prefix=ApiEndPoints.root)
# routing to the V1 router
router.include_router(v1.router)
......@@ -3,6 +3,6 @@ from scripts.constants.api import ApiEndPoints
from scripts.services.v1 import iot_manager_services
# creating the api router with version as the prefix
router = APIRouter()
router = APIRouter(prefix=ApiEndPoints.version)
# routing to the service api
router.include_router(iot_manager_services.router)
......@@ -8,12 +8,12 @@ from scripts.core.handlers.user_management_handler import UserManagement
from scripts.errors import ErrorMessages
from scripts.logging.logger import logger
from scripts.schemas.default_responses import DefaultFailureResponse
from scripts.schemas.project_schema import LoginRequest, UserActions, EmailValidation, UserIDValidation
from scripts.schemas.project_schema import LoginRequest, UserActions, EmailValidation, UserIDValidation, ResetPassword
from scripts.utils.security.authorize_access import AuthorizeAccess
from scripts.utils.security.decorators import MetaInfoSchema, auth
# creating the login api
router = APIRouter(prefix=ApiEndPoints.version)
router = APIRouter()
# initializing the handler
obj_login_handler = LoginHandlers()
obj_user_handler = UserManagement()
......@@ -67,14 +67,32 @@ async def forgot_password(
status_code=status.HTTP_200_OK)
# Reset Password API
# Reset Password API for sending email
@router.get(ApiEndPoints.asset_manager_reset)
async def forgot_password(
token: str
async def reset_password(
request: Request
):
try:
# forgot password
print(token)
# Get the JWT token from the query parameters
response = obj_login_handler.validate_jwt(request)
return response
except Exception as e:
logger.exception(e)
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.OP_FAILED).dict(),
status_code=status.HTTP_200_OK)
# Reset Password API
@router.post(ApiEndPoints.asset_manager_reset)
async def reset_password(
reset_data: ResetPassword
):
try:
# Get the JWT token from the query parameters
response = obj_login_handler.reset_user_password(reset_data)
return response
except Exception as e:
logger.exception(e)
return JSONResponse(
......@@ -154,7 +172,7 @@ async def user_register(
# View users API
@router.post(ApiEndPoints.asset_manager_user_view)
async def user_register(
async def user_register_view(
request: MetaInfoSchema = Depends(auth)
):
try:
......@@ -179,10 +197,38 @@ async def user_register(
status_code=status.HTTP_200_OK)
# API for reset password
@router.post(ApiEndPoints.asset_manager_user_reset)
async def user_reset_password(
reset_data: ResetPassword,
request: MetaInfoSchema = Depends(auth)
):
try:
# authorize the user
response = AuthorizeAccess().login_authorize(reset_data, request)
if not response:
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.ERROR_UNAUTHORIZED_ACCESS).dict(),
status_code=status.HTTP_200_OK)
response = obj_user_handler.reset_password(reset_data)
if not response:
return HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=ErrorMessages.ERROR_IN_FETCHING)
return response
except Exception as e:
logger.exception(e)
return JSONResponse(
content=DefaultFailureResponse(status="failed",
message=ErrorMessages.OP_FAILED).dict(),
status_code=status.HTTP_200_OK)
# download Button Dashboard
@router.post(ApiEndPoints.asset_manager_dashboard_download)
async def dashboard_download(
request: MetaInfoSchema = Depends(auth)
):
try:
# getting the data for the download dashboard
......
......@@ -37,5 +37,5 @@ def create_token(
login_db.set(uid, new_token)
login_db.expire(uid, timedelta(minutes=age))
# Add updated time to mongo db
mongo_user.update_user({"email": user_id}, {"updated_at": current_time})
mongo_user.update_user({"user_id": user_id}, {"updated_at": current_time})
return uid, exp
......@@ -15,3 +15,9 @@ class AuthorizeAccess:
return True
except TypeError:
return False
@staticmethod
def login_authorize(reset_data, request):
if reset_data.user_id != request.user_id:
return False
return True
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment