(20250619) Shifted the timed OTP generation code to this project.
This commit is contained in:
@@ -45,6 +45,9 @@ sys.path.append("..")
|
|||||||
# For using Quart:
|
# For using Quart:
|
||||||
from quart import Blueprint, current_app, make_response
|
from quart import Blueprint, current_app, make_response
|
||||||
|
|
||||||
|
# Common:
|
||||||
|
from shared import constants
|
||||||
|
|
||||||
# My utils:
|
# My utils:
|
||||||
from utils_v2.string import json
|
from utils_v2.string import json
|
||||||
from utils_v2.date_time import date_time
|
from utils_v2.date_time import date_time
|
||||||
@@ -54,6 +57,7 @@ from utils_v2.api.async_quart import (
|
|||||||
set_api_version,
|
set_api_version,
|
||||||
read_input,
|
read_input,
|
||||||
log_request_to_mongo,
|
log_request_to_mongo,
|
||||||
|
log_chain_to_mongo,
|
||||||
should_not_be_under_maintenance,
|
should_not_be_under_maintenance,
|
||||||
only_whitelisted_ips,
|
only_whitelisted_ips,
|
||||||
limit_rate,
|
limit_rate,
|
||||||
@@ -128,13 +132,15 @@ def init(blueprint_setup_state):
|
|||||||
@set_api_version(api_version = "2.2.0")
|
@set_api_version(api_version = "2.2.0")
|
||||||
@read_input(sanitize_headers = True, sanitize_data = True)
|
@read_input(sanitize_headers = True, sanitize_data = True)
|
||||||
@log_request_to_mongo(
|
@log_request_to_mongo(
|
||||||
attr_name = "mongo",
|
attr_name = "logs_mongo",
|
||||||
project = "internal",
|
project = constants.PROJECT_NAME,
|
||||||
log_type = "credData",
|
log_type = "credData",
|
||||||
operation = "set",
|
operation = "set",
|
||||||
log_input = False,
|
log_input = False,
|
||||||
log_output = True
|
log_output = True,
|
||||||
|
sensitive_keys = None
|
||||||
)
|
)
|
||||||
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
||||||
@validate_input(header_validator = lambda x: CredAndDataSetRequestHeaders(**x).model_dump())
|
@validate_input(header_validator = lambda x: CredAndDataSetRequestHeaders(**x).model_dump())
|
||||||
@@ -280,13 +286,15 @@ async def get_data(
|
|||||||
@set_api_version(api_version = "2.1.0")
|
@set_api_version(api_version = "2.1.0")
|
||||||
@read_input(sanitize_headers = True, sanitize_data = True)
|
@read_input(sanitize_headers = True, sanitize_data = True)
|
||||||
@log_request_to_mongo(
|
@log_request_to_mongo(
|
||||||
attr_name = "mongo",
|
attr_name = "logs_mongo",
|
||||||
project = "internal",
|
project = constants.PROJECT_NAME,
|
||||||
log_type = "credData",
|
log_type = "credData",
|
||||||
operation = "update",
|
operation = "update",
|
||||||
log_input = False,
|
log_input = False,
|
||||||
log_output = True
|
log_output = True,
|
||||||
|
sensitive_keys = None
|
||||||
)
|
)
|
||||||
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
||||||
@validate_input(
|
@validate_input(
|
||||||
@@ -353,13 +361,15 @@ async def update_cred(
|
|||||||
@set_api_version(api_version = "2.1.0")
|
@set_api_version(api_version = "2.1.0")
|
||||||
@read_input(sanitize_headers = True, sanitize_data = True)
|
@read_input(sanitize_headers = True, sanitize_data = True)
|
||||||
@log_request_to_mongo(
|
@log_request_to_mongo(
|
||||||
attr_name = "mongo",
|
attr_name = "logs_mongo",
|
||||||
project = "internal",
|
project = constants.PROJECT_NAME,
|
||||||
log_type = "credData",
|
log_type = "credData",
|
||||||
operation = "delete",
|
operation = "delete",
|
||||||
log_input = True,
|
log_input = True,
|
||||||
log_output = True
|
log_output = True,
|
||||||
|
sensitive_keys = None
|
||||||
)
|
)
|
||||||
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
||||||
@validate_input(header_validator = lambda x: CredAndDataDeleteRequestHeaders(**x).model_dump())
|
@validate_input(header_validator = lambda x: CredAndDataDeleteRequestHeaders(**x).model_dump())
|
||||||
@@ -408,13 +418,15 @@ async def delete_data(
|
|||||||
@set_api_version(api_version = "2.1.0")
|
@set_api_version(api_version = "2.1.0")
|
||||||
@read_input(sanitize_headers = True, sanitize_data = True)
|
@read_input(sanitize_headers = True, sanitize_data = True)
|
||||||
@log_request_to_mongo(
|
@log_request_to_mongo(
|
||||||
attr_name = "mongo",
|
attr_name = "logs_mongo",
|
||||||
project = "internal",
|
project = constants.PROJECT_NAME,
|
||||||
log_type = "credData",
|
log_type = "credData",
|
||||||
operation = "listIds",
|
operation = "listIds",
|
||||||
log_input = True,
|
log_input = True,
|
||||||
log_output = True
|
log_output = True,
|
||||||
|
sensitive_keys = None
|
||||||
)
|
)
|
||||||
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
@only_whitelisted_ips(attr_name = "whitelisted_ips")
|
||||||
@validate_input(header_validator = None)
|
@validate_input(header_validator = None)
|
||||||
|
|||||||
@@ -0,0 +1,288 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
AUTHOR:
|
||||||
|
|
||||||
|
Khushal P Soonderji
|
||||||
|
|
||||||
|
DATE:
|
||||||
|
|
||||||
|
Wednesday, 19th Jun., 2025
|
||||||
|
|
||||||
|
OBJECTIVE:
|
||||||
|
|
||||||
|
To generate and verify OTPs that expire.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Common:
|
||||||
|
from shared import constants
|
||||||
|
|
||||||
|
# My utils:
|
||||||
|
from utils_v2.string import json
|
||||||
|
from utils_v2.date_time import date_time
|
||||||
|
from utils_v2.security.otp import HashedOTP
|
||||||
|
from utils_v2.api.response import ResponseModel
|
||||||
|
from utils_v2.api.codes import StatusCodes, HttpCodes
|
||||||
|
from utils_v2.api.async_quart import (
|
||||||
|
log_request_to_mongo,
|
||||||
|
only_whitelisted_ips,
|
||||||
|
should_not_be_under_maintenance,
|
||||||
|
read_input,
|
||||||
|
limit_rate,
|
||||||
|
validate_input,
|
||||||
|
set_api_version,
|
||||||
|
log_chain_to_mongo,
|
||||||
|
handle_cancelled_request
|
||||||
|
)
|
||||||
|
|
||||||
|
# Data models:
|
||||||
|
from models.otp.timed_otp import GenerateTimedOTP, VerifyTimedOTP
|
||||||
|
|
||||||
|
# For asynchronous activities:
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** MACROS / ONE-TIME INIT ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
# Related to Quart:
|
||||||
|
otp_bp = Blueprint("otp", __name__)
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** VARIABLES ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
# --- Nothing Yet
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** FUNCTIONS ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
@otp_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
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@otp_bp.route("/timed/generate", methods = ["POST", "GET"])
|
||||||
|
@set_api_version(api_version = "1.0.0")
|
||||||
|
@read_input(sanitize_headers = True, sanitize_data = True)
|
||||||
|
@log_request_to_mongo(
|
||||||
|
attr_name = "logs_mongo",
|
||||||
|
project = constants.PROJECT_NAME,
|
||||||
|
log_type = "timedOTP",
|
||||||
|
operation = "generate",
|
||||||
|
log_input = True,
|
||||||
|
log_output = True,
|
||||||
|
sensitive_keys = None
|
||||||
|
)
|
||||||
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
|
# @only_whitelisted_ips(attr_name = "whitelisted_ips")
|
||||||
|
@validate_input(data_validator = lambda x: GenerateTimedOTP(**x))
|
||||||
|
# @limit_rate(
|
||||||
|
# attr_name = "otp_redis",
|
||||||
|
# rate_limit = 1,
|
||||||
|
# seconds = 2,
|
||||||
|
# data_keys = ["id"],
|
||||||
|
# allow_if_exception = False
|
||||||
|
# )
|
||||||
|
@handle_cancelled_request()
|
||||||
|
async def generate_otp(
|
||||||
|
inbound_headers: dict = None,
|
||||||
|
inbound_data: dict | GenerateTimedOTP = None,
|
||||||
|
inbound_files: dict = None,
|
||||||
|
log_id: str = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Generates one OTP for the amount of time specified in the request.
|
||||||
|
:param inbound_headers: auto-extracted by the decorators from 'async_quart.py'.
|
||||||
|
:param inbound_data: auto-extracted by the decorators from 'async_quart.py'.
|
||||||
|
:param inbound_files: auto-extracted by the decorators from 'async_quart.py'.
|
||||||
|
:param log_id: An identifier for the logs (if logging is enabled).
|
||||||
|
:return: A standard response structure from the function in 'async_quart.py'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Generate the OTP:
|
||||||
|
otp_key = current_app.otp_redis.make_key(str(inbound_data.id))
|
||||||
|
otp_client = HashedOTP(secret = HashedOTP.generate_secret())
|
||||||
|
otp = otp_client.generate_otp(count = 0)
|
||||||
|
|
||||||
|
# Delete any existing OTP with the same identifiers:
|
||||||
|
await current_app.otp_redis.delete(key = otp_key)
|
||||||
|
|
||||||
|
# Store the OTP in Redis:
|
||||||
|
otp_stored = await current_app.otp_redis.set(
|
||||||
|
key = otp_key,
|
||||||
|
value = {
|
||||||
|
"otp": otp,
|
||||||
|
"att": inbound_data.attempts,
|
||||||
|
"iat": date_time.get_current_utc_date_time().timestamp(),
|
||||||
|
"sec": inbound_data.seconds
|
||||||
|
},
|
||||||
|
expiry = inbound_data.seconds
|
||||||
|
)
|
||||||
|
|
||||||
|
# Done here:
|
||||||
|
return ResponseModel(
|
||||||
|
status_code = StatusCodes.OK if otp_stored else StatusCodes.FAILED,
|
||||||
|
data = {"otp": otp} if otp_stored else None
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@otp_bp.route("/timed/verify", methods = ["POST", "GET"])
|
||||||
|
@set_api_version(api_version = "1.0.0")
|
||||||
|
@read_input(sanitize_headers = True, sanitize_data = True)
|
||||||
|
@log_request_to_mongo(
|
||||||
|
attr_name = "logs_mongo",
|
||||||
|
project = constants.PROJECT_NAME,
|
||||||
|
log_type = "timedOTP",
|
||||||
|
operation = "verify",
|
||||||
|
log_input = True,
|
||||||
|
log_output = True,
|
||||||
|
sensitive_keys = None
|
||||||
|
)
|
||||||
|
@log_chain_to_mongo(attr_name = "logs_mongo")
|
||||||
|
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
|
||||||
|
# @only_whitelisted_ips(attr_name = "whitelisted_ips")
|
||||||
|
@validate_input(data_validator = lambda x: VerifyTimedOTP(**x))
|
||||||
|
# @limit_rate(
|
||||||
|
# attr_name = "otp_redis",
|
||||||
|
# rate_limit = 1,
|
||||||
|
# seconds = 2,
|
||||||
|
# data_keys = ["id"],
|
||||||
|
# allow_if_exception = False,
|
||||||
|
# count_for_http_codes = [200]
|
||||||
|
# )
|
||||||
|
@handle_cancelled_request()
|
||||||
|
async def verify_otp(
|
||||||
|
inbound_headers: dict = None,
|
||||||
|
inbound_data: dict | VerifyTimedOTP = None,
|
||||||
|
inbound_files: dict = None,
|
||||||
|
log_id: str = None,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Verifies the claimed OTP against the stored OTP.
|
||||||
|
:param inbound_headers: auto-extracted by the decorators from 'async_quart.py'.
|
||||||
|
:param inbound_data: auto-extracted by the decorators from 'async_quart.py'.
|
||||||
|
:param inbound_files: auto-extracted by the decorators from 'async_quart.py'.
|
||||||
|
:param log_id: An identifier for the logs (if logging is enabled).
|
||||||
|
:return: A standard response structure from the function in 'async_quart.py'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Start by assuming failure:
|
||||||
|
is_valid = False
|
||||||
|
attempts_left = None
|
||||||
|
|
||||||
|
# Fetch the OTP from Redis:
|
||||||
|
otp_key = current_app.otp_redis.make_key(str(inbound_data.id))
|
||||||
|
stored_otp = await current_app.otp_redis.get(key = otp_key)
|
||||||
|
|
||||||
|
# Test the validity of the OTP:
|
||||||
|
if stored_otp is not None:
|
||||||
|
|
||||||
|
# Delete the record from the cache.:
|
||||||
|
await current_app.otp_redis.delete(key = otp_key)
|
||||||
|
|
||||||
|
# Make note of the attempts left.
|
||||||
|
# We reduce the count by one straightaway:
|
||||||
|
attempts_left = stored_otp["att"] - 1
|
||||||
|
|
||||||
|
# If the stored OTP matches the claimed OTP,
|
||||||
|
# we note down the acceptance and delete the record from the cache:
|
||||||
|
if inbound_data.otp == stored_otp["otp"]:
|
||||||
|
is_valid = True
|
||||||
|
attempts_left = 0
|
||||||
|
|
||||||
|
# If the stored OTP and the claimed OTP don't match,
|
||||||
|
# we reduce the attempt count and :
|
||||||
|
elif attempts_left > 0:
|
||||||
|
|
||||||
|
# Update the OTP info in the cache:
|
||||||
|
otp_updated = await current_app.otp_redis.set(
|
||||||
|
key = otp_key,
|
||||||
|
value = {
|
||||||
|
"otp": stored_otp["otp"],
|
||||||
|
"att": attempts_left,
|
||||||
|
"iat": stored_otp["iat"],
|
||||||
|
"sec": stored_otp["sec"]
|
||||||
|
},
|
||||||
|
expiry = (stored_otp["iat"] + stored_otp["sec"]) - date_time.get_current_utc_date_time().timestamp()
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the update failed:
|
||||||
|
if not otp_updated: attempts_left = None
|
||||||
|
|
||||||
|
# Done here:
|
||||||
|
return ResponseModel(
|
||||||
|
status_code = StatusCodes.OK if is_valid else StatusCodes.FAILED,
|
||||||
|
data = {
|
||||||
|
"isValid": is_valid,
|
||||||
|
"attemptsLeft": attempts_left
|
||||||
|
},
|
||||||
|
http_code = HttpCodes.UNAUTHORIZED
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** MAIN PROGRAM ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
pass
|
||||||
+44
-1
@@ -53,6 +53,7 @@ from utils_v2.string import json
|
|||||||
from utils_v2.api import async_quart
|
from utils_v2.api import async_quart
|
||||||
from utils_v2.date_time import date_time
|
from utils_v2.date_time import date_time
|
||||||
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
from utils_v2.database.async_mongo_v2 import AsyncMongo
|
||||||
|
from utils_v2.cache.async_redis_cache_v3 import AsyncRedisCache
|
||||||
from utils_v2.api.async_quart import (
|
from utils_v2.api.async_quart import (
|
||||||
set_api_version,
|
set_api_version,
|
||||||
read_input,
|
read_input,
|
||||||
@@ -73,6 +74,7 @@ from controllers.servers.server import CoreServerController
|
|||||||
# All the blueprints:
|
# All the blueprints:
|
||||||
from api_v2.blueprints.cred_and_data.blueprint import cred_and_data_bp
|
from api_v2.blueprints.cred_and_data.blueprint import cred_and_data_bp
|
||||||
from api_v2.blueprints.logs.blueprint import logs_bp
|
from api_v2.blueprints.logs.blueprint import logs_bp
|
||||||
|
from api_v2.blueprints.otp.blueprint import otp_bp
|
||||||
from api_v2.blueprints.servers.blueprint import servers_bp
|
from api_v2.blueprints.servers.blueprint import servers_bp
|
||||||
|
|
||||||
|
|
||||||
@@ -100,6 +102,7 @@ app = Quart(__name__)
|
|||||||
app = cors(app)
|
app = cors(app)
|
||||||
app.register_blueprint(cred_and_data_bp, url_prefix = f"/{MODULE_BASE}")
|
app.register_blueprint(cred_and_data_bp, url_prefix = f"/{MODULE_BASE}")
|
||||||
app.register_blueprint(logs_bp, url_prefix = f"/{MODULE_BASE}/logs")
|
app.register_blueprint(logs_bp, url_prefix = f"/{MODULE_BASE}/logs")
|
||||||
|
app.register_blueprint(otp_bp, url_prefix = f"/{MODULE_BASE}/otp")
|
||||||
app.register_blueprint(servers_bp, url_prefix = f"/{MODULE_BASE}/servers")
|
app.register_blueprint(servers_bp, url_prefix = f"/{MODULE_BASE}/servers")
|
||||||
|
|
||||||
|
|
||||||
@@ -136,9 +139,21 @@ async def app_startup(**kwargs):
|
|||||||
# Safe-halt mechanism for upgrades (for a single-worker run):
|
# Safe-halt mechanism for upgrades (for a single-worker run):
|
||||||
current_app.is_under_maintenance = False
|
current_app.is_under_maintenance = False
|
||||||
|
|
||||||
|
# ┳┓ ┓ •
|
||||||
|
# ┃┃┏┓┣┓┓┏┏┓┏┓┓┏┓┏┓
|
||||||
|
# ┻┛┗ ┗┛┗┻┗┫┗┫┗┛┗┗┫
|
||||||
|
# ┛ ┛ ┛
|
||||||
|
|
||||||
# Debugging:
|
# Debugging:
|
||||||
|
enable_debugging = True if os.environ["DEBUG"].strip().lower() == "true" else False
|
||||||
current_app.printer = IceCreamDebugger(prefix = f"{MODULE_BASE} (Q) | ", includeContext = True)
|
current_app.printer = IceCreamDebugger(prefix = f"{MODULE_BASE} (Q) | ", includeContext = True)
|
||||||
if os.environ["DEBUG"] == "True": current_app.printer.disable()
|
if not enable_debugging: current_app.printer.disable()
|
||||||
|
current_app.printer("initializing worker.")
|
||||||
|
|
||||||
|
# ┳┳┓ ┳┓┳┓
|
||||||
|
# ┃┃┃┏┓┏┓┏┓┏┓┃┃┣┫
|
||||||
|
# ┛ ┗┗┛┛┗┗┫┗┛┻┛┻┛
|
||||||
|
# ┛
|
||||||
|
|
||||||
# To connect to Mongo:
|
# To connect to Mongo:
|
||||||
current_app.mongo = AsyncMongo(
|
current_app.mongo = AsyncMongo(
|
||||||
@@ -150,19 +165,47 @@ async def app_startup(**kwargs):
|
|||||||
)
|
)
|
||||||
await current_app.mongo.connect()
|
await current_app.mongo.connect()
|
||||||
|
|
||||||
|
current_app.logs_mongo = current_app.mongo
|
||||||
|
|
||||||
|
current_app.printer("MongoDB ready.")
|
||||||
|
|
||||||
|
# ┏┓ ┓ ┓ ┳┓
|
||||||
|
# ┃ ┏┓┏┓┏┫ ┏┓┏┓┏┫ ┃┃┏┓╋┏┓
|
||||||
|
# ┗┛┛ ┗ ┗┻ ┗┻┛┗┗┻ ┻┛┗┻┗┗┻
|
||||||
|
|
||||||
# Get the script data:
|
# Get the script data:
|
||||||
script_id = os.environ["SCRIPT_ID"]
|
script_id = os.environ["SCRIPT_ID"]
|
||||||
|
script_cred = (await current_app.mongo.find_one(
|
||||||
|
collection = "_scriptCred",
|
||||||
|
filter = {"scriptId": script_id}
|
||||||
|
))["content"]
|
||||||
current_app.script_data = (await current_app.mongo.find_one(
|
current_app.script_data = (await current_app.mongo.find_one(
|
||||||
collection = "_scriptData",
|
collection = "_scriptData",
|
||||||
filter = {"scriptId": script_id}
|
filter = {"scriptId": script_id}
|
||||||
))["content"]
|
))["content"]
|
||||||
|
|
||||||
|
current_app.printer("Cred and Data ready.")
|
||||||
|
|
||||||
|
# ┳┓ ┓• ┏┓ ┓
|
||||||
|
# ┣┫┏┓┏┫┓┏ ┃ ┏┓┏┣┓┏┓
|
||||||
|
# ┛┗┗ ┗┻┗┛ ┗┛┗┻┗┛┗┗
|
||||||
|
|
||||||
|
current_app.otp_redis = AsyncRedisCache(
|
||||||
|
connection_string = script_cred["redisCache"]["general"]["sentinelJson"],
|
||||||
|
debug = enable_debugging,
|
||||||
|
debug_prefix = "OTP Cache | "
|
||||||
|
)
|
||||||
|
|
||||||
|
current_app.printer("Redis ready.")
|
||||||
|
|
||||||
# ┏┓ ┓┓
|
# ┏┓ ┓┓
|
||||||
# ┃ ┏┓┏┓╋┏┓┏┓┃┃┏┓┏┓┏
|
# ┃ ┏┓┏┓╋┏┓┏┓┃┃┏┓┏┓┏
|
||||||
# ┗┛┗┛┛┗┗┛ ┗┛┗┗┗ ┛ ┛
|
# ┗┛┗┛┛┗┗┛ ┗┛┗┗┗ ┛ ┛
|
||||||
|
|
||||||
current_app.server_controller = CoreServerController()
|
current_app.server_controller = CoreServerController()
|
||||||
|
|
||||||
|
current_app.printer("Controllers ready.")
|
||||||
|
|
||||||
# ┳┳┓•
|
# ┳┳┓•
|
||||||
# ┃┃┃┓┏┏
|
# ┃┃┃┓┏┏
|
||||||
# ┛ ┗┗┛┗•
|
# ┛ ┗┗┛┗•
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
"""
|
||||||
|
|
||||||
|
AUTHOR:
|
||||||
|
|
||||||
|
Khushal P Soonderji
|
||||||
|
|
||||||
|
DATE:
|
||||||
|
|
||||||
|
Tuesday, 10th Sept., 2024.
|
||||||
|
|
||||||
|
OBJECTIVE:
|
||||||
|
|
||||||
|
To provide a data structure for the JSON received in the API calls to generate and verify timed OTPs.
|
||||||
|
|
||||||
|
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 models:
|
||||||
|
from pydantic import BaseModel, Field, field_validator, Extra
|
||||||
|
from typing import Optional, Any, Dict, List
|
||||||
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
# My utils:
|
||||||
|
from utils_v2.string import regex
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** MACROS / ONE-TIME INIT ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
# --- Nothing Yet
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** VARIABLES ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
# --- Nothing Yet
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** FUNCTIONS ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
class GenerateTimedOTP(BaseModel):
|
||||||
|
|
||||||
|
id: str | Dict | List
|
||||||
|
attempts: Optional[int] = 3
|
||||||
|
seconds: Optional[int | float] = 30.0
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra = "forbid"
|
||||||
|
|
||||||
|
def get(self, key: str, default = None):
|
||||||
|
return getattr(self, key, default)
|
||||||
|
|
||||||
|
@field_validator("attempts")
|
||||||
|
def validate_attempts(cls, value):
|
||||||
|
if not 1 <= value <= 10: raise ValueError("attempts must be at least 1 and at most 10")
|
||||||
|
return value
|
||||||
|
|
||||||
|
@field_validator("seconds")
|
||||||
|
def validate_expiry(cls, value):
|
||||||
|
if not 30 <= value <= 300: raise ValueError("expiry must be at least 30s and at most 300s")
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class VerifyTimedOTP(BaseModel):
|
||||||
|
|
||||||
|
id: str | Dict | List
|
||||||
|
otp: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra = "forbid"
|
||||||
|
|
||||||
|
def get(self, key: str, default = None):
|
||||||
|
return getattr(self, key, default)
|
||||||
|
|
||||||
|
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
# ***** ****
|
||||||
|
# *** MAIN PROGRAM ***
|
||||||
|
# ***** ****
|
||||||
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
pass
|
||||||
@@ -49,6 +49,10 @@ from utils_v2.system import files
|
|||||||
# *****************************************************************************************************************
|
# *****************************************************************************************************************
|
||||||
|
|
||||||
|
|
||||||
|
APP_VERSION = "1.0.0"
|
||||||
|
PROJECT_NAME = "internal"
|
||||||
|
MODULE_NAME = "internal"
|
||||||
|
|
||||||
# Directories:
|
# Directories:
|
||||||
PROJECT_DIRECTORY = files.get_parent_directory(files.get_file_directory(), depth = 1)
|
PROJECT_DIRECTORY = files.get_parent_directory(files.get_file_directory(), depth = 1)
|
||||||
CREDENTIALS_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "cred")
|
CREDENTIALS_DIRECTORY = os.path.join(PROJECT_DIRECTORY, "cred")
|
||||||
|
|||||||
Reference in New Issue
Block a user