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 ...@@ -21,4 +21,6 @@ EMAIL_SMTP=smtp.gmail.com
EMAIL_PORT=465 EMAIL_PORT=465
EMAIL_PASSWORD=gpphuiweedqukchf EMAIL_PASSWORD=gpphuiweedqukchf
HTML_LINK=scripts/utils/link_email.html HTML_LINK=scripts/utils/link_email.html
RESET_ENDPOINT=http://localhost:8671/v1/login/reset?token RESET_ENDPOINT=http://localhost:8671/asset_manager_api/v1/login/reset?token
\ No newline at end of file
CLIENT_ID=1060631831358-a21djaa3hm165a8976fnmo1lerujs5p6.apps.googleusercontent.com
\ No newline at end of file
uvicorn==0.21.1 uvicorn==0.21.1
python-dotenv~=1.0.0 python-dotenv~=1.0.0
pydantic~=1.10.6 pydantic==1.10.7
fastapi==0.95.0 fastapi==0.95.0
passlib~=1.7.4 passlib~=1.7.4
pymongo~=4.3.3 pymongo~=4.3.3
...@@ -9,4 +9,7 @@ email-validator~=1.3.1 ...@@ -9,4 +9,7 @@ email-validator~=1.3.1
pycryptodomex~=3.17 pycryptodomex~=3.17
PyJWT~=2.6.0 PyJWT~=2.6.0
validate_email~=1.3 validate_email~=1.3
redis~=4.5.2 redis==4.5.4
\ No newline at end of file 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): ...@@ -46,6 +46,7 @@ class _Secrets(BaseSettings):
TOKEN_EXPIRE_TIME = 5 TOKEN_EXPIRE_TIME = 5
leeway_in_minutes: int = 10 leeway_in_minutes: int = 10
KEY_ENCRYPTION: str KEY_ENCRYPTION: str
CLIENT_ID: str
issuer: str = "iotManager" issuer: str = "iotManager"
SECRET_KEY: str SECRET_KEY: str
ALGORITHM = "HS256" ALGORITHM = "HS256"
......
class ApiEndPoints: class ApiEndPoints:
# Main root
root = "/asset_manager_api"
# version # version
version = "/v1" version = "/v1"
...@@ -32,6 +35,7 @@ class ApiEndPoints: ...@@ -32,6 +35,7 @@ class ApiEndPoints:
asset_manager_user_delete: str = asset_manager_user_management + delete asset_manager_user_delete: str = asset_manager_user_management + delete
asset_manager_user_search: str = asset_manager_user_management + search asset_manager_user_search: str = asset_manager_user_management + search
asset_manager_user_logout: str = asset_manager_user_management + logout asset_manager_user_logout: str = asset_manager_user_management + logout
asset_manager_user_reset: str = asset_manager_user_management + reset
# dashboard-management # dashboard-management
asset_manager_dashboard: str = "/dashboard" asset_manager_dashboard: str = "/dashboard"
......
...@@ -4,11 +4,17 @@ from datetime import timedelta, datetime ...@@ -4,11 +4,17 @@ from datetime import timedelta, datetime
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText 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 import status
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse, RedirectResponse
from scripts.config import Services, Secrets 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.database.mongo.mongo_db import MongoUser
from scripts.errors import ErrorMessages from scripts.errors import ErrorMessages
from scripts.logging.logger import logger from scripts.logging.logger import logger
...@@ -64,9 +70,46 @@ class LoginHandlers: ...@@ -64,9 +70,46 @@ class LoginHandlers:
response.set_cookie(key="login-token", value=responses, expires=exp) response.set_cookie(key="login-token", value=responses, expires=exp)
return response return response
# v2 def google_login(self, user_data, request):
def google_login(self, request): user_data_remove_none = {key: value for key, value in user_data if key != 'login_type' and value is not None}
pass 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 # v2
def microsoft_login(self, request): def microsoft_login(self, request):
...@@ -90,7 +133,7 @@ class LoginHandlers: ...@@ -90,7 +133,7 @@ class LoginHandlers:
mail = MIMEMultipart() mail = MIMEMultipart()
mail['From'] = Services.EMAIL_SENDER mail['From'] = Services.EMAIL_SENDER
mail['To'] = email mail['To'] = email
mail['Subject'] = "Link TO Reset Password" mail['Subject'] = "Link To Reset Password"
to_encode = {"email": email} to_encode = {"email": email}
expire = datetime.utcnow() + timedelta(minutes=Secrets.TOKEN_EXPIRE_TIME) expire = datetime.utcnow() + timedelta(minutes=Secrets.TOKEN_EXPIRE_TIME)
to_encode.update({"exp": expire}) to_encode.update({"exp": expire})
...@@ -117,3 +160,61 @@ class LoginHandlers: ...@@ -117,3 +160,61 @@ class LoginHandlers:
status_code=status.HTTP_200_OK) status_code=status.HTTP_200_OK)
except Exception as e: except Exception as e:
logger.exception(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: ...@@ -43,9 +43,17 @@ class NormalLogin:
return False, ErrorMessages.ERROR_UNAUTHORIZED_USER_LOGIN return False, ErrorMessages.ERROR_UNAUTHORIZED_USER_LOGIN
# if the user is not registered through normal login # if the user is not registered through normal login
if self.db_user_data["login_type"] != login_type: 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 # if the user exist
return None, {"message": True} return None, response
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
...@@ -61,12 +69,17 @@ class NormalLogin: ...@@ -61,12 +69,17 @@ class NormalLogin:
if not self.pwd_context.verify(password, self.db_user_data["password"]): if not self.pwd_context.verify(password, self.db_user_data["password"]):
return False, ErrorMessages.ERROR_PASSWORD_MISMATCH return False, ErrorMessages.ERROR_PASSWORD_MISMATCH
# if the password is correct # if the password is correct
return None, {"user_id": self.db_user_data["user_id"], "name": self.db_user_data["name"], return None, message
"email": user_data.email,
"user_role": self.db_user_data["user_role"]}
except Exception as e: except Exception as e:
logger.exception(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 # cookie and token creation
@staticmethod @staticmethod
def generate_cookie_tokens(user_data, request): def generate_cookie_tokens(user_data, request):
......
...@@ -221,6 +221,33 @@ class UserManagement: ...@@ -221,6 +221,33 @@ class UserManagement:
except Exception as e: except Exception as e:
logger.exception(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 # user logout
@staticmethod @staticmethod
def logout_user(user_id, request): def logout_user(user_id, request):
......
...@@ -6,7 +6,7 @@ class ErrorMessages: ...@@ -6,7 +6,7 @@ class ErrorMessages:
ERROR_INVALID_LOGIN = "Your are not authorized to view this website." ERROR_INVALID_LOGIN = "Your are not authorized to view this website."
ERROR_UNAUTHORIZED_USER_LOGIN = "Account is not available" ERROR_UNAUTHORIZED_USER_LOGIN = "Account is not available"
ERROR_UNAUTHORIZED_ACCESS = "Your are not authorized to view this page" 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_USER_NOT_REGISTERED = "Account is not registered in the portal."
ERROR_PASSWORD_MISMATCH = "Please enter the correct password" ERROR_PASSWORD_MISMATCH = "Please enter the correct password"
ERROR_TOKEN_GENERATION = "Unsuccessful token generation" ERROR_TOKEN_GENERATION = "Unsuccessful token generation"
...@@ -16,6 +16,7 @@ class ErrorMessages: ...@@ -16,6 +16,7 @@ class ErrorMessages:
ERROR_IN_UPDATING = "Error in Updating" ERROR_IN_UPDATING = "Error in Updating"
ERROR_INVALID_REQUEST = "Invalid Request" ERROR_INVALID_REQUEST = "Invalid Request"
ERROR_USER_SESSION = "Not The Users Session" ERROR_USER_SESSION = "Not The Users Session"
ERROR_TOKEN_EXPIRED = "Google Token Expired"
# Data Validation # Data Validation
ERROR_INVALID_PASSWORD = "Invalid Password" ERROR_INVALID_PASSWORD = "Invalid Password"
......
This diff is collapsed.
...@@ -6,8 +6,9 @@ from pydantic import BaseModel ...@@ -6,8 +6,9 @@ from pydantic import BaseModel
# model for login request # model for login request
class LoginRequest(BaseModel): class LoginRequest(BaseModel):
login_type: str login_type: str
email: str email: Optional[str] = None
password: str password: Optional[str] = None
id_token: Optional[str] = None
class RegistrationData(BaseModel): class RegistrationData(BaseModel):
...@@ -36,6 +37,11 @@ class UsersFilter(BaseModel): ...@@ -36,6 +37,11 @@ class UsersFilter(BaseModel):
user_role: Optional[str] = None user_role: Optional[str] = None
class ResetPassword(BaseModel):
user_id: str
password: str
class UserIDValidation(BaseModel): class UserIDValidation(BaseModel):
user_id: str user_id: str
......
from fastapi import APIRouter from fastapi import APIRouter
from scripts.constants.api import ApiEndPoints
from scripts.services import v1 from scripts.services import v1
router = APIRouter() router = APIRouter(prefix=ApiEndPoints.root)
# routing to the V1 router # routing to the V1 router
router.include_router(v1.router) router.include_router(v1.router)
...@@ -3,6 +3,6 @@ from scripts.constants.api import ApiEndPoints ...@@ -3,6 +3,6 @@ from scripts.constants.api import ApiEndPoints
from scripts.services.v1 import iot_manager_services from scripts.services.v1 import iot_manager_services
# creating the api router with version as the prefix # creating the api router with version as the prefix
router = APIRouter() router = APIRouter(prefix=ApiEndPoints.version)
# routing to the service api # routing to the service api
router.include_router(iot_manager_services.router) router.include_router(iot_manager_services.router)
...@@ -8,12 +8,12 @@ from scripts.core.handlers.user_management_handler import UserManagement ...@@ -8,12 +8,12 @@ from scripts.core.handlers.user_management_handler import UserManagement
from scripts.errors import ErrorMessages from scripts.errors import ErrorMessages
from scripts.logging.logger import logger from scripts.logging.logger import logger
from scripts.schemas.default_responses import DefaultFailureResponse 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.authorize_access import AuthorizeAccess
from scripts.utils.security.decorators import MetaInfoSchema, auth from scripts.utils.security.decorators import MetaInfoSchema, auth
# creating the login api # creating the login api
router = APIRouter(prefix=ApiEndPoints.version) router = APIRouter()
# initializing the handler # initializing the handler
obj_login_handler = LoginHandlers() obj_login_handler = LoginHandlers()
obj_user_handler = UserManagement() obj_user_handler = UserManagement()
...@@ -67,14 +67,32 @@ async def forgot_password( ...@@ -67,14 +67,32 @@ async def forgot_password(
status_code=status.HTTP_200_OK) status_code=status.HTTP_200_OK)
# Reset Password API # Reset Password API for sending email
@router.get(ApiEndPoints.asset_manager_reset) @router.get(ApiEndPoints.asset_manager_reset)
async def forgot_password( async def reset_password(
token: str request: Request
): ):
try: try:
# forgot password # Get the JWT token from the query parameters
print(token) 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: except Exception as e:
logger.exception(e) logger.exception(e)
return JSONResponse( return JSONResponse(
...@@ -154,7 +172,7 @@ async def user_register( ...@@ -154,7 +172,7 @@ async def user_register(
# View users API # View users API
@router.post(ApiEndPoints.asset_manager_user_view) @router.post(ApiEndPoints.asset_manager_user_view)
async def user_register( async def user_register_view(
request: MetaInfoSchema = Depends(auth) request: MetaInfoSchema = Depends(auth)
): ):
try: try:
...@@ -179,10 +197,38 @@ async def user_register( ...@@ -179,10 +197,38 @@ async def user_register(
status_code=status.HTTP_200_OK) 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 # download Button Dashboard
@router.post(ApiEndPoints.asset_manager_dashboard_download) @router.post(ApiEndPoints.asset_manager_dashboard_download)
async def dashboard_download( async def dashboard_download(
request: MetaInfoSchema = Depends(auth)
): ):
try: try:
# getting the data for the download dashboard # getting the data for the download dashboard
......
...@@ -37,5 +37,5 @@ def create_token( ...@@ -37,5 +37,5 @@ def create_token(
login_db.set(uid, new_token) login_db.set(uid, new_token)
login_db.expire(uid, timedelta(minutes=age)) login_db.expire(uid, timedelta(minutes=age))
# Add updated time to mongo db # 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 return uid, exp
...@@ -15,3 +15,9 @@ class AuthorizeAccess: ...@@ -15,3 +15,9 @@ class AuthorizeAccess:
return True return True
except TypeError: except TypeError:
return False 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