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