Plugin updates: premiumPlugin & paypalPremiumPlugin unified verification, Installed Plugins UI improvements

This commit is contained in:
master3395
2026-02-02 03:39:42 +01:00
parent 8cf762c54f
commit 394cb41a4c
32 changed files with 2696 additions and 651 deletions

View File

@@ -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.
---

View File

@@ -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
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/c/newstargeted/membership</patreon_url>
```
## Author
master3395
## License
MIT

View File

@@ -0,0 +1,4 @@
# PayPal Premium Plugin Example
# This is a paid plugin that requires PayPal payment
default_app_config = 'paypalPremiumPlugin.apps.PaypalpremiumpluginConfig'

View File

@@ -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 {}

View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class PaypalpremiumpluginConfig(AppConfig):
name = 'paypalPremiumPlugin'
verbose_name = 'PayPal Premium Plugin Example'

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<name>PayPal Premium Plugin Example</name>
<type>Utility</type>
<version>1.0.2</version>
<description>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.</description>
<author>master3395</author>
<website>https://github.com/master3395/cyberpanel-plugins</website>
<license>MIT</license>
<dependencies>
<python>3.6+</python>
<django>2.2+</django>
<cyberpanel>2.5.5+</cyberpanel>
</dependencies>
<compatibility>
<min_version>2.5.5</min_version>
<max_version>3.0.0</max_version>
</compatibility>
<permissions>
<admin>true</admin>
<user>false</user>
</permissions>
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/membership/27789984</patreon_url>
<paypal_me_url>https://paypal.me/KimBS?locale.x=en_US&amp;country.x=NO</paypal_me_url>
<paypal_payment_link></paypal_payment_link>
<url>/plugins/paypalPremiumPlugin/</url>
<settings_url>/plugins/paypalPremiumPlugin/settings/</settings_url>
</plugin>

View File

@@ -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',
},
),
]

View File

@@ -0,0 +1 @@
# PayPal Premium Plugin migrations

View File

@@ -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)

View File

@@ -0,0 +1,96 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "PayPal Premium Plugin Example - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.plugin-header {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.plugin-header h1 {
margin: 0 0 10px 0;
color: #2f3640;
}
.features-list {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.features-list h2 {
margin-top: 0;
color: #2f3640;
}
.features-list ul {
list-style: none;
padding: 0;
}
.features-list li {
padding: 12px 0;
border-bottom: 1px solid #e8e9ff;
display: flex;
align-items: center;
gap: 12px;
}
.features-list li:last-child {
border-bottom: none;
}
.features-list li::before {
content: "✓";
color: #28a745;
font-weight: bold;
font-size: 18px;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="plugin-header">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{{ plugin_name }}</h1>
<p>{{ description }}</p>
<p><strong>{% trans "Version:" %}</strong> {{ version }}</p>
</div>
<div class="features-list">
<h2>{% trans "Premium Features" %}</h2>
<ul>
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,331 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "PayPal Premium Plugin Settings - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.settings-form {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.payment-warning {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 25px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px;
}
.payment-warning-content {
flex: 1;
min-width: 250px;
}
.payment-warning-title {
font-weight: 600;
color: #856404;
margin-bottom: 5px;
font-size: 16px;
}
.payment-warning-text {
color: #856404;
font-size: 14px;
margin: 0;
}
.paypal-button {
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 0.2s, box-shadow 0.2s;
white-space: nowrap;
margin: 5px;
}
.paypal-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 112, 186, 0.4);
color: white;
text-decoration: none;
}
.paypal-me-button {
background: linear-gradient(135deg, #0070ba 0%, #003087 100%);
}
.paypal-payment-link-button {
background: linear-gradient(135deg, #009cde 0%, #0070ba 100%);
}
.settings-disabled {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.settings-disabled::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2f3640;
}
.form-control {
width: 100%;
padding: 10px 15px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-control:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
}
.form-control:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #5856d6 0%, #4a90e2 100%);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(88, 86, 214, 0.4);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
/* Plugin Information Section - Ensure visibility */
.plugin-info-section {
background: #d1ecf1 !important;
border: 1px solid #bee5eb !important;
border-radius: 8px !important;
padding: 15px 20px !important;
margin-bottom: 25px !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
z-index: 10 !important;
}
.plugin-info-section ul {
list-style: none !important;
padding-left: 0 !important;
margin-top: 10px !important;
margin-bottom: 0 !important;
}
.plugin-info-section li {
margin-bottom: 8px;
}
.plugin-info-section li:last-child {
margin-bottom: 0;
}
.payment-buttons-container {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="settings-form">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{% trans "PayPal Premium Plugin Settings" %}</h1>
{% if show_payment_ui %}
<!-- Activate Premium Access -->
<div style="background: #e6f7ff; border: 2px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057;">{% trans "If you have an activation key, enter it below." %}</p>
<div id="activation-msg" style="display: none; margin-bottom: 10px; padding: 10px; border-radius: 6px;"></div>
<form id="activation-form" style="display: flex; gap: 10px; align-items: flex-end;">
{% csrf_token %}
<input type="text" id="activation-key" name="activation_key" placeholder="PAYPALPREMIUMPLUGIN-XXXX-XXXX-XXXX" style="flex: 1; padding: 10px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" />
<button type="submit" class="btn btn-primary" id="activate-btn"><i class="fas fa-check-circle"></i> {% trans "Activate" %}</button>
</form>
</div>
<!-- Payment Method Preference -->
<div style="margin-bottom: 25px;">
<label style="font-weight: 600;">{% trans "Payment Method Preference" %}</label>
<form id="payment-method-form" method="post" action="{% url 'paypalPremiumPlugin:save_payment_method' %}" style="display: inline-block; margin-left: 10px;">
{% csrf_token %}
<select name="payment_method" onchange="this.form.submit()" style="padding: 8px 12px; border-radius: 6px;">
<option value="both" {% if config.payment_method == 'both' %}selected{% endif %}>{% trans "Check Both (Patreon or PayPal)" %}</option>
<option value="patreon" {% if config.payment_method == 'patreon' %}selected{% endif %}>{% trans "Patreon Only" %}</option>
<option value="paypal" {% if config.payment_method == 'paypal' %}selected{% endif %}>{% trans "PayPal Only" %}</option>
</select>
</form>
</div>
{% else %}
<div style="background: #d4edda; border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<i class="fas fa-check-circle" style="color: #28a745;"></i> <strong>{% trans "Premium Access Active" %}</strong> — {% trans "Access granted via Plugin Grants or activation key." %}
</div>
{% endif %}
<!-- Plugin Information Section - Always Visible -->
<div class="plugin-info-section" style="background: #d1ecf1 !important; border: 1px solid #bee5eb !important; border-radius: 8px !important; padding: 15px 20px !important; margin-bottom: 25px !important; display: block !important; visibility: visible !important; opacity: 1 !important;">
<i class="fas fa-info-circle" style="color: #0c5460; margin-right: 8px;"></i>
<strong style="color: #0c5460; font-weight: 600;">{% trans "Plugin Information" %}</strong>
<ul style="list-style: none !important; padding-left: 0 !important; margin-top: 10px !important; margin-bottom: 0 !important;">
<li style="margin-bottom: 8px;"><strong>{% trans "Name" %}:</strong> {{ plugin_name|default:"PayPal Premium Plugin Example" }}</li>
<li style="margin-bottom: 8px;"><strong>{% trans "Version" %}:</strong> {{ version|default:"1.0.0" }}</li>
<li style="margin-bottom: 0;">
<strong>{% trans "Status" %}:</strong>
<span class="badge" style="background: {% if has_access %}#28a745{% else %}#6c757d{% endif %} !important; color: white !important; padding: 4px 10px !important; border-radius: 4px !important; font-size: 12px !important; font-weight: 600 !important; display: inline-block !important;">
{{ plugin_status|default:status|default:"Active" }}
</span>
</li>
</ul>
</div>
<p>{{ description }}</p>
<form method="post" id="settings-form">
{% csrf_token %}
<div class="form-group">
<label>{% trans "Setting 1" %}</label>
<input type="text" class="form-control" name="setting1" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 2" %}</label>
<input type="text" class="form-control" name="setting2" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 3 (Advanced)" %}</label>
<textarea class="form-control" name="setting3" rows="4" placeholder="{% trans 'Enter advanced configuration' %}"></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="enable_feature">
{% trans "Enable Premium Feature" %}
</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {% trans "Save Settings" %}
</button>
</form>
{% if show_payment_ui %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-form');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = document.getElementById('activation-key').value.trim().toUpperCase();
if (!key) return;
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-msg');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/paypalPremiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { document.getElementById('activation-key').value = ''; setTimeout(function() { location.reload(); }, 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,139 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Payment Required - PayPal Premium Plugin" %}{% endblock %}
{% block header_scripts %}
<style>
.subscription-required-container { padding: 40px 20px; max-width: 900px; margin: 0 auto; text-align: center; }
.subscription-card { background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
.premium-icon { font-size: 64px; color: #0070ba; margin-bottom: 20px; }
.subscription-card h1 { color: #2f3640; margin-bottom: 15px; }
.subscription-card p { color: #64748b; font-size: 16px; line-height: 1.6; margin-bottom: 30px; }
.payment-methods { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
.payment-method-card { flex: 1; min-width: 250px; max-width: 350px; background: #f8f9ff; border: 2px solid #e2e8f0; border-radius: 12px; padding: 25px; text-align: center; }
.payment-method-card.patreon { border-color: #FF424D; }
.payment-method-card.paypal { border-color: #0070ba; }
.payment-method-card h3 { margin-top: 0; color: #2f3640; font-size: 1.3rem; }
.patreon-button, .paypal-button { display: inline-block; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; transition: all 0.3s ease; width: 100%; box-sizing: border-box; margin-bottom: 10px; }
.patreon-button { background: #FF424D; }
.patreon-button:hover { background: #E62E37; color: white; text-decoration: none; }
.paypal-button { background: #0070ba; }
.paypal-button:hover { background: #003087; color: white; text-decoration: none; }
.info-box { background: #f8f9ff; border-left: 4px solid #0070ba; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: left; }
.payment-method-selector { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: left; }
</style>
{% endblock %}
{% block content %}
<div class="subscription-required-container">
<div class="subscription-card">
<div class="premium-icon"><i class="fab fa-paypal"></i></div>
<h1>{% trans "Premium Plugin Access Required" %}</h1>
<p>{% trans "This plugin requires payment or subscription to access premium features." %}</p>
<!-- Activation Key Section -->
<div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); border: 2px solid #17a2b8; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: left;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057; margin-bottom: 20px;">{% trans "If you received an activation key, enter it below." %}</p>
<div id="activation-message" style="display: none; margin-bottom: 15px; padding: 12px; border-radius: 6px;"></div>
<form id="activation-key-form" style="display: flex; gap: 10px; align-items: flex-end; max-width: 600px;">
{% csrf_token %}
<div style="flex: 1;">
<label for="activation-key-input" style="font-weight: 600; display: block; margin-bottom: 8px;">{% trans "Activation Key" %}</label>
<input type="text" id="activation-key-input" name="activation_key" placeholder="PAYPALPREMIUMPLUGIN-XXXX-XXXX-XXXX" style="width: 100%; padding: 12px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" required />
</div>
<button type="submit" style="background: #17a2b8; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 600; cursor: pointer;" id="activate-btn">
<i class="fas fa-check-circle"></i> {% trans "Activate" %}
</button>
</form>
</div>
{% if payment_method == 'both' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Check Both (Patreon or PayPal)" %}
</div>
{% elif payment_method == 'patreon' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Patreon Subscription Only" %}
</div>
{% elif payment_method == 'paypal' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "PayPal Payment Only" %}
</div>
{% endif %}
<div class="payment-methods">
<div class="payment-method-card patreon">
<h3><i class="fab fa-patreon"></i> {% trans "Patreon Subscription" %}</h3>
<p>{% trans "Subscribe to" %} <strong>"{{ patreon_tier }}"</strong></p>
<a href="{{ patreon_url }}" target="_blank" rel="noopener noreferrer" class="patreon-button">
<i class="fab fa-patreon"></i> {% trans "Subscribe on Patreon" %}
</a>
</div>
<div class="payment-method-card paypal">
<h3><i class="fab fa-paypal"></i> {% trans "PayPal Payment" %}</h3>
<p>{% trans "Complete one-time payment via PayPal" %}</p>
{% if paypal_me_url %}
<a href="{{ paypal_me_url }}" target="_blank" rel="noopener noreferrer" class="paypal-button">
<i class="fab fa-paypal"></i> {% trans "Pay with PayPal.me" %}
</a>
{% endif %}
</div>
</div>
<div class="info-box">
<h3>{% trans "How it works:" %}</h3>
<ul>
<li>{% trans "Install the plugin (already done)" %}</li>
<li>{% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}</li>
<li>{% trans "The plugin will automatically unlock" %}</li>
</ul>
</div>
{% if error %}
<div style="background: #fee; border: 1px solid #fcc; border-radius: 8px; padding: 15px; margin-top: 20px; color: #c33;">
<strong>{% trans "Error:" %}</strong> {{ error }}
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-key-form');
const input = document.getElementById('activation-key-input');
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-message');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = input.value.trim().toUpperCase();
if (!key) return;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/paypalPremiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('input[name=csrfmiddlewaretoken]') ? document.querySelector('input[name=csrfmiddlewaretoken]').value : '' },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { input.value = ''; setTimeout(function() { location.reload(); }, 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endblock %}

View File

@@ -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'),
]

View File

@@ -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"<div style='padding: 20px;'><h2>Plugin Error</h2><p>{str(e)}</p></div>")
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"<div style='padding: 20px;'><h2>Database Error</h2><p>{str(e)}</p></div>")
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'
})

View File

@@ -1091,6 +1091,7 @@
{% endblock %}
{% block content %}
<!-- plugins-v2026-02-02-no-patreon-front -->
{% load static %}
{% get_current_language as LANGUAGE_CODE %}

6
premiumPlugin/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
# Security - Never commit secrets
*.secret
*_secret*
patreon_config.py
.env.patreon
patreon_secrets.env

View File

@@ -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

58
premiumPlugin/README.md Normal file
View File

@@ -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
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/c/newstargeted/membership</patreon_url>
```
## Author
master3395
## License
MIT

57
premiumPlugin/SECURITY.md Normal file
View File

@@ -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

View File

@@ -0,0 +1,4 @@
# Premium Plugin Example
# This is a paid plugin that requires Patreon subscription
default_app_config = 'premiumPlugin.apps.PremiumPluginConfig'

View File

@@ -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 {}

5
premiumPlugin/apps.py Normal file
View File

@@ -0,0 +1,5 @@
from django.apps import AppConfig
class PremiumPluginConfig(AppConfig):
name = 'premiumPlugin'
verbose_name = 'Premium Plugin Example'

30
premiumPlugin/meta.xml Normal file
View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<name>Premium Plugin Example</name>
<type>Utility</type>
<version>1.0.2</version>
<description>An example paid plugin that requires Patreon subscription to "CyberPanel Paid Plugin" tier. Users can install it but cannot run it without subscription.</description>
<author>master3395</author>
<website>https://github.com/master3395/cyberpanel-plugins</website>
<license>MIT</license>
<dependencies>
<python>3.6+</python>
<django>2.2+</django>
<cyberpanel>2.5.5+</cyberpanel>
</dependencies>
<compatibility>
<min_version>2.5.5</min_version>
<max_version>3.0.0</max_version>
</compatibility>
<permissions>
<admin>true</admin>
<user>false</user>
</permissions>
<paid>true</paid>
<patreon_tier>CyberPanel Paid Plugin</patreon_tier>
<patreon_url>https://www.patreon.com/membership/27789984</patreon_url>
<paypal_me_url>https://paypal.me/KimBS?locale.x=en_US&amp;country.x=NO</paypal_me_url>
<paypal_payment_link></paypal_payment_link>
<url>/plugins/premiumPlugin/</url>
<settings_url>/plugins/premiumPlugin/settings/</settings_url>
</plugin>

View File

@@ -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',
},
),
]

View File

@@ -0,0 +1 @@
# Premium Plugin migrations

42
premiumPlugin/models.py Normal file
View File

@@ -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)

View File

@@ -0,0 +1,96 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Premium Plugin Example - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.plugin-header {
background: white;
border-radius: 12px;
padding: 30px;
margin-bottom: 25px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.plugin-header h1 {
margin: 0 0 10px 0;
color: #2f3640;
}
.features-list {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.features-list h2 {
margin-top: 0;
color: #2f3640;
}
.features-list ul {
list-style: none;
padding: 0;
}
.features-list li {
padding: 12px 0;
border-bottom: 1px solid #e8e9ff;
display: flex;
align-items: center;
gap: 12px;
}
.features-list li:last-child {
border-bottom: none;
}
.features-list li::before {
content: "✓";
color: #28a745;
font-weight: bold;
font-size: 18px;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="plugin-header">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{{ plugin_name }}</h1>
<p>{{ description }}</p>
<p><strong>{% trans "Version:" %}</strong> {{ version }}</p>
</div>
<div class="features-list">
<h2>{% trans "Premium Features" %}</h2>
<ul>
{% for feature in features %}
<li>{{ feature }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,315 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Premium Plugin Settings - CyberPanel" %}{% endblock %}
{% block header_scripts %}
<style>
.premium-plugin-container {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.premium-badge {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 20px;
}
.settings-form {
background: white;
border-radius: 12px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.subscription-warning {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 8px;
padding: 15px 20px;
margin-bottom: 25px;
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
gap: 15px;
}
.subscription-warning-content {
flex: 1;
min-width: 250px;
}
.subscription-warning-title {
font-weight: 600;
color: #856404;
margin-bottom: 5px;
font-size: 16px;
}
.subscription-warning-text {
color: #856404;
font-size: 14px;
margin: 0;
}
.subscription-warning-button {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
border: none;
padding: 10px 20px;
border-radius: 6px;
font-weight: 600;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 0.2s, box-shadow 0.2s;
white-space: nowrap;
}
.subscription-warning-button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(245, 87, 108, 0.4);
color: white;
text-decoration: none;
}
.settings-disabled {
opacity: 0.6;
pointer-events: none;
position: relative;
}
.settings-disabled::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.7);
z-index: 1;
border-radius: 8px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #2f3640;
}
.form-control {
width: 100%;
padding: 10px 15px;
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
transition: border-color 0.3s, box-shadow 0.3s;
}
.form-control:focus {
outline: none;
border-color: #5856d6;
box-shadow: 0 0 0 3px rgba(88, 86, 214, 0.1);
}
.form-control:disabled {
background-color: #f5f5f5;
cursor: not-allowed;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: linear-gradient(135deg, #5856d6 0%, #4a90e2 100%);
color: white;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(88, 86, 214, 0.4);
}
.btn-primary:disabled {
background: #ccc;
cursor: not-allowed;
opacity: 0.6;
}
/* Plugin Information Section - Ensure visibility */
.plugin-info-section {
background: #d1ecf1 !important;
border: 1px solid #bee5eb !important;
border-radius: 8px !important;
padding: 15px 20px !important;
margin-bottom: 25px !important;
display: block !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
z-index: 10 !important;
}
.plugin-info-section ul {
list-style: none !important;
padding-left: 0 !important;
margin-top: 10px !important;
margin-bottom: 0 !important;
}
.plugin-info-section li {
margin-bottom: 8px;
}
.plugin-info-section li:last-child {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block content %}
<div class="premium-plugin-container">
<div class="settings-form">
<span class="premium-badge">{% trans "Premium Plugin" %}</span>
<h1>{% trans "Premium Plugin Settings" %}</h1>
{% if show_payment_ui %}
<!-- Activate Premium Access -->
<div style="background: #e6f7ff; border: 2px solid #17a2b8; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057;">{% trans "If you have an activation key, enter it below." %}</p>
<div id="activation-msg" style="display: none; margin-bottom: 10px; padding: 10px; border-radius: 6px;"></div>
<form id="activation-form" style="display: flex; gap: 10px; align-items: flex-end;">
{% csrf_token %}
<input type="text" id="activation-key" name="activation_key" placeholder="PREMIUMPLUGIN-XXXX-XXXX-XXXX" style="flex: 1; padding: 10px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" />
<button type="submit" class="btn btn-primary" id="activate-btn"><i class="fas fa-check-circle"></i> {% trans "Activate" %}</button>
</form>
</div>
<!-- Payment Method Preference -->
<div style="margin-bottom: 25px;">
<label style="font-weight: 600;">{% trans "Payment Method Preference" %}</label>
<form id="payment-method-form" method="post" action="{% url 'premiumPlugin:save_payment_method' %}" style="display: inline-block; margin-left: 10px;">
{% csrf_token %}
<select name="payment_method" onchange="this.form.submit()" style="padding: 8px 12px; border-radius: 6px;">
<option value="both" {% if config.payment_method == 'both' %}selected{% endif %}>{% trans "Check Both (Patreon or PayPal)" %}</option>
<option value="patreon" {% if config.payment_method == 'patreon' %}selected{% endif %}>{% trans "Patreon Only" %}</option>
<option value="paypal" {% if config.payment_method == 'paypal' %}selected{% endif %}>{% trans "PayPal Only" %}</option>
</select>
</form>
</div>
{% else %}
<div style="background: #d4edda; border: 2px solid #28a745; border-radius: 12px; padding: 20px; margin-bottom: 25px;">
<i class="fas fa-check-circle" style="color: #28a745;"></i> <strong>{% trans "Premium Access Active" %}</strong> — {% trans "Access granted via Plugin Grants or activation key." %}
</div>
{% endif %}
<!-- Plugin Information Section - Always Visible -->
<div class="plugin-info-section" style="background: #d1ecf1 !important; border: 1px solid #bee5eb !important; border-radius: 8px !important; padding: 15px 20px !important; margin-bottom: 25px !important; display: block !important; visibility: visible !important; opacity: 1 !important;">
<i class="fas fa-info-circle" style="color: #0c5460; margin-right: 8px;"></i>
<strong style="color: #0c5460; font-weight: 600;">{% trans "Plugin Information" %}</strong>
<ul style="list-style: none !important; padding-left: 0 !important; margin-top: 10px !important; margin-bottom: 0 !important;">
<li style="margin-bottom: 8px;"><strong>{% trans "Name" %}:</strong> {{ plugin_name|default:"Premium Plugin Example" }}</li>
<li style="margin-bottom: 8px;"><strong>{% trans "Version" %}:</strong> {{ version|default:"1.0.0" }}</li>
<li style="margin-bottom: 0;">
<strong>{% trans "Status" %}:</strong>
<span class="badge" style="background: {% if has_access %}#28a745{% else %}#6c757d{% endif %} !important; color: white !important; padding: 4px 10px !important; border-radius: 4px !important; font-size: 12px !important; font-weight: 600 !important; display: inline-block !important;">
{{ plugin_status|default:status|default:"Active" }}
</span>
</li>
</ul>
</div>
<p>{{ description }}</p>
<form method="post" id="settings-form">
{% csrf_token %}
<div class="form-group">
<label>{% trans "Setting 1" %}</label>
<input type="text" class="form-control" name="setting1" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 2" %}</label>
<input type="text" class="form-control" name="setting2" value="" placeholder="{% trans 'Enter setting value' %}">
</div>
<div class="form-group">
<label>{% trans "Setting 3 (Advanced)" %}</label>
<textarea class="form-control" name="setting3" rows="4" placeholder="{% trans 'Enter advanced configuration' %}"></textarea>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="enable_feature">
{% trans "Enable Premium Feature" %}
</label>
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {% trans "Save Settings" %}
</button>
</form>
{% if show_payment_ui %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-form');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = document.getElementById('activation-key').value.trim().toUpperCase();
if (!key) return;
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-msg');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/premiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': form.querySelector('[name=csrfmiddlewaretoken]').value },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { document.getElementById('activation-key').value = ''; setTimeout(function() { location.reload(); }, 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,139 @@
{% extends "baseTemplate/index.html" %}
{% load i18n %}
{% block title %}{% trans "Payment Required - Premium Plugin" %}{% endblock %}
{% block header_scripts %}
<style>
.subscription-required-container { padding: 40px 20px; max-width: 900px; margin: 0 auto; text-align: center; }
.subscription-card { background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
.premium-icon { font-size: 64px; color: #FF6B35; margin-bottom: 20px; }
.subscription-card h1 { color: #2f3640; margin-bottom: 15px; }
.subscription-card p { color: #64748b; font-size: 16px; line-height: 1.6; margin-bottom: 30px; }
.payment-methods { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin: 30px 0; }
.payment-method-card { flex: 1; min-width: 250px; max-width: 350px; background: #f8f9ff; border: 2px solid #e2e8f0; border-radius: 12px; padding: 25px; text-align: center; }
.payment-method-card.patreon { border-color: #FF424D; }
.payment-method-card.paypal { border-color: #0070ba; }
.payment-method-card h3 { margin-top: 0; color: #2f3640; font-size: 1.3rem; }
.patreon-button, .paypal-button { display: inline-block; color: white; padding: 15px 30px; border-radius: 8px; text-decoration: none; font-weight: 600; font-size: 16px; transition: all 0.3s ease; width: 100%; box-sizing: border-box; margin-bottom: 10px; }
.patreon-button { background: #FF424D; }
.patreon-button:hover { background: #E62E37; color: white; text-decoration: none; }
.paypal-button { background: #0070ba; }
.paypal-button:hover { background: #003087; color: white; text-decoration: none; }
.info-box { background: #f8f9ff; border-left: 4px solid #FF6B35; padding: 20px; border-radius: 8px; margin-top: 30px; text-align: left; }
.payment-method-selector { background: #fff3cd; border: 1px solid #ffc107; border-radius: 8px; padding: 15px; margin-bottom: 30px; text-align: left; }
</style>
{% endblock %}
{% block content %}
<div class="subscription-required-container">
<div class="subscription-card">
<div class="premium-icon"><i class="fas fa-crown"></i></div>
<h1>{% trans "Premium Plugin Access Required" %}</h1>
<p>{% trans "This plugin requires payment or subscription to access premium features." %}</p>
<!-- Activation Key Section -->
<div style="background: linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%); border: 2px solid #17a2b8; border-radius: 12px; padding: 25px; margin: 30px 0; text-align: left;">
<h3 style="color: #17a2b8; margin-top: 0;"><i class="fas fa-key"></i> {% trans "Activate Premium Access" %}</h3>
<p style="color: #495057; margin-bottom: 20px;">{% trans "If you received an activation key, enter it below." %}</p>
<div id="activation-message" style="display: none; margin-bottom: 15px; padding: 12px; border-radius: 6px;"></div>
<form id="activation-key-form" style="display: flex; gap: 10px; align-items: flex-end; max-width: 600px;">
{% csrf_token %}
<div style="flex: 1;">
<label for="activation-key-input" style="font-weight: 600; display: block; margin-bottom: 8px;">{% trans "Activation Key" %}</label>
<input type="text" id="activation-key-input" name="activation_key" placeholder="PREMIUMPLUGIN-XXXX-XXXX-XXXX" style="width: 100%; padding: 12px; border: 2px solid #17a2b8; border-radius: 6px; font-family: monospace; text-transform: uppercase;" required />
</div>
<button type="submit" style="background: #17a2b8; color: white; border: none; padding: 12px 24px; border-radius: 6px; font-weight: 600; cursor: pointer;" id="activate-btn">
<i class="fas fa-check-circle"></i> {% trans "Activate" %}
</button>
</form>
</div>
{% if payment_method == 'both' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Check Both (Patreon or PayPal)" %}
</div>
{% elif payment_method == 'patreon' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "Patreon Subscription Only" %}
</div>
{% elif payment_method == 'paypal' %}
<div class="payment-method-selector">
<strong>{% trans "Current Payment Method:" %}</strong> {% trans "PayPal Payment Only" %}
</div>
{% endif %}
<div class="payment-methods">
<div class="payment-method-card patreon">
<h3><i class="fab fa-patreon"></i> {% trans "Patreon Subscription" %}</h3>
<p>{% trans "Subscribe to" %} <strong>"{{ patreon_tier }}"</strong></p>
<a href="{{ patreon_url }}" target="_blank" rel="noopener noreferrer" class="patreon-button">
<i class="fab fa-patreon"></i> {% trans "Subscribe on Patreon" %}
</a>
</div>
<div class="payment-method-card paypal">
<h3><i class="fab fa-paypal"></i> {% trans "PayPal Payment" %}</h3>
<p>{% trans "Complete one-time payment via PayPal" %}</p>
{% if paypal_me_url %}
<a href="{{ paypal_me_url }}" target="_blank" rel="noopener noreferrer" class="paypal-button">
<i class="fab fa-paypal"></i> {% trans "Pay with PayPal.me" %}
</a>
{% endif %}
</div>
</div>
<div class="info-box">
<h3>{% trans "How it works:" %}</h3>
<ul>
<li>{% trans "Install the plugin (already done)" %}</li>
<li>{% trans "Enter activation key, subscribe on Patreon, or pay via PayPal" %}</li>
<li>{% trans "The plugin will automatically unlock" %}</li>
</ul>
</div>
{% if error %}
<div style="background: #fee; border: 1px solid #fcc; border-radius: 8px; padding: 15px; margin-top: 20px; color: #c33;">
<strong>{% trans "Error:" %}</strong> {{ error }}
</div>
{% endif %}
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('activation-key-form');
const input = document.getElementById('activation-key-input');
const btn = document.getElementById('activate-btn');
const msg = document.getElementById('activation-message');
if (form) {
form.addEventListener('submit', async function(e) {
e.preventDefault();
const key = input.value.trim().toUpperCase();
if (!key) return;
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Activating...';
try {
const res = await fetch('/plugins/premiumPlugin/activate-key/', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-CSRFToken': document.querySelector('input[name=csrfmiddlewaretoken]') ? document.querySelector('input[name=csrfmiddlewaretoken]').value : (document.cookie.match(/csrftoken=([^;]+)/) ? document.cookie.match(/csrftoken=([^;]+)/)[1].trim() : '') },
body: JSON.stringify({ activation_key: key })
});
const data = await res.json();
msg.textContent = data.success ? '✅ ' + data.message : '❌ ' + (data.message || 'Invalid key');
msg.style.display = 'block';
msg.style.background = data.success ? '#d4edda' : '#f8d7da';
msg.style.color = data.success ? '#155724' : '#721c24';
if (data.success) { input.value = ''; setTimeout(() => location.reload(), 2000); }
} catch (err) {
msg.textContent = '❌ ' + err.message;
msg.style.display = 'block';
msg.style.background = '#f8d7da';
msg.style.color = '#721c24';
}
btn.disabled = false;
btn.innerHTML = '<i class="fas fa-check-circle"></i> Activate';
});
}
});
</script>
{% endblock %}

12
premiumPlugin/urls.py Normal file
View File

@@ -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'),
]

View File

@@ -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"<div style='padding: 20px;'><h2>Plugin Error</h2><p>{str(e)}</p></div>")
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"<div style='padding: 20px;'><h2>Database Error</h2><p>{str(e)}</p></div>")
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'
})

View File

@@ -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'
})