Coverage for functions \ flipdare \ request \ request_validator.py: 93%
72 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
« prev ^ index » next coverage.py v7.13.0, created at 2026-05-08 12:22 +1000
1#!/usr/bin/env python
2# Copyright (c) 2026 Flipdare Pty Ltd. All rights reserved.
3#
4# This file is part of Flipdare's proprietary software and contains
5# confidential and copyrighted material. Unauthorised copying,
6# modification, distribution, or use of this file is strictly
7# prohibited without prior written permission from Flipdare Pty Ltd.
8#
9# This software includes third-party components licensed under MIT,
10# BSD, and Apache 2.0 licences. See THIRD_PARTY_NOTICES for details.
11#
14from abc import ABC, abstractmethod
15from typing import override
16from flipdare.app_globals import is_text_present, is_valid_doc_id, is_valid_email
17from flipdare.app_log import LOG
18from flipdare.app_types import JsonDict
19from flipdare.constants import HONEYPOT_NAME, IS_DEBUG
20from flipdare.core.ip_address_dto import IpAddressDTO
22__all__ = [
23 "RequestValidator",
24 "EmailRequestValidator",
25 "HoneyPotRequestValidator",
26 "UidRequestValidator",
27 "PaymentCustomerUidRequestValidator",
28 "PaymentAccountUidRequestValidator",
29 "TextPresentRequestValidator",
30 "IpAddressRequestValidator",
31]
33# NOTE: These are field validators ONLY.
34# NOTE: DONT use FOR AUTHENTICATION
37class RequestValidator(ABC):
38 def __init__(self, data: JsonDict) -> None:
39 self._data = data
41 @abstractmethod
42 def validate(self) -> list[str]: ...
45class IpAddressRequestValidator(RequestValidator):
46 @override
47 def validate(self) -> list[str]:
48 ip_address = self._data.get("ip_address")
49 if IS_DEBUG:
50 LOG().debug(f"Validating IP address: {ip_address}")
51 if ip_address is None:
52 return ["Missing required field: IP address"]
53 try:
54 IpAddressDTO.create(ip_address)
55 return []
56 except ValueError:
57 return [f"Invalid IP address format: {ip_address}"]
60class EmailRequestValidator(RequestValidator):
61 @override
62 def validate(self) -> list[str]:
63 email = self._data.get("email")
64 if email is None:
65 return ["Missing required field: email"]
66 if IS_DEBUG:
67 LOG().debug(f"Validating email: {email}")
68 # note: dont check availability here, just format.
69 result = is_valid_email(email)
70 if result.error is not None:
71 return [f"Invalid email: {email}"]
72 return []
75class HoneyPotRequestValidator(RequestValidator):
76 # If honeypot_field is NOT empty: reject (possible bot submission).
77 @override
78 def validate(self) -> list[str]:
79 if IS_DEBUG:
80 LOG().debug("Checking honeypot field")
81 honeypot = self._data.get(HONEYPOT_NAME)
82 if honeypot is not None:
83 return ["Honeypot field should be empty, possible bot submission."]
84 return []
87class UidRequestValidator(RequestValidator):
88 @override
89 def validate(self) -> list[str]:
90 return self._validate_key("uid")
92 def _validate_key(self, key: str) -> list[str]:
93 if IS_DEBUG:
94 LOG().debug(f"Checking {key} field")
95 value = self._data.get(key)
96 if value is None:
97 return [f"Missing {key} field in request."]
98 if not is_valid_doc_id(value):
99 return [f"Invalid {key} format: {value}"]
100 return []
103class PaymentCustomerUidRequestValidator(UidRequestValidator):
104 @override
105 def validate(self) -> list[str]:
106 return self._validate_key("customer_uid")
109class PaymentAccountUidRequestValidator(UidRequestValidator):
110 @override
111 def validate(self) -> list[str]:
112 return self._validate_key("account_uid")
115class TextPresentRequestValidator(RequestValidator):
116 @override
117 def validate(self) -> list[str]:
118 if IS_DEBUG:
119 LOG().debug("Checking text presence")
120 return [
121 f"Missing or empty required field: {field_name}"
122 for field_name, value in self._data.items()
123 if isinstance(value, str) and not is_text_present(value)
124 ]