Commit Graph

1204 Commits

Author SHA1 Message Date
Fringg 7dd67e36b3 feat: add tariff identification to all notifications for multi-tariff mode
When MULTI_TARIFF_ENABLED=true users can have multiple subscriptions,
so notifications must identify which tariff they relate to.

- Webhook notifications: _notify_user auto-injects tariff_label from
  subscription.tariff.name into all 16 webhook notification strings
- Monitoring service: tariff labels in expired, expiring, trial ending,
  follow-up waves, autopay success/failed notifications
- Daily subscription: tariff in insufficient balance notification
- Recurrent payments: tariff in card autopay success/failed
- Auto-purchase: tariff in all 4 auto-purchase notification paths,
  with pre-captured names to avoid MissingGreenlet after db.commit()
- Channel checker: plural form for multi-subscription deactivation
- Payment providers: tariff in YooKassa and Stars activation messages
- Admin: tariff in bulk expiry reminder
- Localization: {tariff_label} in 20 notification keys across all 5
  locales (ru, en, ua, zh, fa) + _MULTI channel keys
- Fix: selectinload(Subscription.tariff) in trial expiring query
- Fix: capture tariff_name before expire_subscription to prevent
  MissingGreenlet from db.refresh() expiring ORM relationships
2026-03-28 19:12:34 +03:00
c0mrade 565c08366b feat: add Remnawave panel 2.7.0 API support
- Add MONTH_ROLLING traffic reset strategy (enum, UI, mapping)
- Fix squad remove-users HTTP method POST → DELETE
- Add forceRestart param to restart_all_nodes (API, service, routes)
- Replace removed /bandwidth-stats/nodes/realtime with /system/nodes/metrics
- Fix parse_bytes suffix matching bug (B was catching GB/MB/KB/TB)
- Add torrent_blocker.report webhook event with admin notifications
- Add subpage_config_changed → auto-invalidate app config cache
- Pass webhook meta field to handlers (notConnectedAfterHours)
- Improve CRM/login webhook formatting (providerName, loginAttempt)
- Update bandwidth display: remove /s suffix, show inbound traffic totals
2026-03-28 12:34:15 +03:00
c0mrade 34b5a9ab3a refactor: remove dead multi-tariff check in guest purchase activation
The else branch of is_multi_tariff_enabled() contained a duplicate
is_multi_tariff_enabled() check that could never execute.
2026-03-27 13:11:51 +03:00
c0mrade 181ef1501b fix: test access promo applies to all active subscriptions in multi-tariff
Previously test squad was added to only one subscription. Now iterates
all active non-daily subscriptions and adds the test squad to each,
creating SubscriptionTemporaryAccess entries per subscription and
syncing each with RemnaWave.
2026-03-27 12:31:12 +03:00
c0mrade cd6913cb84 fix: block classic subscription renewal/autopay when tariff mode enabled
Classic subscriptions (without tariff_id) now cannot be renewed or
auto-renewed when tariff mode is active. Users must purchase a tariff.

Blocked in: cabinet renewal endpoints, cabinet autopay, bot autopay
toggle, and auto-purchase service.
2026-03-27 11:23:45 +03:00
c0mrade 3bbcc1b560 style: ruff format 2026-03-26 20:20:09 +03:00
c0mrade b8662b8bf6 fix: trial promo extends existing subscription with same tariff
Instead of blocking when user already has the trial tariff, extend
that subscription by the promo days.
2026-03-26 20:17:25 +03:00
c0mrade 63e4296197 feat: add tariff_id to promo codes for trial subscription type
- Add tariff_id column to promocodes table (migration 0052)
- Admin can now select any tariff when creating trial_subscription promo
- Activation uses promocode.tariff_id if set, falls back to system
  trial tariff
- Multi-tariff: blocks trial only if user already has that specific tariff
2026-03-26 20:09:20 +03:00
c0mrade 3cbe09ddd5 fix: promocode system broken in multi-tariff mode
- Remove duplicate `from app.config import settings` inside function that
  shadowed the module-level import, causing UnboundLocalError for all
  SUBSCRIPTION_DAYS and TRIAL_SUBSCRIPTION promo types
- TRIAL_SUBSCRIPTION now resolves trial tariff via get_trial_tariff() /
  TRIAL_TARIFF_ID and passes tariff_id, traffic_limit_gb, device_limit,
  connected_squads to create_trial_subscription (was creating bare trial
  without any tariff params)
- Multi-tariff: trial promo only blocks if user already has the specific
  trial tariff, not any active subscription
- Cabinet endpoint now accepts subscription_id and returns
  select_subscription response for multi-tariff SUBSCRIPTION_DAYS promos
2026-03-26 19:43:22 +03:00
c0mrade a12ffb1d6c fix: delete subscription from RemnaWave panel + prevent phantom webhook notifications
- Replace disable_remnawave_user() with delete_remnawave_user() on subscription deletion
  so the panel stops sending webhooks for deleted subscriptions
- Add early return in all webhook handlers when subscription is None (already deleted from DB):
  expired, disabled, enabled, limited, traffic_reset, revoked, expiring reminders
- Add "Delete subscription" button in Telegram bot for expired/disabled subscriptions
  with confirmation step and full cleanup (panel delete + server counts + DB hard delete)
2026-03-26 15:59:36 +03:00
Fringg 6d9bd9915c fix: persist referral to Redis on /start to prevent loss when user opens miniapp
When user clicks /start ref_CODE, the referral code was stored only in
FSM state. If the user opened miniapp/cabinet before completing bot
registration, the referral was lost.

Now /start immediately saves pending_referral:{telegram_id} to Redis
(7-day TTL). The referral is consumed by whichever path creates the
user first — bot create_user(), cabinet auth (initdata/widget/oidc).
Redis key is cleared after consumption to prevent double-referral.

- referral_service: save/get/clear_pending_referral Redis helpers
- start.py: save pending referral for new users only
- crud/user.py: create_user checks Redis if no referred_by_id
- cabinet/auth.py: initdata/widget/oidc routes check + cleanup Redis
2026-03-26 11:55:20 +03:00
Fringg 94ed282381 fix: multi-tariff MEDIUM/LOW batch — 20 issues across 17 files
MEDIUM fixes:
- config: MAX_ACTIVE_SUBSCRIPTIONS=10 limit + check in tariff purchase
- tariff_purchase: add_user_balance return check → _persist_failed_refund
- tariff_purchase: promo restoration atomic with refund (commit=False)
- purchase: handle_toggle_daily_subscription_pause multi-tariff guard
- my_subscriptions: respect HIDE_SUBSCRIPTION_LINK setting
- multi_tariff: delete_subscription uses actual_status
- tariff_switch: guard against switching to already-owned tariff
- monitoring: i18n button texts via texts.t() + per-subscription dedup
- promo_offer_service: multi-tariff subscription resolution
- bot.py: register DisplayNameRestrictionMiddleware + SubscriptionStatusMiddleware on pre_checkout_query
- crud/user: add_user_balance accepts commit=False
- crud/subscription: update_daily_charge_time refresh only on commit, autopay days_before preserved
- daily_subscription: remove redundant outer commit in process_traffic_resets

LOW fixes:
- inline.py: remove dead get_subscription_expiring_keyboard function
- promocode_service: current_uses +1 in response (post-increment)
- yookassa: use already-resolved subscription for admin notification
- subscription_utils: remove dead ensure_single_subscription + update_or_create_subscription
- devices: merge identical daily/non-daily branches in confirm_change_devices
2026-03-26 11:00:15 +03:00
Fringg fec374edba chore: ruff format 2026-03-26 10:34:14 +03:00
Fringg 58d899aab8 fix: renewal status check, int() safety, daily charge atomicity
- renewal.py: block renew/renewal-options for PENDING/DISABLED subscriptions
  (extend_subscription doesn't transition these to ACTIVE — user would pay
  for nothing)
- autopay.py: wrap 2x bare int() card_id parsing in try/except
- devices.py: wrap 2x bare int() device_count parsing in try/except
- daily_subscription_service: atomic daily charge — subtract_user_balance,
  create_transaction, update_daily_charge_time all use commit=False, single
  db.commit() after all three succeed. Prevents re-charge on partial failure.
- subscription.py: update_daily_charge_time accepts commit=False kwarg
2026-03-26 10:05:52 +03:00
Fringg b0273dc8ae fix: account merge no longer nulls transferred subscriptions' remnawave_uuid
In multi-tariff mode, _handle_subscription_merge transfers ALL secondary
subscriptions to primary. Step 14 then iterated stale secondary.subscriptions
and nulled their remnawave_uuid, breaking the panel link for transferred subs.
Removed the UUID-nulling loop since all subs are already on primary.
2026-03-26 09:38:04 +03:00
Fringg 948e4791f4 fix: multi-tariff Stage 5 fixes — auth sync, notifications, cart, race guard
HIGH fixes:
- auth.py: profile description sync now iterates all per-subscription
  remnawave_uuids in multi-tariff mode
- admin_users: sync_from_panel uses subscription UUIDs for panel lookup,
  does not overwrite user.remnawave_uuid in multi-tariff

MEDIUM fixes:
- monitoring_service: _send_subscription_expired_notification now takes
  subscription param, uses se:{sub_id} in multi-tariff
- remnawave_webhook_service: _get_renew_keyboard accepts subscription_id,
  all 7 callers pass it
- recurrent_payment_service: _build_extend_keyboard with subscription_id
- user_service: balance notification keyboards use menu_subscription in
  multi-tariff instead of bare subscription_extend
- autopay.py + purchase.py: per-subscription cart deletion instead of
  global delete_user_cart where subscription context available
- subscription_auto_purchase_service: 60-sec race guard changed from
  per-user to per-subscription (checks subscription.updated_at)
2026-03-26 09:15:50 +03:00
Fringg a49e52cc92 fix: multi-tariff Stage 4 critical fixes — keyboards, guest purchase, monitoring, tariff deletion
- inline.py: open_subscription_link/subscription_connect callbacks now include
  :{subscription_id} suffix in multi-tariff mode. Main menu uses subscription_connect
  (picker) instead of bare open_subscription_link.
- guest_purchase_service: activate_purchase non-tariff path uses proper ordering
  (non-daily, max days_left) instead of arbitrary _active[0]
- monitoring_service: _send_expired_day1_notification and discount notification
  keyboards use se:{subscription.id} in multi-tariff (2 more hardcoded callbacks fixed)
- admin/tariffs: delete_tariff_confirmed now checks active subscription count
  before deletion (RESTRICT FK). Prompt shows blocking message when active subs exist.
  New CRUD function get_active_subscriptions_count_by_tariff_id.
2026-03-26 08:46:00 +03:00
Fringg aa7e461c44 fix: multi-tariff Stage 3 HIGH fixes — phantom, cart, yookassa, auto-extend
- phantom_service: iterate all subscriptions for panel sync after claim
  (was using deprecated user.subscription singular property)
- tariff_purchase: 6x delete_user_cart replaced with per-subscription
  delete_subscription_cart in multi-tariff mode
- yookassa: recurrent payment subscription_id mismatch now resolves
  correct subscription from metadata instead of just logging warning
- subscription_auto_purchase: try_auto_extend_expired and
  try_resume_disabled_daily now query ALL subs (not just active) to find
  expired/disabled subscriptions that need processing
2026-03-26 08:31:49 +03:00
Fringg 49db5f5eed fix: multi-tariff Stage 3 critical fixes — panel sync UUID, admin grant, wheel
- remnawave_service: sync_users_to_panel uses sub.remnawave_uuid in
  multi-tariff instead of user.remnawave_uuid (was targeting wrong panel user)
- admin/users: admin_buy_subscription_execute saves UUID to
  subscription.remnawave_uuid in multi-tariff (was saving to user)
- wheel_service: _process_days_payment and _apply_prize require subscription
  in multi-tariff mode, fallback converts to balance bonus for prizes
2026-03-26 08:26:09 +03:00
Fringg c6bedc6a06 fix: multi-tariff Stage 2 HIGH fixes — 18 issues across 12 files
Bot handlers (H1-H5):
- confirm_extend_subscription: error alert instead of wrong sub fallback
- open_subscription_link/subscription_connect: startswith registration
- handle_subscription_settings: multi-tariff guard
- confirm_reset_traffic: FSM state check in multi-tariff

Services (H6-H12):
- subscription_service: 5 UUID fallback fixes — no user.remnawave_uuid in
  multi-tariff, return None if subscription.remnawave_uuid missing
- auto_purchase: use cart subscription_id for tariff match
- remnawave_service: migrate_squad_users checks subscription.remnawave_uuid
- campaign_service: extend existing sub or create new in multi-tariff
- broadcast_service: check ALL subs for paid-subscription guard
- blocked_users_service: remnawave_uuids list, iterate in cleanup
- user_service: log sub.remnawave_uuid in multi-tariff

Admin (H13-H16):
- grant_trial/paid_subscription: allow in multi-tariff mode
- promo_offers: pick sub with URL, aggregate squads from all subs

CRUD/Frontend (H17-H18):
- get_users_list: .unique() for outerjoin dedup
- refreshTraffic: withSubId in params instead of body
2026-03-26 08:09:07 +03:00
Fringg 4259ba1cb5 fix: multi-tariff Stage 2 critical fixes — panel sync, guest purchase, cart isolation
CRITICAL fixes:
- remnawave_service: panel_user.uuid AttributeError (3 places) — dict needs
  .get('uuid'), not .uuid attribute access. Silent fail caused duplicate subs.
- remnawave_service: removed traffic_limit_gb, device_limit, connected_squads
  overwrites from panel sync — bot is source of truth for these fields
- guest_purchase_service: multi-tariff now checks per-tariff (not any active
  sub), allowing purchase of different tariffs simultaneously
- subscription_auto_purchase_service + user_cart_service: per-subscription cart
  storage via user_cart:{user_id}:sub:{sub_id} keys. Cart resolution no longer
  falls through to heuristic when saved_subscription_id lookup fails.
  _delete_cart_for_subscription replaces delete_user_cart in all paths.
2026-03-26 07:57:59 +03:00
Fringg 5724906517 fix: multi-tariff code review — 13 critical/high bugs fixed across 14 files
CRITICAL fixes:
- promocode_service: NameError (subscription_id not passed), TypeError (dict
  returns), savepoint without commit, dead else branch
- cabinet status/autopay/renewal: resolve_subscription() instead of
  user.subscription fallback in multi-tariff mode
- cabinet devices: MultipleResultsFound crash on 3 POST endpoints
- webhook service: IDOR returning cross-user subscription
- monitoring_service: real expiring notification keyboard with se:{sub_id}

HIGH fixes:
- subscription_purchase_service: FOR UPDATE on both branches of submit_purchase
- miniapp: 8 endpoints now pass subscription_id to _ensure_paid_subscription
- inline.py: se:{subscription_id} callback for expiring keyboard
- tariff_purchase: TransactionType.FAILED_REFUND + _persist_failed_refund()
- account_merge_service: panel sync after subscription transfer
- webhook service: .limit(1) on fallback queries to prevent MultipleResultsFound
2026-03-26 07:26:53 +03:00
c0mrade dbe247ba6f fix: multi-tariff sync auto-links legacy user-level UUIDs to subscriptions
When sync finds a panel user whose UUID matches User.remnawave_uuid
(legacy single-tariff) but not any Subscription.remnawave_uuid, it now
auto-links that UUID to the user's best active non-daily subscription.
This handles migration from single-tariff to multi-tariff mode without
losing panel user associations.
2026-03-25 17:16:12 +03:00
c0mrade c3c2b8137b fix: RemnaWave sync finds user by Subscription.remnawave_uuid in multi-tariff
get_user_by_remnawave_uuid: fallback query searches Subscription table
when User-level UUID not found (multi-tariff stores UUID per-subscription).

Webhook _resolve_user_and_subscription: direct Subscription lookup before
returning None when user not found by telegram_id or User.remnawave_uuid.

Webhook user.deleted: refresh user.subscriptions before iterating to
ensure relationship is loaded from DB.

Account merge: clear subscription-level remnawave_uuid/short_uuid on
secondary user's subscriptions to prevent orphaned panel users.
2026-03-25 17:05:12 +03:00
c0mrade 25b853d629 fix: post-payment keyboard checks all subscriptions instead of LIMIT 1
The MissingGreenlet fallback path in build_topup_success_keyboard
now queries all active/trial subscriptions and checks if ANY is active
paid, instead of only checking the most recently created one.
2026-03-25 16:30:01 +03:00
c0mrade 05d1ae0560 fix: notifications include tariff name for multi-subscription clarity
Expiry, autopay success, daily charge, and traffic reset notifications
now append tariff name when multi-tariff is enabled, so users know which
subscription the notification is about.
2026-03-25 15:11:39 +03:00
c0mrade fe03b587db fix: UUID warnings, phantom merge, yookassa validation, contest prize notification
channel_member: warns on UUID fallback in multi-tariff.
start.py: phantom merge checks subscription-level UUIDs before user-level transfer.
yookassa: validates subscription_id from recurrent payment metadata.
contest prize: notification includes tariff name for multi-subscription clarity.
2026-03-25 11:47:02 +03:00
c0mrade afd7b6d7ec fix: remnawave service uses per-subscription UUID throughout multi-tariff
Squad sync, user sync, UUID assignment, force_cleanup all use
subscription.remnawave_uuid in multi-tariff. Fallback _subs[0] replaced
with smart selection. phantom_service refreshes 'subscriptions' (plural).
2026-03-25 11:46:55 +03:00
c0mrade f89e326a19 fix: auto-purchase processes each autopay subscription independently
Instead of skipping when multiple active subscriptions exist, auto-purchase
now selects the subscription with autopay_enabled and most urgent renewal
(fewest days left). Handles single/multiple autopay subscriptions correctly.
2026-03-25 11:46:50 +03:00
c0mrade 0866c2ea4b fix: services use smart subscription selection + per-subscription UUID
promocode, campaign, guest_purchase, subscription_purchase, user_service,
daily_subscription — all replace active_subs[0] with best non-daily selection.
daily_subscription_service uses subscription.remnawave_uuid in multi-tariff.
2026-03-25 11:46:42 +03:00
c0mrade d2bbeb8624 fix: contest prize applies to best non-daily subscription in multi-tariff 2026-03-24 21:36:31 +03:00
c0mrade 6d468e9ada fix: multi-subscription support for promocodes, contests, phantom merge
Promocodes with days:
- activate_promocode accepts subscription_id parameter
- Multi-tariff + >1 eligible subs: returns select_subscription for UI
- Bot handler: shows subscription picker keyboard, callback applies to chosen sub
- Single sub: auto-applies as before

Contests:
- _resolve_subscription_for_prize: prefers non-daily sub with most days_left
- All 5 contest endpoints use shared resolver

Phantom service:
- merge_phantom_into_user: uses subscriptions collection instead of single
- sync_remnawave_after_phantom_merge: syncs all subscriptions, not just first
2026-03-24 21:29:17 +03:00
c0mrade 34bb87c7ba fix: import Subscription in wheel_service to fix NameError 2026-03-24 14:44:34 +03:00
c0mrade 24edfb6c3f feat: wheel subscription picker for multi-tariff mode
- SpinAvailability returns eligible_subscriptions (non-daily, enough days)
- spin() accepts subscription_id to target specific subscription
- _process_days_payment and _apply_prize use provided subscription
- WheelConfigResponse includes eligible_subscriptions for frontend picker
- SpinRequest accepts subscription_id in body
- Daily tariffs excluded from wheel eligibility
2026-03-24 14:31:19 +03:00
c0mrade 048d208bc1 feat: trial lifecycle + purchase-options filter for multi-tariff
Trial:
- create_trial_subscription: autopay_enabled=False always
- autopay endpoint: block enabling autopay for trial subscriptions
- Purchase flows: deactivate all user trials on paid purchase,
  transfer remaining days if TRIAL_ADD_REMAINING_DAYS_TO_PAID,
  disable trials on RemnaWave panel

Purchase options:
- Return is_purchased per tariff and all_tariffs_purchased flag
  in multi-tariff mode for frontend filtering
2026-03-24 10:47:14 +03:00
c0mrade 344852b852 fix: trial subscription lifecycle — autopay, cleanup on purchase, bonus days
- create_trial_subscription: always set autopay_enabled=False (trial is a
  probe, autopay makes no sense regardless of operator default setting)
- autopay endpoint: block enabling autopay on trial subscriptions via API
- purchase-tariff (cabinet): before creating/extending paid subscription,
  find and deactivate ALL user's trial subscriptions, collect remaining
  time for TRIAL_ADD_REMAINING_DAYS_TO_PAID, disable trials on RemnaWave
  panel, decrement server counts — works for both tariff-based and
  squad-based trials uniformly
- subscription_purchase_service (miniapp): same trial cleanup logic
- New CRUD: deactivate_user_trial_subscriptions() — finds all active
  trials for user, marks them disabled with is_trial=False
2026-03-23 23:11:18 +03:00
c0mrade 78a7eafcb6 fix: prevent sync from overwriting wrong subscription traffic in multi-tariff mode
Remove fallback to first subscription when panel user UUID doesn't match
any subscription. Previously, _subs_upd[0] was used as fallback, causing
panel data (including traffic_used_gb=0 after reset) from one subscription
to overwrite another subscription's data during periodic sync.
2026-03-23 21:15:06 +03:00
c0mrade d87fb47e88 fix: multi-subscription UUID resolution and ownership validation
- Add _resolve_panel_uuid helper for per-subscription UUID in multi-tariff mode
- Add user ownership validation (user_id check) to all subscription queries
- Add unique partial index on (user_id, tariff_id) for active subscriptions
- Generate remnawave_short_id for new subscriptions in all creation paths
- Fix trial endpoints to check all user subscriptions, not just first
- Fix channel member handler to enable/disable per-subscription UUIDs
- Fix channel checker middleware for multi-subscription iteration
- Fix tariff switch, traffic, and device endpoints to use correct panel UUID
- Fix monitoring, auto-purchase, renewal services for multi-subscription
- Fix user_service, miniapp, subscriptions and users webapi routes
2026-03-23 18:45:46 +03:00
c0mrade 82958801b5 fix: harden webhook signature verification across all payment providers
- Replace fail-open with fail-closed in CryptoBot, Heleket, Tribute webhooks
  (missing API key now rejects instead of accepting)
- Use hmac.compare_digest for timing-safe comparison in Freekassa, KassaAI,
  Pal24, and Platega webhook verification
- CloudPayments: reject webhooks when signature header is missing but
  API_SECRET is configured (check, pay, fail, universal endpoints)
- CloudPayments: return code 13 (reject) instead of code 0 on parse errors
  and exception handlers to prevent fail-open
2026-03-23 18:45:46 +03:00
c0mrade d071269b8c fix: comprehensive multi-subscription audit fixes across routes, handlers, and services
- Fix UUID resolution in monitoring and webhook services for multi-tariff mode
- Update cabinet routes to properly resolve per-subscription UUIDs
- Fix account merge service for multi-tariff subscription transfers
- Update admin handlers (users, promo_offers, servers) for multi-subscription
- Fix traffic, devices, servers, daily subscription modules
- Update payment handlers (stars, yookassa) and purchase services
- Fix broadcast, promocode, and subscription auto-purchase services
- Add multi-subscription support to keyboards and localization
- Fix CRUD operations for subscription queries
- Resolve code quality issues (ruff linting)
2026-03-23 18:40:23 +03:00
c0mrade 18f31c565c fix: validate_and_clean_subscription uses per-subscription UUID in multi-tariff mode, not user-level UUID 2026-03-23 18:40:23 +03:00
c0mrade 335be66980 feat: multi-subscription support (1 user = N subscriptions)
- Add MULTI_TARIFF_ENABLED feature flag for gradual rollout
- Migration 0041: remove unique constraint on subscriptions.user_id
- Migration 0042: add remnawave_short_id (NOT NULL, UNIQUE) to subscriptions
- Each subscription gets its own Remnawave user (user_{tg_id}_{short_id})
- Add _resolve_subscription() to all 30+ bot handlers for per-subscription routing
- Add my_subscriptions.py with list/detail views and delegation handlers
- Refactor cabinet subscription.py (4687 lines -> 10 focused modules)
- Add subscription_id parameter to all cabinet API endpoints
- Adapt all services: autopay, wheel, contests, campaign, guest purchase,
  monitoring, blocked users, account merge, promo codes, payments
- Replace all user.subscription (singular) with user.subscriptions iteration
- IDOR protection via get_subscription_by_id_for_user on all endpoints
- Full backward compatibility: MULTI_TARIFF_ENABLED=False = legacy behavior
2026-03-23 18:37:17 +03:00
Fringg 8175bc8bfe fix: comprehensive security hardening across payment and API layers
- CloudPayments: require webhook signature when secret configured (all 4 handlers)
- Platega: timing-safe HMAC comparison via hmac.compare_digest
- CryptoBot/Heleket: return False when API token unconfigured
- Tribute: return 503 when API key not configured
- Freekassa: use request.client.host instead of X-Forwarded-For
- Pal24: verify webhook amount matches stored payment amount
- YooKassa: reject test-mode payments in production; add YOOKASSA_TEST_MODE config
- CloudPayments: reject test-mode payments in production
- WebAPI: add upper bounds to duration_days, traffic_limit_gb, device_limit schemas
- WebAPI: bound balance update amount to ±100M kopeks
- WebAPI: sign-dispatch for balance updates (negative → subtract_user_balance)
- WebAPI miniapp: add blocked/deleted user checks, restriction_topup/subscription guards
- Admin handlers: add @admin_required and @error_handler to moderator panel
- add_user_balance: guard against negative amounts (use subtract_user_balance instead)
2026-03-23 16:44:38 +03:00
Fringg 4660ca5756 fix: validate period_days against tariff in purchase-tariff and auto-purchase
Critical security fix: the POST /cabinet/subscription/purchase-tariff
endpoint accepted arbitrary period_days from client without validating
against the tariff's configured periods. The pricing engine returned 0
for unknown periods, allowing free subscription creation.

Changes:
- Add period_days whitelist validation in /purchase-tariff endpoint
- Add zero-price safety guard as defense in depth
- Add period_days validation in _auto_purchase_tariff (saved cart)
- Add period_days validation in _prepare_auto_extend_context (saved cart)
2026-03-23 16:35:18 +03:00
Fringg f0cdd5dc90 fix: validate FK existence, add FK indexes, expand video brand whitelist
- Validate category_id/tag_id exist before creating/updating articles (422 not misleading 409)
- Add indexes on news_articles.category_id and tag_id for efficient FK lookups
- Expand MP4 brand whitelist: iso2-4, qt (MOV/iPhone), 3gp, M4VH/VP, MSNV, NDAS/C/H/S/M/P
- Log unknown ftyp brands for debugging rejected video uploads
2026-03-23 15:40:16 +03:00
Fringg 2f19c76357 fix: add user ID to payment descriptions for all providers and fix tuple bug
- Add telegram_user_id and user_db_id to get_balance_payment_description() across
  8 cabinet balance providers (heleket, mulenpay, pal24, wata, cloudpayments,
  freekassa, kassa_ai, riopay), all miniapp endpoints, and recurrent payments
- For email/OAuth users without telegram_id, fallback to DB ID with (U{id}) format
- Fix pre-existing tuple bug in bot_configuration.py (trailing comma created tuple)
- Fix typo in nalogo_queue_service.py log message ("Чек уже попыток")
2026-03-23 14:48:12 +03:00
Fringg 6658af6268 refactor: extract phantom service, replace lightweight merge with execute_merge
- Extract _claim_phantom_user and _merge_phantom_into_active_user from
  start.py into app/services/phantom_service.py
- Replace lightweight 3-4 table merge with full execute_merge (30+ tables)
- Add durable AdminAuditLog records for phantom claims and merges
- Use begin_nested() savepoints for audit log writes (session-safe)
- Move Remnawave panel sync to after commit (no HTTP inside locked txn)
- Fix remnawave_uuid transfer in account_merge_service with two-flush
  pattern (clear→flush→assign) to prevent unique constraint violations
- Add db.refresh after rollback in Path A to prevent stale object access
2026-03-23 14:26:36 +03:00
Fringg fad77f8c80 fix: phantom user merge on claim failure, referral assignment, account merge hardening
- Fix orphaned subscriptions/GuestPurchase when phantom claim fails with
  IntegrityError — now merges phantom into existing user across all 3 call sites
- Add explicit db.commit() after merge in both active-user and registration paths
- Fix remnawave_uuid transfer ordering (clear→flush→assign) to prevent unique
  constraint violation during flush
- Clear phantom.referral_code on soft-delete to prevent unique constraint issues
- Add status != DELETED filter to find_phantom_user_by_username (defense in depth)
- Add WARNING-level logging on phantom claims for admin audit trail
- Add functional index on lower(username) for phantom lookup performance (migration 0048)
- Add ON DELETE CASCADE to subscription_servers.subscription_id (migration 0047)
- Add admin endpoint POST /users/{id}/assign-referrer with recursive CTE cycle
  detection, self-enrichment prevention, and audit logging
- Harden account_merge_service: add SubscriptionServer, RioPayPayment,
  SeverPayPayment, SavedPaymentMethod, GuestPurchase, NewsArticle handling
- Fix logger key typo get= → error= in promocode activation
2026-03-23 13:59:38 +03:00
Fringg 172924df0e fix: catch DecompressionBombError, hoist MP4 brands to module level
- Add PIL.Image.DecompressionBombError to except clause (inherits from
  Exception, not ValueError/OSError — was escaping as unhandled 500)
- Move _MP4_VIDEO_BRANDS to module-level frozenset for consistency
2026-03-23 13:05:11 +03:00
Fringg 7ff73e8492 fix: reject HEIC as MP4, close UploadFile, narrow exception handling
- Add ftyp brand allowlist to reject HEIC/HEIF files misclassified as MP4
- Close UploadFile after read to release resources during processing
- Add exception chaining (from None) on HTTPException raises
- Narrow except to ValueError/OSError (let programming errors propagate)
- Add exc_info=True to thumbnail failure log for debuggability
- Type detect_file_type return as tuple[MediaType, str]
2026-03-23 12:42:21 +03:00