new api service and logic implemented
This commit is contained in:
444
ApiLayers/ApiServices/Token/token_handler.py
Normal file
444
ApiLayers/ApiServices/Token/token_handler.py
Normal file
@@ -0,0 +1,444 @@
|
||||
"""Token service for handling authentication tokens and user sessions."""
|
||||
|
||||
from typing import List, Union, TypeVar, Dict, Any, Optional, TYPE_CHECKING
|
||||
|
||||
from AllConfigs.Token.config import Auth
|
||||
from ApiLibrary.common.line_number import get_line_number_for_error
|
||||
from ApiLibrary.date_time_actions.date_functions import DateTimeLocal
|
||||
from ApiLibrary.token.password_module import PasswordModule
|
||||
from ErrorHandlers import HTTPExceptionApi
|
||||
from Schemas.identity.identity import UsersTokens, People
|
||||
from Services.Redis import RedisActions, AccessToken
|
||||
from ApiValidations.Custom.token_objects import (
|
||||
EmployeeTokenObject,
|
||||
OccupantTokenObject,
|
||||
UserType,
|
||||
CompanyToken,
|
||||
OccupantToken,
|
||||
)
|
||||
from Schemas import (
|
||||
Users,
|
||||
BuildLivingSpace,
|
||||
BuildParts,
|
||||
Employees,
|
||||
Addresses,
|
||||
Companies,
|
||||
Staff,
|
||||
Duty,
|
||||
Duties,
|
||||
Departments,
|
||||
OccupantTypes,
|
||||
)
|
||||
from Services.Redis.Models.response import RedisResponse
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastapi import Request
|
||||
|
||||
T = TypeVar("T", EmployeeTokenObject, OccupantTokenObject)
|
||||
|
||||
|
||||
class TokenService:
|
||||
"""Service class for handling authentication tokens and user sessions."""
|
||||
|
||||
@classmethod
|
||||
def _create_access_token(cls, access: bool = True) -> str:
|
||||
"""Generate a new access token."""
|
||||
if not access:
|
||||
return PasswordModule.generate_token(Auth.REFRESHER_TOKEN_LENGTH)
|
||||
return PasswordModule.generate_token(Auth.ACCESS_TOKEN_LENGTH)
|
||||
|
||||
@classmethod
|
||||
def _get_user_tokens(cls, user: Users) -> RedisResponse:
|
||||
"""Get all tokens for a user from Redis."""
|
||||
return RedisActions.get_json(
|
||||
list_keys=AccessToken(
|
||||
userUUID=user.uu_id,
|
||||
).to_list()
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def do_occupant_login(
|
||||
cls, request: "Request", user: Users, domain: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Handle occupant login process and return login information."""
|
||||
db_session = BuildLivingSpace.new_session()
|
||||
living_spaces: list[BuildLivingSpace] = BuildLivingSpace.filter_all(
|
||||
BuildLivingSpace.person_id == user.person_id, db=db_session
|
||||
).data
|
||||
if not living_spaces:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="User does not have any living space",
|
||||
)
|
||||
|
||||
occupants_selection_dict: Dict[str, Any] = {}
|
||||
for living_space in living_spaces:
|
||||
build_parts_selection = BuildParts.filter_all(
|
||||
BuildParts.id == living_space.build_parts_id,
|
||||
db=db_session,
|
||||
).data
|
||||
if not build_parts_selection:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="User does not have any living space",
|
||||
)
|
||||
|
||||
build_part = build_parts_selection.get(1)
|
||||
build = build_part.buildings
|
||||
occupant_type = OccupantTypes.filter_by_one(
|
||||
id=living_space.occupant_type,
|
||||
db=db_session,
|
||||
system=True,
|
||||
).data
|
||||
|
||||
occupant_data = {
|
||||
"part_uu_id": str(build_part.uu_id),
|
||||
"part_name": build_part.part_name,
|
||||
"part_level": build_part.part_level,
|
||||
"uu_id": str(occupant_type.uu_id),
|
||||
"description": occupant_type.occupant_description,
|
||||
"code": occupant_type.occupant_code,
|
||||
}
|
||||
|
||||
build_key = str(build.uu_id)
|
||||
if build_key not in occupants_selection_dict:
|
||||
occupants_selection_dict[build_key] = {
|
||||
"build_uu_id": build_key,
|
||||
"build_name": build.build_name,
|
||||
"build_no": build.build_no,
|
||||
"occupants": [occupant_data],
|
||||
}
|
||||
else:
|
||||
occupants_selection_dict[build_key]["occupants"].append(occupant_data)
|
||||
model_value = OccupantTokenObject(
|
||||
domain=domain,
|
||||
user_type=UserType.occupant.value,
|
||||
user_uu_id=str(user.uu_id),
|
||||
credentials=user.credentials(),
|
||||
user_id=user.id,
|
||||
person_id=user.person_id,
|
||||
person_uu_id=str(user.person.uu_id),
|
||||
request=dict(request.headers),
|
||||
available_occupants=occupants_selection_dict,
|
||||
timezone=user.local_timezone or "GMT+0",
|
||||
lang=user.lang or "tr",
|
||||
).model_dump()
|
||||
if access_token := cls.set_object_to_redis(user, model_value):
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"user_type": UserType.occupant.name,
|
||||
"available_occupants": occupants_selection_dict,
|
||||
}
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Creating Token failed...",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_object_to_redis(cls, user, model: Dict):
|
||||
access_object = AccessToken(
|
||||
userUUID=user.uu_id,
|
||||
accessToken=cls._create_access_token(),
|
||||
)
|
||||
redis_action = RedisActions.set_json(
|
||||
list_keys=access_object.to_list(),
|
||||
value=model,
|
||||
expires={"seconds": int(Auth.TOKEN_EXPIRE_MINUTES_30.seconds)},
|
||||
)
|
||||
if redis_action.status:
|
||||
return access_object.accessToken
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Saving Token failed...",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def update_object_to_redis(cls, access_token: str, user_uu_id: str, model: Dict):
|
||||
access_object = AccessToken(
|
||||
userUUID=user_uu_id,
|
||||
accessToken=access_token,
|
||||
)
|
||||
redis_action = RedisActions.set_json(
|
||||
list_keys=access_object.to_list(),
|
||||
value=model,
|
||||
expires={"seconds": int(Auth.TOKEN_EXPIRE_MINUTES_30.seconds)},
|
||||
)
|
||||
if redis_action.status:
|
||||
return access_object.accessToken
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Saving Token failed...",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def do_employee_login(
|
||||
cls, request: "Request", user: Users, domain: str
|
||||
) -> Dict[str, Any]:
|
||||
"""Handle employee login process and return login information."""
|
||||
db_session = Employees.new_session()
|
||||
list_employee = Employees.filter_all(
|
||||
Employees.people_id == user.person_id, db=db_session
|
||||
).data
|
||||
|
||||
companies_uu_id_list: List[str] = []
|
||||
companies_id_list: List[int] = []
|
||||
companies_list: List[Dict[str, Any]] = []
|
||||
duty_uu_id_list: List[str] = []
|
||||
duty_id_list: List[int] = []
|
||||
|
||||
for employee in list_employee:
|
||||
staff = Staff.filter_one(Staff.id == employee.staff_id, db=db_session).data
|
||||
if duties := Duties.filter_one(
|
||||
Duties.id == staff.duties_id, db=db_session
|
||||
).data:
|
||||
if duty_found := Duty.filter_by_one(
|
||||
id=duties.duties_id, db=db_session
|
||||
).data:
|
||||
duty_uu_id_list.append(str(duty_found.uu_id))
|
||||
duty_id_list.append(duty_found.id)
|
||||
|
||||
department = Departments.filter_one(
|
||||
Departments.id == duties.department_id, db=db_session
|
||||
).data
|
||||
|
||||
if company := Companies.filter_one(
|
||||
Companies.id == department.company_id, db=db_session
|
||||
).data:
|
||||
companies_uu_id_list.append(str(company.uu_id))
|
||||
companies_id_list.append(company.id)
|
||||
company_address = Addresses.filter_by_one(
|
||||
id=company.official_address_id, db=db_session
|
||||
).data
|
||||
companies_list.append(
|
||||
{
|
||||
"uu_id": str(company.uu_id),
|
||||
"public_name": company.public_name,
|
||||
"company_type": company.company_type,
|
||||
"company_address": company_address,
|
||||
}
|
||||
)
|
||||
person = People.filter_one(People.id == user.person_id, db=db_session).data
|
||||
model_value = EmployeeTokenObject(
|
||||
domain=domain,
|
||||
user_type=UserType.employee.value,
|
||||
user_uu_id=str(user.uu_id),
|
||||
credentials=user.credentials(),
|
||||
user_id=user.id,
|
||||
person_id=person.id,
|
||||
person_uu_id=str(person.uu_id),
|
||||
request=dict(request.headers),
|
||||
companies_uu_id_list=companies_uu_id_list,
|
||||
companies_id_list=companies_id_list,
|
||||
duty_uu_id_list=duty_uu_id_list,
|
||||
duty_id_list=duty_id_list,
|
||||
timezone=user.local_timezone or "GMT+0",
|
||||
lang=user.lang or "tr",
|
||||
).model_dump()
|
||||
if access_token := cls.set_object_to_redis(user, model_value):
|
||||
return {
|
||||
"access_token": access_token,
|
||||
"user_type": UserType.employee.name,
|
||||
"companies_list": companies_list,
|
||||
}
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Creating Token failed...",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def remove_token_with_domain(cls, user: Users, domain: str) -> None:
|
||||
"""Remove all tokens for a user with specific domain."""
|
||||
redis_rows = cls._get_user_tokens(user)
|
||||
for redis_row in redis_rows.all:
|
||||
if redis_row.data.get("domain") == domain:
|
||||
RedisActions.delete_key(redis_row.key)
|
||||
|
||||
@classmethod
|
||||
def remove_all_token(cls, user: Users) -> None:
|
||||
"""Remove all tokens for a user."""
|
||||
redis_rows = cls._get_user_tokens(user)
|
||||
RedisActions.delete([redis_row.key for redis_row in redis_rows.all])
|
||||
|
||||
@classmethod
|
||||
def set_access_token_to_redis(
|
||||
cls,
|
||||
request: "Request",
|
||||
user: Users,
|
||||
domain: str,
|
||||
remember: bool,
|
||||
) -> Dict[str, Any]:
|
||||
"""Set access token to redis and handle user session."""
|
||||
cls.remove_token_with_domain(user=user, domain=domain)
|
||||
Users.client_arrow = DateTimeLocal(is_client=True, timezone=user.local_timezone)
|
||||
db_session = UsersTokens.new_session()
|
||||
# Handle login based on user type
|
||||
if user.is_occupant:
|
||||
login_dict = cls.do_occupant_login(
|
||||
request=request, user=user, domain=domain
|
||||
)
|
||||
elif user.is_employee:
|
||||
login_dict = cls.do_employee_login(
|
||||
request=request, user=user, domain=domain
|
||||
)
|
||||
|
||||
# Handle remember me functionality
|
||||
if remember:
|
||||
users_token = UsersTokens.find_or_create(
|
||||
db=db_session,
|
||||
user_id=user.id,
|
||||
token_type="RememberMe",
|
||||
token=cls._create_access_token(access=False),
|
||||
domain=domain,
|
||||
)
|
||||
if users_token.meta_data.get("created"):
|
||||
user.remember_me = True
|
||||
else:
|
||||
if UsersTokens.filter_all(
|
||||
UsersTokens.user_id == user.id,
|
||||
UsersTokens.token_type == "RememberMe",
|
||||
UsersTokens.domain == domain,
|
||||
db=db_session,
|
||||
).data:
|
||||
UsersTokens.filter_all(
|
||||
UsersTokens.user_id == user.id,
|
||||
UsersTokens.token_type == "RememberMe",
|
||||
UsersTokens.domain == domain,
|
||||
db=db_session,
|
||||
).query.delete(synchronize_session=False)
|
||||
user.remember_me = False
|
||||
user.save(db=db_session)
|
||||
return {
|
||||
**login_dict,
|
||||
"user": user.get_dict(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def update_token_at_redis(
|
||||
cls, request: "Request", add_payload: Union[CompanyToken, OccupantToken]
|
||||
) -> Dict[str, Any]:
|
||||
"""Update token at Redis."""
|
||||
access_token = cls.get_access_token_from_request(request=request)
|
||||
token_object = cls.get_object_via_access_key(access_token=access_token)
|
||||
if isinstance(token_object, EmployeeTokenObject) and isinstance(
|
||||
add_payload, CompanyToken
|
||||
):
|
||||
token_object.selected_company = add_payload
|
||||
cls.update_object_to_redis(
|
||||
access_token=access_token,
|
||||
user_uu_id=token_object.user_uu_id,
|
||||
model=token_object.model_dump(),
|
||||
)
|
||||
return token_object.selected_company.model_dump()
|
||||
elif isinstance(token_object, OccupantTokenObject) and isinstance(
|
||||
add_payload, OccupantToken
|
||||
):
|
||||
token_object.selected_occupant = add_payload
|
||||
cls.update_object_to_redis(
|
||||
access_token=access_token,
|
||||
user_uu_id=token_object.user_uu_id,
|
||||
model=token_object.model_dump(),
|
||||
)
|
||||
return token_object.selected_occupant.model_dump()
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Token not found",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def raise_error_if_request_has_no_token(cls, request: "Request") -> None:
|
||||
"""Validate request has required token headers."""
|
||||
if not hasattr(request, "headers"):
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Request has no headers",
|
||||
)
|
||||
if not request.headers.get(Auth.ACCESS_TOKEN_TAG):
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Request has no access token presented",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def access_token_is_valid(cls, request: "Request") -> bool:
|
||||
"""Check if access token in request is valid."""
|
||||
access_token = cls.get_access_token_from_request(request=request)
|
||||
return RedisActions.get_json(
|
||||
list_keys=AccessToken(accessToken=access_token).to_list()
|
||||
).status
|
||||
|
||||
@classmethod
|
||||
def get_access_token_from_request(cls, request: "Request") -> str:
|
||||
"""Extract access token from request headers."""
|
||||
cls.raise_error_if_request_has_no_token(request=request)
|
||||
return request.headers.get(Auth.ACCESS_TOKEN_TAG)
|
||||
|
||||
@classmethod
|
||||
def _process_redis_object(cls, redis_object: Dict[str, Any]) -> T:
|
||||
"""Process Redis object and return appropriate token object."""
|
||||
if not redis_object.get("selected_company"):
|
||||
redis_object["selected_company"] = None
|
||||
if not redis_object.get("selected_occupant"):
|
||||
redis_object["selected_occupant"] = None
|
||||
if redis_object.get("user_type") == UserType.employee.value:
|
||||
return EmployeeTokenObject(**redis_object)
|
||||
elif redis_object.get("user_type") == UserType.occupant.value:
|
||||
return OccupantTokenObject(**redis_object)
|
||||
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Unknown user type",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_object_via_access_key(cls, access_token: str) -> T:
|
||||
"""Get token object using access key."""
|
||||
access_token_obj = AccessToken(accessToken=access_token)
|
||||
redis_response = RedisActions.get_json(list_keys=access_token_obj.to_list())
|
||||
if not redis_response.status:
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Access token token is not found or unable to retrieve",
|
||||
)
|
||||
if redis_object := redis_response.first:
|
||||
redis_object_dict = redis_object.data
|
||||
access_token_obj.userUUID = redis_object_dict.get("user_uu_id")
|
||||
return cls._process_redis_object(redis_object_dict)
|
||||
|
||||
@classmethod
|
||||
def get_object_via_user_uu_id(cls, user_id: str) -> T:
|
||||
"""Get token object using user UUID."""
|
||||
access_token = AccessToken(userUUID=user_id)
|
||||
redis_response = RedisActions.get_json(list_keys=access_token.to_list())
|
||||
|
||||
if redis_object := redis_response.first.data:
|
||||
access_token.userUUID = redis_object.get("user_uu_id")
|
||||
return cls._process_redis_object(redis_object)
|
||||
|
||||
raise HTTPExceptionApi(
|
||||
error_code="",
|
||||
lang="en",
|
||||
loc=get_line_number_for_error(),
|
||||
sys_msg="Invalid access token",
|
||||
)
|
||||
Reference in New Issue
Block a user