Commit Graph

7100 Commits

Author SHA1 Message Date
Egor ba05c5ce92 Merge pull request #2684 from BEDOLAGA-DEV/main
w
2026-03-07 07:16:05 +03:00
Fringg 372d628908 fix: remove executable bit from email_service.py 2026-03-07 07:14:52 +03:00
Fringg ceac29d5e3 style: format admin_landings.py 2026-03-07 07:13:01 +03:00
Egor 3ee108fce8 Merge pull request #2658 from thegrayfoxxx/fix_logo_from_show_qr
fix: reset QR photo when returning to referral
2026-03-07 07:09:38 +03:00
Egor de541ea1c3 Merge pull request #2682 from smediainfo/fix/email-message-id-headers
fix: add Message-ID and Date headers to outgoing emails
2026-03-07 07:08:37 +03:00
Fringg 6d65e15266 fix: read discount overrides from landing model instead of response DTO
After removing overrides from the public LandingDiscountInfo response,
_load_landing_tariffs still referenced discount.overrides which no
longer exists. Read from landing.discount_overrides directly.
2026-03-07 07:05:38 +03:00
Fringg aa7d98630d feat: add discount system for landing pages
Add time-bounded percentage discounts with per-tariff overrides
and countdown timer support for landing pages.

- Add 5 discount columns to LandingPage model (percent, overrides,
  starts_at, ends_at, badge_text) with Alembic migrations 0027-0028
- Add DB CHECK constraints for discount_percent range and date ordering
- Add discount price calculation in validate_and_calculate() and
  public landing config endpoint with consistent formula
- Add admin CRUD with Pydantic validation, cascade-clear on removal,
  merged date validation on partial updates, size bounds
- Remove discount_overrides from public API (baked into prices)
- Add size limits for allowed_tariff_ids and allowed_periods
2026-03-07 07:01:35 +03:00
Fringg 8b77cdae2c feat: add GET /admin/rbac/users endpoint for listing all RBAC users 2026-03-07 06:23:14 +03:00
Fringg c93dbec7a0 feat: add landings to permission registry
Register landings:read, landings:create, landings:edit, landings:delete
permissions so they appear in the role editor permission matrix.
2026-03-07 06:05:03 +03:00
Fringg fa21549cac fix: add activate hint to gift pending activation email link
Append ?activate=1 to success page URL in recipient notification email
for gift purchases with pending_activation status, so the frontend can
distinguish buyer from recipient and show the activate button only to
the recipient.
2026-03-07 06:00:29 +03:00
Fringg c10d6780ba feat: add external squad support for tariffs
- Add external_squad_uuid column to Tariff model with Alembic migration
- Add external_squad_uuid parameter to RemnaWave API create_user/update_user
- Pass external squad from tariff to RemnaWave on subscription creation/update
- Sync external squad in monitoring service, sync service, admin user management
- Clear external squad when tariff has none (consistent across all call sites)
- Add GET /available-external-squads endpoint with UUID validation and response model
- Update tariff schemas with UUID pattern validation
- Fix db.refresh to include tariff relationship for async safety
2026-03-07 05:44:19 +03:00
Fringg 770f19e846 fix: address review findings for guest purchase admin notifications
- Remove double html.escape on payment_method (already escaped in helper)
- Use attribute_names=['landing'] keyword arg in db.refresh for consistency
- Use async with Bot() for guaranteed session cleanup
2026-03-07 04:48:52 +03:00
Fringg dbb9757a3c feat: add admin topic notifications for landing page purchases
- New send_guest_purchase_notification() method in AdminNotificationService
  with blockquote for payment details, landing slug, buyer/recipient info
- Called from fulfill_purchase() for both DELIVERED and PENDING_ACTIVATION
- Called from activate_purchase() when pending purchase is activated
- Different titles: regular purchase, gift purchase, pending activation
- Properly typed GuestPurchase, html.escape on all user data
- Refresh purchase with ['landing'] relationship after commit
- html.escape fallback in _get_payment_method_display
- ruff formatting fixes
2026-03-07 04:44:02 +03:00
Fringg 77456efb75 fix: add X-CSRF-Token and X-Telegram-Init-Data to CORS allow_headers
The security hardening commit changed allow_headers from ['*'] to
['Authorization', 'Content-Type'], but the frontend sends X-CSRF-Token
on all POST/PUT/DELETE/PATCH requests and X-Telegram-Init-Data on all
requests. The missing headers caused preflight OPTIONS requests to fail
with 400 "Disallowed CORS origin".
2026-03-07 04:30:29 +03:00
Fringg c165cca323 fix: use get_rendered_override for proper variable substitution in guest email overrides
Admin-created email template overrides were not substituting {tariff_name},
{period_days}, {cabinet_url} etc. because get_template_override returns raw
body_html. Switched to get_rendered_override which performs variable
substitution with html.escape. Also removed dead is_existing_user from
sample context.
2026-03-07 04:26:09 +03:00
Fringg 6970340e62 feat: add quick purchase email templates to admin panel
- Register 4 guest purchase template types in admin email templates:
  guest_subscription_delivered, guest_activation_required,
  guest_gift_received, guest_cabinet_credentials
- Add sample contexts with placeholders for preview/test
- Add DB override support to send_guest_notification for all 4 types
2026-03-07 04:13:41 +03:00
Fringg 9217352685 fix: remove subscription connection links from guest purchase emails
Replace VPN subscription URLs with cabinet links in all email templates:
- GUEST_SUBSCRIPTION_DELIVERED: unified to always show cabinet link
- GUEST_GIFT_RECEIVED: replaced subscription URL with cabinet link
Both self-purchase and gift flows now only include cabinet links.
2026-03-07 04:10:27 +03:00
Fringg a539d69854 fix: code style and formatting from review
- Format long dict literal in admin_landings.py
- Add blank line after validator in admin_payment_methods.py
- Fix ruff E203 slice spacing in landing.py
- Fix long line wrapping in payment_service.py
2026-03-07 03:50:15 +03:00
Fringg e96fe1ecd8 fix: comprehensive security hardening from 7-agent review
Schema validation:
- Add max_length to init_data (4096), widget fields (first_name 64,
  last_name 64, username 32, photo_url 512, hash 64)
- Add max_length=2048 to all token fields (verify, refresh, reset, auto-login)
- Add max_length=128 to EmailLoginRequest.password (bcrypt DoS prevention)
- Add pattern to OAuthCallbackRequest.referral_code (was missing)
- Add pattern=r'^\d{6}$' to EmailChangeVerifyRequest.code
- Add pattern/max_length to language field (ISO 639-1)

Auth endpoints:
- Add user.status check to auto-login (banned users could authenticate)
- Add exception chaining (from e) to refresh and auto-login endpoints
- Add IP rate limiting to initData, register standalone, verify email,
  forgot password, and reset password endpoints

CORS:
- Add PATCH to allow_methods in both unified_app and webapi (38+ PATCH
  endpoints were blocked for cross-origin requests)

JWKS:
- Fix race condition: move force-refresh cooldown check and cache
  invalidation inside asyncio.Lock via dedicated _force_refresh_jwks()
2026-03-07 03:47:58 +03:00
Fringg 5499ad62dc fix: add referral_code pattern validation, email login rate limiting, and Retry-After headers
- Add pattern=r'^[a-zA-Z0-9_-]+$' to referral_code in TelegramAuthRequest,
  TelegramWidgetAuthRequest, and EmailRegisterStandaloneRequest for consistency
  with TelegramOIDCAuthRequest
- Add IP-based rate limiting (10 req/min) to /email/login endpoint
- Add Retry-After: 60 header to /login/auto 429 response
2026-03-07 03:37:25 +03:00
Fringg 6495384bcf fix: transaction boundary and CORS in webapi
- Revert OIDC flush to commit before _store_refresh_token
  (matches widget/initData pattern, prevents rollback losing user updates)
- Fix CORS wildcard+credentials in webapi/app.py (same as unified_app fix)
2026-03-07 03:33:37 +03:00
Fringg 5c55662e2c fix: comprehensive security and quality fixes from 7-agent review
Security:
- CORS: disable credentials when wildcard origin, restrict methods/headers
- Token replay: Redis-based id_token dedup with TTL matching expiry
- JWT secret: warn when falling back to BOT_TOKEN
- Rate limiting: add to legacy widget endpoint (was missing)
- Retry-After: add header to all 429 responses

Quality:
- Read OIDC CLIENT_ID from DB with env fallback (admin panel works)
- Consolidate db.commit() — flush mid-handler, single commit at end
- Move get_setting_value import to top-level
2026-03-07 03:27:00 +03:00
Fringg b78c01cae9 fix: critical OIDC fixes from 7-agent review
- Fix broken import (system_settings → system_setting) that crashed
  OIDC endpoint on every request
- Extract get_setting_value to shared CRUD module
- Add JWKS force-refresh cooldown (30s) to prevent abuse
- Remove dead _OIDC_TOKEN_URL constant
- Add raise from for exception chaining
- Remove unused _photo_url variable
- Add pattern validation on referral_code field
2026-03-07 03:18:20 +03:00
Fringg 2405dc5c1b fix: read OIDC enabled setting from DB in auth endpoint
Match branding endpoint pattern — check system_settings first,
fall back to env var, ensuring admin panel toggle takes effect.
2026-03-07 03:04:42 +03:00
Fringg da1cc4fe5a fix: address code review findings for Telegram OIDC
- JWKS cache: add asyncio.Lock to prevent thundering herd, extract
  _build_public_keys helper, retry JWKS fetch on kid mismatch (key rotation)
- Remove dead code: exchange_telegram_oidc_code (unused, popup sends id_token directly)
- OIDC auth endpoint: add rate limiting, fix int() parse with try/except,
  extract last_name/photo_url/language from claims, narrow bare Exception
  to (ValueError, LookupError)
- Schema: add max_length=4096 to id_token field
- Branding: read TELEGRAM_OIDC_ENABLED from DB settings with env fallback
2026-03-07 02:54:29 +03:00
Fringg 000b0c0592 feat: expose oidc_enabled and oidc_client_id in telegram-widget config 2026-03-07 02:33:52 +03:00
Fringg 3a400d9f8b feat: add POST /auth/telegram/oidc endpoint for OIDC popup flow 2026-03-07 02:32:40 +03:00
Fringg 2f0a9dc4f3 feat: add Telegram OIDC id_token validation and code exchange 2026-03-07 02:30:33 +03:00
Fringg 3a361628aa feat: register TELEGRAM_OIDC category, hints in admin settings
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:25:54 +03:00
Fringg 833df518d0 feat: add TELEGRAM_OIDC_* settings for new Telegram Login
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 02:24:13 +03:00
Fringg 084a3cd16f feat: configurable Telegram Login Widget with admin settings
- Add 4 TELEGRAM_WIDGET_* settings to config (size, radius, userpic, request_access)
- Register TELEGRAM_WIDGET category with choices, hints, and prefix mapping
- Add public GET /branding/telegram-widget endpoint returning widget config
- Use Literal type for size validation, Field(ge=0, le=20) for radius bounds
- Clamp radius values from DB to prevent out-of-range values
2026-03-07 01:48:14 +03:00
Fringg 694aeccc31 fix: remove decorative cloudpayments sub-options
CloudPayments doesn't support programmatic card/sbp routing — the user
selects the payment method on the provider's payment page. Remove
available_sub_options so no misleading choice is shown on landing pages.
2026-03-07 00:04:32 +03:00
Fringg 5f01783dcb fix: validate payment sub-option suffix and harden payment method handling
- Add regex pattern + max_length constraint on payment_method field
- Validate sub-option suffix against known available_sub_options
- Validate sub-option is enabled on the landing (not disabled via config)
- Sort methods by ID length desc to prevent freekassa/freekassa_sbp ambiguity
- Accept yookassa_card and cloudpayments_card/sbp in create_guest_payment
- Send single sub-option to frontend (not just when >1) for correct routing
2026-03-07 00:01:44 +03:00
Fringg c53e9af744 feat: expose payment sub-options with labels in public landing API
Resolve sub-option display names from payment_method_config_service
and return them as a list of {id, name} in the public landing config.
Accept suffixed payment_method IDs (e.g. platega_2, yookassa_sbp)
in the purchase endpoint for sub-option selection.
2026-03-06 23:53:28 +03:00
Fringg 220196fb7a feat: add sub_options support for landing page payment methods
Allow per-landing override of payment method sub-options (e.g. Card/SBP
for Yookassa). Add validated sub_options field to admin and public schemas
with opt-out model (missing keys = enabled, null = all available).
2026-03-06 23:30:44 +03:00
sMedia.tech e9b4d8e444 fix: add Message-ID and Date headers to outgoing emails
Without these RFC 5322 required headers, Postfix sends messages with
empty message-id=<> which triggers spam filters at receiving MTAs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 22:53:39 +03:00
Fringg d86c29a5d3 fix: preserve connected_squads during subscription replacement cleanup
Root cause: validate_and_clean_subscription() wiped connected_squads=[]
before create_remnawave_user() could send them to the Remnawave panel.
This caused replaced subscriptions to lose squad (server) assignments.

Also: add update_server_counters=True to all guest purchase flows,
commit tariff_id before create_remnawave_user to ensure fresh ORM state.
2026-03-06 22:28:39 +03:00
Fringg 8510597ddb fix: add pending_activation to purchase stats and show total count 2026-03-06 22:16:28 +03:00
Fringg f8edfd7746 feat: guest purchase → cabinet account integration
Create verified cabinet accounts for email-based guest purchasers:
- Auto-generate password for new/existing users without password_hash
- Auto-login JWT token (72h TTL) stored once at fulfillment
- POST /login/auto endpoint with rate limiting (5 req/60s)
- Credentials email template (5 languages)
- Fix forgot_password for guest-created email users
- Clear plaintext password from DB after email delivery
- TTL-capped credential exposure (24h delivered, 72h pending_activation)
- DB cleanup of expired credentials on poll
2026-03-06 21:59:36 +03:00
Fringg 776fc3aadc feat: guest purchase delivery & activation system
- Add PENDING_ACTIVATION status for users with existing subscriptions
- Add activation endpoint POST /landing/activate/{token}
- Send email notifications on delivery and pending activation
- Add 3 email templates (delivered, activation required, gift received) in 5 languages
- Extract purchase status response builder to reusable helper
- Move activation logic to service layer
- Add header injection protection in email service
- Add Literal type guard for contact_type parameter
- Fix _mask_email crash on malformed input
- Pre-resolve notification params before commit to avoid DetachedInstanceError
2026-03-06 19:56:37 +03:00
Fringg b85646af85 fix: pass return_url to all payment providers for guest purchases
Platega, Heleket, WATA, CloudPayments now accept optional return_url
and pass it to their APIs. Guest payments redirect back to cabinet
success page instead of Telegram bot.
2026-03-06 18:18:42 +03:00
Fringg e0f2243f49 fix: make users.promo_group_id nullable — sync DB with model
Migration 0023: ALTER COLUMN promo_group_id SET nullable=True.
Fixes NOT NULL violation when creating guest users during landing purchases.
2026-03-06 18:18:40 +03:00
Fringg ab981dce0d fix: treat empty icon_url as None in payment method validation 2026-03-06 18:00:34 +03:00
Fringg 6f871edc9d fix: CryptoBot guest payment — remove is_paid @property write, use correct status
- Remove `locked.is_paid = True` — is_paid is a read-only @property computed from status
- Change status from 'completed' to 'paid' (matching is_paid property check)
- Use db.commit() instead of db.flush() for guest payment persistence
2026-03-06 16:54:25 +03:00
Fringg 3d3bb3badb fix: миграция 0021 — drop server_default перед сменой типа на JSON
PostgreSQL не может автоматически привести строковый default к типу JSON.
Решение: убрать default, сменить тип, затем поставить новый default через raw SQL.
2026-03-06 16:45:03 +03:00
Fringg 6deab7dd8c feat: мультиязычные лендинги + гостевые платежи для всех провайдеров
Мультиязычность:
- Миграция 0021: текстовые поля лендингов → JSON с ключами локалей
- Утилита resolve_locale_text с fallback-цепочкой (lang → ru → en → first)
- Админ API: dict[str, str] для title/subtitle/footer/meta/features
- Публичный API: ?lang= параметр, резолвит в плоские строки
- Обратная совместимость: plain strings → {"ru": value}

Гостевые платежи:
- Миграция 0022: user_id nullable во всех платёжных таблицах
- Поддержка всех провайдеров кроме Stars
- Общий хелпер try_fulfill_guest_purchase в common.py
- YooKassa переведена на общий хелпер

Исправления по ревью:
- CryptoBot: guest fulfillment после FOR UPDATE lock
- _patch_guest_metadata: commit вместо flush
- Freekassa/KassaAI: metadata как dict вместо JSON-строки
- purchase_token маскирован в логах
- None → "guest" в order_id
- Rate limit на GET /landing/{slug}
- Маскирование contact_value
2026-03-06 16:10:09 +03:00
Fringg ef450955e6 fix: безопасность и качество кода лендингов — 16 исправлений
- CRITICAL: блокировка fulfillment при несовпадении суммы
- CRITICAL: верификация суммы webhook перед фулфилментом
- CRITICAL: TTL 24ч на доступ к subscription_url + rate limit статуса
- HIGH: IntegrityError для telegram-пользователей (race condition)
- HIGH: валидация icon_url (HTTPS/relative only)
- HIGH: строгий whitelist setattr для update_purchase_status
- HIGH: SAVEPOINT вместо full rollback в _find_or_create_user
- HIGH: отложенный commit покупки до успеха платежа
- MEDIUM: N+1 запрос в списке лендингов → batch stats
- MEDIUM: лимиты длины текстовых полей в схемах
- MEDIUM: строгий email regex
- MEDIUM: индекс на guest_purchases.landing_id (миграция 0020)
- LOW: token prefix 5 символов, расширенные reserved slugs
2026-03-06 07:22:48 +03:00
Fringg 5e404cc082 feat: публичные лендинг-страницы для быстрой покупки VPN-подписок
- Модели LandingPage и GuestPurchase + миграции 0018/0019
- CRUD для лендингов и гостевых покупок
- Публичные роуты: GET /{slug}, POST /{slug}/purchase, GET /purchase/{token}
- Админ-роуты: CRUD лендингов с RBAC (manage_landings)
- Сервис guest_purchase_service: валидация, создание, фулфилмент
- Интеграция с PaymentService (YooKassa card/SBP) для гостевых платежей
- Webhook-обработка с идемпотентностью и атомарными транзакциями
- Rate limiting на публичных эндпоинтах
- YooKassaPayment.user_id теперь nullable для гостевых платежей
2026-03-06 07:02:42 +03:00
Egor c669c5951a Merge pull request #2676 from BEDOLAGA-DEV/release-please--branches--main
chore(main): release 3.23.2
2026-03-06 05:33:40 +03:00
github-actions[bot] b68c1c751a chore(main): release 3.23.2 2026-03-06 02:33:15 +00:00