diff --git a/api/blueprints/common/isp/__init__.py b/api/blueprints/common/isp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/blueprints/common/isp/user_login.py b/api/blueprints/common/isp/user_login.py new file mode 100644 index 0000000..8b14040 --- /dev/null +++ b/api/blueprints/common/isp/user_login.py @@ -0,0 +1,201 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Wednesday, 19th Feb., 2025. + + OBJECTIVE: + + To perform a login from an ISP's device like a MikroTik. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + + NOTES: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For using Quart: +from quart import Blueprint, current_app, request + +# My utils: +from utils_v2.string import json +from utils_v2.api.codes import StatusCodes, HttpCodes +from utils_v2.api.response import ResponseModel +from utils_v2.api.async_quart import ( + set_api_version, + read_input, + get_session_info, + log_request_to_mongo, + log_chain_to_mongo, + should_not_be_under_maintenance, + only_whitelisted_ips, + limit_rate, + validate_input, + handle_cancelled_request +) + +# Common: +from shared import constants + +# Data Models: +from models.core.user import CoreUserInfoModel +from models.api.common.isp.user_login import ISPUserLoginRequestHeaders, ISPUserLoginRequestData + +# Helpers: +from api.helpers.user import token_check + +# To work with MongoDB: +from bson import ObjectId + +# To generate UUIDs: +import uuid + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Related to Quart: +isp_user_login_bp = Blueprint("isp_user_login", __name__) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +@isp_user_login_bp.record_once +def init(blueprint_setup_state): + + # This gets called when the blueprint is registered. + # Consider this to be a one-time setup for the whole blueprint: + pass + + +# --------------------------------------------------------------------------------------------------------------------- + + +@isp_user_login_bp.route("/login", methods = ["POST"]) +@set_api_version(api_version = "1.0.0") +@read_input(sanitize_headers = False, sanitize_data = False) +@get_session_info(key = "X-Session-Token", session_coro = "get_session") +@log_request_to_mongo( + attr_name = "logs_mongo", + project = constants.PROJECT_NAME, + log_type = constants.MODULE_NAME, + operation = "ispUsrLoginApi", + log_input = True, + log_output = True, + sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"] +) +@log_chain_to_mongo(attr_name = "logs_mongo") +@should_not_be_under_maintenance(attr_name = "is_under_maintenance") +@validate_input( + header_validator = lambda x: ISPUserLoginRequestHeaders(**x).model_dump(), + data_validator = lambda x: ISPUserLoginRequestData(**x) +) +@handle_cancelled_request() +async def disable_auth_token( + inbound_headers: dict | ISPUserLoginRequestHeaders = None, + inbound_data: dict | ISPUserLoginRequestData = None, + inbound_files: dict = None, + **kwargs +): + + """ + Use this for allowing an ISP's customer to login. + :param inbound_headers: auto-extracted by the decorators. + :param inbound_data: auto-extracted by the decorators. + :param inbound_files: auto-extracted by the decorators. + :param kwargs: Any number of extra inputs supplied by the decorators. + :return: A standard response structure. + """ + + # ┏┓ ┓ ┏┓┓ ┓ + # ┣┫┓┏╋┣┓ ┃ ┣┓┏┓┏┃┏ + # ┛┗┗┻┗┛┗ ┗┛┛┗┗ ┗┛┗ + + # If the session token is invalid/expired: + if kwargs.get("session_info") is None: + return constants.API_RESPONSE_UNAUTHORIZED + + # Get the user's info: + user_info = CoreUserInfoModel(**kwargs.get("session_info")) + + # The user requesting this action needs to be an "Owner": + if user_info.role.strip().lower() != "owner": + return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.UNAUTHORIZED, + message = "Only owners can perform this action." + ) + + # ┏┓ ┳┳┓ • ┓┏ + # ┗┓┏┓┏┳┓┏┓ ┃┃┃┏┓┏┓┓┏ ┣┫┏┓┏┓┏┓ + # ┗┛┗┛┛┗┗┗ ┛ ┗┗┻┗┫┗┗ ┛┗┗ ┛ ┗ + # ┛ + + # Gili-gili chhoo: + pass + + # Done here: + return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.NOT_IMPLEMENTED, + data = None + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/api/blueprints/common/isp/user_signup.py b/api/blueprints/common/isp/user_signup.py new file mode 100644 index 0000000..8a256e8 --- /dev/null +++ b/api/blueprints/common/isp/user_signup.py @@ -0,0 +1,201 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Wednesday, 19th Feb., 2025. + + OBJECTIVE: + + To perform a user signup from an ISP's device like a MikroTik. + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + + NOTES: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For using Quart: +from quart import Blueprint, current_app, request + +# My utils: +from utils_v2.string import json +from utils_v2.api.codes import StatusCodes, HttpCodes +from utils_v2.api.response import ResponseModel +from utils_v2.api.async_quart import ( + set_api_version, + read_input, + get_session_info, + log_request_to_mongo, + log_chain_to_mongo, + should_not_be_under_maintenance, + only_whitelisted_ips, + limit_rate, + validate_input, + handle_cancelled_request +) + +# Common: +from shared import constants + +# Data Models: +from models.core.user import CoreUserInfoModel +from models.api.common.isp.user_signup import ISPUserSignupRequestHeaders, ISPUserSignupRequestData + +# Helpers: +from api.helpers.user import token_check + +# To work with MongoDB: +from bson import ObjectId + +# To generate UUIDs: +import uuid + +# For asynchronous activities: +import asyncio + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# Related to Quart: +isp_user_signup_bp = Blueprint("isp_user_signup", __name__) + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +@isp_user_signup_bp.record_once +def init(blueprint_setup_state): + + # This gets called when the blueprint is registered. + # Consider this to be a one-time setup for the whole blueprint: + pass + + +# --------------------------------------------------------------------------------------------------------------------- + + +@isp_user_signup_bp.route("/signup", methods = ["POST"]) +@set_api_version(api_version = "1.0.0") +@read_input(sanitize_headers = False, sanitize_data = False) +@get_session_info(key = "X-Session-Token", session_coro = "get_session") +@log_request_to_mongo( + attr_name = "logs_mongo", + project = constants.PROJECT_NAME, + log_type = constants.MODULE_NAME, + operation = "ispUsrSignupApi", + log_input = True, + log_output = True, + sensitive_keys = ["sessionToken", "X-Session-Token", "tokenKey"] +) +@log_chain_to_mongo(attr_name = "logs_mongo") +@should_not_be_under_maintenance(attr_name = "is_under_maintenance") +@validate_input( + header_validator = lambda x: ISPUserSignupRequestHeaders(**x).model_dump(), + data_validator = lambda x: ISPUserSignupRequestData(**x) +) +@handle_cancelled_request() +async def disable_auth_token( + inbound_headers: dict | ISPUserSignupRequestHeaders = None, + inbound_data: dict | ISPUserSignupRequestData = None, + inbound_files: dict = None, + **kwargs +): + + """ + Use this for allowing an ISP's customer to signup. + :param inbound_headers: auto-extracted by the decorators. + :param inbound_data: auto-extracted by the decorators. + :param inbound_files: auto-extracted by the decorators. + :param kwargs: Any number of extra inputs supplied by the decorators. + :return: A standard response structure. + """ + + # ┏┓ ┓ ┏┓┓ ┓ + # ┣┫┓┏╋┣┓ ┃ ┣┓┏┓┏┃┏ + # ┛┗┗┻┗┛┗ ┗┛┛┗┗ ┗┛┗ + + # If the session token is invalid/expired: + if kwargs.get("session_info") is None: + return constants.API_RESPONSE_UNAUTHORIZED + + # Get the user's info: + user_info = CoreUserInfoModel(**kwargs.get("session_info")) + + # The user requesting this action needs to be an "Owner": + if user_info.role.strip().lower() != "owner": + return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.UNAUTHORIZED, + message = "Only owners can perform this action." + ) + + # ┏┓ ┳┳┓ • ┓┏ + # ┗┓┏┓┏┳┓┏┓ ┃┃┃┏┓┏┓┓┏ ┣┫┏┓┏┓┏┓ + # ┗┛┗┛┛┗┗┗ ┛ ┗┗┻┗┫┗┗ ┛┗┗ ┛ ┗ + # ┛ + + # Gili-gili chhoo: + pass + + # Done here: + return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.NOT_IMPLEMENTED, + data = None + ) + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/api/main.py b/api/main.py index 96f4b3b..f68a077 100644 --- a/api/main.py +++ b/api/main.py @@ -152,6 +152,8 @@ from api.blueprints.ai.llm.invoke import llm_invoke_bp # Common Blueprints: from api.blueprints.common.disable import auth_token_disable_bp from api.blueprints.common.session_token import session_token_bp +from api.blueprints.common.isp.user_login import isp_user_login_bp +from api.blueprints.common.isp.user_signup import isp_user_signup_bp # Tech and Testing Blueprints: from api.blueprints.tech.chat_alerts import tech_chat_alert_bp @@ -227,6 +229,8 @@ app.register_blueprint(trading_symbols_list_bp, url_prefix = f"/{MODULE_BASE}/fi # Common Blueprints: app.register_blueprint(auth_token_disable_bp, url_prefix = f"/{MODULE_BASE}") app.register_blueprint(session_token_bp, url_prefix = f"/{MODULE_BASE}") +app.register_blueprint(isp_user_login_bp, url_prefix = f"/{MODULE_BASE}/isp/user") +app.register_blueprint(isp_user_signup_bp, url_prefix = f"/{MODULE_BASE}/isp/user") # AI Blueprints: app.register_blueprint(llm_invoke_bp, url_prefix = f"/{MODULE_BASE}/ai") diff --git a/models/api/common/isp/__init__.py b/models/api/common/isp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models/api/common/isp/user_login.py b/models/api/common/isp/user_login.py new file mode 100644 index 0000000..6037f95 --- /dev/null +++ b/models/api/common/isp/user_login.py @@ -0,0 +1,144 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Wednesday, 19th Feb., 2025. + + OBJECTIVE: + + To provide a structure to allow a user login from an ISP's device (like a MikroTik). + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For making data behaviour_models: +from pydantic import BaseModel, Field, field_validator, PastDatetime +from typing import Optional, Literal, List, Any + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with MongoDB: +from bson.objectid import ObjectId + +# To work with date and time: +import datetime + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# RegEx Patterns: +REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class ISPUserLoginRequestHeaders(BaseModel): + + sessionToken: str = Field( + description = "The session token of the user who is requesting the service.", + pattern = REGEX_SESSION_TOKEN, + frozen = True, + alias = "X-Session-Token" + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "allow" + + def model_dump(self, *args, **kwargs): + return super().model_dump(*args, by_alias = True, **kwargs) + + +# --------------------------------------------------------------------------------------------------------------------- + + +class ISPUserLoginRequestData(BaseModel): + + username: str = Field( + description = "The username of the customer of the ISP.", + min_length = 1, + frozen = True + ) + + password: str = Field( + description = "The password of the customer of the ISP.", + min_length = 1, + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + arbitrary_types_allowed = True + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass diff --git a/models/api/common/isp/user_signup.py b/models/api/common/isp/user_signup.py new file mode 100644 index 0000000..aeadfc1 --- /dev/null +++ b/models/api/common/isp/user_signup.py @@ -0,0 +1,149 @@ +""" + + AUTHOR: + + Khushal P Soonderji + + DATE: + + Wednesday, 19th Feb., 2025. + + OBJECTIVE: + + To provide a structure to allow a user signup from an ISP's device (like a MikroTik). + + REFERENCES: + + N/A + + DOWNLOADS: + + N/A + +""" + + +# ***************************************************************************************************************** +# ***** **** +# *** IMPORT *** +# ***** **** +# ***************************************************************************************************************** + + +# To make sibling directories accessible for imports: +import sys +sys.path.append(".") +sys.path.append("..") + +# For making data behaviour_models: +from pydantic import BaseModel, Field, field_validator, PastDatetime, EmailStr +from typing import Optional, Literal, List, Any + +# My utils: +from utils_v2.string import regex +from utils_v2.date_time import date_time + +# To work with MongoDB: +from bson.objectid import ObjectId + +# To work with date and time: +import datetime + + +# ***************************************************************************************************************** +# ***** **** +# *** MACROS / ONE-TIME INIT *** +# ***** **** +# ***************************************************************************************************************** + + +# RegEx Patterns: +REGEX_SESSION_TOKEN = r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$" + + +# ***************************************************************************************************************** +# ***** **** +# *** VARIABLES *** +# ***** **** +# ***************************************************************************************************************** + + +# --- Nothing Yet + + +# ***************************************************************************************************************** +# ***** **** +# *** FUNCTIONS *** +# ***** **** +# ***************************************************************************************************************** + + +class ISPUserSignupRequestHeaders(BaseModel): + + sessionToken: str = Field( + description = "The session token of the user who is requesting the service.", + pattern = REGEX_SESSION_TOKEN, + frozen = True, + alias = "X-Session-Token" + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "allow" + + def model_dump(self, *args, **kwargs): + return super().model_dump(*args, by_alias = True, **kwargs) + + +# --------------------------------------------------------------------------------------------------------------------- + + +class ISPUserSignupRequestData(BaseModel): + + username: str = Field( + description = "The username of the customer of the ISP.", + min_length = 1, + frozen = True + ) + + phoneNo: str = Field( + description = "The mobile phone contact no. of the customer of the ISP.", + min_length = 1, + frozen = True + ) + + email: EmailStr = Field( + description = "The mobile phone contact no. of the customer of the ISP.", + frozen = True + ) + + # ┏┓ ┏• + # ┃ ┏┓┏┓╋┓┏┓ + # ┗┛┗┛┛┗┛┗┗┫ + # ┛ + + class Config: + extra = "forbid" + arbitrary_types_allowed = True + + # ┓┏ ┓• ┓ • + # ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓ + # ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗ + + pass + + +# ***************************************************************************************************************** +# ***** **** +# *** MAIN PROGRAM *** +# ***** **** +# ***************************************************************************************************************** + + +if __name__ == "__main__": + + pass