diff --git a/api/blueprints/finstitutions/payments/list.py b/api/blueprints/finstitutions/payments/list.py index de3f187..056728c 100644 --- a/api/blueprints/finstitutions/payments/list.py +++ b/api/blueprints/finstitutions/payments/list.py @@ -166,7 +166,7 @@ async def payment_list( # ┗┛┛┗┗┗┛┗ ┛ ┗┗┻┗┗┛ # Get the token ids from the token keys: - auth_tokens = await current_app.payment_controller.get_tokens( + auth_tokens = await current_app.payment_controller.get_tokens_from_keys( mongo_conn = current_app.data_mongo, token_keys = inbound_data.tokenKeys ) diff --git a/api/blueprints/finstitutions/payments/request.py b/api/blueprints/finstitutions/payments/request.py index f3e8c21..1263750 100644 --- a/api/blueprints/finstitutions/payments/request.py +++ b/api/blueprints/finstitutions/payments/request.py @@ -167,7 +167,7 @@ async def request_payment( # ┗ ┛ # Get the token from the token key: - auth_token = await current_app.payment_controller.get_token( + auth_token = await current_app.payment_controller.get_token_from_key( mongo_conn = current_app.data_mongo, token_key = inbound_data.tokenKey ) diff --git a/api/blueprints/mail/oauth/callback.py b/api/blueprints/mail/oauth/callback.py index fd403b1..cd92690 100644 --- a/api/blueprints/mail/oauth/callback.py +++ b/api/blueprints/mail/oauth/callback.py @@ -159,7 +159,7 @@ async def handle_gmail_callback() -> render_template: # Here's where we do the checking of the e-mails, # if they don't match, we reject the authorization: - auth_token = await current_app.mail_controller.get_token( + auth_token = await current_app.mail_controller.get_token_from_key( mongo_conn = current_app.data_mongo, token_key = g.inbound_data["state"] ) diff --git a/api/blueprints/mail/retrieve/get.py b/api/blueprints/mail/retrieve/get.py index a3d8722..ec1b46e 100644 --- a/api/blueprints/mail/retrieve/get.py +++ b/api/blueprints/mail/retrieve/get.py @@ -70,6 +70,7 @@ from shared import constants # Data Models: from models.api.mail.get import MailGetRequestHeaders, MailGetRequestData +from models.core.user import CoreUserInfoModel # To work with datatypes: from typing import Literal @@ -80,6 +81,9 @@ import asyncio # To work with date and time: import datetime +# Helpers: +from api.helpers.user import token_check + # ***************************************************************************************************************** # ***** **** @@ -165,25 +169,7 @@ async def get_one_mail( return ResponseModel( status_code = StatusCodes.FAILED, http_code = HttpCodes.UNAUTHORIZED, - messge = "invalid session" - ) - - # ┏┓ ┓ ┏┳┓ ┓ - # ┣ ┏┓╋┏┣┓ ┃ ┏┓┃┏┏┓┏┓┏ - # ┻ ┗ ┗┗┛┗ ┻ ┗┛┛┗┗ ┛┗┛ - - # We first load the authorization tokens: - auth_token = await current_app.mail_controller.get_token( - mongo_conn = current_app.data_mongo, - token_key = inbound_data.tokenKey, - ) - - # If we failed to load the authorization tokens: - if not auth_token: - return ResponseModel( - status_code = StatusCodes.FAILED, - http_code = HttpCodes.UNAUTHORIZED, - message = f"no such token key '{inbound_data.tokenKey}'" + message = "Invalid session." ) # ┏┓ ┓ ┳┳┓ •┓ @@ -193,10 +179,30 @@ async def get_one_mail( # Get the mail: message = await current_app.mail_controller.get_one_mail( mongo_conn = current_app.data_mongo, - token_id = auth_token.authTokenId, message_id = inbound_data.messageId ) + # ┏┓ ┓ • ┏┓┓ ┓ + # ┃┃┓┏┏┏┓┏┓┏┓┏┣┓┓┏┓ ┃ ┣┓┏┓┏┃┏ + # ┗┛┗┻┛┛┗┗ ┛ ┛┛┗┗┣┛ ┗┛┛┗┗ ┗┛┗ + # ┛ + + # We check if the token that was used to fetch the mail is owned by this user: + if not await token_check.is_authorized( + mongo_conn = current_app.data_mongo, + user_info = CoreUserInfoModel(**kwargs["session_info"]), + token_ids = [message.tokenId] + ): return ResponseModel( + status_code = StatusCodes.FAILED, + http_code = HttpCodes.UNAUTHORIZED, + message = "The message does not belong to this user." + ) + + # ┳┓ + # ┣┫┏┓┏┏┓┏┓┏┓┏┏┓ + # ┛┗┗ ┛┣┛┗┛┛┗┛┗ + # ┛ + # Done here: return ResponseModel( status_code = StatusCodes.OK if message else StatusCodes.FAILED, diff --git a/api/blueprints/mail/retrieve/list.py b/api/blueprints/mail/retrieve/list.py index 77dba10..6a890dd 100644 --- a/api/blueprints/mail/retrieve/list.py +++ b/api/blueprints/mail/retrieve/list.py @@ -180,7 +180,7 @@ async def list_mails( # ┗┛┛┗┗┗┛┗ ┛ ┗┗┻┗┗┛ # Get the token ids from the token keys: - auth_tokens = await current_app.mail_controller.get_tokens( + auth_tokens = await current_app.mail_controller.get_tokens_from_keys( mongo_conn = current_app.data_mongo, token_keys = inbound_data.tokenKeys ) diff --git a/api/blueprints/mail/tags/update.py b/api/blueprints/mail/tags/update.py index a3b382c..cf71c4e 100644 --- a/api/blueprints/mail/tags/update.py +++ b/api/blueprints/mail/tags/update.py @@ -172,12 +172,6 @@ async def update_mail_tags( # ┗┛┣┛┗┻┗┻┗┗ ┛ ┗┗┻┗┗ # ┛ - # get the token id from the token key: - auth_token = await current_app.mail_controller.get_token( - mongo_conn = current_app.data_mongo, - token_key = inbound_data.tokenKey - ) - # Update the mail: success = await current_app.mail_controller.update_tags( mongo_conn = current_app.data_mongo, diff --git a/api/blueprints/sms/send.py b/api/blueprints/sms/send.py index 9658741..9355828 100644 --- a/api/blueprints/sms/send.py +++ b/api/blueprints/sms/send.py @@ -162,7 +162,7 @@ async def send_sms( # ┗┛┗ ┛┗┗┻ ┻ ┛┗┗ ┗┛┛ ┗┗┛ # Get the token from the token key: - auth_token = await current_app.mail_controller.get_token( + auth_token = await current_app.mail_controller.get_token_from_key( mongo_conn = current_app.data_mongo, token_key = inbound_data.tokenKey ) diff --git a/api/helpers/user/token_check.py b/api/helpers/user/token_check.py index 005237f..8779be1 100644 --- a/api/helpers/user/token_check.py +++ b/api/helpers/user/token_check.py @@ -112,7 +112,7 @@ async def is_authorized( # Fetch the auth tokens from the database: # Retrieve the document of the token id: - auth_tokens = await current_app.core_auth_token_controller.get_tokens( + auth_tokens = await current_app.core_auth_token_controller.get_tokens_from_ids( mongo_conn = mongo_conn, token_ids = [ObjectId(t) for t in token_ids] ) diff --git a/controllers/api/mail.py b/controllers/api/mail.py index 5766893..ca6445e 100644 --- a/controllers/api/mail.py +++ b/controllers/api/mail.py @@ -286,7 +286,7 @@ class MailController: ) -> CoreAuthTokenModel | None: # Simply call the core model: - return await current_app.core_auth_token_controller.get_token( + return await current_app.core_auth_token_controller.get_token_from_key( mongo_conn = mongo_conn, token_key = token_key ) @@ -298,7 +298,7 @@ class MailController: ) -> List[CoreAuthTokenModel] | None: # Simply call the core model: - return await current_app.core_auth_token_controller.get_tokens( + return await current_app.core_auth_token_controller.get_tokens_from_keys( mongo_conn = mongo_conn, token_keys = token_keys ) @@ -611,14 +611,12 @@ class MailController: @staticmethod async def get_one_mail( mongo_conn: AsyncMongo, - token_id: ObjectId | str, message_id: ObjectId | str ) -> CoreMessageModel | None: # Simply call the core model: return await current_app.core_message_controller.get_message( mongo_conn = mongo_conn, - token_id = token_id, message_id = message_id ) diff --git a/controllers/api/payment.py b/controllers/api/payment.py index 19b84bf..63ec6e5 100644 --- a/controllers/api/payment.py +++ b/controllers/api/payment.py @@ -162,25 +162,25 @@ class PaymentController: return success @staticmethod - async def get_token( + async def get_token_from_key( mongo_conn: AsyncMongo, token_key: ObjectId | str = None, ) -> CoreAuthTokenModel | None: # Simply call the core model: - return await current_app.core_auth_token_controller.get_token( + return await current_app.core_auth_token_controller.get_token_from_key( mongo_conn = mongo_conn, token_key = token_key ) @staticmethod - async def get_tokens( + async def get_tokens_from_keys( mongo_conn: AsyncMongo, token_keys: ObjectId | str = None, ) -> List[CoreAuthTokenModel] | None: # Simply call the core model: - return await current_app.core_auth_token_controller.get_tokens( + return await current_app.core_auth_token_controller.get_tokens_from_keys( mongo_conn = mongo_conn, token_keys = token_keys ) @@ -223,7 +223,7 @@ class PaymentController: substitute_text = "" ) if isinstance(payment_request.customerNo, str) else payment_request.customerNo, type = "CustomerPayBillOnline", - reference = payment_request.reference, + reference = str(payment_id), description = payment_request.description, callback_url = callback_url, payer_no = regex.replace( diff --git a/controllers/api/sms.py b/controllers/api/sms.py index 9a5dc45..fc463f8 100644 --- a/controllers/api/sms.py +++ b/controllers/api/sms.py @@ -173,7 +173,7 @@ class SMSController: ) -> CoreAuthTokenModel | None: # Simply call the core model: - return await current_app.core_auth_token_controller.get_token( + return await current_app.core_auth_token_controller.get_token_from_key( mongo_conn = mongo_conn, token_key = token_key ) diff --git a/controllers/core/auth_token.py b/controllers/core/auth_token.py index 9646d6f..3c55470 100644 --- a/controllers/core/auth_token.py +++ b/controllers/core/auth_token.py @@ -270,10 +270,33 @@ class CoreAuthTokenController(BaseModel): # Done here: return token_saved - async def get_token( + async def get_token_from_id( self, mongo_conn: AsyncMongo, - token_key: ObjectId | str = None, + token_id: ObjectId | str = None, + ) -> CoreAuthTokenModel | None: + + """ + To retrieve stored tokens from the database. One token at a time. + :param mongo_conn: The database connection (MongoDB) to use to perform the action. + :param token_id: The identifier of the document that holds the token's details. + :return: The retrieved record that has the token, and information about the service and client if found, else + None when there is no matching record. + """ + + # If there is some filtering possible, we fetch the token: + token = await mongo_conn.find_one( + collection = self.AUTH_COLLECTION, + filter = {"_id": ObjectId(token_id)} + ) + + # Done here: + return CoreAuthTokenModel(**token) if token else None + + async def get_token_from_key( + self, + mongo_conn: AsyncMongo, + token_key: ObjectId | str = None ) -> CoreAuthTokenModel | None: """ @@ -287,13 +310,36 @@ class CoreAuthTokenController(BaseModel): # If there is some filtering possible, we fetch the token: token = await mongo_conn.find_one( collection = self.AUTH_COLLECTION, - filter = {"key": ObjectId(token_key)}, + filter = {"key": ObjectId(token_key)} ) # Done here: return CoreAuthTokenModel(**token) if token else None - async def get_tokens( + async def get_tokens_from_ids( + self, + mongo_conn: AsyncMongo, + token_ids: List[ObjectId | str] = None, + ) -> List[CoreAuthTokenModel]: + + """ + To retrieve stored tokens from the database. Multiple tokens at a time. + :param mongo_conn: The database connection (MongoDB) to use to perform the action. + :param token_ids: The identifier of the document that holds the token's details. + :return: The retrieved record that has the token, and information about the service and client if found, else + None when there is no matching record. + """ + + # If there is some filtering possible, we fetch the token: + tokens = await mongo_conn.find_many( + collection = self.AUTH_COLLECTION, + filter = {"_id": {"$in": [ObjectId(k) for k in token_ids]}} + ) + + # Done here: + return [CoreAuthTokenModel(**token) for token in tokens] + + async def get_tokens_from_keys( self, mongo_conn: AsyncMongo, token_keys: List[ObjectId | str] = None, diff --git a/controllers/core/message.py b/controllers/core/message.py index b29a658..257d7bb 100644 --- a/controllers/core/message.py +++ b/controllers/core/message.py @@ -302,14 +302,12 @@ class CoreMessageController(BaseModel): async def get_message( self, mongo_conn: AsyncMongo, - token_id: ObjectId | str, message_id: ObjectId | str, ) -> CoreMessageModel | None: """ Gets one message if you know its message id. :param mongo_conn: The instance of the database connector to use for the operation. - :param token_id: The id of the auth-token associated with the message. Needed for security. :param message_id: The id of the message that needs to be read. :return: The contents of that one message in a structured format. """ @@ -317,10 +315,7 @@ class CoreMessageController(BaseModel): # We fetch the whole payload of that one message: record = await mongo_conn.find_one( collection = self.MESSAGES_COLLECTION, - filter = { - "_id": ObjectId(message_id), - "tokenId": ObjectId(token_id) - }, + filter = {"_id": ObjectId(message_id)}, raise_exception = True ) @@ -342,7 +337,6 @@ class CoreMessageController(BaseModel): async def update_tags( self, mongo_conn: AsyncMongo, - token_id: ObjectId | str, message_id: ObjectId | str, unset_tags: List[str] = None, set_tags: List[str] = None @@ -351,7 +345,6 @@ class CoreMessageController(BaseModel): """ Updates the tags on one message. The tags to remove are processed first, the ones to add are processed later. :param mongo_conn: The instance of the database connector to use for the operation. - :param token_id: The id of the auth-token associated with the message. Needed for security. :param message_id: The id of the message that needs to be read. :param unset_tags: The tags to remove from the message. :param set_tags: The tags to add to the message. @@ -361,10 +354,7 @@ class CoreMessageController(BaseModel): # Update the tags: return await mongo_conn.update_one( collection = self.MESSAGES_COLLECTION, - filter = { - "_id": ObjectId(message_id), - "tokenId": ObjectId(token_id) - }, + filter = {"_id": ObjectId(message_id)}, update = [{ "$set": { "tags": { diff --git a/models/api/finstitutions/payments/request.py b/models/api/finstitutions/payments/request.py index ada9bdb..ecf4edf 100644 --- a/models/api/finstitutions/payments/request.py +++ b/models/api/finstitutions/payments/request.py @@ -168,7 +168,7 @@ class PGPaymentRequestData(BaseModel): amount: float | int = Field( description = "the amount of money to be requested", - ge = 0, + ge = 0.0, frozen = True ) @@ -178,11 +178,6 @@ class PGPaymentRequestData(BaseModel): examples = ["INR", "USD", "KES"] ) - reference: str | None = Field( - description = "some reference against which the payment was made", - frozen = True - ) - description: str | None = Field( description = "a short, human-readable description of the payment", frozen = True diff --git a/models/api/mail/get.py b/models/api/mail/get.py index 63fb776..91a369a 100644 --- a/models/api/mail/get.py +++ b/models/api/mail/get.py @@ -101,11 +101,6 @@ class MailGetRequestHeaders(BaseModel): class MailGetRequestData(BaseModel): - tokenKey: str = Field( - description = "the id of the token associated with the mail; needed for security", - frozen = True - ) - messageId: str = Field( description = "the mail identifier (Mongo ObjectId) of the document that holds the mail", frozen = True