From 394cb41a4c4625d71e6a1cd0b5f4589b63b737bd Mon Sep 17 00:00:00 2001 From: master3395 Date: Mon, 2 Feb 2026 03:39:42 +0100 Subject: [PATCH] Plugin updates: premiumPlugin & paypalPremiumPlugin unified verification, Installed Plugins UI improvements --- README.md | 1 + paypalPremiumPlugin/README.md | 58 ++ paypalPremiumPlugin/__init__.py | 4 + paypalPremiumPlugin/api_encryption.py | 80 ++ paypalPremiumPlugin/apps.py | 5 + paypalPremiumPlugin/meta.xml | 30 + .../migrations/0001_initial.py | 27 + paypalPremiumPlugin/migrations/__init__.py | 1 + paypalPremiumPlugin/models.py | 40 + .../templates/paypalPremiumPlugin/index.html | 96 +++ .../paypalPremiumPlugin/settings.html | 331 ++++++++ .../subscription_required.html | 139 ++++ paypalPremiumPlugin/urls.py | 12 + paypalPremiumPlugin/views.py | 763 ++++++++---------- .../templates/pluginHolder/plugins.html | 1 + premiumPlugin/.gitignore | 6 + premiumPlugin/README-REMOTE-VERIFICATION.md | 81 ++ premiumPlugin/README.md | 58 ++ premiumPlugin/SECURITY.md | 57 ++ premiumPlugin/__init__.py | 4 + premiumPlugin/api_encryption.py | 83 ++ premiumPlugin/apps.py | 5 + premiumPlugin/meta.xml | 30 + premiumPlugin/migrations/0001_initial.py | 27 + premiumPlugin/migrations/__init__.py | 1 + premiumPlugin/models.py | 42 + .../templates/premiumPlugin/index.html | 96 +++ .../templates/premiumPlugin/settings.html | 315 ++++++++ .../premiumPlugin/subscription_required.html | 139 ++++ premiumPlugin/urls.py | 12 + premiumPlugin/views.py | 567 ++++++++----- premiumPlugin/views_remote.py | 236 ++++++ 32 files changed, 2696 insertions(+), 651 deletions(-) create mode 100644 paypalPremiumPlugin/README.md create mode 100644 paypalPremiumPlugin/__init__.py create mode 100644 paypalPremiumPlugin/api_encryption.py create mode 100644 paypalPremiumPlugin/apps.py create mode 100644 paypalPremiumPlugin/meta.xml create mode 100644 paypalPremiumPlugin/migrations/0001_initial.py create mode 100644 paypalPremiumPlugin/migrations/__init__.py create mode 100644 paypalPremiumPlugin/models.py create mode 100644 paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html create mode 100644 paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html create mode 100644 paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html create mode 100644 paypalPremiumPlugin/urls.py create mode 100644 premiumPlugin/.gitignore create mode 100644 premiumPlugin/README-REMOTE-VERIFICATION.md create mode 100644 premiumPlugin/README.md create mode 100644 premiumPlugin/SECURITY.md create mode 100644 premiumPlugin/__init__.py create mode 100644 premiumPlugin/api_encryption.py create mode 100644 premiumPlugin/apps.py create mode 100644 premiumPlugin/meta.xml create mode 100644 premiumPlugin/migrations/0001_initial.py create mode 100644 premiumPlugin/migrations/__init__.py create mode 100644 premiumPlugin/models.py create mode 100644 premiumPlugin/templates/premiumPlugin/index.html create mode 100644 premiumPlugin/templates/premiumPlugin/settings.html create mode 100644 premiumPlugin/templates/premiumPlugin/subscription_required.html create mode 100644 premiumPlugin/urls.py create mode 100644 premiumPlugin/views_remote.py diff --git a/README.md b/README.md index 6dcd87f83..00dce7845 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ journalctl -u lscpd -f ## Recent fixes +* **02.02.2026** — Plugin updates: premiumPlugin & paypalPremiumPlugin unified verification (Plugin Grants, activation key, Patreon, PayPal, AES-256-CBC encryption). Installed Plugins UI: bulk activate/deactivate, freshness badges, removed Patreon messaging from front. * **15.11.2025** — Hardened MySQL password rotation: `mysqlUtilities.changePassword` now auto-resolves the backing MySQL account (user + host) even when `DBUsers` metadata is missing, preventing the historical `[mysqlUtilities.changePassword] can only concatenate str (not "int")` error. Regression tests live under `Test/mysqlUtilities/`, and you should restart `lscpd` after deploying the patch so the helper reloads. --- diff --git a/paypalPremiumPlugin/README.md b/paypalPremiumPlugin/README.md new file mode 100644 index 000000000..922bffe2e --- /dev/null +++ b/paypalPremiumPlugin/README.md @@ -0,0 +1,58 @@ +# Premium Plugin Example + +An example paid plugin for CyberPanel that demonstrates how to implement Patreon subscription-based plugin access. + +## Features + +- Requires Patreon subscription to "CyberPanel Paid Plugin" tier +- Users can install the plugin without subscription +- Plugin functionality is locked until subscription is verified +- Shows subscription required page when accessed without subscription + +## Installation + +1. Upload the plugin ZIP file to CyberPanel +2. Install the plugin from the plugin manager +3. The plugin will appear in the installed plugins list + +## Usage + +### For Users Without Subscription + +- Plugin can be installed +- When accessing the plugin, a subscription required page is shown +- Link to Patreon subscription page is provided + +### For Users With Subscription + +- Plugin works normally +- All features are accessible +- Settings page is available + +## Configuration + +The plugin checks for Patreon membership via the Patreon API. Make sure to configure: + +1. Patreon Client ID +2. Patreon Client Secret +3. Patreon Creator ID + +These should be set in CyberPanel environment variables or settings. + +## Meta.xml Structure + +The plugin uses the following meta.xml structure for paid plugins: + +```xml +true +CyberPanel Paid Plugin +https://www.patreon.com/c/newstargeted/membership +``` + +## Author + +master3395 + +## License + +MIT diff --git a/paypalPremiumPlugin/__init__.py b/paypalPremiumPlugin/__init__.py new file mode 100644 index 000000000..f80c90067 --- /dev/null +++ b/paypalPremiumPlugin/__init__.py @@ -0,0 +1,4 @@ +# PayPal Premium Plugin Example +# This is a paid plugin that requires PayPal payment + +default_app_config = 'paypalPremiumPlugin.apps.PaypalpremiumpluginConfig' diff --git a/paypalPremiumPlugin/api_encryption.py b/paypalPremiumPlugin/api_encryption.py new file mode 100644 index 000000000..8f2820ab2 --- /dev/null +++ b/paypalPremiumPlugin/api_encryption.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +""" +AES-256-CBC encryption for plugin <-> api.newstargeted.com communication. +Key must match PLUGIN_VERIFICATION_CIPHER_KEY in config.php on the API server. +""" +import json +import base64 +import os + +CIPHER_KEY_B64 = '1VLPEKTmLGUbIxHUFEtsuVM2MPN1tl8HPFtyJc4dr58=' +ENCRYPTION_ENABLED = True + +_ENCRYPTION_CIPHER_KEY = None + + +def _get_key(): + global _ENCRYPTION_CIPHER_KEY + if _ENCRYPTION_CIPHER_KEY is not None: + return _ENCRYPTION_CIPHER_KEY + try: + key = base64.b64decode(CIPHER_KEY_B64) + if len(key) == 32: + _ENCRYPTION_CIPHER_KEY = key + return key + except Exception: + pass + return None + + +def encrypt_payload(data): + if not ENCRYPTION_ENABLED or not _get_key(): + body = json.dumps(data, separators=(',', ':')).encode('utf-8') + return body, {'Content-Type': 'application/json'} + try: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.backends import default_backend + key = _get_key() + plain = json.dumps(data, separators=(',', ':')).encode('utf-8') + padder = padding.PKCS7(128).padder() + padded = padder.update(plain) + padder.finalize() + iv = os.urandom(16) + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + encryptor = cipher.encryptor() + ciphertext = encryptor.update(padded) + encryptor.finalize() + payload = base64.b64encode(iv).decode('ascii') + '.' + base64.b64encode(ciphertext).decode('ascii') + return payload.encode('utf-8'), {'Content-Type': 'text/plain', 'X-Encrypted': '1'} + except Exception: + body = json.dumps(data, separators=(',', ':')).encode('utf-8') + return body, {'Content-Type': 'application/json'} + + +def decrypt_response(body_bytes, content_type='', expect_encrypted=False): + try: + body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes) + is_encrypted = ( + expect_encrypted or + ('text/plain' in content_type and '.' in body_str) or + ('.' in body_str and body_str.strip() and body_str.strip()[0] not in '{[') + ) + parts = body_str.strip().split('.', 1) + if is_encrypted and len(parts) == 2 and ENCRYPTION_ENABLED and _get_key(): + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.backends import default_backend + iv = base64.b64decode(parts[0]) + ciphertext = base64.b64decode(parts[1]) + key = _get_key() + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + decryptor = cipher.decryptor() + padded = decryptor.update(ciphertext) + decryptor.finalize() + unpadder = padding.PKCS7(128).unpadder() + plain = unpadder.update(padded) + unpadder.finalize() + return json.loads(plain.decode('utf-8')) + return json.loads(body_str) + except Exception: + try: + return json.loads(body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else body_bytes) + except Exception: + return {} diff --git a/paypalPremiumPlugin/apps.py b/paypalPremiumPlugin/apps.py new file mode 100644 index 000000000..0f2389e23 --- /dev/null +++ b/paypalPremiumPlugin/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class PaypalpremiumpluginConfig(AppConfig): + name = 'paypalPremiumPlugin' + verbose_name = 'PayPal Premium Plugin Example' diff --git a/paypalPremiumPlugin/meta.xml b/paypalPremiumPlugin/meta.xml new file mode 100644 index 000000000..6535e1409 --- /dev/null +++ b/paypalPremiumPlugin/meta.xml @@ -0,0 +1,30 @@ + + + PayPal Premium Plugin Example + Utility + 1.0.2 + An example paid plugin that requires PayPal payment. Users can install it but cannot run it without payment. Supports PayPal.me links and PayPal Payment Links/Buttons. + master3395 + https://github.com/master3395/cyberpanel-plugins + MIT + + 3.6+ + 2.2+ + 2.5.5+ + + + 2.5.5 + 3.0.0 + + + true + false + + true + CyberPanel Paid Plugin + https://www.patreon.com/membership/27789984 + https://paypal.me/KimBS?locale.x=en_US&country.x=NO + + /plugins/paypalPremiumPlugin/ + /plugins/paypalPremiumPlugin/settings/ + diff --git a/paypalPremiumPlugin/migrations/0001_initial.py b/paypalPremiumPlugin/migrations/0001_initial.py new file mode 100644 index 000000000..97b0f3fa8 --- /dev/null +++ b/paypalPremiumPlugin/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated migration for PaypalPremiumPluginConfig + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='PaypalPremiumPluginConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_method', models.CharField(choices=[('patreon', 'Patreon Subscription'), ('paypal', 'PayPal Payment'), ('both', 'Check Both (Patreon or PayPal)')], default='both', help_text='Choose which payment method to use for verification.', max_length=10)), + ('activation_key', models.CharField(blank=True, default='', help_text='Validated activation key - grants access without re-entering.', max_length=64)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'PayPal Premium Plugin Configuration', + 'verbose_name_plural': 'PayPal Premium Plugin Configurations', + }, + ), + ] diff --git a/paypalPremiumPlugin/migrations/__init__.py b/paypalPremiumPlugin/migrations/__init__.py new file mode 100644 index 000000000..2a0ee1c3e --- /dev/null +++ b/paypalPremiumPlugin/migrations/__init__.py @@ -0,0 +1 @@ +# PayPal Premium Plugin migrations diff --git a/paypalPremiumPlugin/models.py b/paypalPremiumPlugin/models.py new file mode 100644 index 000000000..b56e0c6e4 --- /dev/null +++ b/paypalPremiumPlugin/models.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +from django.db import models + + +class PaypalPremiumPluginConfig(models.Model): + PAYMENT_METHOD_CHOICES = [ + ('patreon', 'Patreon Subscription'), + ('paypal', 'PayPal Payment'), + ('both', 'Check Both (Patreon or PayPal)'), + ] + payment_method = models.CharField( + max_length=10, + choices=PAYMENT_METHOD_CHOICES, + default='both', + help_text="Choose which payment method to use for verification." + ) + activation_key = models.CharField( + max_length=64, + blank=True, + default='', + help_text="Validated activation key - grants access without re-entering." + ) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "PayPal Premium Plugin Configuration" + verbose_name_plural = "PayPal Premium Plugin Configurations" + + def __str__(self): + return "PayPal Premium Plugin Configuration" + + @classmethod + def get_config(cls): + config, _ = cls.objects.get_or_create(pk=1) + return config + + def save(self, *args, **kwargs): + self.pk = 1 + super().save(*args, **kwargs) diff --git a/paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html b/paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html new file mode 100644 index 000000000..ac5f39dbc --- /dev/null +++ b/paypalPremiumPlugin/templates/paypalPremiumPlugin/index.html @@ -0,0 +1,96 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} + +{% block title %}{% trans "PayPal Premium Plugin Example - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ {% trans "Premium Plugin" %} +

{{ plugin_name }}

+

{{ description }}

+

{% trans "Version:" %} {{ version }}

+
+ +
+

{% trans "Premium Features" %}

+
    + {% for feature in features %} +
  • {{ feature }}
  • + {% endfor %} +
+
+
+{% endblock %} diff --git a/paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html b/paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html new file mode 100644 index 000000000..f7c9c39c6 --- /dev/null +++ b/paypalPremiumPlugin/templates/paypalPremiumPlugin/settings.html @@ -0,0 +1,331 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} + +{% block title %}{% trans "PayPal Premium Plugin Settings - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ {% trans "Premium Plugin" %} +

{% trans "PayPal Premium Plugin Settings" %}

+ + {% if show_payment_ui %} + +
+

{% trans "Activate Premium Access" %}

+

{% trans "If you have an activation key, enter it below." %}

+ +
+ {% csrf_token %} + + +
+
+ +
+ +
+ {% csrf_token %} + +
+
+ {% else %} +
+ {% trans "Premium Access Active" %} — {% trans "Access granted via Plugin Grants or activation key." %} +
+ {% endif %} + + +
+ + {% trans "Plugin Information" %} +
    +
  • {% trans "Name" %}: {{ plugin_name|default:"PayPal Premium Plugin Example" }}
  • +
  • {% trans "Version" %}: {{ version|default:"1.0.0" }}
  • +
  • + {% trans "Status" %}: + + {{ plugin_status|default:status|default:"Active" }} + +
  • +
+
+ +

{{ description }}

+ +
+ {% csrf_token %} +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ + {% if show_payment_ui %} + + {% endif %} +
+
+{% endblock %} diff --git a/paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html b/paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html new file mode 100644 index 000000000..cf67af087 --- /dev/null +++ b/paypalPremiumPlugin/templates/paypalPremiumPlugin/subscription_required.html @@ -0,0 +1,139 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} + +{% block title %}{% trans "Payment Required - PayPal Premium Plugin" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+
+

{% trans "Premium Plugin Access Required" %}

+

{% trans "This plugin requires payment or subscription to access premium features." %}

+ + +
+

{% trans "Activate Premium Access" %}

+

{% trans "If you received an activation key, enter it below." %}

+ +
+ {% csrf_token %} +
+ + +
+ +
+
+ + {% if payment_method == 'both' %} +
+ {% trans "Current Payment Method:" %} {% trans "Check Both (Patreon or PayPal)" %} +
+ {% elif payment_method == 'patreon' %} +
+ {% trans "Current Payment Method:" %} {% trans "Patreon Subscription Only" %} +
+ {% elif payment_method == 'paypal' %} +
+ {% trans "Current Payment Method:" %} {% trans "PayPal Payment Only" %} +
+ {% endif %} + +
+
+

{% trans "Patreon Subscription" %}

+

{% trans "Subscribe to" %} "{{ patreon_tier }}"

+ + {% trans "Subscribe on Patreon" %} + +
+
+

{% trans "PayPal Payment" %}

+

{% trans "Complete one-time payment via PayPal" %}

+ {% if paypal_me_url %} + + {% trans "Pay with PayPal.me" %} + + {% endif %} +
+
+ +
+

{% trans "How it works:" %}

+
    +
  • {% trans "Install the plugin (already done)" %}
  • +
  • {% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}
  • +
  • {% trans "The plugin will automatically unlock" %}
  • +
+
+ + {% if error %} +
+ {% trans "Error:" %} {{ error }} +
+ {% endif %} +
+
+ + +{% endblock %} diff --git a/paypalPremiumPlugin/urls.py b/paypalPremiumPlugin/urls.py new file mode 100644 index 000000000..997fe20f5 --- /dev/null +++ b/paypalPremiumPlugin/urls.py @@ -0,0 +1,12 @@ +from django.urls import path, re_path +from . import views + +app_name = 'paypalPremiumPlugin' + +urlpatterns = [ + path('', views.main_view, name='main'), + path('settings/', views.settings_view, name='settings'), + re_path(r'^activate-key/$', views.activate_key, name='activate_key'), + path('save-payment-method/', views.save_payment_method, name='save_payment_method'), + path('api/status/', views.api_status_view, name='api_status'), +] diff --git a/paypalPremiumPlugin/views.py b/paypalPremiumPlugin/views.py index c16bc0337..c1f96f129 100644 --- a/paypalPremiumPlugin/views.py +++ b/paypalPremiumPlugin/views.py @@ -1,496 +1,387 @@ # -*- coding: utf-8 -*- """ -PayPal Premium Plugin Views - Enhanced Security Version -This version uses remote server verification with multiple security layers -SECURITY: All PayPal verification happens on YOUR server, not user's server +PayPal Premium Plugin Views - Unified Verification (same as contaboAutoSnapshot) +Supports: Plugin Grants, Activation Key, Patreon, PayPal, AES encryption """ from django.shortcuts import render, redirect -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponse +from django.views.decorators.http import require_http_methods from plogical.mailUtilities import mailUtilities from plogical.httpProc import httpProc from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging from functools import wraps -import sys -import os import urllib.request import urllib.error import json -import hashlib -import socket -import platform -import subprocess import time -import uuid -# Remote verification server (YOUR server, not user's server) -REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-paypal-payment' -PLUGIN_NAME = 'paypalPremiumPlugin' # PayPal Premium Plugin Example -PLUGIN_VERSION = '1.0.0' +from .models import PaypalPremiumPluginConfig +from . import api_encryption -# PayPal configuration +PLUGIN_NAME = 'paypalPremiumPlugin' +PLUGIN_VERSION = '1.0.2' + +REMOTE_VERIFICATION_PATREON_URL = 'https://api.newstargeted.com/api/verify-patreon-membership.php' +REMOTE_VERIFICATION_PAYPAL_URL = 'https://api.newstargeted.com/api/verify-paypal-payment.php' +REMOTE_VERIFICATION_PLUGIN_GRANT_URL = 'https://api.newstargeted.com/api/verify-plugin-grant.php' +REMOTE_ACTIVATION_KEY_URL = 'https://api.newstargeted.com/api/activate-plugin-key.php' + +PATREON_TIER = 'CyberPanel Paid Plugin' +PATREON_URL = 'https://www.patreon.com/membership/27789984' PAYPAL_ME_URL = 'https://paypal.me/KimBS?locale.x=en_US&country.x=NO' -PAYPAL_PAYMENT_LINK = '' # Can be set to a PayPal Payment Link URL +PAYPAL_PAYMENT_LINK = '' -# Security configuration -CACHE_FILE = '/tmp/.paypalPremiumPlugin_license_cache' -CACHE_DURATION = 3600 # 1 hour - -# File integrity hashes (generated after plugin finalization) -# To regenerate: python3 -c "import hashlib; print(hashlib.sha256(open('views.py', 'rb').read()).hexdigest())" -PLUGIN_FILE_HASHES = { - 'views.py': '4899d70dde220b38d691a5cefdc4fd77b6d3e250ac1c7e12fa280d6f4ad31eb1', # Updated with security features - 'urls.py': '92433d401c358cd33ffd1926881920fd1867bb6d7dad1c3c2ed1e7d3b0abc2c6', -} - -def get_server_fingerprint(): - """ - Generate unique server fingerprint - Ties license to specific server hardware/configuration - """ - fingerprint_data = [] - - try: - # Server hostname - fingerprint_data.append(socket.gethostname()) - - # Primary IP - fingerprint_data.append(socket.gethostbyname(socket.gethostname())) - - # System information - fingerprint_data.append(platform.node()) - fingerprint_data.append(platform.machine()) - fingerprint_data.append(platform.processor()) - - # MAC address - fingerprint_data.append(str(uuid.getnode())) - - # Disk information (if available) - try: - result = subprocess.run(['df', '-h', '/'], capture_output=True, text=True, timeout=2) - fingerprint_data.append(result.stdout[:100]) - except: - pass - - # Create hash - fingerprint_string = '|'.join(str(x) for x in fingerprint_data) - return hashlib.sha256(fingerprint_string.encode()).hexdigest() - except Exception as e: - # Fallback fingerprint - return hashlib.sha256(f"{socket.gethostname()}|{platform.node()}".encode()).hexdigest() - -def verify_code_integrity(): - """ - Verify plugin files haven't been tampered with - Returns: (is_valid, error_message) - """ - plugin_dir = os.path.dirname(os.path.abspath(__file__)) - - for filename, expected_hash in PLUGIN_FILE_HASHES.items(): - if not expected_hash: - continue # Skip if hash not set - - filepath = os.path.join(plugin_dir, filename) - if os.path.exists(filepath): - try: - with open(filepath, 'rb') as f: - file_content = f.read() - file_hash = hashlib.sha256(file_content).hexdigest() - - if file_hash != expected_hash: - return False, f"File {filename} has been modified (integrity check failed)" - except Exception as e: - return False, f"Error checking {filename}: {str(e)}" - - return True, None - -def get_cached_verification(): - """Get cached verification result""" - if os.path.exists(CACHE_FILE): - try: - with open(CACHE_FILE, 'r') as f: - cache_data = json.load(f) - cache_time = cache_data.get('timestamp', 0) - server_fp = cache_data.get('server_fingerprint') - - # Verify server fingerprint matches - current_fp = get_server_fingerprint() - if server_fp != current_fp: - return None # Server changed, invalidate cache - - # Check if cache is still valid - if time.time() - cache_time < CACHE_DURATION: - return cache_data.get('has_access', False) - except: - pass - return None - -def cache_verification_result(has_access, server_fp): - """Cache verification result""" - try: - with open(CACHE_FILE, 'w') as f: - json.dump({ - 'has_access': has_access, - 'server_fingerprint': server_fp, - 'timestamp': time.time() - }, f) - os.chmod(CACHE_FILE, 0o600) # Secure permissions (owner read/write only) - except Exception as e: - pass # Silently fail caching def cyberpanel_login_required(view_func): - """ - Custom decorator that checks for CyberPanel session userID - """ @wraps(view_func) def _wrapped_view(request, *args, **kwargs): try: - userID = request.session['userID'] - # User is authenticated via CyberPanel session + if not request.session.get('userID'): + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) return view_func(request, *args, **kwargs) except KeyError: - # Not logged in, redirect to login from loginSystem.views import loadLoginPage return redirect(loadLoginPage) return _wrapped_view -def secure_verification_required(view_func): - """ - Enhanced decorator with multiple security checks - """ - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - # Check 1: Login required - try: - userID = request.session['userID'] - except KeyError: - from loginSystem.views import loadLoginPage - return redirect(loadLoginPage) - - # Check 2: Code integrity - is_valid, integrity_error = verify_code_integrity() - if not is_valid: - # Log security violation - logging.writeToFile(f"SECURITY VIOLATION: {integrity_error} - User: {request.session.get('userID')}") - - # Show error (don't reveal details) - context = { - 'error': 'Plugin integrity check failed. Please reinstall the plugin.', - 'security_violation': True - } - proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin') - return proc.render() - - # Check 3: Remote verification - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - domain = request.get_host() - user_ip = request.META.get('REMOTE_ADDR', '') - - verification_result = check_remote_payment_secure( - user_email, - user_ip, - domain - ) - - if not verification_result.get('has_access', False): - # Show payment required page - context = { - 'plugin_name': 'PayPal Premium Plugin Example', - 'is_paid': True, - 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL), - 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), - 'message': verification_result.get('message', 'PayPal payment required'), - 'error': verification_result.get('error') - } - proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin') - return proc.render() - - # All checks passed - proceed - return view_func(request, *args, **kwargs) - - return _wrapped_view -def remote_verification_required(view_func): - """ - Decorator that checks PayPal payment via remote server - No secrets stored in plugin - all verification happens on your server - """ - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - # First check login - try: - userID = request.session['userID'] - except KeyError: - from loginSystem.views import loadLoginPage - return redirect(loadLoginPage) - - # Get user email - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - # Try to get from session or username - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - # Check payment via remote server - verification_result = check_remote_payment_secure( - user_email, - request.META.get('REMOTE_ADDR', ''), - request.get_host() - ) - - if not verification_result.get('has_access', False): - # User doesn't have payment - show payment required page - context = { - 'plugin_name': 'PayPal Premium Plugin Example', - 'is_paid': True, - 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL), - 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), - 'message': verification_result.get('message', 'PayPal payment required'), - 'error': verification_result.get('error') - } - proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin') - return proc.render() - - # User has access - proceed with view - return view_func(request, *args, **kwargs) - - return _wrapped_view - -def check_remote_payment_secure(user_email, user_ip='', domain=''): - """ - Enhanced remote payment verification with multiple security layers - - Args: - user_email: User's email address - user_ip: User's IP address (for logging/security) - domain: Current domain (for domain binding) - - Returns: - dict: { - 'has_access': bool, - 'paypal_me_url': str, - 'paypal_payment_link': str, - 'message': str, - 'error': str or None - } - """ - # Layer 1: Code integrity check - is_valid, integrity_error = verify_code_integrity() - if not is_valid: - return { - 'has_access': False, - 'paypal_me_url': PAYPAL_ME_URL, - 'paypal_payment_link': PAYPAL_PAYMENT_LINK, - 'message': 'Plugin integrity check failed', - 'error': integrity_error, - 'security_violation': True - } - - # Layer 2: Check cache - cached_result = get_cached_verification() - if cached_result is not None: - return { - 'has_access': cached_result, - 'paypal_me_url': PAYPAL_ME_URL, - 'paypal_payment_link': PAYPAL_PAYMENT_LINK, - 'message': 'Access granted' if cached_result else 'PayPal payment required' - } - - # Layer 3: Server fingerprinting - server_fp = get_server_fingerprint() - - # Layer 4: Prepare secure request - request_data = { - 'user_email': user_email, - 'plugin_name': PLUGIN_NAME, - 'plugin_version': PLUGIN_VERSION, - 'server_fingerprint': server_fp, - 'domain': domain, - 'user_ip': user_ip, - 'timestamp': int(time.time()) - } - +def _api_request(url, data, timeout=10): try: - # Make request to remote verification server - req = urllib.request.Request( - REMOTE_VERIFICATION_URL, - data=json.dumps(request_data).encode('utf-8'), - headers={ - 'Content-Type': 'application/json', - 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}', - 'X-Plugin-Name': PLUGIN_NAME, - 'X-Timestamp': str(request_data['timestamp']) - } - ) - - # Send request with timeout - try: - with urllib.request.urlopen(req, timeout=10) as response: - response_data = json.loads(response.read().decode('utf-8')) - - if response_data.get('success', False): - has_access = response_data.get('has_access', False) - - # Cache result - cache_verification_result(has_access, server_fp) - - return { - 'has_access': has_access, - 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL), - 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), - 'message': response_data.get('message', 'Access granted' if has_access else 'PayPal payment required'), - 'error': None - } - else: - return { - 'has_access': False, - 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL), - 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), - 'message': response_data.get('message', 'PayPal payment required'), - 'error': response_data.get('error') - } - except urllib.error.HTTPError as e: - # Server returned error - error_body = e.read().decode('utf-8') if e.fp else 'Unknown error' - return { - 'has_access': False, - 'paypal_me_url': PAYPAL_ME_URL, - 'paypal_payment_link': PAYPAL_PAYMENT_LINK, - 'message': 'Unable to verify payment. Please try again later.', - 'error': f'HTTP {e.code}: {error_body}' - } - except urllib.error.URLError as e: - # Network error - return { - 'has_access': False, - 'paypal_me_url': PAYPAL_ME_URL, - 'paypal_payment_link': PAYPAL_PAYMENT_LINK, - 'message': 'Unable to connect to verification server. Please check your internet connection.', - 'error': str(e.reason) if hasattr(e, 'reason') else str(e) - } - except Exception as e: - # Other errors - return { - 'has_access': False, - 'paypal_me_url': PAYPAL_ME_URL, - 'paypal_payment_link': PAYPAL_PAYMENT_LINK, - 'message': 'Verification error occurred. Please try again later.', - 'error': str(e) - } - + body, extra_headers = api_encryption.encrypt_payload(data) + headers = { + 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}', + 'X-Plugin-Name': PLUGIN_NAME + } + headers.update(extra_headers) + req = urllib.request.Request(url, data=body, headers=headers) + with urllib.request.urlopen(req, timeout=timeout) as response: + raw = response.read() + ct = response.headers.get('Content-Type', '') + expect_enc = extra_headers.get('X-Encrypted') == '1' + return api_encryption.decrypt_response(raw, ct, expect_encrypted=expect_enc) except Exception as e: - logging.writeToFile(f"Error in remote payment check: {str(e)}") + logging.writeToFile(f"PayPal Premium Plugin: API request error to {url}: {str(e)}") + return {} + + +def check_plugin_grant(user_email, user_ip='', domain=''): + try: + request_data = { + 'user_email': user_email or '', + 'plugin_name': PLUGIN_NAME, + 'user_ip': user_ip, + 'domain': domain, + } + data = _api_request(REMOTE_VERIFICATION_PLUGIN_GRANT_URL, request_data) + if data.get('success') and data.get('has_access'): + return {'has_access': True, 'message': data.get('message', 'Access granted via Plugin Grants')} + return {'has_access': False, 'message': data.get('message', '')} + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Plugin grant check error: {str(e)}") + return {'has_access': False, 'message': ''} + + +def check_patreon_membership(user_email, user_ip='', domain=''): + try: + request_data = { + 'user_email': user_email, + 'plugin_name': PLUGIN_NAME, + 'plugin_version': PLUGIN_VERSION, + 'user_ip': user_ip, + 'domain': domain, + 'tier_id': '27789984' + } + response_data = _api_request(REMOTE_VERIFICATION_PATREON_URL, request_data) + if response_data.get('success', False): + return { + 'has_access': response_data.get('has_access', False), + 'patreon_tier': response_data.get('patreon_tier', PATREON_TIER), + 'patreon_url': response_data.get('patreon_url', PATREON_URL), + 'message': response_data.get('message', 'Access granted'), + 'error': None + } + return { + 'has_access': False, + 'patreon_tier': PATREON_TIER, + 'patreon_url': PATREON_URL, + 'message': response_data.get('message', 'Patreon subscription required'), + 'error': response_data.get('error') + } + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Patreon check error: {str(e)}") + return {'has_access': False, 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, 'message': 'Unable to verify Patreon.', 'error': str(e)} + + +def check_paypal_payment(user_email, user_ip='', domain=''): + try: + request_data = { + 'user_email': user_email, + 'plugin_name': PLUGIN_NAME, + 'plugin_version': PLUGIN_VERSION, + 'user_ip': user_ip, + 'domain': domain, + 'timestamp': int(time.time()), + } + response_data = _api_request(REMOTE_VERIFICATION_PAYPAL_URL, request_data) + if response_data.get('success', False): + return { + 'has_access': response_data.get('has_access', False), + 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': response_data.get('message', 'Access granted'), + 'error': None + } return { 'has_access': False, 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, - 'message': 'Verification error occurred. Please try again later.', - 'error': str(e) + 'message': response_data.get('message', 'PayPal payment required'), + 'error': response_data.get('error') } + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: PayPal check error: {str(e)}") + return {'has_access': False, 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, 'message': 'Unable to verify PayPal.', 'error': str(e)} + + +def unified_verification_required(view_func): + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + try: + if not request.session.get('userID'): + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) + + user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') or getattr(request.user, 'username', '') + + try: + config = PaypalPremiumPluginConfig.get_config() + payment_method = config.payment_method + except Exception: + payment_method = 'both' + + has_access = False + verification_result = {} + + activation_key = request.GET.get('activation_key') or request.POST.get('activation_key') + if not activation_key: + try: + config = PaypalPremiumPluginConfig.get_config() + activation_key = getattr(config, 'activation_key', '') or '' + except Exception: + activation_key = '' + + if activation_key: + try: + request_data = {'activation_key': activation_key.strip(), 'plugin_name': PLUGIN_NAME, 'user_email': user_email} + response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data) + if response_data.get('success', False) and response_data.get('has_access', False): + has_access = True + verification_result = {'method': 'activation_key', 'has_access': True, 'message': response_data.get('message', 'Access activated via key')} + try: + config = PaypalPremiumPluginConfig.get_config() + config.activation_key = activation_key.strip() + config.save(update_fields=['activation_key', 'updated_at']) + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Could not persist activation key: {str(e)}") + elif not response_data.get('success') and activation_key: + try: + config = PaypalPremiumPluginConfig.get_config() + if getattr(config, 'activation_key', '') == activation_key.strip(): + config.activation_key = '' + config.save(update_fields=['activation_key', 'updated_at']) + except Exception: + pass + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Activation key check error: {str(e)}") + + if not has_access: + grant_result = check_plugin_grant(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + if grant_result.get('has_access'): + has_access = True + verification_result = {'method': 'plugin_grant', 'has_access': True, 'message': grant_result.get('message', 'Access granted via Plugin Grants')} + + if not has_access: + try: + if payment_method == 'patreon': + result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + has_access = result.get('has_access', False) + verification_result = { + 'method': 'patreon', 'has_access': has_access, + 'patreon_tier': result.get('patreon_tier', PATREON_TIER), + 'patreon_url': result.get('patreon_url', PATREON_URL), + 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'message': result.get('message', 'Patreon subscription required'), + 'error': result.get('error') + } + elif payment_method == 'paypal': + result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + has_access = result.get('has_access', False) + verification_result = { + 'method': 'paypal', 'has_access': has_access, + 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, + 'paypal_me_url': result.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': result.get('message', 'PayPal payment required'), + 'error': result.get('error') + } + else: + patreon_result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + paypal_result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + has_access = patreon_result.get('has_access', False) or paypal_result.get('has_access', False) + verification_result = { + 'method': 'both', 'has_access': has_access, + 'patreon_tier': patreon_result.get('patreon_tier', PATREON_TIER), + 'patreon_url': patreon_result.get('patreon_url', PATREON_URL), + 'paypal_me_url': paypal_result.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': paypal_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': 'Payment or subscription required' if not has_access else 'Access granted' + } + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Verification error: {str(e)}") + has_access = False + verification_result = { + 'method': payment_method, 'has_access': False, + 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, + 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'message': 'Unable to verify access.', 'error': str(e) + } + + if not has_access: + context = { + 'plugin_name': 'PayPal Premium Plugin Example', + 'is_paid': True, + 'payment_method': payment_method, + 'verification_result': verification_result, + 'patreon_tier': verification_result.get('patreon_tier', PATREON_TIER), + 'patreon_url': verification_result.get('patreon_url', PATREON_URL), + 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': verification_result.get('message', 'Payment or subscription required'), + 'error': verification_result.get('error') + } + proc = httpProc(request, 'paypalPremiumPlugin/subscription_required.html', context, 'admin') + return proc.render() + + if has_access and verification_result: + request.session['paypal_premium_access_via'] = verification_result.get('method', '') + + return view_func(request, *args, **kwargs) + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Decorator error: {str(e)}") + return HttpResponse(f"

Plugin Error

{str(e)}

") + return _wrapped_view -def check_remote_payment(user_email, user_ip=''): - """ - Legacy function for backward compatibility - """ - return check_remote_payment_secure(user_email, user_ip, '') @cyberpanel_login_required def main_view(request): - """ - Main view for PayPal premium plugin - Shows plugin information and features if paid, or payment required message if not - """ mailUtilities.checkHome() - - # Get user email for verification - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - # Check payment status (but don't block access) - verification_result = check_remote_payment_secure( - user_email, - request.META.get('REMOTE_ADDR', ''), - request.get_host() - ) - has_access = verification_result.get('has_access', False) - - # Determine plugin status - plugin_status = 'Active' if has_access else 'Payment Required' - - context = { - 'plugin_name': 'PayPal Premium Plugin Example', - 'version': PLUGIN_VERSION, - 'status': plugin_status, - 'has_access': has_access, - 'description': 'This is an example paid plugin that requires PayPal payment.' if not has_access else 'This is an example paid plugin. You have access because payment has been verified!', - 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL), - 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), - 'features': [ - 'Premium Feature 1', - 'Premium Feature 2', - 'Premium Feature 3', - 'Advanced Configuration', - 'Priority Support' - ] if has_access else [] - } - - proc = httpProc(request, 'paypalPremiumPlugin/index.html', context, 'admin') - return proc.render() + return redirect('paypalPremiumPlugin:settings') + @cyberpanel_login_required +@unified_verification_required def settings_view(request): - """ - Settings page for PayPal premium plugin - Shows settings but disables them if user doesn't have PayPal payment - """ mailUtilities.checkHome() - - # Get user email for verification - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - # Check payment status (but don't block access) - verification_result = check_remote_payment_secure( - user_email, - request.META.get('REMOTE_ADDR', ''), - request.get_host() - ) - has_access = verification_result.get('has_access', False) - - # Determine plugin status - plugin_status = 'Active' if has_access else 'Payment Required' - + try: + config = PaypalPremiumPluginConfig.get_config() + except Exception: + from django.core.management import call_command + try: + call_command('migrate', 'paypalPremiumPlugin', verbosity=0, interactive=False) + config = PaypalPremiumPluginConfig.get_config() + except Exception as e: + return HttpResponse(f"

Database Error

{str(e)}

") + + access_via = request.session.get('paypal_premium_access_via', '') + show_payment_ui = access_via not in ('plugin_grant', 'activation_key') + context = { 'plugin_name': 'PayPal Premium Plugin Example', 'version': PLUGIN_VERSION, - 'plugin_status': plugin_status, - 'status': plugin_status, # Keep both for compatibility - 'description': 'Configure your premium plugin settings', - 'has_access': has_access, - 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL), - 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), - 'verification_message': verification_result.get('message', '') + 'status': 'Active', + 'config': config, + 'has_access': True, + 'show_payment_ui': show_payment_ui, + 'access_via_grant_or_key': not show_payment_ui, + 'patreon_tier': PATREON_TIER, + 'patreon_url': PATREON_URL, + 'paypal_me_url': PAYPAL_ME_URL, + 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'description': 'Configure your PayPal premium plugin settings.', } - proc = httpProc(request, 'paypalPremiumPlugin/settings.html', context, 'admin') return proc.render() + @cyberpanel_login_required -@secure_verification_required +@require_http_methods(["POST"]) +def activate_key(request): + try: + if request.content_type == 'application/json': + data = json.loads(request.body) + else: + data = request.POST + + activation_key = data.get('activation_key', '').strip() + user_email = data.get('user_email', '').strip() + if not user_email: + user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') + + if not activation_key: + return JsonResponse({'success': False, 'message': 'Activation key is required'}, status=400) + + request_data = {'activation_key': activation_key, 'plugin_name': PLUGIN_NAME, 'user_email': user_email} + response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data) + + if response_data.get('success', False) and response_data.get('has_access', False): + try: + config = PaypalPremiumPluginConfig.get_config() + config.activation_key = activation_key + config.save(update_fields=['activation_key', 'updated_at']) + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: Could not persist activation key: {str(e)}") + + return JsonResponse({ + 'success': True, + 'has_access': True, + 'message': response_data.get('message', 'Access activated successfully') + }) + + return JsonResponse({ + 'success': False, + 'has_access': False, + 'message': response_data.get('message', 'Invalid activation key') + }) + + except Exception as e: + logging.writeToFile(f"PayPal Premium Plugin: activate_key error: {str(e)}") + return JsonResponse({'success': False, 'message': str(e)}, status=500) + + +@cyberpanel_login_required +@require_http_methods(["POST"]) +def save_payment_method(request): + try: + payment_method = request.POST.get('payment_method', 'both') + if payment_method not in ('patreon', 'paypal', 'both'): + payment_method = 'both' + config = PaypalPremiumPluginConfig.get_config() + config.payment_method = payment_method + config.save(update_fields=['payment_method', 'updated_at']) + return JsonResponse({'success': True, 'message': 'Payment method saved'}) + except Exception as e: + return JsonResponse({'success': False, 'message': str(e)}, status=500) + + +@cyberpanel_login_required +@unified_verification_required def api_status_view(request): - """ - API endpoint for plugin status - Only accessible with PayPal payment (verified remotely with enhanced security) - """ return JsonResponse({ 'plugin_name': 'PayPal Premium Plugin Example', 'version': PLUGIN_VERSION, 'status': 'active', 'payment': 'verified', - 'description': 'Premium plugin is active and accessible', - 'verification_method': 'remote_secure' + 'verification_method': 'unified' }) diff --git a/pluginHolder/templates/pluginHolder/plugins.html b/pluginHolder/templates/pluginHolder/plugins.html index 1f74dd360..78e158354 100644 --- a/pluginHolder/templates/pluginHolder/plugins.html +++ b/pluginHolder/templates/pluginHolder/plugins.html @@ -1091,6 +1091,7 @@ {% endblock %} {% block content %} + {% load static %} {% get_current_language as LANGUAGE_CODE %} diff --git a/premiumPlugin/.gitignore b/premiumPlugin/.gitignore new file mode 100644 index 000000000..2ef72c88d --- /dev/null +++ b/premiumPlugin/.gitignore @@ -0,0 +1,6 @@ +# Security - Never commit secrets +*.secret +*_secret* +patreon_config.py +.env.patreon +patreon_secrets.env diff --git a/premiumPlugin/README-REMOTE-VERIFICATION.md b/premiumPlugin/README-REMOTE-VERIFICATION.md new file mode 100644 index 000000000..09decfcd6 --- /dev/null +++ b/premiumPlugin/README-REMOTE-VERIFICATION.md @@ -0,0 +1,81 @@ +# Remote Verification Setup + +## Overview + +This version of the plugin uses **remote verification** - all Patreon API calls happen on YOUR server, not the user's server. + +## Benefits + +✅ **No secrets in plugin** - Users can see all plugin code, but no credentials +✅ **Secure** - All Patreon API credentials stay on your server +✅ **Centralized** - You control access, can revoke, update logic, etc. +✅ **Public code** - Plugin code can be open source + +## Architecture + +``` +User's Server Your Server Patreon API + | | | + |-- Verify Request ------------> | | + | |-- Check Membership --> | + | |<-- Membership Status - | + |<-- Access Granted/Denied ----- | | +``` + +## Setup + +### 1. Deploy Verification API + +Deploy the verification endpoint to your server: +- File: `/home/newstargeted.com/api.newstargeted.com/modules/patreon/verify-membership.php` +- URL: `https://api.newstargeted.com/api/verify-patreon-membership` + +### 2. Configure Your Server + +Add Patreon credentials to your server's `config.php`: + +```php +define('PATREON_CLIENT_ID', 'your_client_id'); +define('PATREON_CLIENT_SECRET', 'your_client_secret'); +define('PATREON_CREATOR_ACCESS_TOKEN', 'your_access_token'); +``` + +### 3. Update Plugin + +Replace `views.py` with `views_remote.py`: + +```bash +mv views.py views_local.py # Backup local version +mv views_remote.py views.py # Use remote version +``` + +### 4. Configure Plugin URL + +Update `REMOTE_VERIFICATION_URL` in `views.py` to point to your server: + +```python +REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership' +``` + +## Security Features + +- **Rate limiting** - Prevents abuse (60 requests/hour per IP) +- **HTTPS only** - All communication encrypted +- **No secrets** - Plugin only makes API calls +- **Caching** - Reduces Patreon API calls (5 min cache) + +## Testing + +1. Install plugin on user's server +2. Try accessing plugin (should show subscription required) +3. Subscribe to Patreon tier +4. Access plugin again (should work) + +## Migration from Local Verification + +If you were using local verification: + +1. Keep `views_local.py` as backup +2. Use `views_remote.py` as `views.py` +3. Deploy verification API to your server +4. Update plugin URL in code diff --git a/premiumPlugin/README.md b/premiumPlugin/README.md new file mode 100644 index 000000000..922bffe2e --- /dev/null +++ b/premiumPlugin/README.md @@ -0,0 +1,58 @@ +# Premium Plugin Example + +An example paid plugin for CyberPanel that demonstrates how to implement Patreon subscription-based plugin access. + +## Features + +- Requires Patreon subscription to "CyberPanel Paid Plugin" tier +- Users can install the plugin without subscription +- Plugin functionality is locked until subscription is verified +- Shows subscription required page when accessed without subscription + +## Installation + +1. Upload the plugin ZIP file to CyberPanel +2. Install the plugin from the plugin manager +3. The plugin will appear in the installed plugins list + +## Usage + +### For Users Without Subscription + +- Plugin can be installed +- When accessing the plugin, a subscription required page is shown +- Link to Patreon subscription page is provided + +### For Users With Subscription + +- Plugin works normally +- All features are accessible +- Settings page is available + +## Configuration + +The plugin checks for Patreon membership via the Patreon API. Make sure to configure: + +1. Patreon Client ID +2. Patreon Client Secret +3. Patreon Creator ID + +These should be set in CyberPanel environment variables or settings. + +## Meta.xml Structure + +The plugin uses the following meta.xml structure for paid plugins: + +```xml +true +CyberPanel Paid Plugin +https://www.patreon.com/c/newstargeted/membership +``` + +## Author + +master3395 + +## License + +MIT diff --git a/premiumPlugin/SECURITY.md b/premiumPlugin/SECURITY.md new file mode 100644 index 000000000..b2df3648e --- /dev/null +++ b/premiumPlugin/SECURITY.md @@ -0,0 +1,57 @@ +# Security Guidelines for Premium Plugin + +## ⚠️ IMPORTANT: Never Expose Secrets + +This plugin is designed to be **publicly shareable**. It contains **NO secrets** and is safe to publish. + +## What's Safe to Share + +✅ **Safe to commit:** +- Plugin code (views.py, urls.py, etc.) +- Templates (HTML files) +- meta.xml (no secrets, only tier name and URL) +- README.md +- Documentation + +❌ **Never commit:** +- Patreon Client Secret +- Patreon Access Tokens +- Patreon Refresh Tokens +- Any hardcoded credentials + +## Configuration + +All Patreon credentials are configured on the **server side** via: +- Environment variables +- Django settings (from environment) +- Secure config files (not in repository) + +## For Your Own Setup + +When setting up this plugin on your server: + +1. **Do NOT** modify plugin files with your secrets +2. **Do** configure environment variables on the server +3. **Do** use Django settings.py (with environment variable fallbacks) +4. **Do** add any secret config files to .gitignore + +## Example Secure Configuration + +```python +# In settings.py (safe to commit) +PATREON_CLIENT_ID = os.environ.get('PATREON_CLIENT_ID', '') +PATREON_CLIENT_SECRET = os.environ.get('PATREON_CLIENT_SECRET', '') + +# On server (NOT in repo) +export PATREON_CLIENT_ID="your_actual_secret" +export PATREON_CLIENT_SECRET="your_actual_secret" +``` + +## Verification + +Before publishing, verify: +- [ ] No secrets in plugin files +- [ ] No secrets in meta.xml +- [ ] No secrets in README +- [ ] All credentials use environment variables +- [ ] .gitignore excludes secret files diff --git a/premiumPlugin/__init__.py b/premiumPlugin/__init__.py new file mode 100644 index 000000000..deac71067 --- /dev/null +++ b/premiumPlugin/__init__.py @@ -0,0 +1,4 @@ +# Premium Plugin Example +# This is a paid plugin that requires Patreon subscription + +default_app_config = 'premiumPlugin.apps.PremiumPluginConfig' diff --git a/premiumPlugin/api_encryption.py b/premiumPlugin/api_encryption.py new file mode 100644 index 000000000..50f8b3d10 --- /dev/null +++ b/premiumPlugin/api_encryption.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +""" +AES-256-CBC encryption for plugin <-> api.newstargeted.com communication. +Key must match PLUGIN_VERIFICATION_CIPHER_KEY in config.php on the API server. +""" +import json +import base64 +import os + +CIPHER_KEY_B64 = '1VLPEKTmLGUbIxHUFEtsuVM2MPN1tl8HPFtyJc4dr58=' +ENCRYPTION_ENABLED = True + +_ENCRYPTION_CIPHER_KEY = None + + +def _get_key(): + """Get 32-byte AES key from base64.""" + global _ENCRYPTION_CIPHER_KEY + if _ENCRYPTION_CIPHER_KEY is not None: + return _ENCRYPTION_CIPHER_KEY + try: + key = base64.b64decode(CIPHER_KEY_B64) + if len(key) == 32: + _ENCRYPTION_CIPHER_KEY = key + return key + except Exception: + pass + return None + + +def encrypt_payload(data): + """Encrypt JSON payload for API request. Returns (body_bytes, headers_dict).""" + if not ENCRYPTION_ENABLED or not _get_key(): + body = json.dumps(data, separators=(',', ':')).encode('utf-8') + return body, {'Content-Type': 'application/json'} + try: + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.backends import default_backend + key = _get_key() + plain = json.dumps(data, separators=(',', ':')).encode('utf-8') + padder = padding.PKCS7(128).padder() + padded = padder.update(plain) + padder.finalize() + iv = os.urandom(16) + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + encryptor = cipher.encryptor() + ciphertext = encryptor.update(padded) + encryptor.finalize() + payload = base64.b64encode(iv).decode('ascii') + '.' + base64.b64encode(ciphertext).decode('ascii') + return payload.encode('utf-8'), {'Content-Type': 'text/plain', 'X-Encrypted': '1'} + except Exception: + body = json.dumps(data, separators=(',', ':')).encode('utf-8') + return body, {'Content-Type': 'application/json'} + + +def decrypt_response(body_bytes, content_type='', expect_encrypted=False): + """Decrypt API response. Handles both encrypted and plain JSON.""" + try: + body_str = body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else str(body_bytes) + is_encrypted = ( + expect_encrypted or + ('text/plain' in content_type and '.' in body_str) or + ('.' in body_str and body_str.strip() and body_str.strip()[0] not in '{[') + ) + parts = body_str.strip().split('.', 1) + if is_encrypted and len(parts) == 2 and ENCRYPTION_ENABLED and _get_key(): + from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + from cryptography.hazmat.primitives import padding + from cryptography.hazmat.backends import default_backend + iv = base64.b64decode(parts[0]) + ciphertext = base64.b64decode(parts[1]) + key = _get_key() + cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend()) + decryptor = cipher.decryptor() + padded = decryptor.update(ciphertext) + decryptor.finalize() + unpadder = padding.PKCS7(128).unpadder() + plain = unpadder.update(padded) + unpadder.finalize() + return json.loads(plain.decode('utf-8')) + return json.loads(body_str) + except Exception: + try: + return json.loads(body_bytes.decode('utf-8') if isinstance(body_bytes, bytes) else body_bytes) + except Exception: + return {} diff --git a/premiumPlugin/apps.py b/premiumPlugin/apps.py new file mode 100644 index 000000000..261eb7905 --- /dev/null +++ b/premiumPlugin/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + +class PremiumPluginConfig(AppConfig): + name = 'premiumPlugin' + verbose_name = 'Premium Plugin Example' diff --git a/premiumPlugin/meta.xml b/premiumPlugin/meta.xml new file mode 100644 index 000000000..a986da653 --- /dev/null +++ b/premiumPlugin/meta.xml @@ -0,0 +1,30 @@ + + + Premium Plugin Example + Utility + 1.0.2 + An example paid plugin that requires Patreon subscription to "CyberPanel Paid Plugin" tier. Users can install it but cannot run it without subscription. + master3395 + https://github.com/master3395/cyberpanel-plugins + MIT + + 3.6+ + 2.2+ + 2.5.5+ + + + 2.5.5 + 3.0.0 + + + true + false + + true + CyberPanel Paid Plugin + https://www.patreon.com/membership/27789984 + https://paypal.me/KimBS?locale.x=en_US&country.x=NO + + /plugins/premiumPlugin/ + /plugins/premiumPlugin/settings/ + diff --git a/premiumPlugin/migrations/0001_initial.py b/premiumPlugin/migrations/0001_initial.py new file mode 100644 index 000000000..e403a7fbd --- /dev/null +++ b/premiumPlugin/migrations/0001_initial.py @@ -0,0 +1,27 @@ +# Generated migration for PremiumPluginConfig + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name='PremiumPluginConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('payment_method', models.CharField(choices=[('patreon', 'Patreon Subscription'), ('paypal', 'PayPal Payment'), ('both', 'Check Both (Patreon or PayPal)')], default='both', help_text='Choose which payment method to use for verification.', max_length=10)), + ('activation_key', models.CharField(blank=True, default='', help_text='Validated activation key - grants access without re-entering.', max_length=64)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'verbose_name': 'Premium Plugin Configuration', + 'verbose_name_plural': 'Premium Plugin Configurations', + }, + ), + ] diff --git a/premiumPlugin/migrations/__init__.py b/premiumPlugin/migrations/__init__.py new file mode 100644 index 000000000..80dc97892 --- /dev/null +++ b/premiumPlugin/migrations/__init__.py @@ -0,0 +1 @@ +# Premium Plugin migrations diff --git a/premiumPlugin/models.py b/premiumPlugin/models.py new file mode 100644 index 000000000..a4396a32a --- /dev/null +++ b/premiumPlugin/models.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from django.db import models + + +class PremiumPluginConfig(models.Model): + """Config for Premium Plugin - activation key and payment preference.""" + PAYMENT_METHOD_CHOICES = [ + ('patreon', 'Patreon Subscription'), + ('paypal', 'PayPal Payment'), + ('both', 'Check Both (Patreon or PayPal)'), + ] + payment_method = models.CharField( + max_length=10, + choices=PAYMENT_METHOD_CHOICES, + default='both', + help_text="Choose which payment method to use for verification." + ) + activation_key = models.CharField( + max_length=64, + blank=True, + default='', + help_text="Validated activation key - grants access without re-entering." + ) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + verbose_name = "Premium Plugin Configuration" + verbose_name_plural = "Premium Plugin Configurations" + + def __str__(self): + return "Premium Plugin Configuration" + + @classmethod + def get_config(cls): + """Get or create the singleton config instance.""" + config, _ = cls.objects.get_or_create(pk=1) + return config + + def save(self, *args, **kwargs): + self.pk = 1 + super().save(*args, **kwargs) diff --git a/premiumPlugin/templates/premiumPlugin/index.html b/premiumPlugin/templates/premiumPlugin/index.html new file mode 100644 index 000000000..f7ab4e200 --- /dev/null +++ b/premiumPlugin/templates/premiumPlugin/index.html @@ -0,0 +1,96 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} + +{% block title %}{% trans "Premium Plugin Example - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ {% trans "Premium Plugin" %} +

{{ plugin_name }}

+

{{ description }}

+

{% trans "Version:" %} {{ version }}

+
+ +
+

{% trans "Premium Features" %}

+
    + {% for feature in features %} +
  • {{ feature }}
  • + {% endfor %} +
+
+
+{% endblock %} diff --git a/premiumPlugin/templates/premiumPlugin/settings.html b/premiumPlugin/templates/premiumPlugin/settings.html new file mode 100644 index 000000000..59a83d132 --- /dev/null +++ b/premiumPlugin/templates/premiumPlugin/settings.html @@ -0,0 +1,315 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} + +{% block title %}{% trans "Premium Plugin Settings - CyberPanel" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+ {% trans "Premium Plugin" %} +

{% trans "Premium Plugin Settings" %}

+ + {% if show_payment_ui %} + +
+

{% trans "Activate Premium Access" %}

+

{% trans "If you have an activation key, enter it below." %}

+ +
+ {% csrf_token %} + + +
+
+ +
+ +
+ {% csrf_token %} + +
+
+ {% else %} +
+ {% trans "Premium Access Active" %} — {% trans "Access granted via Plugin Grants or activation key." %} +
+ {% endif %} + + +
+ + {% trans "Plugin Information" %} +
    +
  • {% trans "Name" %}: {{ plugin_name|default:"Premium Plugin Example" }}
  • +
  • {% trans "Version" %}: {{ version|default:"1.0.0" }}
  • +
  • + {% trans "Status" %}: + + {{ plugin_status|default:status|default:"Active" }} + +
  • +
+
+ +

{{ description }}

+ +
+ {% csrf_token %} +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ + {% if show_payment_ui %} + + {% endif %} +
+
+{% endblock %} diff --git a/premiumPlugin/templates/premiumPlugin/subscription_required.html b/premiumPlugin/templates/premiumPlugin/subscription_required.html new file mode 100644 index 000000000..cfc11a35a --- /dev/null +++ b/premiumPlugin/templates/premiumPlugin/subscription_required.html @@ -0,0 +1,139 @@ +{% extends "baseTemplate/index.html" %} +{% load i18n %} + +{% block title %}{% trans "Payment Required - Premium Plugin" %}{% endblock %} + +{% block header_scripts %} + +{% endblock %} + +{% block content %} +
+
+
+

{% trans "Premium Plugin Access Required" %}

+

{% trans "This plugin requires payment or subscription to access premium features." %}

+ + +
+

{% trans "Activate Premium Access" %}

+

{% trans "If you received an activation key, enter it below." %}

+ +
+ {% csrf_token %} +
+ + +
+ +
+
+ + {% if payment_method == 'both' %} +
+ {% trans "Current Payment Method:" %} {% trans "Check Both (Patreon or PayPal)" %} +
+ {% elif payment_method == 'patreon' %} +
+ {% trans "Current Payment Method:" %} {% trans "Patreon Subscription Only" %} +
+ {% elif payment_method == 'paypal' %} +
+ {% trans "Current Payment Method:" %} {% trans "PayPal Payment Only" %} +
+ {% endif %} + +
+
+

{% trans "Patreon Subscription" %}

+

{% trans "Subscribe to" %} "{{ patreon_tier }}"

+ + {% trans "Subscribe on Patreon" %} + +
+
+

{% trans "PayPal Payment" %}

+

{% trans "Complete one-time payment via PayPal" %}

+ {% if paypal_me_url %} + + {% trans "Pay with PayPal.me" %} + + {% endif %} +
+
+ +
+

{% trans "How it works:" %}

+
    +
  • {% trans "Install the plugin (already done)" %}
  • +
  • {% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}
  • +
  • {% trans "The plugin will automatically unlock" %}
  • +
+
+ + {% if error %} +
+ {% trans "Error:" %} {{ error }} +
+ {% endif %} +
+
+ + +{% endblock %} diff --git a/premiumPlugin/urls.py b/premiumPlugin/urls.py new file mode 100644 index 000000000..a9c206dbe --- /dev/null +++ b/premiumPlugin/urls.py @@ -0,0 +1,12 @@ +from django.urls import path, re_path +from . import views + +app_name = 'premiumPlugin' + +urlpatterns = [ + path('', views.main_view, name='main'), + path('settings/', views.settings_view, name='settings'), + re_path(r'^activate-key/$', views.activate_key, name='activate_key'), + path('save-payment-method/', views.save_payment_method, name='save_payment_method'), + path('api/status/', views.api_status_view, name='api_status'), +] diff --git a/premiumPlugin/views.py b/premiumPlugin/views.py index e620f97c5..fb887850f 100644 --- a/premiumPlugin/views.py +++ b/premiumPlugin/views.py @@ -1,269 +1,406 @@ # -*- coding: utf-8 -*- """ -Premium Plugin Views - Remote Verification Version -This version uses remote server verification (no secrets in plugin) -SECURITY: All Patreon API calls happen on YOUR server, not user's server +Premium Plugin Views - Unified Verification (same as contaboAutoSnapshot) +Supports: Plugin Grants, Activation Key, Patreon, PayPal, AES encryption """ from django.shortcuts import render, redirect -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponse +from django.views.decorators.http import require_http_methods from plogical.mailUtilities import mailUtilities from plogical.httpProc import httpProc +from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging from functools import wraps -import sys -import os import urllib.request import urllib.error import json -# Remote verification server (YOUR server, not user's server) -REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership' -PLUGIN_NAME = 'premiumPlugin' # Patreon Premium Plugin Example -PLUGIN_VERSION = '1.0.0' +from .models import PremiumPluginConfig +from . import api_encryption + +PLUGIN_NAME = 'premiumPlugin' +PLUGIN_VERSION = '1.0.2' + +REMOTE_VERIFICATION_PATREON_URL = 'https://api.newstargeted.com/api/verify-patreon-membership.php' +REMOTE_VERIFICATION_PAYPAL_URL = 'https://api.newstargeted.com/api/verify-paypal-payment.php' +REMOTE_VERIFICATION_PLUGIN_GRANT_URL = 'https://api.newstargeted.com/api/verify-plugin-grant.php' +REMOTE_ACTIVATION_KEY_URL = 'https://api.newstargeted.com/api/activate-plugin-key.php' + +PATREON_TIER = 'CyberPanel Paid Plugin' +PATREON_URL = 'https://www.patreon.com/membership/27789984' +PAYPAL_ME_URL = 'https://paypal.me/KimBS?locale.x=en_US&country.x=NO' +PAYPAL_PAYMENT_LINK = '' + def cyberpanel_login_required(view_func): - """ - Custom decorator that checks for CyberPanel session userID - """ @wraps(view_func) def _wrapped_view(request, *args, **kwargs): try: - userID = request.session['userID'] - # User is authenticated via CyberPanel session + if not request.session.get('userID'): + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) return view_func(request, *args, **kwargs) except KeyError: - # Not logged in, redirect to login from loginSystem.views import loadLoginPage return redirect(loadLoginPage) return _wrapped_view -def remote_verification_required(view_func): - """ - Decorator that checks Patreon membership via remote server - No secrets stored in plugin - all verification happens on your server - """ - @wraps(view_func) - def _wrapped_view(request, *args, **kwargs): - # First check login - try: - userID = request.session['userID'] - except KeyError: - from loginSystem.views import loadLoginPage - return redirect(loadLoginPage) - - # Get user email - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - # Try to get from session or username - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - # Check membership via remote server - verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', '')) - - if not verification_result.get('has_access', False): - # User doesn't have subscription - show subscription required page - context = { - 'plugin_name': 'Patreon Premium Plugin Example', - 'is_paid': True, - 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'), - 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'), - 'message': verification_result.get('message', 'Patreon subscription required'), - 'error': verification_result.get('error') - } - proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin') - return proc.render() - - # User has access - proceed with view - return view_func(request, *args, **kwargs) - - return _wrapped_view -def check_remote_membership(user_email, user_ip=''): - """ - Check Patreon membership via remote verification server - - Args: - user_email: User's email address - user_ip: User's IP address (for logging/security) - - Returns: - dict: { - 'has_access': bool, - 'patreon_tier': str, - 'patreon_url': str, - 'message': str, - 'error': str or None - } - """ +def _api_request(url, data, timeout=10): + """Send encrypted API request and return decoded response dict.""" + try: + body, extra_headers = api_encryption.encrypt_payload(data) + headers = { + 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}', + 'X-Plugin-Name': PLUGIN_NAME + } + headers.update(extra_headers) + req = urllib.request.Request(url, data=body, headers=headers) + with urllib.request.urlopen(req, timeout=timeout) as response: + raw = response.read() + ct = response.headers.get('Content-Type', '') + expect_enc = extra_headers.get('X-Encrypted') == '1' + return api_encryption.decrypt_response(raw, ct, expect_encrypted=expect_enc) + except Exception as e: + logging.writeToFile(f"Premium Plugin: API request error to {url}: {str(e)}") + return {} + + +def check_plugin_grant(user_email, user_ip='', domain=''): + try: + request_data = { + 'user_email': user_email or '', + 'plugin_name': PLUGIN_NAME, + 'user_ip': user_ip, + 'domain': domain, + } + data = _api_request(REMOTE_VERIFICATION_PLUGIN_GRANT_URL, request_data) + if data.get('success') and data.get('has_access'): + return {'has_access': True, 'message': data.get('message', 'Access granted via Plugin Grants')} + return {'has_access': False, 'message': data.get('message', '')} + except Exception as e: + logging.writeToFile(f"Premium Plugin: Plugin grant check error: {str(e)}") + return {'has_access': False, 'message': ''} + + +def check_patreon_membership(user_email, user_ip='', domain=''): try: - # Prepare request data request_data = { 'user_email': user_email, 'plugin_name': PLUGIN_NAME, 'plugin_version': PLUGIN_VERSION, 'user_ip': user_ip, - 'tier_id': '27789984' # CyberPanel Paid Plugin tier ID + 'domain': domain, + 'tier_id': '27789984' } - - # Make request to remote verification server - req = urllib.request.Request( - REMOTE_VERIFICATION_URL, - data=json.dumps(request_data).encode('utf-8'), - headers={ - 'Content-Type': 'application/json', - 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}', - 'X-Plugin-Name': PLUGIN_NAME - } - ) - - # Send request with timeout - try: - with urllib.request.urlopen(req, timeout=10) as response: - response_data = json.loads(response.read().decode('utf-8')) - - if response_data.get('success', False): - return { - 'has_access': response_data.get('has_access', False), - 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'), - 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'), - 'message': response_data.get('message', 'Access granted'), - 'error': None - } - else: - return { - 'has_access': False, - 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'), - 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'), - 'message': response_data.get('message', 'Patreon subscription required'), - 'error': response_data.get('error') - } - except urllib.error.HTTPError as e: - # Server returned error - error_body = e.read().decode('utf-8') if e.fp else 'Unknown error' + response_data = _api_request(REMOTE_VERIFICATION_PATREON_URL, request_data) + if response_data.get('success', False): return { - 'has_access': False, - 'patreon_tier': 'CyberPanel Paid Plugin', - 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', - 'message': 'Unable to verify subscription. Please try again later.', - 'error': f'HTTP {e.code}: {error_body}' + 'has_access': response_data.get('has_access', False), + 'patreon_tier': response_data.get('patreon_tier', PATREON_TIER), + 'patreon_url': response_data.get('patreon_url', PATREON_URL), + 'message': response_data.get('message', 'Access granted'), + 'error': None } - except urllib.error.URLError as e: - # Network error - return { - 'has_access': False, - 'patreon_tier': 'CyberPanel Paid Plugin', - 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', - 'message': 'Unable to connect to verification server. Please check your internet connection.', - 'error': str(e.reason) if hasattr(e, 'reason') else str(e) - } - except Exception as e: - # Other errors - return { - 'has_access': False, - 'patreon_tier': 'CyberPanel Paid Plugin', - 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', - 'message': 'Verification error occurred. Please try again later.', - 'error': str(e) - } - - except Exception as e: - import logging - logging.writeToFile(f"Error in remote membership check: {str(e)}") return { 'has_access': False, - 'patreon_tier': 'CyberPanel Paid Plugin', - 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', - 'message': 'Verification error occurred. Please try again later.', + 'patreon_tier': PATREON_TIER, + 'patreon_url': PATREON_URL, + 'message': response_data.get('message', 'Patreon subscription required'), + 'error': response_data.get('error') + } + except Exception as e: + logging.writeToFile(f"Premium Plugin: Patreon check error: {str(e)}") + return { + 'has_access': False, + 'patreon_tier': PATREON_TIER, + 'patreon_url': PATREON_URL, + 'message': 'Unable to verify Patreon membership.', 'error': str(e) } -@cyberpanel_login_required -def main_view(request): - """ - Main view for premium plugin - Shows plugin information and features if subscribed, or subscription required message if not - """ - mailUtilities.checkHome() - - # Get user email for verification - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - # Check membership status (but don't block access) - verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', '')) - has_access = verification_result.get('has_access', False) - - # Determine plugin status - plugin_status = 'Active' if has_access else 'Subscription Required' - - context = { - 'plugin_name': 'Patreon Premium Plugin Example', - 'version': PLUGIN_VERSION, - 'status': plugin_status, - 'has_access': has_access, - 'description': 'This is an example paid plugin that requires Patreon subscription.' if not has_access else 'This is an example paid plugin. You have access because you are subscribed to Patreon!', - 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'), - 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/membership/27789984'), - 'features': [ - 'Premium Feature 1', - 'Premium Feature 2', - 'Premium Feature 3', - 'Advanced Configuration', - 'Priority Support' - ] if has_access else [] - } - - proc = httpProc(request, 'premiumPlugin/index.html', context, 'admin') - return proc.render() + +def check_paypal_payment(user_email, user_ip='', domain=''): + try: + request_data = { + 'user_email': user_email, + 'plugin_name': PLUGIN_NAME, + 'plugin_version': PLUGIN_VERSION, + 'user_ip': user_ip, + 'domain': domain, + 'timestamp': 0, + } + import time + request_data['timestamp'] = int(time.time()) + response_data = _api_request(REMOTE_VERIFICATION_PAYPAL_URL, request_data) + if response_data.get('success', False): + return { + 'has_access': response_data.get('has_access', False), + 'paypal_me_url': response_data.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': response_data.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': response_data.get('message', 'Access granted'), + 'error': None + } + return { + 'has_access': False, + 'paypal_me_url': PAYPAL_ME_URL, + 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'message': response_data.get('message', 'PayPal payment required'), + 'error': response_data.get('error') + } + except Exception as e: + logging.writeToFile(f"Premium Plugin: PayPal check error: {str(e)}") + return { + 'has_access': False, + 'paypal_me_url': PAYPAL_ME_URL, + 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'message': 'Unable to verify PayPal payment.', + 'error': str(e) + } + + +def unified_verification_required(view_func): + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + try: + if not request.session.get('userID'): + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) + + user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') or getattr(request.user, 'username', '') + + try: + config = PremiumPluginConfig.get_config() + payment_method = config.payment_method + except Exception: + payment_method = 'both' + + has_access = False + verification_result = {} + + activation_key = request.GET.get('activation_key') or request.POST.get('activation_key') + if not activation_key: + try: + config = PremiumPluginConfig.get_config() + activation_key = getattr(config, 'activation_key', '') or '' + except Exception: + activation_key = '' + + if activation_key: + try: + request_data = { + 'activation_key': activation_key.strip(), + 'plugin_name': PLUGIN_NAME, + 'user_email': user_email + } + response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data) + if response_data.get('success', False) and response_data.get('has_access', False): + has_access = True + verification_result = {'method': 'activation_key', 'has_access': True, 'message': response_data.get('message', 'Access activated via key')} + try: + config = PremiumPluginConfig.get_config() + config.activation_key = activation_key.strip() + config.save(update_fields=['activation_key', 'updated_at']) + except Exception as e: + logging.writeToFile(f"Premium Plugin: Could not persist activation key: {str(e)}") + elif not response_data.get('success') and activation_key: + try: + config = PremiumPluginConfig.get_config() + if getattr(config, 'activation_key', '') == activation_key.strip(): + config.activation_key = '' + config.save(update_fields=['activation_key', 'updated_at']) + except Exception: + pass + except Exception as e: + logging.writeToFile(f"Premium Plugin: Activation key check error: {str(e)}") + + if not has_access: + grant_result = check_plugin_grant(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + if grant_result.get('has_access'): + has_access = True + verification_result = {'method': 'plugin_grant', 'has_access': True, 'message': grant_result.get('message', 'Access granted via Plugin Grants')} + + if not has_access: + try: + if payment_method == 'patreon': + result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + has_access = result.get('has_access', False) + verification_result = { + 'method': 'patreon', 'has_access': has_access, + 'patreon_tier': result.get('patreon_tier', PATREON_TIER), + 'patreon_url': result.get('patreon_url', PATREON_URL), + 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'message': result.get('message', 'Patreon subscription required'), + 'error': result.get('error') + } + elif payment_method == 'paypal': + result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + has_access = result.get('has_access', False) + verification_result = { + 'method': 'paypal', 'has_access': has_access, + 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, + 'paypal_me_url': result.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': result.get('message', 'PayPal payment required'), + 'error': result.get('error') + } + else: + patreon_result = check_patreon_membership(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + paypal_result = check_paypal_payment(user_email, request.META.get('REMOTE_ADDR', ''), request.get_host()) + has_access = patreon_result.get('has_access', False) or paypal_result.get('has_access', False) + verification_result = { + 'method': 'both', 'has_access': has_access, + 'patreon_tier': patreon_result.get('patreon_tier', PATREON_TIER), + 'patreon_url': patreon_result.get('patreon_url', PATREON_URL), + 'paypal_me_url': paypal_result.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': paypal_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': 'Payment or subscription required' if not has_access else 'Access granted' + } + except Exception as e: + logging.writeToFile(f"Premium Plugin: Verification error: {str(e)}") + has_access = False + verification_result = { + 'method': payment_method, 'has_access': False, + 'patreon_tier': PATREON_TIER, 'patreon_url': PATREON_URL, + 'paypal_me_url': PAYPAL_ME_URL, 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'message': 'Unable to verify access.', + 'error': str(e) + } + + if not has_access: + context = { + 'plugin_name': 'Premium Plugin Example', + 'is_paid': True, + 'payment_method': payment_method, + 'verification_result': verification_result, + 'patreon_tier': verification_result.get('patreon_tier', PATREON_TIER), + 'patreon_url': verification_result.get('patreon_url', PATREON_URL), + 'paypal_me_url': verification_result.get('paypal_me_url', PAYPAL_ME_URL), + 'paypal_payment_link': verification_result.get('paypal_payment_link', PAYPAL_PAYMENT_LINK), + 'message': verification_result.get('message', 'Payment or subscription required'), + 'error': verification_result.get('error') + } + proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin') + return proc.render() + + if has_access and verification_result: + request.session['premium_plugin_access_via'] = verification_result.get('method', '') + + return view_func(request, *args, **kwargs) + except Exception as e: + logging.writeToFile(f"Premium Plugin: Decorator error: {str(e)}") + return HttpResponse(f"

Plugin Error

{str(e)}

") + return _wrapped_view + @cyberpanel_login_required -def settings_view(request): - """ - Settings page for premium plugin - Shows settings but disables them if user doesn't have Patreon subscription - """ +def main_view(request): mailUtilities.checkHome() - - # Get user email for verification - user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None - if not user_email: - user_email = request.session.get('email', '') or getattr(request.user, 'username', '') - - # Check membership status (but don't block access) - verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', '')) - has_access = verification_result.get('has_access', False) - - # Determine plugin status - plugin_status = 'Active' if has_access else 'Subscription Required' - + return redirect('premiumPlugin:settings') + + +@cyberpanel_login_required +@unified_verification_required +def settings_view(request): + mailUtilities.checkHome() + try: + config = PremiumPluginConfig.get_config() + except Exception: + from django.core.management import call_command + try: + call_command('migrate', 'premiumPlugin', verbosity=0, interactive=False) + config = PremiumPluginConfig.get_config() + except Exception as e: + return HttpResponse(f"

Database Error

{str(e)}

") + + access_via = request.session.get('premium_plugin_access_via', '') + show_payment_ui = access_via not in ('plugin_grant', 'activation_key') + context = { - 'plugin_name': 'Patreon Premium Plugin Example', + 'plugin_name': 'Premium Plugin Example', 'version': PLUGIN_VERSION, - 'plugin_status': plugin_status, - 'status': plugin_status, # Keep both for compatibility - 'description': 'Configure your premium plugin settings', - 'has_access': has_access, - 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'), - 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/membership/27789984'), - 'verification_message': verification_result.get('message', '') + 'status': 'Active', + 'config': config, + 'has_access': True, + 'show_payment_ui': show_payment_ui, + 'access_via_grant_or_key': not show_payment_ui, + 'patreon_tier': PATREON_TIER, + 'patreon_url': PATREON_URL, + 'paypal_me_url': PAYPAL_ME_URL, + 'paypal_payment_link': PAYPAL_PAYMENT_LINK, + 'description': 'Configure your premium plugin settings.', } - proc = httpProc(request, 'premiumPlugin/settings.html', context, 'admin') return proc.render() + @cyberpanel_login_required -@remote_verification_required +@require_http_methods(["POST"]) +def activate_key(request): + try: + if request.content_type == 'application/json': + data = json.loads(request.body) + else: + data = request.POST + + activation_key = data.get('activation_key', '').strip() + user_email = data.get('user_email', '').strip() + if not user_email: + user_email = request.session.get('email', '') or (getattr(request.user, 'email', '') if hasattr(request, 'user') and request.user else '') + + if not activation_key: + return JsonResponse({'success': False, 'message': 'Activation key is required'}, status=400) + + request_data = {'activation_key': activation_key, 'plugin_name': PLUGIN_NAME, 'user_email': user_email} + response_data = _api_request(REMOTE_ACTIVATION_KEY_URL, request_data) + + if response_data.get('success', False) and response_data.get('has_access', False): + try: + config = PremiumPluginConfig.get_config() + config.activation_key = activation_key + config.save(update_fields=['activation_key', 'updated_at']) + except Exception as e: + logging.writeToFile(f"Premium Plugin: Could not persist activation key: {str(e)}") + + return JsonResponse({ + 'success': True, + 'has_access': True, + 'message': response_data.get('message', 'Access activated successfully') + }) + + return JsonResponse({ + 'success': False, + 'has_access': False, + 'message': response_data.get('message', 'Invalid activation key') + }) + + except Exception as e: + logging.writeToFile(f"Premium Plugin: activate_key error: {str(e)}") + return JsonResponse({'success': False, 'message': str(e)}, status=500) + + +@cyberpanel_login_required +@require_http_methods(["POST"]) +def save_payment_method(request): + try: + payment_method = request.POST.get('payment_method', 'both') + if payment_method not in ('patreon', 'paypal', 'both'): + payment_method = 'both' + config = PremiumPluginConfig.get_config() + config.payment_method = payment_method + config.save(update_fields=['payment_method', 'updated_at']) + return JsonResponse({'success': True, 'message': 'Payment method saved'}) + except Exception as e: + return JsonResponse({'success': False, 'message': str(e)}, status=500) + + +@cyberpanel_login_required +@unified_verification_required def api_status_view(request): - """ - API endpoint for plugin status - Only accessible with Patreon subscription (verified remotely) - """ return JsonResponse({ - 'plugin_name': 'Patreon Premium Plugin Example', + 'plugin_name': 'Premium Plugin Example', 'version': PLUGIN_VERSION, 'status': 'active', 'subscription': 'active', - 'description': 'Premium plugin is active and accessible', - 'verification_method': 'remote' + 'verification_method': 'unified' }) diff --git a/premiumPlugin/views_remote.py b/premiumPlugin/views_remote.py new file mode 100644 index 000000000..a4adfaeeb --- /dev/null +++ b/premiumPlugin/views_remote.py @@ -0,0 +1,236 @@ +# -*- coding: utf-8 -*- +""" +Premium Plugin Views - Remote Verification Version +This version uses remote server verification (no secrets in plugin) +""" + +from django.shortcuts import render, redirect +from django.http import JsonResponse +from plogical.mailUtilities import mailUtilities +from plogical.httpProc import httpProc +from functools import wraps +import sys +import os +import urllib.request +import urllib.error +import json + +# Remote verification server (YOUR server, not user's server) +REMOTE_VERIFICATION_URL = 'https://api.newstargeted.com/api/verify-patreon-membership' +PLUGIN_NAME = 'premiumPlugin' +PLUGIN_VERSION = '1.0.0' + +def cyberpanel_login_required(view_func): + """ + Custom decorator that checks for CyberPanel session userID + """ + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + try: + userID = request.session['userID'] + # User is authenticated via CyberPanel session + return view_func(request, *args, **kwargs) + except KeyError: + # Not logged in, redirect to login + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) + return _wrapped_view + +def remote_verification_required(view_func): + """ + Decorator that checks Patreon membership via remote server + No secrets stored in plugin - all verification happens on your server + """ + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + # First check login + try: + userID = request.session['userID'] + except KeyError: + from loginSystem.views import loadLoginPage + return redirect(loadLoginPage) + + # Get user email + user_email = getattr(request.user, 'email', None) if hasattr(request, 'user') and request.user else None + if not user_email: + # Try to get from session or username + user_email = request.session.get('email', '') or getattr(request.user, 'username', '') + + # Check membership via remote server + verification_result = check_remote_membership(user_email, request.META.get('REMOTE_ADDR', '')) + + if not verification_result.get('has_access', False): + # User doesn't have subscription - show subscription required page + context = { + 'plugin_name': 'Premium Plugin Example', + 'is_paid': True, + 'patreon_tier': verification_result.get('patreon_tier', 'CyberPanel Paid Plugin'), + 'patreon_url': verification_result.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'), + 'message': verification_result.get('message', 'Patreon subscription required'), + 'error': verification_result.get('error') + } + proc = httpProc(request, 'premiumPlugin/subscription_required.html', context, 'admin') + return proc.render() + + # User has access - proceed with view + return view_func(request, *args, **kwargs) + + return _wrapped_view + +def check_remote_membership(user_email, user_ip=''): + """ + Check Patreon membership via remote verification server + + Args: + user_email: User's email address + user_ip: User's IP address (for logging/security) + + Returns: + dict: { + 'has_access': bool, + 'patreon_tier': str, + 'patreon_url': str, + 'message': str, + 'error': str or None + } + """ + try: + # Prepare request data + request_data = { + 'user_email': user_email, + 'plugin_name': PLUGIN_NAME, + 'plugin_version': PLUGIN_VERSION, + 'user_ip': user_ip, + 'tier_id': '27789984' # CyberPanel Paid Plugin tier ID + } + + # Make request to remote verification server + req = urllib.request.Request( + REMOTE_VERIFICATION_URL, + data=json.dumps(request_data).encode('utf-8'), + headers={ + 'Content-Type': 'application/json', + 'User-Agent': f'CyberPanel-Plugin/{PLUGIN_VERSION}', + 'X-Plugin-Name': PLUGIN_NAME + } + ) + + # Send request with timeout + try: + with urllib.request.urlopen(req, timeout=10) as response: + response_data = json.loads(response.read().decode('utf-8')) + + if response_data.get('success', False): + return { + 'has_access': response_data.get('has_access', False), + 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'), + 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'), + 'message': response_data.get('message', 'Access granted'), + 'error': None + } + else: + return { + 'has_access': False, + 'patreon_tier': response_data.get('patreon_tier', 'CyberPanel Paid Plugin'), + 'patreon_url': response_data.get('patreon_url', 'https://www.patreon.com/c/newstargeted/membership'), + 'message': response_data.get('message', 'Patreon subscription required'), + 'error': response_data.get('error') + } + except urllib.error.HTTPError as e: + # Server returned error + error_body = e.read().decode('utf-8') if e.fp else 'Unknown error' + return { + 'has_access': False, + 'patreon_tier': 'CyberPanel Paid Plugin', + 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', + 'message': 'Unable to verify subscription. Please try again later.', + 'error': f'HTTP {e.code}: {error_body}' + } + except urllib.error.URLError as e: + # Network error + return { + 'has_access': False, + 'patreon_tier': 'CyberPanel Paid Plugin', + 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', + 'message': 'Unable to connect to verification server. Please check your internet connection.', + 'error': str(e.reason) if hasattr(e, 'reason') else str(e) + } + except Exception as e: + # Other errors + return { + 'has_access': False, + 'patreon_tier': 'CyberPanel Paid Plugin', + 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', + 'message': 'Verification error occurred. Please try again later.', + 'error': str(e) + } + + except Exception as e: + import logging + logging.writeToFile(f"Error in remote membership check: {str(e)}") + return { + 'has_access': False, + 'patreon_tier': 'CyberPanel Paid Plugin', + 'patreon_url': 'https://www.patreon.com/c/newstargeted/membership', + 'message': 'Verification error occurred. Please try again later.', + 'error': str(e) + } + +@cyberpanel_login_required +@remote_verification_required +def main_view(request): + """ + Main view for premium plugin + Only accessible with Patreon subscription (verified remotely) + """ + mailUtilities.checkHome() + + context = { + 'plugin_name': 'Premium Plugin Example', + 'version': PLUGIN_VERSION, + 'description': 'This is an example paid plugin. You have access because you are subscribed to Patreon!', + 'features': [ + 'Premium Feature 1', + 'Premium Feature 2', + 'Premium Feature 3', + 'Advanced Configuration', + 'Priority Support' + ] + } + + proc = httpProc(request, 'premiumPlugin/index.html', context, 'admin') + return proc.render() + +@cyberpanel_login_required +@remote_verification_required +def settings_view(request): + """ + Settings page for premium plugin + Only accessible with Patreon subscription (verified remotely) + """ + mailUtilities.checkHome() + + context = { + 'plugin_name': 'Premium Plugin Example', + 'version': PLUGIN_VERSION, + 'description': 'Configure your premium plugin settings' + } + + proc = httpProc(request, 'premiumPlugin/settings.html', context, 'admin') + return proc.render() + +@cyberpanel_login_required +@remote_verification_required +def api_status_view(request): + """ + API endpoint for plugin status + Only accessible with Patreon subscription (verified remotely) + """ + return JsonResponse({ + 'plugin_name': 'Premium Plugin Example', + 'version': PLUGIN_VERSION, + 'status': 'active', + 'subscription': 'active', + 'description': 'Premium plugin is active and accessible', + 'verification_method': 'remote' + })