(20241127) Testing GMail auth.

This commit is contained in:
2024-11-27 20:14:04 +05:30
parent 269e36ec27
commit 2e7f1cb626
9 changed files with 336 additions and 238 deletions
+40 -29
View File
@@ -62,6 +62,9 @@ from utils_v2.api.async_quart import (
# Common: # Common:
from shared import constants from shared import constants
# Behaviour Models:
from models.behaviour.mail.oauth import MailOAuthModel
# For asynchronous activities: # For asynchronous activities:
import asyncio import asyncio
@@ -105,7 +108,7 @@ def init(blueprint_setup_state):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
@mail_callback_bp.route("/callback/gmail", methods = ["POST", "GET"]) @mail_callback_bp.route("/callback/<mail_client>", methods = ["POST", "GET"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@log_request_to_mongo( @log_request_to_mongo(
@@ -121,6 +124,7 @@ def init(blueprint_setup_state):
@should_not_be_under_maintenance(attr_name = "is_under_maintenance") @should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@handle_cancelled_request() @handle_cancelled_request()
async def mail_callback( async def mail_callback(
mail_client: str = None,
inbound_headers: dict = None, inbound_headers: dict = None,
inbound_data: dict = None, inbound_data: dict = None,
inbound_files: dict = None, inbound_files: dict = None,
@@ -136,39 +140,46 @@ async def mail_callback(
:return: A standard response structure. :return: A standard response structure.
""" """
# Construct a message: # Start by assuming failure:
message = "🪝 *WEBHOOK/CALLBACK ALERT!* 🪝\n\n" tokens_saved = False
message += f"Method: *{request.method}*\nLog Id.: `{kwargs.get('log_id')}`\n\n"
message += "*Headers:*\n```json\n"
message += json.to_string({k: v for k, v in request.headers.items()})
message += "\n```\n"
message += "*Query Args:*\n```json\n"
message += json.to_string(request.args.to_dict())
message += "\n```\n"
message += "*JSON:*\n```json\n"
message += json.to_string(await request.get_json())
message += "\n```\n"
message += "*Form-Data:*\n```json\n"
message += json.to_string((await request.form).to_dict())
message += "\n```\n"
message += "*Form-Files:*\n```json\n"
message += json.to_string(inbound_files, default = str)
message += "\n```\n"
# Send a message on Telegram: # ┏┓ ┏┓┳┳┓ •┓
api_response = await current_app.http_client.post( # ┣ ┏┓┏┓ ┃┓┃┃┃┏┓┓┃
url = current_app.script_data["alerts"]["url"], # ┻ ┗┛┛ ┗┛┛ ┗┗┻┗┗
json = {
"message": message, if mail_client == "gmail":
"type": "info",
"chatId": "1275560043" # ... KPS # Generate the tokens from the callback:
} tokens = await current_app.gmail_client.get_authorization_tokens(
redirect_url = request.url
) )
if tokens:
# Add information and :
user_profile = await current_app.gmail_client.get_user_profile(tokens = tokens)
tokens.email = user_profile.data["emailAddress"] if user_profile.success else None
# Save the tokens to the database
tokens_saved = await MailOAuthModel.set_token(
db_conn = current_app.data_mongo,
user_identifier = inbound_data["state"],
token = tokens.model_dump()
)
# ┳┓
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
# ┛
# Return a success response: # Return a success response:
return ResponseModel( return ResponseModel(
status_code = StatusCodes.OK, status_code = StatusCodes.OK if tokens_saved else StatusCodes.FAILED,
data = {"accepted": True} http_code = HttpCodes.SUCCESS if tokens_saved else HttpCodes.INTERNAL_SERVER_ERROR,
data = {
"mailClient": mail_client,
"authorized": True
}
) )
+89 -40
View File
@@ -6,11 +6,12 @@
DATE: DATE:
Monday, 25th Nov., 2024 Wednesday, 27th Nov., 2024
OBJECTIVE: OBJECTIVE:
To receive callbacks (webhooks). To receive authorization requests (OAuth2.0) for various mail providers and accordingly respond with the
authorization request URLs.
REFERENCES: REFERENCES:
@@ -62,6 +63,15 @@ from utils_v2.api.async_quart import (
# Common: # Common:
from shared import constants from shared import constants
# Behaviour Models:
from models.behaviour.mail.oauth import MailOAuthModel
# Data Models:
from models.data.mail.oauth import (
OAuthMailAuthorizationRequestHeaders,
OAuthMailAuthorizationRequestData
)
# For asynchronous activities: # For asynchronous activities:
import asyncio import asyncio
@@ -74,7 +84,7 @@ import asyncio
# Related to Quart: # Related to Quart:
mail_callback_bp = Blueprint("mail_cb", __name__) mail_oauth_bp = Blueprint("mail_oauth", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -94,7 +104,7 @@ mail_callback_bp = Blueprint("mail_cb", __name__)
# ***************************************************************************************************************** # *****************************************************************************************************************
@mail_callback_bp.record_once @mail_oauth_bp.record_once
def init(blueprint_setup_state): def init(blueprint_setup_state):
# This gets called when the blueprint is registered. # This gets called when the blueprint is registered.
@@ -105,30 +115,38 @@ def init(blueprint_setup_state):
# --------------------------------------------------------------------------------------------------------------------- # ---------------------------------------------------------------------------------------------------------------------
@mail_callback_bp.route("/callback/gmail", methods = ["POST", "GET"]) @mail_oauth_bp.route("/auth/oauth/url/request", methods = ["GET"])
@set_api_version(api_version = "1.0.0") @set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False) @read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
@log_request_to_mongo( @log_request_to_mongo(
attr_name = "logs_mongo", attr_name = "logs_mongo",
project = constants.PROJECT_NAME, project = constants.PROJECT_NAME,
log_type = constants.MODULE_NAME, log_type = constants.MODULE_NAME,
operation = "gmailCllBckApi", operation = "mailOAuthUrlReqApi",
log_input = True, log_input = True,
log_output = True, log_output = True,
sensitive_keys = None sensitive_keys = ["sessionToken"]
) )
@log_chain_to_mongo(attr_name = "logs_mongo") @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")
@validate_input(
header_validator = lambda x: OAuthMailAuthorizationRequestHeaders(**x).model_dump(),
data_validator = lambda x: OAuthMailAuthorizationRequestData(**x)
)
@handle_cancelled_request() @handle_cancelled_request()
async def mail_callback( async def request_oauth_authorization_url(
inbound_headers: dict = None, inbound_headers: dict | OAuthMailAuthorizationRequestHeaders = None,
inbound_data: dict = None, inbound_data: dict | OAuthMailAuthorizationRequestData = None,
inbound_files: dict = None, inbound_files: dict = None,
**kwargs **kwargs
): ):
""" """
Use this when authorizing access to someone's GMail account. This can be used to capture the authentication token. Use this when requesting access to someone's GMail account. This API should be used from the UI. A button click
"Connect to GMail" should hit this API, which will generate a request to gain access to the user's GMail account.
When the URL is hit, it opens Google's own UI, and, when the user clicks "Continue", Google hits your 'redirect_url'
to inform you about the user's action.
:param inbound_headers: auto-extracted by the decorators. :param inbound_headers: auto-extracted by the decorators.
:param inbound_data: auto-extracted by the decorators. :param inbound_data: auto-extracted by the decorators.
:param inbound_files: auto-extracted by the decorators. :param inbound_files: auto-extracted by the decorators.
@@ -136,39 +154,70 @@ async def mail_callback(
:return: A standard response structure. :return: A standard response structure.
""" """
# Construct a message: # ┏┓
message = "🪝 *WEBHOOK/CALLBACK ALERT!* 🪝\n\n" # ┃┃┏┓┏┓┏┓┏┓┏┓┏┏┓┏┏
message += f"Method: *{request.method}*\nLog Id.: `{kwargs.get('log_id')}`\n\n" # ┣┛┛ ┗ ┣┛┛ ┗┛┗┗ ┛┛
message += "*Headers:*\n```json\n" # ┛
message += json.to_string({k: v for k, v in request.headers.items()})
message += "\n```\n"
message += "*Query Args:*\n```json\n"
message += json.to_string(request.args.to_dict())
message += "\n```\n"
message += "*JSON:*\n```json\n"
message += json.to_string(await request.get_json())
message += "\n```\n"
message += "*Form-Data:*\n```json\n"
message += json.to_string((await request.form).to_dict())
message += "\n```\n"
message += "*Form-Files:*\n```json\n"
message += json.to_string(inbound_files, default = str)
message += "\n```\n"
# Send a message on Telegram: # If the session token is invalid/expired:
api_response = await current_app.http_client.post( if kwargs.get("session_info") is None:
url = current_app.script_data["alerts"]["url"], return ResponseModel(
json = { status_code = StatusCodes.FAILED,
"message": message, http_code = HttpCodes.UNAUTHORIZED
"type": "info",
"chatId": "1275560043" # ... KPS
}
) )
# Return a success response: # Start by assuming failure:
auth_url = None
# ┳ ┓ •┏ ┳┳
# ┃┏┫┏┓┏┓╋┓╋┓┏ ┃┃┏┏┓┏┓
# ┻┗┻┗ ┛┗┗┗┛┗┫ ┗┛┛┗ ┛
# ┛
# Make a user identifier from the session info:
user_identifier = await MailOAuthModel.get_id(
db_conn = current_app.data_mongo,
user_info = kwargs["session_info"],
service_type = "email",
service_client = inbound_data.mailClient,
auth_type = "oauth"
)
if user_identifier is None:
return ResponseModel( return ResponseModel(
status_code = StatusCodes.OK, status_code = StatusCodes.FAILED,
data = {"accepted": True} http_code = HttpCodes.INTERNAL_SERVER_ERROR,
message = "failed to generate user identifier"
)
user_identifier = str(user_identifier)
# ┏┓ ┏┓┳┳┓ •┓
# ┣ ┏┓┏┓ ┃┓┃┃┃┏┓┓┃
# ┻ ┗┛┛ ┗┛┛ ┗┗┻┗┗
if inbound_data.mailClient == "gmail":
# Get the authorization URL:
auth_url = await current_app.gmail_client.get_authorization_url(
state = user_identifier,
access_type = "offline",
approval_prompt = "force",
include_granted_scopes = "true",
user_email = None
)
# ┳┓
# ┣┫┏┓┏┏┓┏┓┏┓┏┏┓
# ┛┗┗ ┛┣┛┗┛┛┗┛┗
# ┛
# Done here:
return ResponseModel(
status_code = StatusCodes.OK if auth_url else StatusCodes.FAILED,
http_code = HttpCodes.SUCCESS if auth_url else HttpCodes.INTERNAL_SERVER_ERROR,
data = {
"mailClient": inbound_data.mailClient,
"authorizationUrl": auth_url
}
) )
+14 -14
View File
@@ -6,7 +6,7 @@
DATE: DATE:
Tuesday, 12th Nov., 2024 Wednesday, 27th Nov., 2024
OBJECTIVE: OBJECTIVE:
@@ -37,11 +37,6 @@ sys.path.append("..")
# To use Quart: # To use Quart:
from quart import current_app from quart import current_app
# The data models:
from models.data.user import (
IsSessionRequest
)
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
@@ -88,14 +83,19 @@ async def get_session(session_token):
:return: The info or None. :return: The info or None.
""" """
session_info = await current_app.user.is_session( try:
db_conn = current_app.sql_reader, raw_info = await current_app.module_cache.get(key = session_token)
request = IsSessionRequest(sessionToken = session_token), session_info = {
cache = current_app.module_cache, "fullName": raw_info["value"]["full_name"],
) "entityId": raw_info["value"]["entity_id"],
"billingAccountId": raw_info["value"]["billing_account_id"],
if isinstance(session_info, dict): return session_info.get("data") "departmentId": raw_info["value"]["department_id"],
else: return None "branchId": raw_info["value"]["branch_id"],
"industry": raw_info["value"]["industry"]
}
return session_info
except Exception as exception:
return None
# ***************************************************************************************************************** # *****************************************************************************************************************
+40 -2
View File
@@ -54,6 +54,7 @@ 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_v2 import AsyncRedisCache from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache
from utils_v2.serialization.json_serializer import JSONSerializer
from utils_v2.api.async_quart import ( from utils_v2.api.async_quart import (
set_api_version, set_api_version,
read_input, read_input,
@@ -65,6 +66,9 @@ from utils_v2.api.async_quart import (
handle_cancelled_request handle_cancelled_request
) )
# GMail-related utils:
from utils_v2.goog.gmail.gmail_client import AsyncGMailClient, SCOPES_GMAIL_MAIL_MANAGEMENT
# To make REST API calls: # To make REST API calls:
import httpx import httpx
@@ -72,9 +76,13 @@ import httpx
from icecream import IceCreamDebugger from icecream import IceCreamDebugger
# All the blueprints: # All the blueprints:
from api.blueprints.mail.oauth import mail_oauth_bp
from api.blueprints.mail.callback import mail_callback_bp from api.blueprints.mail.callback import mail_callback_bp
from api.blueprints.test.callback import test_callback_bp from api.blueprints.test.callback import test_callback_bp
# All the helpers:
from api.helpers.user import session
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
@@ -98,6 +106,7 @@ APP_VERSION = constants.APP_VERSION
# The Quart app: # The Quart app:
app = Quart(__name__) app = Quart(__name__)
app = cors(app) app = cors(app)
app.register_blueprint(mail_oauth_bp, url_prefix = f"/{MODULE_BASE}/mail")
app.register_blueprint(mail_callback_bp, url_prefix = f"/{MODULE_BASE}/mail") app.register_blueprint(mail_callback_bp, url_prefix = f"/{MODULE_BASE}/mail")
app.register_blueprint(test_callback_bp, url_prefix = f"/{MODULE_BASE}/test") app.register_blueprint(test_callback_bp, url_prefix = f"/{MODULE_BASE}/test")
@@ -163,9 +172,15 @@ async def app_startup(**kwargs):
# Make an instance of an HTTP client to use to make API calls: # Make an instance of an HTTP client to use to make API calls:
current_app.http_client = httpx.AsyncClient( current_app.http_client = httpx.AsyncClient(
limits = httpx.Limits(
max_connections = 100, # ............ Maximum number of connections allowed in the pool.
max_keepalive_connections = 50, # ... Maximum number of connections that can be kept alive.
),
timeout = httpx.Timeout( timeout = httpx.Timeout(
10.0, connect = 2.5, # ... Shorter connection timeout.
read = 5.0 read = 2.5, # ...... Like what EasyEcom gives.
write = 10.0, # .... Time to wait for sending data.
pool = 120.0 # ..... Time to wait for a free connection from the pool.
) )
) )
@@ -194,6 +209,7 @@ async def app_startup(**kwargs):
) )
current_app.module_cache = AsyncRedisCache( current_app.module_cache = AsyncRedisCache(
connection_string = script_cred["redisCache"]["funcReturn"]["connectionString"], connection_string = script_cred["redisCache"]["funcReturn"]["connectionString"],
serializer = JSONSerializer(),
debug = enable_debugging, debug = enable_debugging,
debug_prefix = "User Cache | " debug_prefix = "User Cache | "
) )
@@ -206,10 +222,32 @@ async def app_startup(**kwargs):
debug = enable_debugging debug = enable_debugging
) )
await current_app.logs_mongo.connect() await current_app.logs_mongo.connect()
current_app.data_mongo = AsyncMongo(
connection_string = script_cred["mongoDb"]["data"]["connectionString"],
database_name = script_cred["mongoDb"]["data"]["dbName"],
max_connections = script_cred["mongoDb"]["data"]["poolSize"],
debug = enable_debugging
)
await current_app.data_mongo.connect()
# Create an instance to handle GMail-related activities:
current_app.gmail_client = AsyncGMailClient(
service_name = "gmail",
oauth_json = script_cred["google"]["oauth"]["tcaoff"],
http_client = current_app.http_client,
redirect_url = r"https://api.thecaoffice.com/converse/mail/callback/gmail",
scopes = SCOPES_GMAIL_MAIL_MANAGEMENT,
debug = True,
debug_prefix = "GMail (M) | ",
debug_only_errors = False
)
# Pick the important stuff: # Pick the important stuff:
current_app.whitelisted_ips = current_app.script_data["whitelistedIps"] current_app.whitelisted_ips = current_app.script_data["whitelistedIps"]
# Register helpers:
current_app.get_session = session.get_session
# Remove unwanted/sensitive variables from RAM: # Remove unwanted/sensitive variables from RAM:
del script_cred del script_cred
gc.collect() gc.collect()
@@ -0,0 +1,10 @@
{
"accessToken": "ya29.a0AeDClZAYoo85BXRId_n-hwo_amKshzi46c33GaJcsZZvGB7A7OGU2RFYcWBM_BleNBfAFUSJP2NHAvmd7Nsp_U5Kg68hXSy0iO99PNTm3pvKrJSzbkA-rXsVLsCnBIfPUMyNt2nOOVJmGwm17DNN0jAELkm1fPNTju7SZzmuaCgYKAZwSARMSFQHGX2Mis8TZui2rZT1gKySVds-N0w0175",
"refreshToken": "1//0gnqzjMf9YT19CgYIARAAGBASNgF-L9Ir3rcY37nGrV45XyOUBRllEH7Txui7T1JbwevlmDoNw7PuMu149cCWQSwsScuKaZusUQ",
"expiresIn": 3539,
"expiresAt": "2024-11-25 10:40:40.833699+00:00",
"scopes": [
"https://www.googleapis.com/auth/gmail.labels",
"https://www.googleapis.com/auth/gmail.modify"
]
}
+105 -33
View File
@@ -6,12 +6,12 @@
DATE: DATE:
Thursday, 24th Oct., 2024 Wednesday, 27th Nov., 2024
OBJECTIVE: OBJECTIVE:
To define the interaction between the UI layer and the database connectivity in one place. Here we will handle To define the interaction between the UI layer and the database connectivity in one place. Here we shall handle
all user-related interactions. all the activities for OAuth2.0 authorization requests for all the users of our service.
REFERENCES: REFERENCES:
@@ -35,18 +35,19 @@ import sys
sys.path.append(".") sys.path.append(".")
sys.path.append("..") sys.path.append("..")
# The base model:
from models.behaviour.base import BaseModel
# My async utils: # My async utils:
from utils_v2.string import json from utils_v2.string import json
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache from utils_v2.date_time import date_time
from utils_v2.database.async_mysql_v2 import AsyncMySQL from utils_v2.database.async_mongo_v2 import AsyncMongo
# The data models: # To work with MongoDB:
from models.data.user.user import ( from bson import ObjectId
IsSessionRequest
) # To work with datatypes:
from typing import Literal
# To make deep-copies:
import copy
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -86,35 +87,106 @@ from models.data.user.user import (
# ***************************************************************************************************************** # *****************************************************************************************************************
class UserModel(BaseModel): class MailOAuthModel:
async def is_session( AUTH_COLLECTION = "_authTokens"
self,
db_conn: AsyncMySQL, def __init__(self):
request: IsSessionRequest, pass
cache: AsyncRedisCache,
cache_expiry: int = 3_600 @staticmethod
): async def get_id(
db_conn: AsyncMongo,
user_info: dict,
service_type: Literal["email", "chat"],
service_client: Literal["gmail"],
auth_type: Literal["oauth"]
) -> ObjectId:
""" """
Fetches information about the current user from his session. Stores params from the session info and gives an identifier to use in the authorization URL. Use this when the
user requests an authorization URL to link your service to another service (like GMail).
:param db_conn: The database connection to use to perform the action. :param db_conn: The database connection to use to perform the action.
:param request: The instance of the data model that defines the structure of the request. :param user_info: The dictionary that has the user's session information.
:param cache: The caching object to use to set the session in cache memory. :param service_type: The type of service being provided.
:param cache_expiry: The no. of seconds after which this information will be deleted from the cache. :param service_client: The name of the company or brand that is providing this service that is being integrated.
:return: The raw response from the database call (SQL). :param auth_type: To identify the type of authentication being done here. This could indicate simple password
authentication, more advance OAuth2.0 authentication, etc.
:return: An ObjectId to later store the granted tokens.
""" """
return await self.call_cached_procedure( # Note down the timestamp at which this event occurred:
cache = cache, request_ts = date_time.get_current_utc_date_time(as_string = False)
cache_key = "usr_is_" + str(request.sessionToken),
cache_expiry = cache_expiry, # Get the identifier from the database:
db_conn = db_conn, db_json = await db_conn.find_one_and_update(
proc_name = "isSession", collection = MailOAuthModel.AUTH_COLLECTION,
proc_args = (request.sessionToken,), filter = {
session_token = request.sessionToken "serviceType": service_type,
"client": service_client,
"authType": auth_type,
"user": user_info,
},
update = {
"$set": {
"lastRequestTs": request_ts
},
"$setOnInsert": {
"version": "1.0.0",
"serviceType": service_type,
"client": service_client,
"authType": auth_type,
"user": user_info,
"token": None,
"firstRefreshTs": None,
"lastRefreshTs": None,
"firstRequestTs": request_ts,
}
},
projection = {
"_id": True
},
upsert = True,
return_updated = True
) )
# Done here:
return db_json["_id"] if db_json else None
@staticmethod
async def set_token(
db_conn: AsyncMongo,
user_identifier: ObjectId | str,
token: dict
) -> bool:
"""
This method is to be called when the end user authorizes your service to connect to his third-party account. For
example, when the end user allows you to access his GMail account.
:param db_conn: The database connection to use to perform the action.
:param user_identifier: The identifier granted by the 'get_id' method.
:param token: The token granted by the third-party service.
:return:
"""
# Note down the timestamp at which this event occurred:
request_ts = date_time.get_current_utc_date_time(as_string = False)
# Save the token to the database:
token_saved = await db_conn.update_one(
collection = MailOAuthModel.AUTH_COLLECTION,
filter = {"_id": ObjectId(user_identifier)},
update = {
"$set": {
"token": token,
"firstRefreshTs": request_ts,
}
}
)
# Done here:
return token_saved
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
+37 -12
View File
@@ -51,7 +51,8 @@ from utils_v2.string import regex
# ***************************************************************************************************************** # *****************************************************************************************************************
# --- Nothing Yet # 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}$"
# ***************************************************************************************************************** # *****************************************************************************************************************
@@ -71,20 +72,35 @@ from utils_v2.string import regex
# ***************************************************************************************************************** # *****************************************************************************************************************
class LoginRequest(BaseModel): class OAuthMailAuthorizationRequestHeaders(BaseModel):
username: str = Field(description = "the username of the user") sessionToken: str = Field(
description = "the session token of the user who is requesting the service",
password: str = Field(description = "the password of the user") pattern = REGEX_SESSION_TOKEN,
frozen = True,
mode: Optional[str] = Field( alias = "X-Session-Token"
description = "the mode through which this request came in",
default = "N/A"
) )
remoteIp: Optional[str] = Field( # ┏┓ ┏•
description = "the ip addr of the client", # ┃ ┏┓┏┓╋┓┏┓
default = "N/A" # ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "allow"
def model_dump(self, *args, **kwargs):
return super().model_dump(*args, by_alias = True, **kwargs)
# ---------------------------------------------------------------------------------------------------------------------
class OAuthMailAuthorizationRequestData(BaseModel):
mailClient: Literal["gmail"] = Field(
description = "the e-mail provider like 'gmail'",
frozen = True
) )
# ┏┓ ┏• # ┏┓ ┏•
@@ -95,6 +111,15 @@ class LoginRequest(BaseModel):
class Config: class Config:
extra = "forbid" extra = "forbid"
# ┓┏ ┓• ┓ •
# ┃┃┏┓┃┓┏┫┏┓╋┓┏┓┏┓
# ┗┛┗┻┗┗┗┻┗┻┗┗┗┛┛┗
@field_validator("mailClient", mode = "before")
def to_lowercase(cls, value):
if isinstance(value, str): value = value.strip().lower()
return value
# ***************************************************************************************************************** # *****************************************************************************************************************
# ***** **** # ***** ****
View File
-107
View File
@@ -1,107 +0,0 @@
"""
AUTHOR:
Khushal P Soonderji
DATE:
Friday, 22nd Nov., 2024.
OBJECTIVE:
To provide the data model for the structure of each message.
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
from typing import Optional, Literal
# My utils:
from utils_v2.string import regex
# *****************************************************************************************************************
# ***** ****
# *** MACROS / ONE-TIME INIT ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** VARIABLES ***
# ***** ****
# *****************************************************************************************************************
# --- Nothing Yet
# *****************************************************************************************************************
# ***** ****
# *** FUNCTIONS ***
# ***** ****
# *****************************************************************************************************************
class LoginRequest(BaseModel):
username: str = Field(description = "the username of the user")
password: str = Field(description = "the password of the user")
mode: Optional[str] = Field(
description = "the mode through which this request came in",
default = "N/A"
)
remoteIp: Optional[str] = Field(
description = "the ip addr of the client",
default = "N/A"
)
# ┏┓ ┏•
# ┃ ┏┓┏┓╋┓┏┓
# ┗┛┗┛┛┗┛┗┗┫
# ┛
class Config:
extra = "forbid"
# *****************************************************************************************************************
# ***** ****
# *** MAIN PROGRAM ***
# ***** ****
# *****************************************************************************************************************
if __name__ == "__main__":
pass