django-cfg 1.2.29__py3-none-any.whl → 1.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. django_cfg/__init__.py +1 -1
  2. django_cfg/apps/api/health/views.py +4 -2
  3. django_cfg/apps/knowbase/config/settings.py +16 -15
  4. django_cfg/apps/payments/README.md +326 -0
  5. django_cfg/apps/payments/admin/__init__.py +20 -9
  6. django_cfg/apps/payments/admin/api_keys_admin.py +521 -237
  7. django_cfg/apps/payments/admin/balance_admin.py +592 -297
  8. django_cfg/apps/payments/admin/currencies_admin.py +600 -108
  9. django_cfg/apps/payments/admin/filters.py +306 -199
  10. django_cfg/apps/payments/admin/payments_admin.py +470 -64
  11. django_cfg/apps/payments/admin/subscriptions_admin.py +578 -128
  12. django_cfg/apps/payments/admin_interface/__init__.py +18 -0
  13. django_cfg/apps/payments/admin_interface/templates/payments/base.html +162 -0
  14. django_cfg/apps/payments/admin_interface/templates/payments/components/dev_tool_card.html +38 -0
  15. django_cfg/apps/payments/admin_interface/templates/payments/components/loading_spinner.html +16 -0
  16. django_cfg/apps/payments/admin_interface/templates/payments/components/notification.html +27 -0
  17. django_cfg/apps/payments/admin_interface/templates/payments/components/provider_card.html +86 -0
  18. django_cfg/apps/payments/admin_interface/templates/payments/components/status_card.html +39 -0
  19. django_cfg/apps/payments/admin_interface/templates/payments/currency_converter.html +382 -0
  20. django_cfg/apps/payments/admin_interface/templates/payments/payment_dashboard.html +300 -0
  21. django_cfg/apps/payments/admin_interface/templates/payments/payment_form.html +303 -0
  22. django_cfg/apps/payments/admin_interface/templates/payments/payment_list.html +382 -0
  23. django_cfg/apps/payments/admin_interface/templates/payments/payment_status.html +500 -0
  24. django_cfg/apps/payments/admin_interface/templates/payments/webhook_dashboard.html +594 -0
  25. django_cfg/apps/payments/admin_interface/views/__init__.py +23 -0
  26. django_cfg/apps/payments/admin_interface/views/payment_views.py +259 -0
  27. django_cfg/apps/payments/admin_interface/views/webhook_dashboard.py +37 -0
  28. django_cfg/apps/payments/apps.py +34 -9
  29. django_cfg/apps/payments/config/__init__.py +28 -51
  30. django_cfg/apps/payments/config/constance/__init__.py +22 -0
  31. django_cfg/apps/payments/config/constance/config_service.py +123 -0
  32. django_cfg/apps/payments/config/constance/fields.py +69 -0
  33. django_cfg/apps/payments/config/constance/settings.py +160 -0
  34. django_cfg/apps/payments/config/django_cfg_integration.py +202 -0
  35. django_cfg/apps/payments/config/helpers.py +130 -0
  36. django_cfg/apps/payments/management/__init__.py +1 -3
  37. django_cfg/apps/payments/management/commands/__init__.py +1 -3
  38. django_cfg/apps/payments/management/commands/manage_currencies.py +381 -0
  39. django_cfg/apps/payments/management/commands/manage_providers.py +408 -0
  40. django_cfg/apps/payments/middleware/__init__.py +3 -1
  41. django_cfg/apps/payments/middleware/api_access.py +329 -222
  42. django_cfg/apps/payments/middleware/rate_limiting.py +343 -163
  43. django_cfg/apps/payments/middleware/usage_tracking.py +250 -238
  44. django_cfg/apps/payments/migrations/0001_initial.py +708 -536
  45. django_cfg/apps/payments/models/__init__.py +16 -20
  46. django_cfg/apps/payments/models/api_keys.py +121 -43
  47. django_cfg/apps/payments/models/balance.py +150 -115
  48. django_cfg/apps/payments/models/base.py +68 -15
  49. django_cfg/apps/payments/models/currencies.py +207 -67
  50. django_cfg/apps/payments/models/managers/__init__.py +44 -0
  51. django_cfg/apps/payments/models/managers/api_key_managers.py +329 -0
  52. django_cfg/apps/payments/models/managers/balance_managers.py +599 -0
  53. django_cfg/apps/payments/models/managers/currency_managers.py +385 -0
  54. django_cfg/apps/payments/models/managers/payment_managers.py +511 -0
  55. django_cfg/apps/payments/models/managers/subscription_managers.py +641 -0
  56. django_cfg/apps/payments/models/payments.py +235 -284
  57. django_cfg/apps/payments/models/subscriptions.py +257 -177
  58. django_cfg/apps/payments/models/tariffs.py +147 -40
  59. django_cfg/apps/payments/services/__init__.py +209 -56
  60. django_cfg/apps/payments/services/cache/__init__.py +6 -6
  61. django_cfg/apps/payments/services/cache/{simple_cache.py → cache_service.py} +112 -12
  62. django_cfg/apps/payments/services/core/__init__.py +10 -6
  63. django_cfg/apps/payments/services/core/balance_service.py +435 -360
  64. django_cfg/apps/payments/services/core/base.py +166 -0
  65. django_cfg/apps/payments/services/core/currency_service.py +478 -0
  66. django_cfg/apps/payments/services/core/payment_service.py +344 -468
  67. django_cfg/apps/payments/services/core/subscription_service.py +425 -484
  68. django_cfg/apps/payments/services/core/webhook_service.py +410 -0
  69. django_cfg/apps/payments/services/integrations/__init__.py +29 -0
  70. django_cfg/apps/payments/services/integrations/ngrok_service.py +47 -0
  71. django_cfg/apps/payments/services/integrations/providers_config.py +107 -0
  72. django_cfg/apps/payments/services/providers/__init__.py +9 -14
  73. django_cfg/apps/payments/services/providers/base.py +232 -71
  74. django_cfg/apps/payments/services/providers/nowpayments.py +404 -219
  75. django_cfg/apps/payments/services/providers/registry.py +429 -80
  76. django_cfg/apps/payments/services/types/__init__.py +78 -0
  77. django_cfg/apps/payments/services/types/data.py +177 -0
  78. django_cfg/apps/payments/services/types/requests.py +150 -0
  79. django_cfg/apps/payments/services/types/responses.py +156 -0
  80. django_cfg/apps/payments/services/types/webhooks.py +232 -0
  81. django_cfg/apps/payments/signals/__init__.py +33 -8
  82. django_cfg/apps/payments/signals/api_key_signals.py +211 -130
  83. django_cfg/apps/payments/signals/balance_signals.py +174 -0
  84. django_cfg/apps/payments/signals/payment_signals.py +129 -98
  85. django_cfg/apps/payments/signals/subscription_signals.py +195 -143
  86. django_cfg/apps/payments/static/payments/css/components.css +380 -0
  87. django_cfg/apps/payments/static/payments/css/dashboard.css +188 -0
  88. django_cfg/apps/payments/static/payments/js/components.js +545 -0
  89. django_cfg/apps/payments/static/payments/js/utils.js +412 -0
  90. django_cfg/apps/payments/templatetags/__init__.py +1 -1
  91. django_cfg/apps/payments/templatetags/payment_tags.py +466 -0
  92. django_cfg/apps/payments/urls.py +46 -47
  93. django_cfg/apps/payments/urls_admin.py +49 -0
  94. django_cfg/apps/payments/views/api/__init__.py +101 -0
  95. django_cfg/apps/payments/views/api/api_keys.py +387 -0
  96. django_cfg/apps/payments/views/api/balances.py +381 -0
  97. django_cfg/apps/payments/views/api/base.py +298 -0
  98. django_cfg/apps/payments/views/api/currencies.py +402 -0
  99. django_cfg/apps/payments/views/api/payments.py +415 -0
  100. django_cfg/apps/payments/views/api/subscriptions.py +475 -0
  101. django_cfg/apps/payments/views/api/webhooks.py +476 -0
  102. django_cfg/apps/payments/views/serializers/__init__.py +99 -0
  103. django_cfg/apps/payments/views/serializers/api_keys.py +424 -0
  104. django_cfg/apps/payments/views/serializers/balances.py +300 -0
  105. django_cfg/apps/payments/views/serializers/currencies.py +335 -0
  106. django_cfg/apps/payments/views/serializers/payments.py +387 -0
  107. django_cfg/apps/payments/views/serializers/subscriptions.py +429 -0
  108. django_cfg/apps/payments/views/serializers/webhooks.py +137 -0
  109. django_cfg/apps/tasks/urls.py +0 -2
  110. django_cfg/apps/tasks/urls_admin.py +14 -0
  111. django_cfg/apps/urls.py +4 -4
  112. django_cfg/config.py +1 -1
  113. django_cfg/core/config.py +75 -4
  114. django_cfg/core/generation.py +25 -4
  115. django_cfg/core/integration/README.md +363 -0
  116. django_cfg/core/integration/__init__.py +47 -0
  117. django_cfg/core/integration/commands_collector.py +239 -0
  118. django_cfg/core/integration/display/__init__.py +15 -0
  119. django_cfg/core/integration/display/base.py +157 -0
  120. django_cfg/core/integration/display/ngrok.py +164 -0
  121. django_cfg/core/integration/display/startup.py +815 -0
  122. django_cfg/core/integration/url_integration.py +123 -0
  123. django_cfg/core/integration/version_checker.py +160 -0
  124. django_cfg/management/commands/auto_generate.py +4 -0
  125. django_cfg/management/commands/check_settings.py +6 -0
  126. django_cfg/management/commands/clear_constance.py +5 -2
  127. django_cfg/management/commands/create_token.py +6 -0
  128. django_cfg/management/commands/list_urls.py +6 -0
  129. django_cfg/management/commands/migrate_all.py +6 -0
  130. django_cfg/management/commands/migrator.py +3 -0
  131. django_cfg/management/commands/rundramatiq.py +6 -0
  132. django_cfg/management/commands/runserver_ngrok.py +51 -29
  133. django_cfg/management/commands/script.py +6 -0
  134. django_cfg/management/commands/show_config.py +12 -2
  135. django_cfg/management/commands/show_urls.py +4 -0
  136. django_cfg/management/commands/superuser.py +6 -0
  137. django_cfg/management/commands/task_clear.py +4 -1
  138. django_cfg/management/commands/task_status.py +3 -1
  139. django_cfg/management/commands/test_email.py +3 -0
  140. django_cfg/management/commands/test_telegram.py +6 -0
  141. django_cfg/management/commands/test_twilio.py +6 -0
  142. django_cfg/management/commands/tree.py +6 -0
  143. django_cfg/management/commands/validate_config.py +155 -149
  144. django_cfg/models/constance.py +31 -11
  145. django_cfg/models/payments.py +175 -498
  146. django_cfg/modules/django_currency/__init__.py +16 -11
  147. django_cfg/modules/django_currency/clients/__init__.py +4 -4
  148. django_cfg/modules/django_currency/clients/coinpaprika_client.py +289 -0
  149. django_cfg/modules/django_currency/clients/yahoo_client.py +157 -0
  150. django_cfg/modules/django_currency/core/__init__.py +1 -7
  151. django_cfg/modules/django_currency/core/converter.py +18 -23
  152. django_cfg/modules/django_currency/core/models.py +122 -11
  153. django_cfg/modules/django_currency/database/__init__.py +4 -4
  154. django_cfg/modules/django_currency/database/database_loader.py +190 -309
  155. django_cfg/modules/django_logger.py +160 -146
  156. django_cfg/modules/django_unfold/dashboard.py +65 -12
  157. django_cfg/registry/core.py +1 -0
  158. django_cfg/template_archive/django_sample.zip +0 -0
  159. django_cfg/templates/admin/components/action_grid.html +9 -9
  160. django_cfg/templates/admin/components/metric_card.html +5 -5
  161. django_cfg/templates/admin/components/status_badge.html +2 -2
  162. django_cfg/templates/admin/layouts/dashboard_with_tabs.html +152 -24
  163. django_cfg/templates/admin/snippets/components/quick_actions.html +3 -3
  164. django_cfg/templates/admin/snippets/components/system_health.html +1 -1
  165. django_cfg/templates/admin/snippets/tabs/overview_tab.html +49 -52
  166. django_cfg/utils/smart_defaults.py +222 -571
  167. django_cfg/utils/toolkit.py +51 -11
  168. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/METADATA +5 -4
  169. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/RECORD +172 -182
  170. django_cfg/apps/payments/__init__.py +0 -8
  171. django_cfg/apps/payments/admin/tariffs_admin.py +0 -199
  172. django_cfg/apps/payments/config/module.py +0 -70
  173. django_cfg/apps/payments/config/providers.py +0 -105
  174. django_cfg/apps/payments/config/settings.py +0 -96
  175. django_cfg/apps/payments/config/utils.py +0 -52
  176. django_cfg/apps/payments/decorators.py +0 -291
  177. django_cfg/apps/payments/management/commands/README.md +0 -178
  178. django_cfg/apps/payments/management/commands/currency_stats.py +0 -323
  179. django_cfg/apps/payments/management/commands/populate_currencies.py +0 -246
  180. django_cfg/apps/payments/management/commands/update_currencies.py +0 -336
  181. django_cfg/apps/payments/managers/__init__.py +0 -22
  182. django_cfg/apps/payments/managers/api_key_manager.py +0 -35
  183. django_cfg/apps/payments/managers/balance_manager.py +0 -361
  184. django_cfg/apps/payments/managers/currency_manager.py +0 -83
  185. django_cfg/apps/payments/managers/payment_manager.py +0 -44
  186. django_cfg/apps/payments/managers/subscription_manager.py +0 -37
  187. django_cfg/apps/payments/managers/tariff_manager.py +0 -29
  188. django_cfg/apps/payments/models/events.py +0 -73
  189. django_cfg/apps/payments/serializers/__init__.py +0 -56
  190. django_cfg/apps/payments/serializers/api_keys.py +0 -51
  191. django_cfg/apps/payments/serializers/balance.py +0 -59
  192. django_cfg/apps/payments/serializers/currencies.py +0 -55
  193. django_cfg/apps/payments/serializers/payments.py +0 -62
  194. django_cfg/apps/payments/serializers/subscriptions.py +0 -71
  195. django_cfg/apps/payments/serializers/tariffs.py +0 -56
  196. django_cfg/apps/payments/services/billing/__init__.py +0 -8
  197. django_cfg/apps/payments/services/cache/base.py +0 -30
  198. django_cfg/apps/payments/services/core/fallback_service.py +0 -432
  199. django_cfg/apps/payments/services/internal_types.py +0 -297
  200. django_cfg/apps/payments/services/middleware/__init__.py +0 -8
  201. django_cfg/apps/payments/services/monitoring/__init__.py +0 -22
  202. django_cfg/apps/payments/services/monitoring/api_schemas.py +0 -222
  203. django_cfg/apps/payments/services/monitoring/provider_health.py +0 -372
  204. django_cfg/apps/payments/services/providers/cryptapi.py +0 -273
  205. django_cfg/apps/payments/services/providers/cryptomus.py +0 -311
  206. django_cfg/apps/payments/services/security/__init__.py +0 -34
  207. django_cfg/apps/payments/services/security/error_handler.py +0 -637
  208. django_cfg/apps/payments/services/security/payment_notifications.py +0 -342
  209. django_cfg/apps/payments/services/security/webhook_validator.py +0 -475
  210. django_cfg/apps/payments/services/validators/__init__.py +0 -8
  211. django_cfg/apps/payments/static/payments/css/payments.css +0 -340
  212. django_cfg/apps/payments/static/payments/js/notifications.js +0 -202
  213. django_cfg/apps/payments/static/payments/js/payment-utils.js +0 -318
  214. django_cfg/apps/payments/static/payments/js/theme.js +0 -86
  215. django_cfg/apps/payments/tasks/__init__.py +0 -12
  216. django_cfg/apps/payments/tasks/webhook_processing.py +0 -177
  217. django_cfg/apps/payments/templates/payments/base.html +0 -182
  218. django_cfg/apps/payments/templates/payments/components/payment_card.html +0 -201
  219. django_cfg/apps/payments/templates/payments/components/payment_qr_code.html +0 -109
  220. django_cfg/apps/payments/templates/payments/components/progress_bar.html +0 -36
  221. django_cfg/apps/payments/templates/payments/components/provider_stats.html +0 -40
  222. django_cfg/apps/payments/templates/payments/components/status_badge.html +0 -27
  223. django_cfg/apps/payments/templates/payments/components/status_overview.html +0 -144
  224. django_cfg/apps/payments/templates/payments/dashboard.html +0 -346
  225. django_cfg/apps/payments/templatetags/payments_tags.py +0 -315
  226. django_cfg/apps/payments/urls_templates.py +0 -52
  227. django_cfg/apps/payments/utils/__init__.py +0 -45
  228. django_cfg/apps/payments/utils/billing_utils.py +0 -342
  229. django_cfg/apps/payments/utils/config_utils.py +0 -245
  230. django_cfg/apps/payments/utils/middleware_utils.py +0 -228
  231. django_cfg/apps/payments/utils/validation_utils.py +0 -94
  232. django_cfg/apps/payments/views/__init__.py +0 -62
  233. django_cfg/apps/payments/views/api_key_views.py +0 -164
  234. django_cfg/apps/payments/views/balance_views.py +0 -75
  235. django_cfg/apps/payments/views/currency_views.py +0 -111
  236. django_cfg/apps/payments/views/payment_views.py +0 -149
  237. django_cfg/apps/payments/views/subscription_views.py +0 -135
  238. django_cfg/apps/payments/views/tariff_views.py +0 -131
  239. django_cfg/apps/payments/views/templates/__init__.py +0 -25
  240. django_cfg/apps/payments/views/templates/ajax.py +0 -312
  241. django_cfg/apps/payments/views/templates/base.py +0 -204
  242. django_cfg/apps/payments/views/templates/dashboard.py +0 -60
  243. django_cfg/apps/payments/views/templates/payment_detail.py +0 -102
  244. django_cfg/apps/payments/views/templates/payment_management.py +0 -164
  245. django_cfg/apps/payments/views/templates/qr_code.py +0 -174
  246. django_cfg/apps/payments/views/templates/stats.py +0 -240
  247. django_cfg/apps/payments/views/templates/utils.py +0 -181
  248. django_cfg/apps/payments/views/webhook_views.py +0 -266
  249. django_cfg/apps/payments/viewsets.py +0 -65
  250. django_cfg/core/integration.py +0 -160
  251. django_cfg/modules/django_currency/clients/coingecko_client.py +0 -257
  252. django_cfg/modules/django_currency/clients/yfinance_client.py +0 -246
  253. django_cfg/template_archive/.gitignore +0 -1
  254. django_cfg/template_archive/__init__.py +0 -0
  255. django_cfg/urls.py +0 -33
  256. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/WHEEL +0 -0
  257. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/entry_points.txt +0 -0
  258. {django_cfg-1.2.29.dist-info → django_cfg-1.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,475 +0,0 @@
1
- """
2
- Enhanced Webhook Signature Validation Service.
3
- Critical Foundation Security Component.
4
- """
5
-
6
- import json
7
- import hmac
8
- import hashlib
9
- import logging
10
- import time
11
- from typing import Dict, Any, Optional, Tuple
12
- from datetime import datetime, timedelta
13
- from django.core.cache import cache
14
- from django.utils import timezone
15
- from django.conf import settings
16
-
17
- from django_cfg.apps.payments.config import get_payments_config
18
- from django_cfg.apps.payments.models.events import PaymentEvent
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- class WebhookValidator:
24
- """
25
- Secure webhook signature validation with replay attack protection.
26
-
27
- Foundation Security Component - CRITICAL for system security.
28
- """
29
-
30
- def __init__(self):
31
- self.config = get_payments_config()
32
- self.nonce_cache_timeout = 3600 # 1 hour nonce validity
33
- self.max_timestamp_drift = 300 # 5 minutes max timestamp drift
34
-
35
- def validate_webhook(
36
- self,
37
- provider: str,
38
- webhook_data: Dict[str, Any],
39
- request_headers: Dict[str, str],
40
- raw_body: bytes = None
41
- ) -> Tuple[bool, Optional[str]]:
42
- """
43
- Comprehensive webhook validation with security checks.
44
-
45
- Returns:
46
- Tuple[bool, Optional[str]]: (is_valid, error_message)
47
- """
48
-
49
- try:
50
- # Step 1: Provider-specific signature validation
51
- signature_valid, signature_error = self._validate_provider_signature(
52
- provider, webhook_data, request_headers, raw_body
53
- )
54
-
55
- if not signature_valid:
56
- self._log_security_event('signature_validation_failed', provider, signature_error)
57
- return False, signature_error
58
-
59
- # Step 2: Replay attack protection
60
- replay_valid, replay_error = self._validate_against_replay(
61
- provider, webhook_data, request_headers
62
- )
63
-
64
- if not replay_valid:
65
- self._log_security_event('replay_attack_detected', provider, replay_error)
66
- return False, replay_error
67
-
68
- # Step 3: Timestamp validation
69
- timestamp_valid, timestamp_error = self._validate_timestamp(
70
- webhook_data, request_headers
71
- )
72
-
73
- if not timestamp_valid:
74
- self._log_security_event('timestamp_validation_failed', provider, timestamp_error)
75
- return False, timestamp_error
76
-
77
- # Step 4: Rate limiting check
78
- rate_limit_valid, rate_limit_error = self._check_rate_limits(
79
- provider, request_headers
80
- )
81
-
82
- if not rate_limit_valid:
83
- self._log_security_event('rate_limit_exceeded', provider, rate_limit_error)
84
- return False, rate_limit_error
85
-
86
- # All validations passed
87
- self._log_security_event('webhook_validated', provider, 'Validation successful')
88
- return True, None
89
-
90
- except Exception as e:
91
- error_msg = f"Webhook validation error: {str(e)}"
92
- logger.error(f"Critical validation error for {provider}: {e}", exc_info=True)
93
- self._log_security_event('validation_exception', provider, error_msg)
94
- return False, error_msg
95
-
96
- def _validate_provider_signature(
97
- self,
98
- provider: str,
99
- webhook_data: Dict[str, Any],
100
- request_headers: Dict[str, str],
101
- raw_body: bytes = None
102
- ) -> Tuple[bool, Optional[str]]:
103
- """Validate signature based on provider-specific method."""
104
-
105
- if provider == 'cryptapi':
106
- return self._validate_cryptapi_signature(webhook_data, request_headers)
107
- elif provider == 'cryptomus':
108
- return self._validate_cryptomus_signature(webhook_data, request_headers, raw_body)
109
- elif provider == 'nowpayments':
110
- return self._validate_nowpayments_signature(webhook_data, request_headers, raw_body)
111
- elif provider == 'test':
112
- return True, None # Allow test webhooks in development
113
- else:
114
- return False, f"Unknown provider: {provider}"
115
-
116
- def _validate_cryptapi_signature(
117
- self,
118
- webhook_data: Dict[str, Any],
119
- request_headers: Dict[str, str]
120
- ) -> Tuple[bool, Optional[str]]:
121
- """
122
- CryptAPI signature validation with nonce verification.
123
-
124
- CRITICAL FIX: Proper nonce validation to prevent replay attacks.
125
- """
126
-
127
- # Get security nonce from webhook data
128
- security_nonce = webhook_data.get('nonce')
129
- if not security_nonce:
130
- return False, "Missing security nonce in CryptAPI webhook"
131
-
132
- # Validate nonce format and uniqueness
133
- nonce_valid, nonce_error = self._validate_nonce(security_nonce, 'cryptapi')
134
- if not nonce_valid:
135
- return False, f"Invalid security nonce: {nonce_error}"
136
-
137
- # Check required fields
138
- required_fields = ['order_id', 'value_coin', 'confirmations']
139
- missing_fields = [field for field in required_fields if field not in webhook_data]
140
- if missing_fields:
141
- return False, f"Missing required fields: {', '.join(missing_fields)}"
142
-
143
- # Validate order_id format
144
- order_id = webhook_data.get('order_id')
145
- if not self._validate_order_id_format(order_id):
146
- return False, f"Invalid order_id format: {order_id}"
147
-
148
- # CryptAPI specific validation: check if address exists in our system
149
- address_in = webhook_data.get('address')
150
- if address_in and not self._validate_payment_address(address_in, 'cryptapi'):
151
- return False, f"Unknown payment address: {address_in}"
152
-
153
- return True, None
154
-
155
- def _validate_cryptomus_signature(
156
- self,
157
- webhook_data: Dict[str, Any],
158
- request_headers: Dict[str, str],
159
- raw_body: bytes = None
160
- ) -> Tuple[bool, Optional[str]]:
161
- """
162
- Cryptomus webhook signature validation.
163
-
164
- Uses HMAC-SHA256 signature verification.
165
- """
166
-
167
- # Get webhook secret from configuration
168
- if not self.config or not hasattr(self.config, 'providers'):
169
- return False, "Cryptomus configuration not found"
170
-
171
- cryptomus_config = self.config.providers.get('cryptomus')
172
- if not cryptomus_config:
173
- return False, "Cryptomus provider not configured"
174
-
175
- webhook_secret = getattr(cryptomus_config, 'webhook_secret', None)
176
- if not webhook_secret:
177
- logger.warning("Cryptomus webhook secret not configured, skipping validation")
178
- return True, None # Allow if not configured (development mode)
179
-
180
- # Get signature from headers
181
- signature = request_headers.get('HTTP_X_CRYPTOMUS_SIGNATURE')
182
- if not signature:
183
- return False, "Missing Cryptomus signature header"
184
-
185
- # Calculate expected signature
186
- if raw_body:
187
- payload = raw_body
188
- else:
189
- payload = json.dumps(webhook_data, separators=(',', ':'), sort_keys=True).encode()
190
-
191
- expected_signature = hmac.new(
192
- webhook_secret.encode(),
193
- payload,
194
- hashlib.sha256
195
- ).hexdigest()
196
-
197
- # Secure comparison
198
- if not hmac.compare_digest(signature, expected_signature):
199
- return False, "Invalid Cryptomus signature"
200
-
201
- # Validate required fields
202
- required_fields = ['order_id', 'status']
203
- missing_fields = [field for field in required_fields if field not in webhook_data]
204
- if missing_fields:
205
- return False, f"Missing required fields: {', '.join(missing_fields)}"
206
-
207
- return True, None
208
-
209
- def _validate_nowpayments_signature(
210
- self,
211
- webhook_data: Dict[str, Any],
212
- request_headers: Dict[str, str],
213
- raw_body: bytes = None
214
- ) -> Tuple[bool, Optional[str]]:
215
- """
216
- NowPayments IPN signature validation.
217
-
218
- Uses HMAC-SHA512 signature verification.
219
- """
220
-
221
- # Get IPN secret from configuration
222
- if not self.config or not hasattr(self.config, 'providers'):
223
- return False, "NowPayments configuration not found"
224
-
225
- nowpayments_config = self.config.providers.get('nowpayments')
226
- if not nowpayments_config:
227
- return False, "NowPayments provider not configured"
228
-
229
- ipn_secret = getattr(nowpayments_config, 'ipn_secret', None)
230
- if not ipn_secret:
231
- logger.warning("NowPayments IPN secret not configured, skipping validation")
232
- return True, None # Allow if not configured (development mode)
233
-
234
- # Get signature from headers
235
- signature = request_headers.get('HTTP_X_NOWPAYMENTS_SIG')
236
- if not signature:
237
- return False, "Missing NowPayments signature header"
238
-
239
- # Calculate expected signature
240
- if raw_body:
241
- payload = raw_body.decode('utf-8')
242
- else:
243
- payload = json.dumps(webhook_data, separators=(',', ':'), sort_keys=True)
244
-
245
- expected_signature = hmac.new(
246
- ipn_secret.encode(),
247
- payload.encode(),
248
- hashlib.sha512
249
- ).hexdigest()
250
-
251
- # Secure comparison
252
- if not hmac.compare_digest(signature, expected_signature):
253
- return False, "Invalid NowPayments signature"
254
-
255
- return True, None
256
-
257
- def _validate_against_replay(
258
- self,
259
- provider: str,
260
- webhook_data: Dict[str, Any],
261
- request_headers: Dict[str, str]
262
- ) -> Tuple[bool, Optional[str]]:
263
- """
264
- Protect against replay attacks using idempotency keys.
265
- """
266
-
267
- # Generate idempotency key
268
- idempotency_key = self._generate_idempotency_key(provider, webhook_data, request_headers)
269
-
270
- # Check if we've seen this webhook before
271
- cache_key = f"webhook_idempotency:{idempotency_key}"
272
- if cache.get(cache_key):
273
- return False, f"Replay attack detected: duplicate webhook {idempotency_key}"
274
-
275
- # Store idempotency key to prevent replays
276
- cache.set(cache_key, True, timeout=self.nonce_cache_timeout)
277
-
278
- return True, None
279
-
280
- def _validate_timestamp(
281
- self,
282
- webhook_data: Dict[str, Any],
283
- request_headers: Dict[str, str]
284
- ) -> Tuple[bool, Optional[str]]:
285
- """
286
- Validate webhook timestamp to prevent old webhook replay.
287
- """
288
-
289
- # Try different timestamp fields
290
- timestamp_fields = ['timestamp', 'created_at', 'updated_at', 'time']
291
- webhook_timestamp = None
292
-
293
- for field in timestamp_fields:
294
- if field in webhook_data:
295
- webhook_timestamp = webhook_data[field]
296
- break
297
-
298
- # Also check headers
299
- if not webhook_timestamp:
300
- webhook_timestamp = request_headers.get('HTTP_X_TIMESTAMP')
301
-
302
- if not webhook_timestamp:
303
- # If no timestamp provided, skip validation (some providers don't include it)
304
- return True, None
305
-
306
- try:
307
- # Parse timestamp (support multiple formats)
308
- if isinstance(webhook_timestamp, (int, float)):
309
- webhook_time = datetime.fromtimestamp(webhook_timestamp, tz=timezone.utc)
310
- else:
311
- # Try to parse ISO format
312
- webhook_time = datetime.fromisoformat(webhook_timestamp.replace('Z', '+00:00'))
313
-
314
- current_time = timezone.now()
315
- time_diff = abs((current_time - webhook_time).total_seconds())
316
-
317
- if time_diff > self.max_timestamp_drift:
318
- return False, f"Webhook timestamp too old or too new: {time_diff}s drift"
319
-
320
- return True, None
321
-
322
- except Exception as e:
323
- logger.warning(f"Could not validate timestamp: {e}")
324
- return True, None # Skip validation if timestamp format is unknown
325
-
326
- def _check_rate_limits(
327
- self,
328
- provider: str,
329
- request_headers: Dict[str, str]
330
- ) -> Tuple[bool, Optional[str]]:
331
- """
332
- Check webhook rate limits to prevent abuse.
333
- """
334
-
335
- # Extract IP address
336
- ip_address = (
337
- request_headers.get('HTTP_X_FORWARDED_FOR', '').split(',')[0].strip() or
338
- request_headers.get('HTTP_X_REAL_IP', '') or
339
- request_headers.get('REMOTE_ADDR', 'unknown')
340
- )
341
-
342
- # Rate limit key
343
- rate_limit_key = f"webhook_rate_limit:{provider}:{ip_address}"
344
-
345
- # Check current rate
346
- current_count = cache.get(rate_limit_key, 0)
347
- max_webhooks_per_minute = 60 # Configurable limit
348
-
349
- if current_count >= max_webhooks_per_minute:
350
- return False, f"Rate limit exceeded: {current_count} webhooks/minute from {ip_address}"
351
-
352
- # Increment counter
353
- cache.set(rate_limit_key, current_count + 1, timeout=60)
354
-
355
- return True, None
356
-
357
- def _validate_nonce(self, nonce: str, provider: str) -> Tuple[bool, Optional[str]]:
358
- """
359
- Validate nonce format and uniqueness.
360
-
361
- CRITICAL: Prevents replay attacks for CryptAPI.
362
- """
363
-
364
- # Validate nonce format
365
- if not nonce or len(nonce) < 8:
366
- return False, "Nonce too short"
367
-
368
- if len(nonce) > 64:
369
- return False, "Nonce too long"
370
-
371
- # Check nonce uniqueness
372
- nonce_key = f"webhook_nonce:{provider}:{nonce}"
373
- if cache.get(nonce_key):
374
- return False, "Nonce already used (replay attack)"
375
-
376
- # Store nonce to prevent reuse
377
- cache.set(nonce_key, True, timeout=self.nonce_cache_timeout)
378
-
379
- return True, None
380
-
381
- def _validate_order_id_format(self, order_id: str) -> bool:
382
- """Validate order ID format."""
383
- if not order_id:
384
- return False
385
-
386
- # Basic validation (customize per your order ID format)
387
- if len(order_id) < 3 or len(order_id) > 50:
388
- return False
389
-
390
- # Allow alphanumeric, dash, underscore
391
- import re
392
- if not re.match(r'^[a-zA-Z0-9_-]+$', order_id):
393
- return False
394
-
395
- return True
396
-
397
- def _validate_payment_address(self, address: str, provider: str) -> bool:
398
- """
399
- Validate that payment address exists in our system.
400
-
401
- CRITICAL: Prevents webhooks for unknown addresses.
402
- """
403
- from ..models.payments import UniversalPayment
404
-
405
- try:
406
- # Check if address exists in our payments
407
- return UniversalPayment.objects.filter(
408
- provider=provider,
409
- pay_address=address
410
- ).exists()
411
- except Exception as e:
412
- logger.error(f"Error validating payment address: {e}")
413
- return True # Allow if validation fails (avoid false negatives)
414
-
415
- def _generate_idempotency_key(
416
- self,
417
- provider: str,
418
- webhook_data: Dict[str, Any],
419
- request_headers: Dict[str, str]
420
- ) -> str:
421
- """Generate secure idempotency key for webhook deduplication."""
422
-
423
- # Use multiple fields for uniqueness
424
- payment_id = (
425
- webhook_data.get('payment_id') or
426
- webhook_data.get('order_id') or
427
- webhook_data.get('id') or
428
- webhook_data.get('uuid') or
429
- 'unknown'
430
- )
431
-
432
- # Include status to allow multiple status updates for same payment
433
- status = webhook_data.get('status', 'unknown')
434
-
435
- # Include timestamp for additional uniqueness
436
- timestamp = (
437
- webhook_data.get('timestamp') or
438
- webhook_data.get('created_at') or
439
- webhook_data.get('updated_at') or
440
- str(int(time.time()))
441
- )
442
-
443
- # Create secure hash
444
- key_data = f"{provider}:{payment_id}:{status}:{timestamp}"
445
- return hashlib.sha256(key_data.encode()).hexdigest()[:32]
446
-
447
- def _log_security_event(self, event_type: str, provider: str, details: str):
448
- """Log security events for monitoring and alerting."""
449
-
450
- try:
451
- # Create security event log
452
- PaymentEvent.objects.create(
453
- event_type=f'security_{event_type}',
454
- provider=provider,
455
- metadata={
456
- 'event_type': event_type,
457
- 'provider': provider,
458
- 'details': details,
459
- 'timestamp': timezone.now().isoformat(),
460
- 'severity': 'HIGH' if 'attack' in event_type else 'MEDIUM'
461
- }
462
- )
463
-
464
- # Log to application logger
465
- if 'attack' in event_type or 'failed' in event_type:
466
- logger.warning(f"🚨 Security Event [{event_type}] {provider}: {details}")
467
- else:
468
- logger.info(f"🔒 Security Event [{event_type}] {provider}: {details}")
469
-
470
- except Exception as e:
471
- logger.error(f"Failed to log security event: {e}")
472
-
473
-
474
- # Singleton instance for import
475
- webhook_validator = WebhookValidator()
@@ -1,8 +0,0 @@
1
- """
2
- Validators for payment services.
3
-
4
- TODO: Implement payment validators when needed.
5
- """
6
-
7
- # Placeholder for future validators
8
- __all__ = []