Commit Graph

6678 Commits

Author SHA1 Message Date
Fringg
ee2e79db31 refactor: remove modem functionality from classic subscriptions
Remove all modem purchase/management code:
- Delete modem handler, service, and tests
- Remove modem button from keyboards and admin panel
- Remove modem pricing from calculations
- Remove modem REST API endpoint and schemas
- Remove modem decorator, config settings, and notification formatting
- Keep DB column and migration for backwards compatibility
2026-02-11 21:14:08 +03:00
Fringg
d05ff678ab fix: HTML parse fallback, email change race condition, username length limit
- start.py: retry welcome message with parse_mode=None on TelegramBadRequest HTML parse error
- auth.py: handle IntegrityError race condition on email change, wrap email sending in try-except
- config.py: truncate RemnaWave username to 36 chars (API limit) instead of 64
2026-02-11 20:51:50 +03:00
Fringg
fcaa9dfb27 fix: clean stale squad UUIDs from tariffs during server sync
When squads are deleted from the RemnaWave panel and servers are synced,
the bot cleaned subscription connected_squads but left stale UUIDs in
tariff.allowed_squads. This caused errors when users tried to purchase
or extend subscriptions with tariffs referencing deleted squads.

Now sync_with_remnawave also removes stale UUIDs from all tariffs.
2026-02-11 18:37:19 +03:00
Fringg
c30c2feee1 fix: handle StaleDataError in webhook user.deleted server counter decrement
When a user is deleted from the panel, the subscription may already be
cascade-deleted by the time the webhook handler tries to decrement
server counters. This caused StaleDataError followed by
PendingRollbackError when accessing subscription.id in the error handler.

- Save subscription.id before DB operations to avoid lazy load after rollback
- Catch StaleDataError explicitly and rollback the session
- Re-fetch subscription/user after potential rollback in _handle_user_deleted
- Skip subscription cleanup if it was already cascade-deleted
2026-02-11 18:35:36 +03:00
Fringg
640da34736 fix: remove DisplayNameRestrictionMiddleware
Blocking users based on display name patterns caused false positives
for legitimate users. Removed middleware registration from dispatcher.
2026-02-11 18:31:50 +03:00
Fringg
93bb8e0eb4 fix: allow email change for unverified emails
Unverified email users could not change their email (e.g. to fix a typo)
because the endpoint required email_verified=True. Now unverified emails
are replaced directly without code verification, and a new verification
email is sent to the updated address.
2026-02-11 18:28:52 +03:00
Fringg
7d9ced8f4f fix: delete subscription_servers before subscription to prevent FK violation
reset_user_subscription and reset_trial endpoints did not clean up
subscription_servers rows before deleting the subscription, causing
ForeignKeyViolationError on subscription_servers.subscription_id_fkey.

Also fixed the same missing cleanup in user_service.hard_delete_user.
2026-02-11 18:25:42 +03:00
Fringg
b5998ea9d2 fix: use traffic topup config and add WATA 429 retry
- Cabinet API: use get_traffic_topup_packages() instead of
  get_traffic_packages() in classic mode endpoints (lines 622, 727, 2410)
  to prevent infinite free traffic exploit via initial-purchase packages
- WATA service: add retry logic for 429 rate limit responses with
  Retry-After parsing from header and response body, up to 2 retries,
  downgrade 429 from error to warning log level
2026-02-11 18:20:30 +03:00
Fringg
3193ffbd1b fix: change CryptoBot URL priority to bot_invoice_url for Telegram opening 2026-02-11 05:50:43 +03:00
Egor
5da01cc6df Merge pull request #2590 from BEDOLAGA-DEV/main
w
2026-02-11 04:47:13 +03:00
Fringg
887ea9cf5a style: format subscription.py with ruff 2026-02-11 04:45:42 +03:00
Fringg
bee4aa4284 fix: protect server counter callers and fix tariff change detection
- Wrap unprotected add/remove_user_to/from_servers calls in try/except
  in miniapp.py and cabinet subscription.py to prevent 500 errors
- Fix is_tariff_change to include classic-to-tariff transitions
  (subscription.tariff_id=None → new tariff_id) so purchased traffic
  is properly reset when switching modes
2026-02-11 04:44:15 +03:00
Fringg
b167ed3dd1 fix: preserve purchased traffic when extending same tariff
extend_subscription was unconditionally resetting purchased_traffic_gb
and deleting TrafficPurchase records whenever traffic_limit_gb was passed,
even when extending the same tariff (not changing). Now only resets
on actual tariff change (is_tariff_change=True), preserving purchased
traffic on same-tariff extensions.
2026-02-11 04:38:08 +03:00
Fringg
6cec024e46 fix: use flush instead of commit in server counter functions
add_user_to_servers and remove_user_from_servers were calling
db.commit() internally, breaking transaction atomicity for all
callers that perform additional operations afterward. Changed to
db.flush() so the caller controls the commit boundary.
2026-02-11 04:15:50 +03:00
Fringg
2094886990 fix: address review issues in backup, updates, and webhook handlers
- backup: add DATE column parsing in restore, use is_file() in delete_backup
- updates: add missing callback.answer() in show_updates_menu early return
- webhook: add server counter decrement and SubscriptionServer cleanup on user deletion, use single commit
2026-02-11 04:09:39 +03:00
Fringg
b0fd38d60c fix: clear subscription data when user deleted from Remnawave panel
Previously only status was set to expired and remnawave_uuid cleared.
Now also clears subscription_url, subscription_crypto_link,
remnawave_short_uuid, and connected_squads so the bot correctly
shows no active subscription after panel deletion.
2026-02-11 04:02:09 +03:00
Fringg
3a680b41b0 fix: suppress 'message is not modified' error in updates panel
- Remove dangling version_info['repo_url'] expression
- Handle 'message is not modified' in all three update handlers
  to prevent error screen on repeated button clicks
2026-02-11 03:47:30 +03:00
Fringg
02e40bd6f7 fix: expand backup coverage to all 68 models and harden restore
- Add 37 missing models to backup (payment providers, polls, contests,
  wheel, FAQ, promo offers, webhooks, configs, menu buttons, etc.)
- Add tariff_promo_groups and payment_method_promo_groups association tables
- Replace hardcoded association restore with generic handler
- Fix transaction atomicity: flush instead of commit in inner methods,
  remove inner rollback calls, single commit/rollback in outer handler
- Fix composite PK support for UserPromoGroup (was only detecting first PK)
- Fix duplicate insert bug when clear_existing=True and record already exists
- Add cabinet_refresh_tokens to clear list, fix support_audit_logs deletion order
- Add Time column parsing for ReferralContest.daily_summary_time
- Security: tarfile filter='data', path traversal protection in _restore_files
  and delete_backup, os.sep in startswith checks
2026-02-11 03:35:16 +03:00
Fringg
19dabf3851 fix: allow purchase when recalculated price is lower than cached
Only block purchase when the price increased (user would overpay).
When a promo discount activates between viewing price and confirming,
the recalculated price is lower — allow the purchase at the new price
instead of forcing the user to restart the checkout flow.
2026-02-11 02:30:40 +03:00
Fringg
eaf3a07579 fix: use callback fallback when MINIAPP_CUSTOM_URL is not set
Only consider MINIAPP_CUSTOM_URL for miniapp buttons, not the
purchase-only MINIAPP_PURCHASE_URL which cannot display subscription
info and loads indefinitely. When no custom URL is configured, fall
back to regular callback_data so the bot shows subscription natively.
2026-02-11 01:58:40 +03:00
Fringg
be1da976e1 fix: ignore 'message is not modified' on privacy policy decline
User clicking Decline twice produced the same edit_text causing
TelegramBadRequest. Silently ignore it and remove pointless retry.
2026-02-11 01:40:49 +03:00
Fringg
a1ffd5bda6 fix: prevent cascading greenlet errors after sync rollback
After db.rollback() all ORM objects expire. Subsequent attribute access
triggers lazy load in async context causing greenlet_spawn errors for
every remaining user. Break the sync loop after rollback instead of
continuing with a corrupted session.

Also downgrade TelegramNetworkError to warning in channel_checker.
2026-02-11 01:39:39 +03:00
Fringg
d58a80f3ea fix: handle StaleDataError in webhook when user already deleted
When a user is deleted via cabinet, RemnaWave sends user.disabled webhook
but the subscription row is already cascade-deleted. This caused
StaleDataError on commit + PendingRollbackError when logging user.id.

Save user_id before handler call and catch StaleDataError as warning.
2026-02-11 01:18:58 +03:00
Egor
45c7afe34c Update README.md 2026-02-11 01:03:58 +03:00
Fringg
e43a8d6ce4 fix: downgrade Telegram timeout errors to warning in monitoring service
Add TelegramNetworkError handling before generic Exception catch in all
notification methods to prevent timeout errors from generating error
reports in chat. Timeouts are transient network issues, not bugs.
2026-02-10 23:11:48 +03:00
Fringg
e94b93d0c1 fix: handle nullable traffic_limit_gb and end_date in subscription model
Add None-safety guards to Subscription model properties (is_active,
is_expired, should_be_expired, actual_status, days_left,
traffic_used_percent) and pricing handler comparisons to prevent
TypeError when nullable columns contain None values.
2026-02-10 20:35:42 +03:00
Egor
2ad26a9156 Merge pull request #2588 from BEDOLAGA-DEV/release-please--branches--main
chore(main): release 3.10.0
v3.10.0
2026-02-10 07:59:27 +03:00
github-actions[bot]
3383b9c790 chore(main): release 3.10.0 2026-02-10 04:58:24 +00:00
Egor
6acaf18203 Merge pull request #2587 from BEDOLAGA-DEV/dev
Release: dev → main
2026-02-10 07:57:54 +03:00
Fringg
019fbc12b6 fix: webhook:close button not working due to channel check timeout
Channel checker middleware called bot.get_chat_member() which could
timeout (60s), causing callback.answer() to fail with "query too old".

Skip channel check for lightweight UI callbacks (webhook:close,
ban_notify:delete, noop). Also answer callback before delete attempt
and add fallback to remove keyboard if delete fails.
2026-02-10 07:54:24 +03:00
Fringg
5156d635f0 fix: sync subscription status from panel in user.modified webhook
When subscription was extended in panel, webhook updated end_date but
left status as expired. Now syncs ACTIVE/DISABLED status from panel
payload when end_date is in the future.
2026-02-10 07:49:10 +03:00
Fringg
f77922522a fix: allow non-HTTP deep links in crypto link webhook updates
_is_valid_url only accepted http(s), silently dropping valid deep links
like happ://, vless://, ss:// from revoked webhook payloads.
Added _is_valid_link that accepts any URI scheme.
2026-02-10 07:41:32 +03:00
Fringg
0db00a8f90 style: format cryptobot.py with ruff 2026-02-10 07:33:42 +03:00
Fringg
fe54640885 fix: add missing placeholders to Arabic SUBSCRIPTION_INFO template
Was just a header text without {status}, {type}, etc. placeholders,
causing KeyError when .format() was called.
2026-02-10 07:30:52 +03:00
Fringg
ec8eaf52bf fix: downgrade transient API errors (502/503/504) to warning level
502/503/504 are transient errors that don't need ERROR reports in chat.
Also downgrade API connection test failure to warning.
2026-02-10 07:27:20 +03:00
Fringg
fe5f5ded96 feat: add MULENPAY_WEBSITE_URL setting for post-payment redirect
Previously website_url was hardcoded to WEBHOOK_URL, redirecting users
to the webhook endpoint after payment. Now configurable via env var.
2026-02-10 07:25:58 +03:00
Fringg
2cb6d731e9 fix: stop CryptoBot webhook retry loop and save cabinet payments to DB
Cabinet was calling CryptoBotService.create_invoice() directly without
saving CryptoBotPayment to DB. When webhook arrived, payment lookup
failed and returned HTTP 400, causing infinite retries.

Now cabinet uses PaymentService.create_cryptobot_payment() (same as
miniapp) with proper USD conversion via currency_converter.

Also return HTTP 200 for unknown invoice_ids to stop retry spam.
2026-02-10 07:25:54 +03:00
Fringg
184c52d4ea feat: webhook protection — prevent sync/monitoring from overwriting webhook data
Add last_webhook_update_at timestamp to Subscription model. When a webhook
handler modifies a subscription, it stamps this field. Auto-sync, monitoring,
and force-check services skip subscriptions updated by webhook within the
last 60 seconds, preventing stale panel data from overwriting fresh
real-time changes.

- Add last_webhook_update_at column + migration
- Stamp all 8 webhook handlers with commit in every code path
- Add is_recently_updated_by_webhook() guard in 12 sync/monitoring paths
- Add REMNAWAVE_WEBHOOK_* variables to .env.example
- Add webhook setup documentation to README with Caddy/nginx examples
- Fix pre-existing yookassa webhook test (mock AsyncSessionLocal)
2026-02-10 07:16:22 +03:00
Fringg
8e85e244cb feat: handle errors.bandwidth_usage_threshold_reached_max_notifications webhook
Last remaining unhandled RemnaWave backend event — sends admin
notification when the bandwidth threshold notification limit is reached.
2026-02-10 06:34:20 +03:00
Fringg
43a326a98c feat: handle service.subpage_config_changed webhook event
Add admin notification when subscription page config is
created, updated or deleted in RemnaWave panel.
2026-02-10 06:32:52 +03:00
Fringg
d9de15a5a0 feat: add close button to all webhook notifications
Add dismissible close button (✖️) to every webhook notification message.
Users can now close any webhook notification by tapping the button,
which deletes the message via webhook:close callback handler.
2026-02-10 06:22:17 +03:00
Fringg
17ce64037f fix: build composite device name from platform + hwid short suffix
Show "tag (platform)" when tag is set, "iOS (ab12cd34)" when only
platform and hwid available, or just platform as last resort.
2026-02-10 06:16:37 +03:00
Fringg
79793c47bb fix: extract device name from nested hwidUserDevice object
RemnaWave sends device info in data.hwidUserDevice, not top-level.
Try tag, deviceName, platform, hwid fields from nested object first.
2026-02-10 06:14:01 +03:00
Fringg
7091eb9c14 fix: add action buttons to webhook notifications and fix empty device names
- Add keyboard buttons to all webhook notifications: renew, connect,
  my subscription, buy traffic — context-appropriate per event type
- Extract device name from multiple possible payload fields (deviceName,
  tag, hwid, device, platform, name) with fallback to dash
- Log payload keys for device events to identify correct field names
2026-02-10 06:09:00 +03:00
Fringg
dc1e96bbe9 fix: security and architecture fixes for webhook handlers
- Add html.escape() to all untrusted webhook data in admin and device
  notifications (prevents HTML/Telegram injection)
- Add public send_webhook_notification() and is_enabled property to
  AdminNotificationService (eliminates private method access)
- Add dedicated NotificationType enum values for device and not_connected
  events (fixes incorrect semantic mapping)
- Extend user resolution to handle nested user objects and userUuid for
  device-scope events
- Replace manual __anext__() DB session with AsyncSessionLocal context
  manager; skip DB session for admin-only events
- Replace deprecated datetime.utcnow() with datetime.now(UTC)
- Use db.flush() instead of db.commit() in handlers (router commits)
- Wrap _notify_user in try/except to prevent notification failures from
  rolling back successful DB mutations
2026-02-10 05:55:48 +03:00
Fringg
1e37fd9dd2 feat: add all remaining RemnaWave webhook events (node, service, crm, device)
Handle all 44 webhook events: admin alerts for node health (connection
lost/restored), service security (login attempts), CRM billing reminders,
plus user-facing device added/deleted and not_connected notifications
with localized messages across all 5 languages.
2026-02-10 05:47:35 +03:00
Fringg
9aa22af339 fix: use event field directly as event_name (already includes scope prefix)
RemnaWave sends event as "user.modified", not "modified".
Concatenating scope + event produced "user.user.modified" which
didn't match any handler keys.
2026-02-10 05:31:39 +03:00
Fringg
26637f0ae5 feat: unified notification delivery for webhook events (email + WS support)
- Replace direct bot.send_message with notification_delivery_service
- Email-only and OAuth users now receive webhook notifications via email/WS
- Add 10 new NotificationType enum values for webhook subscription events
- Map all webhook text_keys to NotificationType for unified routing
2026-02-10 05:16:59 +03:00
Fringg
6d67cad3e7 feat: add RemnaWave incoming webhooks for real-time subscription events
- Add FastAPI webhook endpoint with HMAC-SHA256 signature verification
- Handle 16 user events: expired, disabled, enabled, limited, traffic_reset,
  modified, deleted, revoked, created, expires_in_72h/48h/24h,
  expired_24h_ago, first_connected, bandwidth_threshold
- URL validation for subscription_url/subscription_crypto_link (XSS prevention)
- 64KB body size limit, 32-char minimum secret enforcement
- Sanitized percent value in bandwidth threshold notifications
- DB rollback on handler errors to prevent dirty session commits
- Localization for all 5 languages (ru, en, ua, zh, fa)
2026-02-10 05:13:39 +03:00
Fringg
90d9df8f0e fix: preserve payment initiation time in transaction created_at
Transaction created_at and completed_at showed identical timestamps
because webhook handlers created transactions with is_completed=True
in a single step. Now all 10 payment providers pass payment.created_at
to the transaction so created_at reflects when the user initiated
the payment, not when the webhook processed it.

Also: remove duplicate datetime import in inline.py, upgrade button
stats DB error logging from debug to warning, add index on
button_click_logs.button_type for analytics queries.
2026-02-10 04:26:23 +03:00