Remove deprecated CyberPanel installation fix script and update README and guides to include new 2FA authentication features and installation instructions. Enhance user management with WebAuthn passkey support, including UI updates for passkey registration and management.
> Thank you! > > One more question: is it possible to add WebAuthn 2FA/passkeys/passwordless authentication? Right now, the panel login is the weakest link (assuming SSH key login for the server and tight security on the website). It has now been added: https://github.com/usmannasir/cyberpanel/issues/1509#issuecomment-3315474043
This commit is contained in:
parent
61b0507703
commit
54da24dd55
221
README.md
221
README.md
|
|
@ -30,6 +30,7 @@ Web Hosting Control Panel powered by OpenLiteSpeed, designed to simplify hosting
|
|||
- 📀 **One-click Backups and Restores**.
|
||||
- 🐳 **Docker Management** with command execution capabilities.
|
||||
- 🤖 **AI-Powered Security Scanner** for enhanced protection.
|
||||
- 🔐 **Advanced 2FA Authentication** - TOTP and WebAuthn/Passkey support.
|
||||
- 📊 **Monthly Bandwidth Reset** - Automatic bandwidth usage reset (Fixed in latest version).
|
||||
|
||||
---
|
||||
|
|
@ -41,8 +42,10 @@ CyberPanel comes with comprehensive documentation and step-by-step guides:
|
|||
- 📚 **[Complete Guides Index](guides/INDEX.md)** - All available documentation in one place
|
||||
- 🐳 **[Docker Command Execution](guides/Docker_Command_Execution_Guide.md)** - Execute commands in Docker containers
|
||||
- 🤖 **[AI Scanner Setup](guides/AIScannerDocs.md)** - Configure AI-powered security scanning
|
||||
- 🔐 **[2FA Authentication Guide](guides/2FA_AUTHENTICATION_GUIDE.md)** - Complete Two-Factor Authentication and WebAuthn setup
|
||||
- 📧 **[Mautic Installation](guides/MAUTIC_INSTALLATION_GUIDE.md)** - Email marketing platform setup
|
||||
- 🎨 **[Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md)** - Create custom themes for CyberPanel 2.5.5-dev
|
||||
- 🛠️ **[Utility Scripts](utils/README.md)** - Installation, upgrade, and maintenance scripts for Windows and Linux
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -85,7 +88,7 @@ For additional PHP versions or specific requirements, you can install third-part
|
|||
|
||||
## 🌐 Supported Operating Systems
|
||||
|
||||
CyberPanel runs on x86_64 architecture and supports the following operating systems:
|
||||
CyberPanel runs on x86_64 architecture and supports the following **Linux** operating systems:
|
||||
|
||||
### **✅ Currently Supported**
|
||||
|
||||
|
|
@ -114,62 +117,223 @@ Additional operating systems may be supported through third-party repositories o
|
|||
- **openEuler** - Community-supported with limited testing
|
||||
- **Other RHEL derivatives** - May work with AlmaLinux/RockyLinux packages
|
||||
|
||||
### **⚠️ Important Notes**
|
||||
|
||||
- **Linux Only**: CyberPanel is designed specifically for Linux systems and does not support Windows
|
||||
- **Architecture**: Requires x86_64 (64-bit) architecture
|
||||
- **Virtual Machines**: Windows users can run CyberPanel in a Linux VM
|
||||
- **Docker**: Alternative option for Windows users via Docker containers
|
||||
|
||||
> **Note**: For unsupported operating systems, compatibility is not guaranteed. Always test in a non-production environment first.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Installation Instructions
|
||||
|
||||
Install CyberPanel easily with the following command:
|
||||
### **Quick Installation (Recommended)**
|
||||
|
||||
Install CyberPanel on supported Linux distributions with a single command:
|
||||
|
||||
```bash
|
||||
sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh)
|
||||
```
|
||||
|
||||
**Alternative Installation Methods:**
|
||||
```bash
|
||||
# Using wget only
|
||||
wget -O - https://cyberpanel.net/install.sh | sh
|
||||
|
||||
# Download and run manually
|
||||
wget https://cyberpanel.net/install.sh
|
||||
chmod +x install.sh
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
### **Prerequisites**
|
||||
|
||||
Before installation, ensure your system meets these requirements:
|
||||
|
||||
- **Operating System**: One of the supported Linux distributions listed above
|
||||
- **Architecture**: x86_64 (64-bit)
|
||||
- **RAM**: Minimum 1GB (2GB+ recommended)
|
||||
- **Storage**: Minimum 10GB free space (20GB+ recommended)
|
||||
- **Network**: Internet connection required
|
||||
- **Root Access**: Installation requires root/sudo privileges
|
||||
|
||||
### **Installation Process**
|
||||
|
||||
The installation script will automatically:
|
||||
|
||||
1. **Detect your operating system** and version
|
||||
2. **Install required dependencies** (Python, Git, etc.)
|
||||
3. **Download and configure** OpenLiteSpeed web server
|
||||
4. **Set up MariaDB** database server
|
||||
5. **Install CyberPanel** and configure all services
|
||||
6. **Create admin user** with default credentials
|
||||
7. **Start all services** and provide access information
|
||||
|
||||
### **Post-Installation**
|
||||
|
||||
After successful installation:
|
||||
|
||||
1. **Access CyberPanel**: Open your browser to `http://your-server-ip:8090`
|
||||
2. **Default Login**:
|
||||
- **Username**: `admin`
|
||||
- **Password**: `123456`
|
||||
3. **Change Password**: Immediately change the default password
|
||||
4. **Configure SSL**: Set up SSL certificates for secure access
|
||||
|
||||
---
|
||||
|
||||
## 📊 Upgrading CyberPanel
|
||||
|
||||
Upgrade your CyberPanel installation using:
|
||||
### **Quick Upgrade (Recommended)**
|
||||
|
||||
Upgrade your existing CyberPanel installation:
|
||||
|
||||
```bash
|
||||
sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh || wget -O - https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh)
|
||||
```
|
||||
|
||||
---
|
||||
**Alternative Upgrade Methods:**
|
||||
```bash
|
||||
# Using wget only
|
||||
wget -O - https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh | sh
|
||||
|
||||
## 🆕 Recent Updates & Fixes
|
||||
# Download and run manually
|
||||
wget https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh
|
||||
chmod +x preUpgrade.sh
|
||||
sudo ./preUpgrade.sh
|
||||
```
|
||||
|
||||
### **File Integrity & Verification System** (September 2025)
|
||||
### **Upgrade Process**
|
||||
|
||||
- **Enhancement**: Comprehensive file integrity verification system implemented
|
||||
- **Features**:
|
||||
- Automatic detection of missing critical files
|
||||
- Python syntax validation for all core modules
|
||||
- Environment configuration verification
|
||||
- Django application integrity checks
|
||||
- **Coverage**: All core components (Django settings, URLs, middleware, application modules)
|
||||
- **Status**: ✅ All files verified and synchronized (5,597 files)
|
||||
The upgrade script will automatically:
|
||||
|
||||
### **Bandwidth Reset Issue Fixed** (September 2025)
|
||||
1. **Backup current installation** to prevent data loss
|
||||
2. **Download latest version** from the stable branch
|
||||
3. **Update all dependencies** and requirements
|
||||
4. **Run database migrations** to update schema
|
||||
5. **Restart services** with new configuration
|
||||
6. **Verify installation** and report any issues
|
||||
|
||||
- **Enhancement**: Implemented automatic monthly bandwidth reset for all websites and child domains
|
||||
- **Coverage**: All supported operating systems (Ubuntu 20.04+, AlmaLinux, RockyLinux, RHEL, CloudLinux 8, CentOS 7/9)
|
||||
- **Status**: ✅ Automatic monthly reset now functional
|
||||
### **Manual Upgrade (Advanced Users)**
|
||||
|
||||
### **New Operating System Support Added** (September 2025)
|
||||
For manual upgrades or troubleshooting:
|
||||
|
||||
- **Ubuntu 24.04.3**: Full compatibility with latest Ubuntu LTS
|
||||
- **Debian 13**: Full compatibility with latest Debian stable release
|
||||
- **AlmaLinux 10**: Full compatibility with latest AlmaLinux release
|
||||
- **Long-term Support**: All supported until 2029-2030
|
||||
```bash
|
||||
# Navigate to CyberPanel directory
|
||||
cd /usr/local/CyberCP
|
||||
|
||||
### **Core Module Enhancements** (September 2025)
|
||||
# Update source code
|
||||
git pull origin stable
|
||||
|
||||
- **Django Configuration**: Enhanced settings.py with improved environment variable handling
|
||||
- **Security Middleware**: Updated security middleware for better protection
|
||||
- **Application Modules**: Verified and synchronized all core application modules
|
||||
- **Static Assets**: Complete synchronization of UI assets and templates
|
||||
# Update dependencies
|
||||
pip3 install -r requirments.txt
|
||||
|
||||
# Run database migrations
|
||||
python3 manage.py migrate
|
||||
|
||||
# Collect static files
|
||||
python3 manage.py collectstatic --noinput
|
||||
|
||||
# Restart services
|
||||
systemctl restart lscpd
|
||||
```
|
||||
|
||||
### **⚠️ Important Upgrade Notes**
|
||||
|
||||
- **Backup First**: Always backup your data before upgrading
|
||||
- **Test Environment**: Test upgrades in a non-production environment first
|
||||
- **Service Restart**: Some services may restart during upgrade
|
||||
- **Configuration**: Custom configurations may need manual updates
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### **Common Installation Issues**
|
||||
|
||||
#### **"Command not found" Errors**
|
||||
```bash
|
||||
# Install missing packages
|
||||
# Ubuntu/Debian
|
||||
sudo apt update && sudo apt install curl wget git python3
|
||||
|
||||
# RHEL/CentOS/AlmaLinux/RockyLinux
|
||||
sudo yum install curl wget git python3
|
||||
```
|
||||
|
||||
#### **Permission Denied Errors**
|
||||
```bash
|
||||
# Ensure you're running as root
|
||||
sudo sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh)
|
||||
```
|
||||
|
||||
#### **Network Connectivity Issues**
|
||||
```bash
|
||||
# Check internet connection
|
||||
ping -c 4 google.com
|
||||
|
||||
# Check DNS resolution
|
||||
nslookup cyberpanel.net
|
||||
|
||||
# Try alternative download method
|
||||
wget https://cyberpanel.net/install.sh
|
||||
chmod +x install.sh
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
#### **Port Already in Use**
|
||||
```bash
|
||||
# Check what's using port 8090
|
||||
sudo netstat -tlnp | grep :8090
|
||||
|
||||
# Kill process if necessary
|
||||
sudo kill -9 <PID>
|
||||
```
|
||||
|
||||
### **Common Upgrade Issues**
|
||||
|
||||
#### **Backup Creation Failed**
|
||||
```bash
|
||||
# Check disk space
|
||||
df -h
|
||||
|
||||
# Free up space if necessary
|
||||
sudo apt autoremove && sudo apt autoclean
|
||||
```
|
||||
|
||||
#### **Service Restart Failed**
|
||||
```bash
|
||||
# Check service status
|
||||
systemctl status lscpd
|
||||
|
||||
# Restart services manually
|
||||
sudo systemctl restart lscpd
|
||||
sudo systemctl restart lsws
|
||||
```
|
||||
|
||||
### **Verification Commands**
|
||||
|
||||
#### **Check Installation Status**
|
||||
```bash
|
||||
# Check CyberPanel service
|
||||
systemctl status lscpd
|
||||
|
||||
# Check web interface
|
||||
curl -I http://localhost:8090
|
||||
|
||||
# Check database
|
||||
systemctl status mariadb
|
||||
```
|
||||
|
||||
#### **View Logs**
|
||||
```bash
|
||||
# CyberPanel logs
|
||||
tail -f /usr/local/lscp/logs/error.log
|
||||
|
||||
# System logs
|
||||
journalctl -u lscpd -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -222,6 +386,7 @@ For detailed troubleshooting, installation guides, and advanced configuration:
|
|||
|
||||
- **🐳 [Docker Command Execution Guide](guides/Docker_Command_Execution_Guide.md)** - Docker management and troubleshooting
|
||||
- **🤖 [AI Scanner Documentation](guides/AIScannerDocs.md)** - Security scanner setup and configuration
|
||||
- **🔐 [2FA Authentication Guide](guides/2FA_AUTHENTICATION_GUIDE.md)** - Two-Factor Authentication and WebAuthn setup
|
||||
- **📧 [Mautic Installation Guide](guides/MAUTIC_INSTALLATION_GUIDE.md)** - Email marketing platform setup
|
||||
- **🎨 [Custom CSS Guide](guides/CUSTOM_CSS_GUIDE.md)** - Interface customization and theming
|
||||
- **🔥 [Firewall Blocking Feature Guide](guides/FIREWALL_BLOCKING_FEATURE.md)** - Security features and configuration
|
||||
|
|
|
|||
|
|
@ -1,128 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# CyberPanel Post-Upgrade Fix Script
|
||||
# This script completes the installation when the upgrade exits early due to TypeError
|
||||
|
||||
set -e # Exit on error
|
||||
|
||||
echo "==================================="
|
||||
echo "CyberPanel Installation Fix Script"
|
||||
echo "==================================="
|
||||
echo ""
|
||||
|
||||
# Check if running as root
|
||||
if [[ $(id -u) != 0 ]]; then
|
||||
echo "This script must be run as root!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to print colored output
|
||||
print_status() {
|
||||
echo -e "\033[1;32m[$(date +"%Y-%m-%d %H:%M:%S")]\033[0m $1"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "\033[1;31m[$(date +"%Y-%m-%d %H:%M:%S")] ERROR:\033[0m $1"
|
||||
}
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [[ ! -f /usr/local/CyberCP/bin/activate ]]; then
|
||||
print_error "CyberPanel virtual environment not found!"
|
||||
print_status "Creating virtual environment..."
|
||||
|
||||
# Try python3 -m venv first
|
||||
if python3 -m venv --system-site-packages /usr/local/CyberCP 2>/dev/null; then
|
||||
print_status "Virtual environment created successfully with python3 -m venv"
|
||||
else
|
||||
# Fallback to virtualenv
|
||||
virtualenv -p /usr/bin/python3 --system-site-packages /usr/local/CyberCP
|
||||
fi
|
||||
fi
|
||||
|
||||
# Activate virtual environment
|
||||
print_status "Activating CyberPanel virtual environment..."
|
||||
source /usr/local/CyberCP/bin/activate
|
||||
|
||||
# Check if Django is already installed
|
||||
if python -c "import django" 2>/dev/null; then
|
||||
print_status "Django is already installed. Checking version..."
|
||||
python -c "import django; print(f'Django version: {django.__version__}')"
|
||||
else
|
||||
print_status "Installing Python requirements..."
|
||||
|
||||
# Download requirements file
|
||||
print_status "Downloading requirements.txt..."
|
||||
if [[ -f /tmp/requirements.txt ]]; then
|
||||
rm -f /tmp/requirements.txt
|
||||
fi
|
||||
|
||||
# Detect OS version and download appropriate requirements
|
||||
if grep -q "22.04" /etc/os-release || grep -q "VERSION_ID=\"9" /etc/os-release; then
|
||||
wget -q -O /tmp/requirements.txt https://raw.githubusercontent.com/usmannasir/cyberpanel/v2.4.4-dev/requirments.txt
|
||||
else
|
||||
wget -q -O /tmp/requirements.txt https://raw.githubusercontent.com/usmannasir/cyberpanel/v2.4.4-dev/requirments-old.txt
|
||||
fi
|
||||
|
||||
# Upgrade pip first
|
||||
print_status "Upgrading pip, setuptools, and wheel..."
|
||||
pip install --upgrade pip setuptools wheel packaging
|
||||
|
||||
# Install requirements
|
||||
print_status "Installing CyberPanel requirements (this may take a few minutes)..."
|
||||
pip install --default-timeout=3600 --ignore-installed -r /tmp/requirements.txt
|
||||
fi
|
||||
|
||||
# Install WSGI-LSAPI if not present
|
||||
if [[ ! -f /usr/local/CyberCP/bin/lswsgi ]]; then
|
||||
print_status "Installing WSGI-LSAPI..."
|
||||
|
||||
cd /tmp
|
||||
rm -rf wsgi-lsapi-2.1*
|
||||
|
||||
wget -q https://www.litespeedtech.com/packages/lsapi/wsgi-lsapi-2.1.tgz
|
||||
tar xf wsgi-lsapi-2.1.tgz
|
||||
cd wsgi-lsapi-2.1
|
||||
|
||||
/usr/local/CyberCP/bin/python ./configure.py
|
||||
make
|
||||
|
||||
cp lswsgi /usr/local/CyberCP/bin/
|
||||
print_status "WSGI-LSAPI installed successfully"
|
||||
fi
|
||||
|
||||
# Fix permissions
|
||||
print_status "Fixing permissions..."
|
||||
chown -R cyberpanel:cyberpanel /usr/local/CyberCP/lib 2>/dev/null || true
|
||||
chown -R cyberpanel:cyberpanel /usr/local/CyberCP/lib64 2>/dev/null || true
|
||||
|
||||
# Test Django installation
|
||||
print_status "Testing Django installation..."
|
||||
cd /usr/local/CyberCP
|
||||
|
||||
if python manage.py check 2>&1 | grep -q "System check identified no issues"; then
|
||||
print_status "Django is working correctly!"
|
||||
else
|
||||
print_error "Django check failed. Checking for specific issues..."
|
||||
python manage.py check
|
||||
fi
|
||||
|
||||
# Restart LSCPD
|
||||
print_status "Restarting LSCPD service..."
|
||||
systemctl restart lscpd
|
||||
|
||||
# Check service status
|
||||
if systemctl is-active --quiet lscpd; then
|
||||
print_status "LSCPD service is running"
|
||||
else
|
||||
print_error "LSCPD service failed to start"
|
||||
systemctl status lscpd
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_status "CyberPanel fix completed!"
|
||||
echo ""
|
||||
echo "You can now access CyberPanel at: https://$(hostname -I | awk '{print $1}'):8090"
|
||||
echo ""
|
||||
|
||||
# Deactivate virtual environment
|
||||
deactivate 2>/dev/null || true
|
||||
|
|
@ -0,0 +1,431 @@
|
|||
# CyberPanel 2FA Authentication Guide
|
||||
|
||||
## Overview
|
||||
|
||||
CyberPanel supports multiple two-factor authentication (2FA) methods to enhance security for user accounts. This guide covers all available authentication methods, their setup, and best practices.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Available Authentication Methods](#available-authentication-methods)
|
||||
2. [Traditional 2FA (TOTP)](#traditional-2fa-totp)
|
||||
3. [WebAuthn/Passkey Authentication](#webauthnpasskey-authentication)
|
||||
4. [Password Authentication](#password-authentication)
|
||||
5. [Combined Authentication Strategies](#combined-authentication-strategies)
|
||||
6. [Administrator Management](#administrator-management)
|
||||
7. [Troubleshooting](#troubleshooting)
|
||||
8. [Security Best Practices](#security-best-practices)
|
||||
|
||||
## Available Authentication Methods
|
||||
|
||||
### 1. Traditional 2FA (TOTP)
|
||||
- **Type**: Time-based One-Time Password
|
||||
- **Method**: Google Authenticator, Authy, or similar apps
|
||||
- **Security Level**: High
|
||||
- **User Experience**: Requires manual code entry
|
||||
|
||||
### 2. WebAuthn/Passkey Authentication
|
||||
- **Type**: Modern passwordless authentication
|
||||
- **Method**: Biometric authentication, security keys, or device passkeys
|
||||
- **Security Level**: Very High
|
||||
- **User Experience**: Seamless and user-friendly
|
||||
|
||||
### 3. Password Authentication
|
||||
- **Type**: Traditional username/password
|
||||
- **Method**: Standard login credentials
|
||||
- **Security Level**: Basic
|
||||
- **User Experience**: Simple but less secure
|
||||
|
||||
## Traditional 2FA (TOTP)
|
||||
|
||||
### How It Works
|
||||
TOTP generates time-based codes that change every 30 seconds. Users scan a QR code with their authenticator app to set up 2FA.
|
||||
|
||||
### Setting Up TOTP 2FA
|
||||
|
||||
#### For Users
|
||||
1. **Access User Management**
|
||||
- Log in to CyberPanel
|
||||
- Go to **User Management** → **Modify User**
|
||||
- Select your user account
|
||||
|
||||
2. **Enable 2FA**
|
||||
- Scroll to **Additional Features** section
|
||||
- Check **"Enable Two-Factor Authentication (2FA)"**
|
||||
- A QR code will appear
|
||||
|
||||
3. **Configure Authenticator App**
|
||||
- Open your authenticator app (Google Authenticator, Authy, etc.)
|
||||
- Scan the QR code displayed
|
||||
- Or manually enter the secret key shown below the QR code
|
||||
|
||||
4. **Test 2FA**
|
||||
- Enter a 6-digit code from your authenticator app
|
||||
- Verify the setup works correctly
|
||||
|
||||
#### For Administrators
|
||||
1. **Access User Management**
|
||||
- Go to **User Management** → **Modify User**
|
||||
- Select the user you want to configure
|
||||
|
||||
2. **Enable 2FA for User**
|
||||
- Check **"Enable Two-Factor Authentication (2FA)"**
|
||||
- Provide the QR code or secret key to the user
|
||||
- Ensure the user completes the setup
|
||||
|
||||
### TOTP Configuration Options
|
||||
|
||||
#### Secret Key Management
|
||||
- **Auto-generation**: CyberPanel automatically generates secure secret keys
|
||||
- **Manual Entry**: Users can manually enter the secret key if QR scanning fails
|
||||
- **Regeneration**: Secret keys can be regenerated if needed
|
||||
|
||||
#### QR Code Display
|
||||
- **Automatic Display**: QR code appears when 2FA is enabled
|
||||
- **Manual Key**: Secret key is always displayed for manual entry
|
||||
- **Copy Function**: One-click copy to clipboard functionality
|
||||
|
||||
### Supported Authenticator Apps
|
||||
- **Google Authenticator** (iOS/Android)
|
||||
- **Authy** (iOS/Android/Desktop)
|
||||
- **Microsoft Authenticator** (iOS/Android)
|
||||
- **1Password** (iOS/Android/Desktop)
|
||||
- **Bitwarden** (iOS/Android/Desktop)
|
||||
- **Any TOTP-compatible app**
|
||||
|
||||
## WebAuthn/Passkey Authentication
|
||||
|
||||
### What is WebAuthn?
|
||||
WebAuthn is a web standard that enables secure, passwordless authentication using public-key cryptography. It supports biometric authentication, security keys, and device passkeys.
|
||||
|
||||
### Setting Up WebAuthn
|
||||
|
||||
#### Prerequisites
|
||||
- **HTTPS Required**: WebAuthn only works over secure connections
|
||||
- **Modern Browser**: Chrome 67+, Firefox 60+, Safari 14+, Edge 79+
|
||||
- **Compatible Device**: Device with biometric sensors or security key
|
||||
|
||||
#### For Users
|
||||
1. **Access User Management**
|
||||
- Go to **User Management** → **Modify User**
|
||||
- Select your user account
|
||||
|
||||
2. **Enable WebAuthn**
|
||||
- Scroll to **Passkey Authentication (WebAuthn)** section
|
||||
- Check **"Enable Passkey Authentication"**
|
||||
|
||||
3. **Configure Settings**
|
||||
- **Require Passkey for Login**: Enable for passwordless authentication
|
||||
- **Allow Multiple Passkeys**: Enable to register multiple devices
|
||||
- **Max Passkeys**: Set limit (default: 10)
|
||||
- **Timeout**: Set timeout in seconds (default: 60)
|
||||
|
||||
4. **Register Passkeys**
|
||||
- Click **"Register New Passkey"**
|
||||
- Enter a name for your passkey (e.g., "iPhone", "Laptop")
|
||||
- Follow your browser's prompts to complete registration
|
||||
- Repeat for additional devices
|
||||
|
||||
#### For Administrators
|
||||
1. **Access User Management**
|
||||
- Go to **User Management** → **Modify User**
|
||||
- Select the user you want to configure
|
||||
|
||||
2. **Enable WebAuthn for User**
|
||||
- Check **"Enable Passkey Authentication"**
|
||||
- Configure security policies
|
||||
- Monitor registered passkeys
|
||||
|
||||
### WebAuthn Features
|
||||
|
||||
#### Passkey Management
|
||||
- **Multiple Devices**: Register passkeys on phones, laptops, security keys
|
||||
- **Custom Names**: Give descriptive names to your passkeys
|
||||
- **Easy Removal**: Delete passkeys you no longer need
|
||||
- **Backup Options**: Multiple passkeys for redundancy
|
||||
|
||||
#### Security Features
|
||||
- **Replay Protection**: Each authentication uses a unique challenge
|
||||
- **Credential Isolation**: Passkeys cannot be extracted or shared
|
||||
- **User Verification**: Optional biometric or PIN verification
|
||||
- **Session Management**: Secure session handling with expiration
|
||||
|
||||
### Supported WebAuthn Devices
|
||||
- **Smartphones**: iPhone, Android phones with biometrics
|
||||
- **Laptops**: MacBooks with Touch ID, Windows Hello devices
|
||||
- **Security Keys**: YubiKey, Google Titan, etc.
|
||||
- **Tablets**: iPads, Android tablets with biometrics
|
||||
|
||||
## Password Authentication
|
||||
|
||||
### Traditional Login
|
||||
- **Username/Password**: Standard login credentials
|
||||
- **Security Level**: Basic (not recommended alone)
|
||||
- **Use Case**: Fallback authentication method
|
||||
|
||||
### Password Requirements
|
||||
- **Minimum Length**: 8 characters (recommended: 12+)
|
||||
- **Complexity**: Mix of uppercase, lowercase, numbers, symbols
|
||||
- **Uniqueness**: Different from other accounts
|
||||
- **Regular Updates**: Change passwords periodically
|
||||
|
||||
## Combined Authentication Strategies
|
||||
|
||||
### Strategy 1: Password + 2FA
|
||||
- **Login Process**: Username + Password + TOTP code
|
||||
- **Security Level**: High
|
||||
- **User Experience**: Moderate (requires manual code entry)
|
||||
- **Best For**: Users who prefer traditional 2FA
|
||||
|
||||
### Strategy 2: Password + Passkeys
|
||||
- **Login Process**: Username + Password + Passkey
|
||||
- **Security Level**: Very High
|
||||
- **User Experience**: Good (biometric authentication)
|
||||
- **Best For**: Users with compatible devices
|
||||
|
||||
### Strategy 3: Passkeys Only (Passwordless)
|
||||
- **Login Process**: Username + Passkey only
|
||||
- **Security Level**: Very High
|
||||
- **User Experience**: Excellent (no password required)
|
||||
- **Best For**: Security-conscious users with multiple devices
|
||||
|
||||
### Strategy 4: All Methods (Maximum Security)
|
||||
- **Login Process**: Username + Password + 2FA + Passkeys
|
||||
- **Security Level**: Maximum
|
||||
- **User Experience**: Complex but flexible
|
||||
- **Best For**: High-security environments
|
||||
|
||||
## Administrator Management
|
||||
|
||||
### User Authentication Settings
|
||||
|
||||
#### Accessing User Settings
|
||||
1. **Navigate to User Management**
|
||||
- Go to **User Management** → **Modify User**
|
||||
- Select the user to manage
|
||||
|
||||
#### Available Settings
|
||||
- **Enable/Disable 2FA**: Toggle TOTP authentication
|
||||
- **Enable/Disable WebAuthn**: Toggle passkey authentication
|
||||
- **Security Policies**: Set passkey limits and timeouts
|
||||
- **View Credentials**: See registered passkeys and 2FA status
|
||||
|
||||
### Security Policies
|
||||
|
||||
#### Passkey Policies
|
||||
- **Maximum Passkeys**: Limit number of registered passkeys per user
|
||||
- **Timeout Settings**: Set authentication timeout duration
|
||||
- **Device Requirements**: Specify required device capabilities
|
||||
|
||||
#### 2FA Policies
|
||||
- **Secret Key Management**: Control secret key generation and regeneration
|
||||
- **QR Code Display**: Manage QR code visibility
|
||||
- **Backup Codes**: Generate backup codes for recovery
|
||||
|
||||
### Monitoring and Auditing
|
||||
|
||||
#### Authentication Logs
|
||||
- **Login Attempts**: Track all authentication attempts
|
||||
- **Method Used**: Record which authentication method was used
|
||||
- **Success/Failure**: Monitor authentication success rates
|
||||
- **Device Information**: Log device and browser details
|
||||
|
||||
#### Security Alerts
|
||||
- **Failed Attempts**: Alert on multiple failed login attempts
|
||||
- **Unusual Activity**: Detect suspicious authentication patterns
|
||||
- **Device Changes**: Notify when new devices are registered
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common TOTP Issues
|
||||
|
||||
#### QR Code Not Scanning
|
||||
**Problem**: QR code cannot be scanned by authenticator app
|
||||
**Solutions**:
|
||||
- Try manual key entry instead
|
||||
- Ensure good lighting and camera focus
|
||||
- Try a different authenticator app
|
||||
- Regenerate the QR code
|
||||
|
||||
#### Time Synchronization Issues
|
||||
**Problem**: Codes not working due to time differences
|
||||
**Solutions**:
|
||||
- Check device time is correct
|
||||
- Sync device time with internet
|
||||
- Try a different authenticator app
|
||||
- Contact administrator for assistance
|
||||
|
||||
#### Lost Authenticator Device
|
||||
**Problem**: Cannot access authenticator app
|
||||
**Solutions**:
|
||||
- Use backup codes if available
|
||||
- Contact administrator to reset 2FA
|
||||
- Set up 2FA on a new device
|
||||
- Use alternative authentication methods
|
||||
|
||||
### Common WebAuthn Issues
|
||||
|
||||
#### "WebAuthn not supported" Error
|
||||
**Problem**: Browser or device doesn't support WebAuthn
|
||||
**Solutions**:
|
||||
- Update browser to latest version
|
||||
- Ensure HTTPS is enabled
|
||||
- Check device compatibility
|
||||
- Try a different browser
|
||||
|
||||
#### Registration Failed
|
||||
**Problem**: Passkey registration fails
|
||||
**Solutions**:
|
||||
- Check browser console for errors
|
||||
- Ensure user has permission to register passkeys
|
||||
- Verify WebAuthn settings are properly configured
|
||||
- Try a different device or browser
|
||||
|
||||
#### Authentication Failed
|
||||
**Problem**: Passkey authentication fails
|
||||
**Solutions**:
|
||||
- Ensure passkey is still active
|
||||
- Check that user has registered passkeys
|
||||
- Verify challenge hasn't expired
|
||||
- Try a different passkey or device
|
||||
|
||||
### General Authentication Issues
|
||||
|
||||
#### Login Not Working
|
||||
**Problem**: Cannot log in with any method
|
||||
**Solutions**:
|
||||
- Check username and password
|
||||
- Verify 2FA codes are current
|
||||
- Ensure passkeys are properly registered
|
||||
- Contact administrator for assistance
|
||||
|
||||
#### Account Locked
|
||||
**Problem**: Account is locked due to failed attempts
|
||||
**Solutions**:
|
||||
- Wait for lockout period to expire
|
||||
- Contact administrator to unlock account
|
||||
- Check for security alerts
|
||||
- Review recent login attempts
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
### For Users
|
||||
|
||||
#### Password Security
|
||||
- **Use Strong Passwords**: 12+ characters with mixed case, numbers, symbols
|
||||
- **Unique Passwords**: Different password for each account
|
||||
- **Regular Updates**: Change passwords every 90 days
|
||||
- **Password Manager**: Use a reputable password manager
|
||||
|
||||
#### 2FA Security
|
||||
- **Secure Device**: Use a trusted device for authenticator apps
|
||||
- **Backup Codes**: Save backup codes in a secure location
|
||||
- **Multiple Methods**: Set up multiple authentication methods
|
||||
- **Regular Testing**: Test authentication methods regularly
|
||||
|
||||
#### WebAuthn Security
|
||||
- **Multiple Passkeys**: Register passkeys on multiple devices
|
||||
- **Secure Devices**: Only register passkeys on trusted devices
|
||||
- **Regular Review**: Periodically review registered passkeys
|
||||
- **Device Security**: Keep devices updated and secure
|
||||
|
||||
### For Administrators
|
||||
|
||||
#### System Security
|
||||
- **HTTPS Enforcement**: Ensure all authentication uses HTTPS
|
||||
- **Regular Updates**: Keep CyberPanel and dependencies updated
|
||||
- **Monitoring**: Monitor authentication logs and alerts
|
||||
- **Backup**: Regular backups of authentication data
|
||||
|
||||
#### User Management
|
||||
- **Access Control**: Implement proper user access controls
|
||||
- **Security Policies**: Enforce strong security policies
|
||||
- **Training**: Provide security training to users
|
||||
- **Incident Response**: Have procedures for security incidents
|
||||
|
||||
#### Configuration Security
|
||||
- **Default Settings**: Change default passwords and settings
|
||||
- **Network Security**: Implement proper network security
|
||||
- **Logging**: Enable comprehensive logging
|
||||
- **Auditing**: Regular security audits
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom Security Policies
|
||||
|
||||
#### Passkey Policies
|
||||
```python
|
||||
# Example: Custom passkey policies
|
||||
WEBAUTHN_POLICIES = {
|
||||
'max_credentials_per_user': 5,
|
||||
'timeout_seconds': 120,
|
||||
'require_user_verification': True,
|
||||
'allowed_attestation_formats': ['none', 'packed']
|
||||
}
|
||||
```
|
||||
|
||||
#### 2FA Policies
|
||||
```python
|
||||
# Example: Custom 2FA policies
|
||||
TOTP_POLICIES = {
|
||||
'secret_key_length': 32,
|
||||
'time_step': 30,
|
||||
'window': 1,
|
||||
'issuer_name': 'CyberPanel'
|
||||
}
|
||||
```
|
||||
|
||||
### Integration with External Systems
|
||||
|
||||
#### LDAP Integration
|
||||
- **Active Directory**: Integrate with Windows Active Directory
|
||||
- **OpenLDAP**: Connect to OpenLDAP servers
|
||||
- **Multi-Factor**: Combine with external MFA systems
|
||||
|
||||
#### SSO Integration
|
||||
- **SAML**: Single Sign-On with SAML providers
|
||||
- **OAuth**: OAuth-based authentication
|
||||
- **Custom Providers**: Integration with custom identity providers
|
||||
|
||||
## Migration and Upgrades
|
||||
|
||||
### Upgrading Authentication Methods
|
||||
|
||||
#### From Password to 2FA
|
||||
1. **Enable 2FA**: Add TOTP authentication
|
||||
2. **Test Setup**: Verify 2FA works correctly
|
||||
3. **User Training**: Train users on 2FA usage
|
||||
4. **Gradual Rollout**: Enable for users progressively
|
||||
|
||||
#### From 2FA to WebAuthn
|
||||
1. **Enable WebAuthn**: Add passkey support
|
||||
2. **User Migration**: Help users register passkeys
|
||||
3. **Dual Support**: Support both methods during transition
|
||||
4. **Full Migration**: Complete transition to WebAuthn
|
||||
|
||||
### Backup and Recovery
|
||||
|
||||
#### Authentication Backup
|
||||
- **Secret Keys**: Backup TOTP secret keys securely
|
||||
- **Passkey Data**: Backup WebAuthn credential data
|
||||
- **User Data**: Regular backups of user authentication data
|
||||
- **Configuration**: Backup authentication configuration
|
||||
|
||||
#### Recovery Procedures
|
||||
- **Account Recovery**: Procedures for locked accounts
|
||||
- **Data Restoration**: Restore authentication data from backups
|
||||
- **Emergency Access**: Emergency access procedures
|
||||
- **User Support**: Support procedures for authentication issues
|
||||
|
||||
## Conclusion
|
||||
|
||||
CyberPanel's multi-layered authentication system provides flexible and secure access control. By combining traditional 2FA with modern WebAuthn passkeys, administrators can offer users both security and convenience.
|
||||
|
||||
Choose the authentication methods that best fit your security requirements and user needs. Remember to regularly review and update your authentication policies to maintain optimal security.
|
||||
|
||||
For additional support and advanced configuration options, refer to the CyberPanel documentation and community resources.
|
||||
|
||||
---
|
||||
|
||||
**Note**: This guide covers all available authentication methods in CyberPanel. For the latest updates and additional features, refer to the official CyberPanel documentation.
|
||||
|
||||
*Last updated: January 2025*
|
||||
|
|
@ -16,6 +16,9 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
|
|||
|
||||
### 🐧 Operating System Support
|
||||
|
||||
#### **Windows Family**
|
||||
- **[Windows Installation Guide](WINDOWS_INSTALLATION_GUIDE.md)** - Complete installation and configuration guide for CyberPanel on Windows 7/8.1/10/11
|
||||
|
||||
#### **Debian Family**
|
||||
- **[Debian 13 Installation Guide](DEBIAN_13_INSTALLATION_GUIDE.md)** - Complete installation and configuration guide for CyberPanel on Debian 13 (Bookworm)
|
||||
- **[Debian 12 Troubleshooting Guide](DEBIAN_12_TROUBLESHOOTING.md)** - Troubleshooting guide for Debian 12 (Bookworm)
|
||||
|
|
@ -40,8 +43,12 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
|
|||
### 🎨 Customization & Design
|
||||
- **[Custom CSS Guide](CUSTOM_CSS_GUIDE.md)** - Complete guide for creating custom CSS that works with CyberPanel 2.5.5-dev design system
|
||||
|
||||
### 🔐 Security & Authentication
|
||||
- **[2FA Authentication Guide](2FA_AUTHENTICATION_GUIDE.md)** - Complete guide for Two-Factor Authentication and WebAuthn/Passkey setup
|
||||
|
||||
### 🔧 Troubleshooting & Support
|
||||
- **[Troubleshooting Guide](TROUBLESHOOTING.md)** - Comprehensive troubleshooting and diagnostic commands
|
||||
- **[Installation Verification Guide](INSTALLATION_VERIFICATION.md)** - Verify installation and upgrade commands work across all supported OS
|
||||
|
||||
### 💻 Command Line Interface
|
||||
- **[CLI Command Reference](CLI_COMMAND_REFERENCE.md)** - Complete reference for all CyberPanel CLI commands
|
||||
|
|
@ -51,6 +58,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
|
|||
|
||||
### 📖 General Documentation
|
||||
- **[README](../README.md)** - Main CyberPanel documentation with installation instructions and feature overview
|
||||
- **[Utility Scripts](../utils/README.md)** - Installation, upgrade, and maintenance scripts for Windows and Linux
|
||||
- **[Contributing Guide](CONTRIBUTING.md)** - Guidelines for contributing to the CyberPanel project
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
|
@ -69,6 +77,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
|
|||
- **General Troubleshooting**: [Troubleshooting Guide](TROUBLESHOOTING.md)
|
||||
|
||||
### **OS-Specific Troubleshooting**
|
||||
- **🪟 Windows**: [Windows Installation Guide](WINDOWS_INSTALLATION_GUIDE.md) - Installation & troubleshooting
|
||||
- **🐧 Debian 13**: [Debian 13 Installation Guide](DEBIAN_13_INSTALLATION_GUIDE.md) - Installation & troubleshooting
|
||||
- **🐧 Debian 12**: [Debian 12 Troubleshooting Guide](DEBIAN_12_TROUBLESHOOTING.md) - Troubleshooting
|
||||
- **🐧 Debian 11**: [Debian 11 Troubleshooting Guide](DEBIAN_11_TROUBLESHOOTING.md) - Troubleshooting
|
||||
|
|
@ -86,6 +95,7 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
|
|||
### **Feature-Specific Guides**
|
||||
- **Docker Features**: [Docker Command Execution Guide](Docker_Command_Execution_Guide.md)
|
||||
- **Security Features**: [AI Scanner Documentation](AIScannerDocs.md)
|
||||
- **Authentication**: [2FA Authentication Guide](2FA_AUTHENTICATION_GUIDE.md)
|
||||
- **Email Marketing**: [Mautic Installation Guide](MAUTIC_INSTALLATION_GUIDE.md)
|
||||
- **Storage Management**: [Home Directory Management Guide](HOME_DIRECTORY_MANAGEMENT_GUIDE.md)
|
||||
- **Customization & Design**: [Custom CSS Guide](CUSTOM_CSS_GUIDE.md)
|
||||
|
|
@ -98,6 +108,8 @@ Welcome to the CyberPanel documentation hub! This folder contains all guides, tu
|
|||
- Docker container management
|
||||
- Command execution
|
||||
- Security scanning
|
||||
- Two-factor authentication (2FA)
|
||||
- WebAuthn/Passkey authentication
|
||||
- Home directory management
|
||||
- CLI command reference
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,415 @@
|
|||
# CyberPanel Installation Verification Guide
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
This guide provides verification steps to ensure CyberPanel installation and upgrade commands work correctly across all supported operating systems.
|
||||
|
||||
## ✅ Installation Command Verification
|
||||
|
||||
### **Primary Installation Command**
|
||||
```bash
|
||||
sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh)
|
||||
```
|
||||
|
||||
### **Verification Steps**
|
||||
|
||||
#### 1. **Test URL Accessibility**
|
||||
```bash
|
||||
# Test if install script is accessible
|
||||
curl -I https://cyberpanel.net/install.sh
|
||||
|
||||
# Expected response: HTTP/1.1 200 OK
|
||||
# Content-Type: text/plain
|
||||
```
|
||||
|
||||
#### 2. **Test Download**
|
||||
```bash
|
||||
# Download and check script content
|
||||
curl -s https://cyberpanel.net/install.sh | head -20
|
||||
|
||||
# Should show script header and OS detection logic
|
||||
```
|
||||
|
||||
#### 3. **Test with wget fallback**
|
||||
```bash
|
||||
# Test wget fallback
|
||||
wget -qO- https://cyberpanel.net/install.sh | head -20
|
||||
|
||||
# Should show same content as curl
|
||||
```
|
||||
|
||||
## ✅ Upgrade Command Verification
|
||||
|
||||
### **Primary Upgrade Command**
|
||||
```bash
|
||||
sh <(curl https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh || wget -O - https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh)
|
||||
```
|
||||
|
||||
### **Verification Steps**
|
||||
|
||||
#### 1. **Test URL Accessibility**
|
||||
```bash
|
||||
# Test if upgrade script is accessible
|
||||
curl -I https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh
|
||||
|
||||
# Expected response: HTTP/1.1 200 OK
|
||||
# Content-Type: text/plain
|
||||
```
|
||||
|
||||
#### 2. **Test Download**
|
||||
```bash
|
||||
# Download and check script content
|
||||
curl -s https://raw.githubusercontent.com/usmannasir/cyberpanel/stable/preUpgrade.sh | head -20
|
||||
|
||||
# Should show script header and upgrade logic
|
||||
```
|
||||
|
||||
## 🐧 Operating System Support Verification
|
||||
|
||||
### **Ubuntu Family**
|
||||
- **Ubuntu 24.04.3**: ✅ Supported
|
||||
- **Ubuntu 22.04**: ✅ Supported
|
||||
- **Ubuntu 20.04**: ✅ Supported
|
||||
|
||||
### **Debian Family**
|
||||
- **Debian 13**: ✅ Supported
|
||||
- **Debian 12**: ✅ Supported
|
||||
- **Debian 11**: ✅ Supported
|
||||
|
||||
### **RHEL Family**
|
||||
- **AlmaLinux 10**: ✅ Supported
|
||||
- **AlmaLinux 9**: ✅ Supported
|
||||
- **AlmaLinux 8**: ✅ Supported
|
||||
- **RockyLinux 9**: ✅ Supported
|
||||
- **RockyLinux 8**: ✅ Supported
|
||||
- **RHEL 9**: ✅ Supported
|
||||
- **RHEL 8**: ✅ Supported
|
||||
|
||||
### **Other Distributions**
|
||||
- **CloudLinux 8**: ✅ Supported
|
||||
- **CentOS 9**: ✅ Supported
|
||||
- **CentOS 7**: ✅ Supported (until June 2024)
|
||||
- **CentOS Stream 9**: ✅ Supported
|
||||
|
||||
## 🔧 Installation Process Verification
|
||||
|
||||
### **What the Installation Script Does**
|
||||
|
||||
1. **System Detection**
|
||||
- Detects operating system and version
|
||||
- Checks architecture (x86_64 required)
|
||||
- Verifies system requirements
|
||||
|
||||
2. **Dependency Installation**
|
||||
- Installs Python 3.8+
|
||||
- Installs Git
|
||||
- Installs system packages (curl, wget, etc.)
|
||||
|
||||
3. **Web Server Setup**
|
||||
- Downloads and installs OpenLiteSpeed
|
||||
- Configures web server settings
|
||||
- Sets up virtual hosts
|
||||
|
||||
4. **Database Setup**
|
||||
- Installs and configures MariaDB
|
||||
- Creates CyberPanel database
|
||||
- Sets up database users
|
||||
|
||||
5. **CyberPanel Installation**
|
||||
- Downloads CyberPanel source code
|
||||
- Installs Python dependencies
|
||||
- Configures Django settings
|
||||
- Runs database migrations
|
||||
|
||||
6. **Service Configuration**
|
||||
- Creates systemd services
|
||||
- Starts all required services
|
||||
- Configures firewall rules
|
||||
|
||||
7. **Final Setup**
|
||||
- Creates admin user
|
||||
- Sets up file permissions
|
||||
- Provides access information
|
||||
|
||||
## 🔄 Upgrade Process Verification
|
||||
|
||||
### **What the Upgrade Script Does**
|
||||
|
||||
1. **Backup Creation**
|
||||
- Creates backup of current installation
|
||||
- Backs up database and configuration files
|
||||
- Stores backup in safe location
|
||||
|
||||
2. **Source Update**
|
||||
- Downloads latest CyberPanel source code
|
||||
- Updates all files to latest version
|
||||
- Preserves custom configurations
|
||||
|
||||
3. **Dependency Update**
|
||||
- Updates Python packages
|
||||
- Updates system dependencies
|
||||
- Handles version conflicts
|
||||
|
||||
4. **Database Migration**
|
||||
- Runs Django migrations
|
||||
- Updates database schema
|
||||
- Preserves existing data
|
||||
|
||||
5. **Service Restart**
|
||||
- Restarts all CyberPanel services
|
||||
- Verifies service status
|
||||
- Reports any issues
|
||||
|
||||
## 🧪 Testing Procedures
|
||||
|
||||
### **Pre-Installation Testing**
|
||||
|
||||
#### 1. **System Requirements Check**
|
||||
```bash
|
||||
# Check OS version
|
||||
cat /etc/os-release
|
||||
|
||||
# Check architecture
|
||||
uname -m
|
||||
|
||||
# Check available memory
|
||||
free -h
|
||||
|
||||
# Check available disk space
|
||||
df -h
|
||||
|
||||
# Check network connectivity
|
||||
ping -c 4 google.com
|
||||
```
|
||||
|
||||
#### 2. **Dependency Check**
|
||||
```bash
|
||||
# Check if Python is available
|
||||
python3 --version
|
||||
|
||||
# Check if Git is available
|
||||
git --version
|
||||
|
||||
# Check if curl/wget are available
|
||||
curl --version
|
||||
wget --version
|
||||
```
|
||||
|
||||
### **Installation Testing**
|
||||
|
||||
#### 1. **Dry Run Test**
|
||||
```bash
|
||||
# Download and examine script without executing
|
||||
curl -s https://cyberpanel.net/install.sh > install_test.sh
|
||||
chmod +x install_test.sh
|
||||
|
||||
# Review script content
|
||||
head -50 install_test.sh
|
||||
```
|
||||
|
||||
#### 2. **Full Installation Test**
|
||||
```bash
|
||||
# Run installation in test environment
|
||||
sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh)
|
||||
|
||||
# Monitor installation process
|
||||
# Check for any errors or warnings
|
||||
```
|
||||
|
||||
### **Post-Installation Verification**
|
||||
|
||||
#### 1. **Service Status Check**
|
||||
```bash
|
||||
# Check CyberPanel service
|
||||
systemctl status lscpd
|
||||
|
||||
# Check web server
|
||||
systemctl status lsws
|
||||
|
||||
# Check database
|
||||
systemctl status mariadb
|
||||
```
|
||||
|
||||
#### 2. **Web Interface Test**
|
||||
```bash
|
||||
# Test web interface accessibility
|
||||
curl -I http://localhost:8090
|
||||
|
||||
# Expected: HTTP/1.1 200 OK
|
||||
```
|
||||
|
||||
#### 3. **Database Connection Test**
|
||||
```bash
|
||||
# Test database connection
|
||||
mysql -u root -p -e "SHOW DATABASES;"
|
||||
|
||||
# Should show CyberPanel database
|
||||
```
|
||||
|
||||
## 🐛 Common Issues and Solutions
|
||||
|
||||
### **Installation Issues**
|
||||
|
||||
#### 1. **"Command not found" Errors**
|
||||
**Problem**: Required commands not available
|
||||
**Solution**:
|
||||
```bash
|
||||
# Install missing packages
|
||||
# Ubuntu/Debian
|
||||
sudo apt update && sudo apt install curl wget git python3
|
||||
|
||||
# RHEL/CentOS/AlmaLinux/RockyLinux
|
||||
sudo yum install curl wget git python3
|
||||
```
|
||||
|
||||
#### 2. **Permission Denied Errors**
|
||||
**Problem**: Insufficient privileges
|
||||
**Solution**:
|
||||
```bash
|
||||
# Run with sudo
|
||||
sudo sh <(curl https://cyberpanel.net/install.sh || wget -O - https://cyberpanel.net/install.sh)
|
||||
```
|
||||
|
||||
#### 3. **Network Connectivity Issues**
|
||||
**Problem**: Cannot download installation script
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check internet connection
|
||||
ping -c 4 google.com
|
||||
|
||||
# Check DNS resolution
|
||||
nslookup cyberpanel.net
|
||||
|
||||
# Try alternative download method
|
||||
wget https://cyberpanel.net/install.sh
|
||||
chmod +x install.sh
|
||||
sudo ./install.sh
|
||||
```
|
||||
|
||||
#### 4. **Port Already in Use**
|
||||
**Problem**: Port 8090 already occupied
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check what's using port 8090
|
||||
sudo netstat -tlnp | grep :8090
|
||||
|
||||
# Kill process if necessary
|
||||
sudo kill -9 <PID>
|
||||
|
||||
# Or change CyberPanel port in configuration
|
||||
```
|
||||
|
||||
### **Upgrade Issues**
|
||||
|
||||
#### 1. **Backup Creation Failed**
|
||||
**Problem**: Cannot create backup
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check disk space
|
||||
df -h
|
||||
|
||||
# Free up space if necessary
|
||||
sudo apt autoremove
|
||||
sudo apt autoclean
|
||||
|
||||
# Or specify different backup location
|
||||
```
|
||||
|
||||
#### 2. **Database Migration Failed**
|
||||
**Problem**: Database migration errors
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check database status
|
||||
systemctl status mariadb
|
||||
|
||||
# Restart database service
|
||||
sudo systemctl restart mariadb
|
||||
|
||||
# Run migration manually
|
||||
cd /usr/local/CyberCP
|
||||
python3 manage.py migrate
|
||||
```
|
||||
|
||||
#### 3. **Service Restart Failed**
|
||||
**Problem**: Services won't restart
|
||||
**Solution**:
|
||||
```bash
|
||||
# Check service logs
|
||||
journalctl -u lscpd -f
|
||||
|
||||
# Restart services manually
|
||||
sudo systemctl restart lscpd
|
||||
sudo systemctl restart lsws
|
||||
```
|
||||
|
||||
## 📊 Verification Checklist
|
||||
|
||||
### **Installation Verification**
|
||||
- [ ] Installation script downloads successfully
|
||||
- [ ] All dependencies installed correctly
|
||||
- [ ] Web server starts and responds
|
||||
- [ ] Database server starts and responds
|
||||
- [ ] CyberPanel web interface accessible
|
||||
- [ ] Admin user can log in
|
||||
- [ ] All services running properly
|
||||
- [ ] No error messages in logs
|
||||
|
||||
### **Upgrade Verification**
|
||||
- [ ] Upgrade script downloads successfully
|
||||
- [ ] Backup created successfully
|
||||
- [ ] Source code updated correctly
|
||||
- [ ] Dependencies updated properly
|
||||
- [ ] Database migrations completed
|
||||
- [ ] Services restarted successfully
|
||||
- [ ] Web interface still accessible
|
||||
- [ ] Data integrity maintained
|
||||
|
||||
## 🔍 Monitoring and Logging
|
||||
|
||||
### **Installation Logs**
|
||||
```bash
|
||||
# Check installation logs
|
||||
tail -f /root/cyberpanel-install.log
|
||||
|
||||
# Check system logs
|
||||
journalctl -f
|
||||
```
|
||||
|
||||
### **Service Logs**
|
||||
```bash
|
||||
# Check CyberPanel logs
|
||||
tail -f /usr/local/lscp/logs/error.log
|
||||
|
||||
# Check web server logs
|
||||
tail -f /usr/local/lsws/logs/error.log
|
||||
|
||||
# Check database logs
|
||||
tail -f /var/log/mysql/error.log
|
||||
```
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
### **If Installation Fails**
|
||||
1. **Check the logs** for specific error messages
|
||||
2. **Verify system requirements** are met
|
||||
3. **Try alternative installation methods** (wget, manual download)
|
||||
4. **Use troubleshooting commands** from the README
|
||||
5. **Contact support** with detailed error information
|
||||
|
||||
### **If Upgrade Fails**
|
||||
1. **Restore from backup** if available
|
||||
2. **Check service status** and restart if needed
|
||||
3. **Run manual upgrade** steps
|
||||
4. **Use troubleshooting commands** from the README
|
||||
5. **Contact support** with upgrade logs
|
||||
|
||||
### **Support Resources**
|
||||
- **CyberPanel Forums**: https://community.cyberpanel.net
|
||||
- **GitHub Issues**: https://github.com/usmannasir/cyberpanel/issues
|
||||
- **Discord Server**: https://discord.gg/cyberpanel
|
||||
|
||||
---
|
||||
|
||||
**Note**: This verification guide ensures CyberPanel installation and upgrade processes work correctly across all supported operating systems. Always test in a non-production environment first.
|
||||
|
||||
*Last updated: January 2025*
|
||||
|
|
@ -0,0 +1,329 @@
|
|||
# CyberPanel Windows Development Guide
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
**⚠️ IMPORTANT**: CyberPanel is designed specifically for Linux systems and does not officially support Windows. This guide is for **development and testing purposes only**.
|
||||
|
||||
This guide provides instructions for running CyberPanel in a Windows development environment using Python and Django. This setup is intended for:
|
||||
- **Developers** working on CyberPanel features
|
||||
- **Testing** CyberPanel functionality
|
||||
- **Learning** CyberPanel architecture
|
||||
- **Development** of CyberPanel extensions
|
||||
|
||||
**For production use, always use a supported Linux distribution.**
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
### System Requirements
|
||||
- **OS**: Windows 7/8.1/10/11 (64-bit recommended)
|
||||
- **RAM**: Minimum 4GB (8GB+ recommended)
|
||||
- **Storage**: Minimum 20GB free space
|
||||
- **CPU**: 2+ cores recommended
|
||||
- **Network**: Internet connection required
|
||||
|
||||
### Required Software
|
||||
- **Python 3.8+**: Download from [python.org](https://python.org)
|
||||
- **Git**: Download from [git-scm.com](https://git-scm.com)
|
||||
- **Administrator Access**: Required for installation
|
||||
|
||||
### ⚠️ Limitations
|
||||
- **No Web Server**: OpenLiteSpeed/LiteSpeed not available on Windows
|
||||
- **No System Services**: MariaDB, PowerDNS, etc. not included
|
||||
- **Limited Functionality**: Many CyberPanel features require Linux
|
||||
- **Development Only**: Not suitable for production use
|
||||
|
||||
## 🚀 Installation Methods
|
||||
|
||||
### Method 1: Automated Installation (Recommended)
|
||||
|
||||
#### Step 1: Download Installation Script
|
||||
1. Navigate to the `utils/windows/` folder
|
||||
2. Download `cyberpanel_install.bat`
|
||||
3. Right-click and select "Run as administrator"
|
||||
|
||||
#### Step 2: Follow Installation Prompts
|
||||
The script will automatically:
|
||||
- Check system requirements
|
||||
- Create Python virtual environment
|
||||
- Download CyberPanel source code
|
||||
- Install all dependencies
|
||||
- Set up the web interface
|
||||
|
||||
#### Step 3: Access CyberPanel
|
||||
1. Open your web browser
|
||||
2. Navigate to: `http://localhost:8090`
|
||||
3. Use default credentials:
|
||||
- **Username**: `admin`
|
||||
- **Password**: `123456`
|
||||
|
||||
### Method 2: Manual Installation
|
||||
|
||||
#### Step 1: Install Python
|
||||
1. Download Python 3.8+ from [python.org](https://python.org)
|
||||
2. **Important**: Check "Add Python to PATH" during installation
|
||||
3. Verify installation:
|
||||
```cmd
|
||||
python --version
|
||||
pip --version
|
||||
```
|
||||
|
||||
#### Step 2: Install Git
|
||||
1. Download Git from [git-scm.com](https://git-scm.com)
|
||||
2. Install with default settings
|
||||
3. Verify installation:
|
||||
```cmd
|
||||
git --version
|
||||
```
|
||||
|
||||
#### Step 3: Create CyberPanel Directory
|
||||
```cmd
|
||||
mkdir C:\usr\local\CyberCP
|
||||
cd C:\usr\local\CyberCP
|
||||
```
|
||||
|
||||
#### Step 4: Set Up Python Environment
|
||||
```cmd
|
||||
python -m venv . --system-site-packages
|
||||
Scripts\activate.bat
|
||||
```
|
||||
|
||||
#### Step 5: Download CyberPanel
|
||||
```cmd
|
||||
git clone https://github.com/usmannasir/cyberpanel.git
|
||||
cd cyberpanel
|
||||
```
|
||||
|
||||
#### Step 6: Install Dependencies
|
||||
```cmd
|
||||
pip install --upgrade pip setuptools wheel
|
||||
pip install -r requirments.txt
|
||||
```
|
||||
|
||||
#### Step 7: Set Up Django
|
||||
```cmd
|
||||
python manage.py collectstatic --noinput
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
#### Step 8: Create Admin User
|
||||
```cmd
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
#### Step 9: Start CyberPanel
|
||||
```cmd
|
||||
python manage.py runserver 0.0.0.0:8090
|
||||
```
|
||||
|
||||
## 🔧 Post-Installation Configuration
|
||||
|
||||
### Change Default Password
|
||||
1. Access CyberPanel at `http://localhost:8090`
|
||||
2. Log in with default credentials
|
||||
3. Go to **User Management** → **Modify User**
|
||||
4. Change the admin password immediately
|
||||
|
||||
### Configure Windows Firewall
|
||||
1. Open Windows Defender Firewall
|
||||
2. Add inbound rule for port 8090
|
||||
3. Allow Python through firewall
|
||||
|
||||
### Set Up Windows Service (Optional)
|
||||
1. Run `install_service.bat` as administrator
|
||||
2. CyberPanel will start automatically on boot
|
||||
3. Manage service through Windows Services
|
||||
|
||||
## 🔄 Upgrading CyberPanel
|
||||
|
||||
### Using Upgrade Script
|
||||
1. Download `cyberpanel_upgrade.bat`
|
||||
2. Right-click and select "Run as administrator"
|
||||
3. Script will automatically backup and upgrade
|
||||
|
||||
### Manual Upgrade
|
||||
```cmd
|
||||
cd C:\usr\local\CyberCP\cyberpanel
|
||||
Scripts\activate.bat
|
||||
git pull origin stable
|
||||
pip install --upgrade -r requirments.txt
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic --noinput
|
||||
```
|
||||
|
||||
## 🛠️ Utility Scripts
|
||||
|
||||
### Available Scripts
|
||||
- **`cyberpanel_install.bat`**: Complete installation
|
||||
- **`cyberpanel_upgrade.bat`**: Upgrade existing installation
|
||||
- **`install_webauthn.bat`**: Enable WebAuthn/Passkey authentication
|
||||
|
||||
### Script Usage
|
||||
1. **Right-click** on any script
|
||||
2. Select **"Run as administrator"**
|
||||
3. Follow on-screen prompts
|
||||
4. Check output for any errors
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### "Python not found" Error
|
||||
**Problem**: Python is not installed or not in PATH
|
||||
**Solution**:
|
||||
1. Install Python from [python.org](https://python.org)
|
||||
2. Check "Add Python to PATH" during installation
|
||||
3. Restart Command Prompt
|
||||
4. Verify with `python --version`
|
||||
|
||||
#### "Access Denied" Error
|
||||
**Problem**: Insufficient privileges
|
||||
**Solution**:
|
||||
1. Right-click script and select "Run as administrator"
|
||||
2. Or open Command Prompt as administrator
|
||||
3. Navigate to script location and run
|
||||
|
||||
#### "Failed to install requirements" Error
|
||||
**Problem**: Network or dependency issues
|
||||
**Solution**:
|
||||
1. Check internet connection
|
||||
2. Try running script again
|
||||
3. Install requirements manually:
|
||||
```cmd
|
||||
pip install --upgrade pip
|
||||
pip install -r requirments.txt
|
||||
```
|
||||
|
||||
#### "Port 8090 already in use" Error
|
||||
**Problem**: Another service is using port 8090
|
||||
**Solution**:
|
||||
1. Stop other services using port 8090
|
||||
2. Or change CyberPanel port in settings
|
||||
3. Check with: `netstat -an | findstr :8090`
|
||||
|
||||
#### "Django not found" Error
|
||||
**Problem**: Virtual environment not activated
|
||||
**Solution**:
|
||||
1. Navigate to CyberPanel directory
|
||||
2. Activate virtual environment:
|
||||
```cmd
|
||||
Scripts\activate.bat
|
||||
```
|
||||
3. Verify with `python -c "import django"`
|
||||
|
||||
### Advanced Troubleshooting
|
||||
|
||||
#### Check Installation Logs
|
||||
```cmd
|
||||
cd C:\usr\local\CyberCP\cyberpanel
|
||||
python manage.py check
|
||||
```
|
||||
|
||||
#### Verify Dependencies
|
||||
```cmd
|
||||
pip list
|
||||
pip check
|
||||
```
|
||||
|
||||
#### Test Database Connection
|
||||
```cmd
|
||||
python manage.py dbshell
|
||||
```
|
||||
|
||||
#### Reset Database (Last Resort)
|
||||
```cmd
|
||||
python manage.py flush
|
||||
python manage.py migrate
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
## 🔒 Security Considerations
|
||||
|
||||
### Initial Security Setup
|
||||
1. **Change Default Password**: Immediately after installation
|
||||
2. **Enable 2FA**: Use the 2FA guide for additional security
|
||||
3. **Configure Firewall**: Restrict access to necessary ports only
|
||||
4. **Regular Updates**: Keep CyberPanel and dependencies updated
|
||||
|
||||
### Windows-Specific Security
|
||||
1. **User Account Control**: Keep UAC enabled
|
||||
2. **Windows Defender**: Ensure real-time protection is on
|
||||
3. **Regular Backups**: Backup CyberPanel data regularly
|
||||
4. **Service Account**: Consider using dedicated service account
|
||||
|
||||
## 📊 Performance Optimization
|
||||
|
||||
### System Optimization
|
||||
1. **Disable Unnecessary Services**: Free up system resources
|
||||
2. **SSD Storage**: Use SSD for better performance
|
||||
3. **Memory**: Ensure adequate RAM (8GB+ recommended)
|
||||
4. **CPU**: Multi-core processor recommended
|
||||
|
||||
### CyberPanel Optimization
|
||||
1. **Static Files**: Ensure static files are properly collected
|
||||
2. **Database**: Regular database maintenance
|
||||
3. **Logs**: Regular log cleanup
|
||||
4. **Caching**: Enable appropriate caching
|
||||
|
||||
## 🔄 Maintenance
|
||||
|
||||
### Regular Maintenance Tasks
|
||||
1. **Updates**: Regular CyberPanel updates
|
||||
2. **Backups**: Regular data backups
|
||||
3. **Logs**: Monitor and clean logs
|
||||
4. **Security**: Regular security checks
|
||||
|
||||
### Backup Procedures
|
||||
1. **Database Backup**:
|
||||
```cmd
|
||||
python manage.py dumpdata > backup.json
|
||||
```
|
||||
|
||||
2. **File Backup**:
|
||||
```cmd
|
||||
xcopy C:\usr\local\CyberCP backup_folder /E /I /H
|
||||
```
|
||||
|
||||
3. **Restore from Backup**:
|
||||
```cmd
|
||||
python manage.py loaddata backup.json
|
||||
```
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### Documentation
|
||||
- **Main Guide**: [2FA Authentication Guide](2FA_AUTHENTICATION_GUIDE.md)
|
||||
- **Troubleshooting**: [Troubleshooting Guide](TROUBLESHOOTING.md)
|
||||
- **Utility Scripts**: [Utility Scripts](../utils/README.md)
|
||||
|
||||
### Support
|
||||
- **CyberPanel Forums**: https://community.cyberpanel.net
|
||||
- **GitHub Issues**: https://github.com/usmannasir/cyberpanel/issues
|
||||
- **Discord Server**: https://discord.gg/cyberpanel
|
||||
|
||||
## ✅ Verification Checklist
|
||||
|
||||
After installation, verify these components:
|
||||
- [ ] CyberPanel accessible at `http://localhost:8090`
|
||||
- [ ] Admin password changed from default
|
||||
- [ ] Windows Firewall configured
|
||||
- [ ] Python virtual environment working
|
||||
- [ ] All dependencies installed
|
||||
- [ ] Database migrations applied
|
||||
- [ ] Static files collected
|
||||
- [ ] Logs are clean
|
||||
- [ ] Service starts automatically (if configured)
|
||||
|
||||
## 🆘 Getting Help
|
||||
|
||||
If you encounter issues:
|
||||
1. **Check the logs** for error messages
|
||||
2. **Run the troubleshooting commands** above
|
||||
3. **Search the documentation** for solutions
|
||||
4. **Ask in the community forum** for help
|
||||
5. **Create a GitHub issue** with detailed information
|
||||
|
||||
---
|
||||
|
||||
**Note**: This guide is specifically for Windows installations. For Linux installations, refer to the main CyberPanel documentation.
|
||||
|
||||
*Last updated: January 2025*
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# Generated migration for WebAuthn models
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('loginSystem', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='WebAuthnCredential',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('credential_id', models.CharField(help_text='Base64 encoded credential ID', max_length=255, unique=True)),
|
||||
('public_key', models.TextField(help_text='Base64 encoded public key')),
|
||||
('counter', models.BigIntegerField(default=0, help_text='Signature counter for replay protection')),
|
||||
('name', models.CharField(help_text='User-friendly name for the passkey', max_length=100)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('last_used', models.DateTimeField(blank=True, null=True)),
|
||||
('is_active', models.BooleanField(default=True, help_text='Whether this credential is active')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='webauthn_credentials', to='loginSystem.administrator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'WebAuthn Credential',
|
||||
'verbose_name_plural': 'WebAuthn Credentials',
|
||||
'db_table': 'webauthn_credentials',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebAuthnChallenge',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('challenge', models.CharField(help_text='Base64 encoded challenge', max_length=255)),
|
||||
('challenge_type', models.CharField(choices=[('registration', 'Registration'), ('authentication', 'Authentication')], max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('expires_at', models.DateTimeField()),
|
||||
('used', models.BooleanField(default=False)),
|
||||
('metadata', models.TextField(default='{}', help_text='Additional challenge metadata as JSON')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='webauthn_challenges', to='loginSystem.administrator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'WebAuthn Challenge',
|
||||
'verbose_name_plural': 'WebAuthn Challenges',
|
||||
'db_table': 'webauthn_challenges',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebAuthnSession',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('session_id', models.CharField(help_text='Unique session identifier', max_length=255, unique=True)),
|
||||
('session_type', models.CharField(choices=[('registration', 'Registration'), ('authentication', 'Authentication')], max_length=20)),
|
||||
('data', models.TextField(help_text='Session data as JSON')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('expires_at', models.DateTimeField()),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='webauthn_sessions', to='loginSystem.administrator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'WebAuthn Session',
|
||||
'verbose_name_plural': 'WebAuthn Sessions',
|
||||
'db_table': 'webauthn_sessions',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WebAuthnSettings',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('enabled', models.BooleanField(default=False, help_text='Whether WebAuthn is enabled for this user')),
|
||||
('require_passkey', models.BooleanField(default=False, help_text='Require passkey for login (passwordless)')),
|
||||
('allow_multiple_credentials', models.BooleanField(default=True, help_text='Allow multiple passkeys per user')),
|
||||
('max_credentials', models.IntegerField(default=10, help_text='Maximum number of passkeys allowed')),
|
||||
('timeout_seconds', models.IntegerField(default=60, help_text='WebAuthn operation timeout in seconds')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='webauthn_settings', to='loginSystem.administrator')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'WebAuthn Settings',
|
||||
'verbose_name_plural': 'WebAuthn Settings',
|
||||
'db_table': 'webauthn_settings',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='webauthncredential',
|
||||
index=models.Index(fields=['user', 'is_active'], name='webauthn_cre_user_id_123456_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='webauthncredential',
|
||||
index=models.Index(fields=['credential_id'], name='webauthn_cre_credent_123456_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='webauthnchallenge',
|
||||
index=models.Index(fields=['user', 'challenge_type', 'used'], name='webauthn_cha_user_id_123456_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='webauthnchallenge',
|
||||
index=models.Index(fields=['expires_at'], name='webauthn_cha_expires_123456_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='webauthnsession',
|
||||
index=models.Index(fields=['session_id'], name='webauthn_ses_session_123456_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='webauthnsession',
|
||||
index=models.Index(fields=['expires_at'], name='webauthn_ses_expires_123456_idx'),
|
||||
),
|
||||
]
|
||||
|
|
@ -0,0 +1,396 @@
|
|||
/**
|
||||
* WebAuthn JavaScript integration for CyberPanel
|
||||
* Provides passkey registration and authentication functionality
|
||||
*/
|
||||
|
||||
class CyberPanelWebAuthn {
|
||||
constructor() {
|
||||
this.isSupported = this.checkSupport();
|
||||
this.baseUrl = window.location.origin;
|
||||
this.apiEndpoints = {
|
||||
registrationStart: '/webauthn/registration/start/',
|
||||
registrationComplete: '/webauthn/registration/complete/',
|
||||
authenticationStart: '/webauthn/authentication/start/',
|
||||
authenticationComplete: '/webauthn/authentication/complete/',
|
||||
credentialsList: '/webauthn/credentials/',
|
||||
credentialDelete: '/webauthn/credential/delete/',
|
||||
credentialUpdate: '/webauthn/credential/update/',
|
||||
settingsUpdate: '/webauthn/settings/update/',
|
||||
};
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (!this.isSupported) {
|
||||
console.warn('WebAuthn is not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add CSRF token to all requests
|
||||
this.csrfToken = this.getCSRFToken();
|
||||
|
||||
// Initialize UI elements
|
||||
this.initializeUI();
|
||||
}
|
||||
|
||||
checkSupport() {
|
||||
return !!(navigator.credentials &&
|
||||
navigator.credentials.create &&
|
||||
navigator.credentials.get &&
|
||||
window.PublicKeyCredential);
|
||||
}
|
||||
|
||||
getCSRFToken() {
|
||||
const cookieValue = document.cookie
|
||||
.split('; ')
|
||||
.find(row => row.startsWith('csrftoken='))
|
||||
?.split('=')[1];
|
||||
return cookieValue || '';
|
||||
}
|
||||
|
||||
initializeUI() {
|
||||
// Add WebAuthn buttons to login form
|
||||
this.addLoginButtons();
|
||||
|
||||
// Add WebAuthn management to user settings
|
||||
this.addUserManagementUI();
|
||||
}
|
||||
|
||||
addLoginButtons() {
|
||||
const loginForm = document.querySelector('#loginForm');
|
||||
if (!loginForm) return;
|
||||
|
||||
// Add WebAuthn login button
|
||||
const webauthnButton = document.createElement('button');
|
||||
webauthnButton.type = 'button';
|
||||
webauthnButton.className = 'btn btn-primary btn-block';
|
||||
webauthnButton.innerHTML = '<i class="fas fa-fingerprint"></i> Login with Passkey';
|
||||
webauthnButton.onclick = () => this.startPasswordlessLogin();
|
||||
|
||||
// Insert after password field
|
||||
const passwordField = loginForm.querySelector('input[type="password"]');
|
||||
if (passwordField) {
|
||||
passwordField.parentNode.insertBefore(webauthnButton, passwordField.parentNode.nextSibling);
|
||||
}
|
||||
}
|
||||
|
||||
addUserManagementUI() {
|
||||
// This will be called when user management page loads
|
||||
// Implementation depends on the specific UI structure
|
||||
}
|
||||
|
||||
async startPasswordlessLogin() {
|
||||
try {
|
||||
const username = document.querySelector('input[name="username"]').value;
|
||||
if (!username) {
|
||||
this.showError('Please enter your username first');
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading('Starting passkey authentication...');
|
||||
|
||||
// Get authentication challenge
|
||||
const challengeResponse = await this.makeRequest('POST', this.apiEndpoints.authenticationStart, {
|
||||
username: username
|
||||
});
|
||||
|
||||
if (!challengeResponse.success) {
|
||||
throw new Error(challengeResponse.error || 'Failed to start authentication');
|
||||
}
|
||||
|
||||
// Convert challenge to proper format
|
||||
const challenge = this.convertChallenge(challengeResponse.challenge);
|
||||
|
||||
// Get credential
|
||||
const credential = await navigator.credentials.get({
|
||||
publicKey: challenge
|
||||
});
|
||||
|
||||
// Complete authentication
|
||||
const authResponse = await this.makeRequest('POST', this.apiEndpoints.authenticationComplete, {
|
||||
challenge_id: challengeResponse.challenge_id,
|
||||
credential: {
|
||||
id: this.arrayBufferToBase64(credential.rawId),
|
||||
type: credential.type
|
||||
},
|
||||
client_data_json: this.arrayBufferToBase64(credential.response.clientDataJSON),
|
||||
authenticator_data: this.arrayBufferToBase64(credential.response.authenticatorData),
|
||||
signature: this.arrayBufferToBase64(credential.response.signature),
|
||||
user_handle: credential.response.userHandle ?
|
||||
this.arrayBufferToBase64(credential.response.userHandle) : null
|
||||
});
|
||||
|
||||
if (authResponse.success) {
|
||||
this.showSuccess('Authentication successful! Redirecting...');
|
||||
setTimeout(() => {
|
||||
window.location.href = '/';
|
||||
}, 1000);
|
||||
} else {
|
||||
throw new Error(authResponse.error || 'Authentication failed');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('WebAuthn authentication error:', error);
|
||||
this.showError(error.message || 'Authentication failed');
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async registerPasskey(username, credentialName = '') {
|
||||
try {
|
||||
this.showLoading('Starting passkey registration...');
|
||||
|
||||
// Get registration challenge
|
||||
const challengeResponse = await this.makeRequest('POST', this.apiEndpoints.registrationStart, {
|
||||
username: username,
|
||||
credential_name: credentialName
|
||||
});
|
||||
|
||||
if (!challengeResponse.success) {
|
||||
throw new Error(challengeResponse.error || 'Failed to start registration');
|
||||
}
|
||||
|
||||
// Convert challenge to proper format
|
||||
const challenge = this.convertChallenge(challengeResponse.challenge);
|
||||
|
||||
// Create credential
|
||||
const credential = await navigator.credentials.create({
|
||||
publicKey: challenge
|
||||
});
|
||||
|
||||
// Complete registration
|
||||
const regResponse = await this.makeRequest('POST', this.apiEndpoints.registrationComplete, {
|
||||
challenge_id: challengeResponse.challenge_id,
|
||||
credential: {
|
||||
id: this.arrayBufferToBase64(credential.rawId),
|
||||
type: credential.type
|
||||
},
|
||||
client_data_json: this.arrayBufferToBase64(credential.response.clientDataJSON),
|
||||
attestation_object: this.arrayBufferToBase64(credential.response.attestationObject)
|
||||
});
|
||||
|
||||
if (regResponse.success) {
|
||||
this.showSuccess('Passkey registered successfully!');
|
||||
return regResponse;
|
||||
} else {
|
||||
throw new Error(regResponse.error || 'Registration failed');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('WebAuthn registration error:', error);
|
||||
this.showError(error.message || 'Registration failed');
|
||||
throw error;
|
||||
} finally {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
async listCredentials(username) {
|
||||
try {
|
||||
const response = await this.makeRequest('GET',
|
||||
`${this.apiEndpoints.credentialsList}${username}/`);
|
||||
|
||||
if (response.success) {
|
||||
return response.credentials;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to list credentials');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error listing credentials:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteCredential(username, credentialId) {
|
||||
try {
|
||||
const response = await this.makeRequest('POST', this.apiEndpoints.credentialDelete, {
|
||||
username: username,
|
||||
credential_id: credentialId
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
this.showSuccess('Credential deleted successfully');
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to delete credential');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting credential:', error);
|
||||
this.showError(error.message || 'Failed to delete credential');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateCredentialName(username, credentialId, newName) {
|
||||
try {
|
||||
const response = await this.makeRequest('POST', this.apiEndpoints.credentialUpdate, {
|
||||
username: username,
|
||||
credential_id: credentialId,
|
||||
new_name: newName
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
this.showSuccess('Credential name updated successfully');
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to update credential name');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating credential name:', error);
|
||||
this.showError(error.message || 'Failed to update credential name');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async updateSettings(username, settings) {
|
||||
try {
|
||||
const response = await this.makeRequest('POST', this.apiEndpoints.settingsUpdate, {
|
||||
username: username,
|
||||
...settings
|
||||
});
|
||||
|
||||
if (response.success) {
|
||||
this.showSuccess('Settings updated successfully');
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.error || 'Failed to update settings');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating settings:', error);
|
||||
this.showError(error.message || 'Failed to update settings');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
convertChallenge(challenge) {
|
||||
// Convert base64 challenge to ArrayBuffer
|
||||
const challengeBytes = this.base64ToArrayBuffer(challenge.challenge);
|
||||
|
||||
return {
|
||||
...challenge,
|
||||
challenge: challengeBytes,
|
||||
user: {
|
||||
...challenge.user,
|
||||
id: this.base64ToArrayBuffer(challenge.user.id)
|
||||
},
|
||||
excludeCredentials: challenge.excludeCredentials?.map(cred => ({
|
||||
...cred,
|
||||
id: this.base64ToArrayBuffer(cred.id)
|
||||
})) || [],
|
||||
allowCredentials: challenge.allowCredentials?.map(cred => ({
|
||||
...cred,
|
||||
id: this.base64ToArrayBuffer(cred.id)
|
||||
})) || []
|
||||
};
|
||||
}
|
||||
|
||||
base64ToArrayBuffer(base64) {
|
||||
const binaryString = window.atob(base64);
|
||||
const bytes = new Uint8Array(binaryString.length);
|
||||
for (let i = 0; i < binaryString.length; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return bytes.buffer;
|
||||
}
|
||||
|
||||
arrayBufferToBase64(buffer) {
|
||||
const bytes = new Uint8Array(buffer);
|
||||
let binary = '';
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
}
|
||||
|
||||
async makeRequest(method, url, data = null) {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': this.csrfToken
|
||||
}
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const response = await fetch(url, options);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
showLoading(message) {
|
||||
// Create or update loading indicator
|
||||
let loadingDiv = document.getElementById('webauthn-loading');
|
||||
if (!loadingDiv) {
|
||||
loadingDiv = document.createElement('div');
|
||||
loadingDiv.id = 'webauthn-loading';
|
||||
loadingDiv.className = 'alert alert-info';
|
||||
loadingDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + message;
|
||||
document.body.appendChild(loadingDiv);
|
||||
} else {
|
||||
loadingDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i> ' + message;
|
||||
loadingDiv.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
hideLoading() {
|
||||
const loadingDiv = document.getElementById('webauthn-loading');
|
||||
if (loadingDiv) {
|
||||
loadingDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
showSuccess(message) {
|
||||
this.showAlert('success', message);
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
this.showAlert('danger', message);
|
||||
}
|
||||
|
||||
showAlert(type, message) {
|
||||
// Create alert element
|
||||
const alertDiv = document.createElement('div');
|
||||
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
|
||||
alertDiv.innerHTML = `
|
||||
${message}
|
||||
<button type="button" class="close" data-dismiss="alert">
|
||||
<span>×</span>
|
||||
</button>
|
||||
`;
|
||||
|
||||
// Insert at top of page
|
||||
const container = document.querySelector('.container') || document.body;
|
||||
container.insertBefore(alertDiv, container.firstChild);
|
||||
|
||||
// Auto-dismiss after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (alertDiv.parentNode) {
|
||||
alertDiv.remove();
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Utility method to check if WebAuthn is available
|
||||
static isSupported() {
|
||||
return !!(navigator.credentials &&
|
||||
navigator.credentials.create &&
|
||||
navigator.credentials.get &&
|
||||
window.PublicKeyCredential);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize WebAuthn when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (CyberPanelWebAuthn.isSupported()) {
|
||||
window.cyberPanelWebAuthn = new CyberPanelWebAuthn();
|
||||
}
|
||||
});
|
||||
|
||||
// Export for use in other scripts
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = CyberPanelWebAuthn;
|
||||
}
|
||||
|
|
@ -361,6 +361,18 @@
|
|||
<button type="button" style="background-color: #33CCCC;" ng-click="verifyLoginCredentials()"
|
||||
class="btn btn-success btn-block btn-login">Sign In
|
||||
</button>
|
||||
|
||||
<!-- WebAuthn Passkey Login Button -->
|
||||
<div id="webauthn-login-section" style="margin-top: 15px; display: none;">
|
||||
<button type="button" id="webauthn-login-btn"
|
||||
class="btn btn-primary btn-block btn-login"
|
||||
style="background-color: #5b5fcf; border-color: #5b5fcf;">
|
||||
<i class="fas fa-fingerprint"></i> Login with Passkey
|
||||
</button>
|
||||
<div class="text-center" style="margin-top: 10px;">
|
||||
<small class="text-muted">or</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -374,6 +386,36 @@
|
|||
<script src="https://code.angularjs.org/1.6.5/angular.min.js"></script>
|
||||
<script src="https://code.angularjs.org/1.6.5/angular-route.min.js"></script>
|
||||
<script src="{% static 'loginSystem/login-system.js' %}"></script>
|
||||
<script src="{% static 'loginSystem/webauthn.js' %}"></script>
|
||||
<script>
|
||||
// Initialize WebAuthn login functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const webauthnSection = document.getElementById('webauthn-login-section');
|
||||
const webauthnBtn = document.getElementById('webauthn-login-btn');
|
||||
const usernameInput = document.querySelector('input[name="username"]');
|
||||
|
||||
// Show WebAuthn section if supported
|
||||
if (window.cyberPanelWebAuthn && window.cyberPanelWebAuthn.isSupported()) {
|
||||
webauthnSection.style.display = 'block';
|
||||
|
||||
// Add click handler for WebAuthn login
|
||||
webauthnBtn.addEventListener('click', function() {
|
||||
if (window.cyberPanelWebAuthn) {
|
||||
window.cyberPanelWebAuthn.startPasswordlessLogin();
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide WebAuthn button based on username input
|
||||
usernameInput.addEventListener('input', function() {
|
||||
if (this.value.trim()) {
|
||||
webauthnBtn.disabled = false;
|
||||
} else {
|
||||
webauthnBtn.disabled = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,452 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.test import TestCase, Client
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import base64
|
||||
from .models import Administrator
|
||||
from .webauthn_models import WebAuthnCredential, WebAuthnChallenge, WebAuthnSettings
|
||||
from .webauthn_backend import WebAuthnBackend
|
||||
|
||||
|
||||
class WebAuthnTestCase(TestCase):
|
||||
"""Test cases for WebAuthn functionality"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test data"""
|
||||
self.client = Client()
|
||||
|
||||
# Create test user
|
||||
self.user = Administrator.objects.create(
|
||||
userName='testuser',
|
||||
password='hashedpassword',
|
||||
email='test@example.com',
|
||||
firstName='Test',
|
||||
lastName='User',
|
||||
type=1,
|
||||
acl_id=1
|
||||
)
|
||||
|
||||
# Create WebAuthn settings
|
||||
self.webauthn_settings = WebAuthnSettings.objects.create(
|
||||
user=self.user,
|
||||
enabled=True,
|
||||
require_passkey=False,
|
||||
allow_multiple_credentials=True,
|
||||
max_credentials=10,
|
||||
timeout_seconds=60
|
||||
)
|
||||
|
||||
self.webauthn_backend = WebAuthnBackend()
|
||||
|
||||
def test_webauthn_models(self):
|
||||
"""Test WebAuthn models"""
|
||||
# Test WebAuthnCredential
|
||||
credential = WebAuthnCredential.objects.create(
|
||||
user=self.user,
|
||||
credential_id='test_credential_id',
|
||||
public_key='test_public_key',
|
||||
name='Test Passkey',
|
||||
counter=0
|
||||
)
|
||||
|
||||
self.assertEqual(credential.user, self.user)
|
||||
self.assertEqual(credential.name, 'Test Passkey')
|
||||
self.assertTrue(credential.is_active)
|
||||
|
||||
# Test WebAuthnChallenge
|
||||
challenge = WebAuthnChallenge.objects.create(
|
||||
user=self.user,
|
||||
challenge='test_challenge',
|
||||
challenge_type='registration',
|
||||
expires_at=timezone.now() + timedelta(minutes=5)
|
||||
)
|
||||
|
||||
self.assertEqual(challenge.user, self.user)
|
||||
self.assertEqual(challenge.challenge_type, 'registration')
|
||||
self.assertFalse(challenge.is_expired())
|
||||
|
||||
# Test WebAuthnSettings
|
||||
self.assertTrue(self.webauthn_settings.enabled)
|
||||
self.assertTrue(self.webauthn_settings.can_add_credential())
|
||||
|
||||
def test_registration_challenge_creation(self):
|
||||
"""Test WebAuthn registration challenge creation"""
|
||||
result = self.webauthn_backend.create_registration_challenge(
|
||||
self.user, 'Test Passkey'
|
||||
)
|
||||
|
||||
self.assertTrue(result['success'])
|
||||
self.assertIn('challenge', result)
|
||||
self.assertIn('challenge_id', result)
|
||||
|
||||
# Verify challenge was stored in database
|
||||
challenge_id = result['challenge_id']
|
||||
challenge = WebAuthnChallenge.objects.get(id=challenge_id)
|
||||
self.assertEqual(challenge.user, self.user)
|
||||
self.assertEqual(challenge.challenge_type, 'registration')
|
||||
|
||||
def test_authentication_challenge_creation(self):
|
||||
"""Test WebAuthn authentication challenge creation"""
|
||||
result = self.webauthn_backend.create_authentication_challenge(self.user)
|
||||
|
||||
self.assertTrue(result['success'])
|
||||
self.assertIn('challenge', result)
|
||||
self.assertIn('challenge_id', result)
|
||||
|
||||
# Verify challenge was stored in database
|
||||
challenge_id = result['challenge_id']
|
||||
challenge = WebAuthnChallenge.objects.get(id=challenge_id)
|
||||
self.assertEqual(challenge.user, self.user)
|
||||
self.assertEqual(challenge.challenge_type, 'authentication')
|
||||
|
||||
def test_credential_management(self):
|
||||
"""Test credential management functions"""
|
||||
# Create test credential
|
||||
credential = WebAuthnCredential.objects.create(
|
||||
user=self.user,
|
||||
credential_id='test_credential_id',
|
||||
public_key='test_public_key',
|
||||
name='Test Passkey',
|
||||
counter=0
|
||||
)
|
||||
|
||||
# Test get_user_credentials
|
||||
credentials = self.webauthn_backend.get_user_credentials(self.user)
|
||||
self.assertEqual(len(credentials), 1)
|
||||
self.assertEqual(credentials[0]['name'], 'Test Passkey')
|
||||
|
||||
# Test delete_credential
|
||||
result = self.webauthn_backend.delete_credential(self.user, credential.id)
|
||||
self.assertTrue(result['success'])
|
||||
|
||||
# Verify credential is deactivated
|
||||
credential.refresh_from_db()
|
||||
self.assertFalse(credential.is_active)
|
||||
|
||||
# Test update_credential_name
|
||||
credential.is_active = True
|
||||
credential.save()
|
||||
|
||||
result = self.webauthn_backend.update_credential_name(
|
||||
self.user, credential.id, 'Updated Name'
|
||||
)
|
||||
self.assertTrue(result['success'])
|
||||
|
||||
credential.refresh_from_db()
|
||||
self.assertEqual(credential.name, 'Updated Name')
|
||||
|
||||
def test_webauthn_api_endpoints(self):
|
||||
"""Test WebAuthn API endpoints"""
|
||||
# Test registration start endpoint
|
||||
response = self.client.post('/webauthn/registration/start/',
|
||||
json.dumps({'username': 'testuser', 'credential_name': 'Test Passkey'}),
|
||||
content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = json.loads(response.content)
|
||||
self.assertTrue(data['success'])
|
||||
|
||||
# Test credentials list endpoint
|
||||
response = self.client.get('/webauthn/credentials/testuser/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = json.loads(response.content)
|
||||
self.assertTrue(data['success'])
|
||||
self.assertIn('credentials', data)
|
||||
self.assertIn('settings', data)
|
||||
|
||||
# Test settings update endpoint
|
||||
response = self.client.post('/webauthn/settings/update/',
|
||||
json.dumps({
|
||||
'username': 'testuser',
|
||||
'enabled': True,
|
||||
'require_passkey': False
|
||||
}),
|
||||
content_type='application/json')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
data = json.loads(response.content)
|
||||
self.assertTrue(data['success'])
|
||||
|
||||
def test_webauthn_settings_validation(self):
|
||||
"""Test WebAuthn settings validation"""
|
||||
# Test max credentials limit
|
||||
settings = WebAuthnSettings.objects.get(user=self.user)
|
||||
settings.max_credentials = 1
|
||||
settings.save()
|
||||
|
||||
# Create first credential
|
||||
WebAuthnCredential.objects.create(
|
||||
user=self.user,
|
||||
credential_id='cred1',
|
||||
public_key='key1',
|
||||
name='First Passkey'
|
||||
)
|
||||
|
||||
# Should not be able to add more credentials
|
||||
self.assertFalse(settings.can_add_credential())
|
||||
|
||||
# Test multiple credentials disabled
|
||||
settings.allow_multiple_credentials = False
|
||||
settings.max_credentials = 10
|
||||
settings.save()
|
||||
|
||||
# Should not be able to add more credentials
|
||||
self.assertFalse(settings.can_add_credential())
|
||||
|
||||
def test_challenge_expiration(self):
|
||||
"""Test challenge expiration handling"""
|
||||
# Create expired challenge
|
||||
expired_challenge = WebAuthnChallenge.objects.create(
|
||||
user=self.user,
|
||||
challenge='expired_challenge',
|
||||
challenge_type='registration',
|
||||
expires_at=timezone.now() - timedelta(minutes=1)
|
||||
)
|
||||
|
||||
self.assertTrue(expired_challenge.is_expired())
|
||||
|
||||
# Test cleanup
|
||||
self.webauthn_backend.cleanup_expired_challenges()
|
||||
|
||||
# Expired challenge should be deleted
|
||||
with self.assertRaises(WebAuthnChallenge.DoesNotExist):
|
||||
WebAuthnChallenge.objects.get(id=expired_challenge.id)
|
||||
|
||||
def test_webauthn_integration_with_existing_2fa(self):
|
||||
"""Test WebAuthn integration with existing 2FA system"""
|
||||
# Enable 2FA for user
|
||||
self.user.twoFA = 1
|
||||
self.user.secretKey = 'test_secret_key'
|
||||
self.user.save()
|
||||
|
||||
# Enable WebAuthn
|
||||
settings = WebAuthnSettings.objects.get(user=self.user)
|
||||
settings.enabled = True
|
||||
settings.save()
|
||||
|
||||
# Both should be enabled
|
||||
self.assertTrue(self.user.twoFA)
|
||||
self.assertTrue(settings.enabled)
|
||||
|
||||
# User should be able to use either authentication method
|
||||
# (This would be tested in the actual authentication flow)
|
||||
|
||||
def test_webauthn_security_features(self):
|
||||
"""Test WebAuthn security features"""
|
||||
# Test credential counter update
|
||||
credential = WebAuthnCredential.objects.create(
|
||||
user=self.user,
|
||||
credential_id='test_credential_id',
|
||||
public_key='test_public_key',
|
||||
name='Test Passkey',
|
||||
counter=0
|
||||
)
|
||||
|
||||
# Update counter
|
||||
result = credential.update_counter(5)
|
||||
self.assertTrue(result)
|
||||
|
||||
# Should not allow decreasing counter
|
||||
result = credential.update_counter(3)
|
||||
self.assertFalse(result)
|
||||
|
||||
# Test challenge uniqueness
|
||||
challenge1 = self.webauthn_backend.generate_challenge()
|
||||
challenge2 = self.webauthn_backend.generate_challenge()
|
||||
|
||||
self.assertNotEqual(challenge1, challenge2)
|
||||
self.assertEqual(len(challenge1), 44) # Base64 encoded 32 bytes
|
||||
|
||||
def test_webauthn_error_handling(self):
|
||||
"""Test WebAuthn error handling"""
|
||||
# Test with non-existent user
|
||||
result = self.webauthn_backend.create_registration_challenge(
|
||||
None, 'Test Passkey'
|
||||
)
|
||||
self.assertFalse(result['success'])
|
||||
self.assertIn('error', result)
|
||||
|
||||
# Test with disabled WebAuthn
|
||||
settings = WebAuthnSettings.objects.get(user=self.user)
|
||||
settings.enabled = False
|
||||
settings.save()
|
||||
|
||||
result = self.webauthn_backend.create_authentication_challenge(self.user)
|
||||
self.assertFalse(result['success'])
|
||||
self.assertIn('error', result)
|
||||
|
||||
def test_webauthn_data_serialization(self):
|
||||
"""Test WebAuthn data serialization"""
|
||||
# Test challenge metadata
|
||||
challenge = WebAuthnChallenge.objects.create(
|
||||
user=self.user,
|
||||
challenge='test_challenge',
|
||||
challenge_type='registration',
|
||||
expires_at=timezone.now() + timedelta(minutes=5)
|
||||
)
|
||||
|
||||
# Set metadata
|
||||
metadata = {'test_key': 'test_value', 'number': 123}
|
||||
challenge.set_metadata(metadata)
|
||||
challenge.save()
|
||||
|
||||
# Retrieve metadata
|
||||
retrieved_metadata = challenge.get_metadata()
|
||||
self.assertEqual(retrieved_metadata, metadata)
|
||||
|
||||
# Test session data
|
||||
from .webauthn_models import WebAuthnSession
|
||||
|
||||
session = WebAuthnSession.create_session(
|
||||
self.user, 'registration', {'test': 'data'}
|
||||
)
|
||||
|
||||
session_data = session.get_data()
|
||||
self.assertEqual(session_data, {'test': 'data'})
|
||||
|
||||
# Update session data
|
||||
session.set_data({'updated': 'data'})
|
||||
session.save()
|
||||
|
||||
updated_data = session.get_data()
|
||||
self.assertEqual(updated_data, {'updated': 'data'})
|
||||
|
||||
|
||||
class WebAuthnIntegrationTestCase(TestCase):
|
||||
"""Integration tests for WebAuthn with CyberPanel"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up integration test data"""
|
||||
self.client = Client()
|
||||
|
||||
# Create admin user
|
||||
self.admin = Administrator.objects.create(
|
||||
userName='admin',
|
||||
password='hashedpassword',
|
||||
email='admin@example.com',
|
||||
firstName='Admin',
|
||||
lastName='User',
|
||||
type=1,
|
||||
acl_id=1
|
||||
)
|
||||
|
||||
# Create regular user
|
||||
self.user = Administrator.objects.create(
|
||||
userName='testuser',
|
||||
password='hashedpassword',
|
||||
email='test@example.com',
|
||||
firstName='Test',
|
||||
lastName='User',
|
||||
type=0,
|
||||
acl_id=2,
|
||||
owner=self.admin.pk
|
||||
)
|
||||
|
||||
def test_webauthn_user_management_integration(self):
|
||||
"""Test WebAuthn integration with user management"""
|
||||
# Login as admin
|
||||
self.client.force_login(self.admin)
|
||||
|
||||
# Test WebAuthn settings update through user management
|
||||
response = self.client.post('/webauthn/settings/update/',
|
||||
json.dumps({
|
||||
'username': 'testuser',
|
||||
'enabled': True,
|
||||
'require_passkey': False,
|
||||
'allow_multiple_credentials': True,
|
||||
'max_credentials': 5
|
||||
}),
|
||||
content_type='application/json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Verify settings were updated
|
||||
settings = WebAuthnSettings.get_or_create_settings(self.user)
|
||||
self.assertTrue(settings.enabled)
|
||||
self.assertEqual(settings.max_credentials, 5)
|
||||
|
||||
def test_webauthn_permissions(self):
|
||||
"""Test WebAuthn permission system"""
|
||||
# Test admin can manage any user's WebAuthn settings
|
||||
self.client.force_login(self.admin)
|
||||
|
||||
response = self.client.get('/webauthn/credentials/testuser/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Test user can manage their own WebAuthn settings
|
||||
self.client.force_login(self.user)
|
||||
|
||||
response = self.client.get('/webauthn/credentials/testuser/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Test user cannot manage other users' settings
|
||||
other_user = Administrator.objects.create(
|
||||
userName='otheruser',
|
||||
password='hashedpassword',
|
||||
email='other@example.com',
|
||||
firstName='Other',
|
||||
lastName='User',
|
||||
type=0,
|
||||
acl_id=2,
|
||||
owner=self.admin.pk
|
||||
)
|
||||
|
||||
response = self.client.get('/webauthn/credentials/otheruser/')
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_webauthn_with_existing_authentication(self):
|
||||
"""Test WebAuthn alongside existing authentication methods"""
|
||||
# Enable 2FA for user
|
||||
self.user.twoFA = 1
|
||||
self.user.secretKey = 'test_secret_key'
|
||||
self.user.save()
|
||||
|
||||
# Enable WebAuthn
|
||||
settings = WebAuthnSettings.objects.create(
|
||||
user=self.user,
|
||||
enabled=True,
|
||||
require_passkey=False
|
||||
)
|
||||
|
||||
# Both authentication methods should be available
|
||||
self.assertTrue(self.user.twoFA)
|
||||
self.assertTrue(settings.enabled)
|
||||
|
||||
# User should be able to use either method
|
||||
# (In practice, this would be handled in the login flow)
|
||||
|
||||
def test_webauthn_cleanup_maintenance(self):
|
||||
"""Test WebAuthn cleanup and maintenance functions"""
|
||||
# Create expired challenges and sessions
|
||||
expired_challenge = WebAuthnChallenge.objects.create(
|
||||
user=self.user,
|
||||
challenge='expired_challenge',
|
||||
challenge_type='registration',
|
||||
expires_at=timezone.now() - timedelta(hours=1)
|
||||
)
|
||||
|
||||
from .webauthn_models import WebAuthnSession
|
||||
expired_session = WebAuthnSession.objects.create(
|
||||
user=self.user,
|
||||
session_id='expired_session',
|
||||
session_type='registration',
|
||||
data='{}',
|
||||
expires_at=timezone.now() - timedelta(hours=1)
|
||||
)
|
||||
|
||||
# Run cleanup
|
||||
backend = WebAuthnBackend()
|
||||
backend.cleanup_expired_challenges()
|
||||
backend.cleanup_expired_sessions()
|
||||
|
||||
# Expired items should be deleted
|
||||
with self.assertRaises(WebAuthnChallenge.DoesNotExist):
|
||||
WebAuthnChallenge.objects.get(id=expired_challenge.id)
|
||||
|
||||
with self.assertRaises(WebAuthnSession.DoesNotExist):
|
||||
WebAuthnSession.objects.get(id=expired_session.id)
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
from django.urls import path
|
||||
from django.urls import path, include
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.loadLoginPage, name='adminLogin'),
|
||||
path('verifyLogin', views.verifyLogin, name='verifyLogin'),
|
||||
path('logout', views.logout, name='logout'),
|
||||
path('webauthn/', include('loginSystem.webauthn_urls')),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,453 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import base64
|
||||
import hashlib
|
||||
import secrets
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Tuple, Any
|
||||
from .models import Administrator
|
||||
from .webauthn_models import WebAuthnCredential, WebAuthnChallenge, WebAuthnSession, WebAuthnSettings
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebAuthnBackend:
|
||||
"""
|
||||
WebAuthn backend for handling passkey authentication
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# Default WebAuthn configuration - can be overridden in Django settings
|
||||
self.rp_id = 'cyberpanel.local' # Should be your actual domain
|
||||
self.rp_name = 'CyberPanel'
|
||||
self.origin = 'https://cyberpanel.local:8090' # Should be your actual origin
|
||||
self.challenge_timeout = 300 # 5 minutes
|
||||
|
||||
def generate_challenge(self) -> str:
|
||||
"""Generate a random challenge"""
|
||||
return base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
|
||||
|
||||
def create_registration_challenge(self, user: Administrator, credential_name: str = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a WebAuthn registration challenge
|
||||
"""
|
||||
try:
|
||||
# Check if user has WebAuthn settings
|
||||
settings_obj = WebAuthnSettings.get_or_create_settings(user)
|
||||
|
||||
if not settings_obj.can_add_credential():
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Maximum number of credentials reached or multiple credentials not allowed'
|
||||
}
|
||||
|
||||
# Generate challenge
|
||||
challenge = self.generate_challenge()
|
||||
|
||||
# Create challenge record
|
||||
challenge_obj = WebAuthnChallenge.objects.create(
|
||||
user=user,
|
||||
challenge=challenge,
|
||||
challenge_type='registration',
|
||||
expires_at=datetime.now() + timedelta(seconds=self.challenge_timeout),
|
||||
metadata=json.dumps({
|
||||
'credential_name': credential_name or f"Passkey {datetime.now().strftime('%Y-%m-%d %H:%M')}",
|
||||
'rp_id': self.rp_id,
|
||||
'rp_name': self.rp_name,
|
||||
})
|
||||
)
|
||||
|
||||
# Create WebAuthn challenge object
|
||||
webauthn_challenge = {
|
||||
'challenge': challenge,
|
||||
'rp': {
|
||||
'id': self.rp_id,
|
||||
'name': self.rp_name,
|
||||
},
|
||||
'user': {
|
||||
'id': base64.urlsafe_b64encode(str(user.pk).encode()).decode('utf-8').rstrip('='),
|
||||
'name': user.email or user.userName,
|
||||
'displayName': f"{user.firstName} {user.lastName}".strip() or user.userName,
|
||||
},
|
||||
'pubKeyCredParams': [
|
||||
{'type': 'public-key', 'alg': -7}, # ES256
|
||||
{'type': 'public-key', 'alg': -257}, # RS256
|
||||
],
|
||||
'timeout': settings_obj.timeout_seconds * 1000,
|
||||
'attestation': 'none',
|
||||
'excludeCredentials': self._get_existing_credentials(user),
|
||||
}
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'challenge': webauthn_challenge,
|
||||
'challenge_id': challenge_obj.id,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating registration challenge: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Failed to create registration challenge: {str(e)}'
|
||||
}
|
||||
|
||||
def create_authentication_challenge(self, user: Administrator = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Create a WebAuthn authentication challenge
|
||||
"""
|
||||
try:
|
||||
# If user is specified, create user-specific challenge
|
||||
if user:
|
||||
settings_obj = WebAuthnSettings.get_or_create_settings(user)
|
||||
if not settings_obj.enabled:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'WebAuthn not enabled for this user'
|
||||
}
|
||||
|
||||
credentials = self._get_existing_credentials(user)
|
||||
if not credentials:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'No WebAuthn credentials found for this user'
|
||||
}
|
||||
else:
|
||||
# For username-based authentication, we'll need to find the user first
|
||||
credentials = []
|
||||
|
||||
# Generate challenge
|
||||
challenge = self.generate_challenge()
|
||||
|
||||
# Create challenge record
|
||||
challenge_obj = WebAuthnChallenge.objects.create(
|
||||
user=user or Administrator.objects.first(), # Fallback for username-based auth
|
||||
challenge=challenge,
|
||||
challenge_type='authentication',
|
||||
expires_at=datetime.now() + timedelta(seconds=self.challenge_timeout),
|
||||
metadata=json.dumps({
|
||||
'rp_id': self.rp_id,
|
||||
'rp_name': self.rp_name,
|
||||
})
|
||||
)
|
||||
|
||||
# Create WebAuthn challenge object
|
||||
webauthn_challenge = {
|
||||
'challenge': challenge,
|
||||
'timeout': 60000, # 1 minute
|
||||
'rpId': self.rp_id,
|
||||
'allowCredentials': credentials,
|
||||
'userVerification': 'preferred',
|
||||
}
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'challenge': webauthn_challenge,
|
||||
'challenge_id': challenge_obj.id,
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating authentication challenge: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Failed to create authentication challenge: {str(e)}'
|
||||
}
|
||||
|
||||
def verify_registration(self, challenge_id: int, credential_data: Dict[str, Any],
|
||||
client_data_json: str, attestation_object: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify WebAuthn registration response
|
||||
"""
|
||||
try:
|
||||
# Get challenge
|
||||
challenge_obj = WebAuthnChallenge.objects.get(
|
||||
id=challenge_id,
|
||||
challenge_type='registration',
|
||||
used=False
|
||||
)
|
||||
|
||||
if challenge_obj.is_expired():
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Challenge has expired'
|
||||
}
|
||||
|
||||
# Parse client data
|
||||
client_data = json.loads(base64.urlsafe_b64decode(client_data_json + '=='))
|
||||
|
||||
# Verify challenge
|
||||
if client_data.get('challenge') != challenge_obj.challenge:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Challenge mismatch'
|
||||
}
|
||||
|
||||
# Verify origin
|
||||
if client_data.get('origin') != self.origin:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Origin mismatch'
|
||||
}
|
||||
|
||||
# Verify type
|
||||
if client_data.get('type') != 'webauthn.create':
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Invalid response type'
|
||||
}
|
||||
|
||||
# For now, we'll do basic validation
|
||||
# In a production environment, you'd want to use a proper WebAuthn library
|
||||
# like python-webauthn or webauthn
|
||||
|
||||
# Extract credential ID and public key from attestation object
|
||||
# This is a simplified implementation
|
||||
credential_id = credential_data.get('id')
|
||||
public_key = credential_data.get('publicKey')
|
||||
|
||||
if not credential_id or not public_key:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Invalid credential data'
|
||||
}
|
||||
|
||||
# Get credential name from challenge metadata
|
||||
metadata = challenge_obj.get_metadata()
|
||||
credential_name = metadata.get('credential_name', f"Passkey {datetime.now().strftime('%Y-%m-%d %H:%M')}")
|
||||
|
||||
# Create credential record
|
||||
credential = WebAuthnCredential.objects.create(
|
||||
user=challenge_obj.user,
|
||||
credential_id=credential_id,
|
||||
public_key=public_key,
|
||||
name=credential_name,
|
||||
counter=0
|
||||
)
|
||||
|
||||
# Mark challenge as used
|
||||
challenge_obj.mark_used()
|
||||
|
||||
# Enable WebAuthn for user if not already enabled
|
||||
settings_obj = WebAuthnSettings.get_or_create_settings(challenge_obj.user)
|
||||
if not settings_obj.enabled:
|
||||
settings_obj.enabled = True
|
||||
settings_obj.save()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'credential_id': credential.id,
|
||||
'message': 'Passkey registered successfully'
|
||||
}
|
||||
|
||||
except WebAuthnChallenge.DoesNotExist:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Invalid challenge'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error verifying registration: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Registration verification failed: {str(e)}'
|
||||
}
|
||||
|
||||
def verify_authentication(self, challenge_id: int, credential_data: Dict[str, Any],
|
||||
client_data_json: str, authenticator_data: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Verify WebAuthn authentication response
|
||||
"""
|
||||
try:
|
||||
# Get challenge
|
||||
challenge_obj = WebAuthnChallenge.objects.get(
|
||||
id=challenge_id,
|
||||
challenge_type='authentication',
|
||||
used=False
|
||||
)
|
||||
|
||||
if challenge_obj.is_expired():
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Challenge has expired'
|
||||
}
|
||||
|
||||
# Parse client data
|
||||
client_data = json.loads(base64.urlsafe_b64decode(client_data_json + '=='))
|
||||
|
||||
# Verify challenge
|
||||
if client_data.get('challenge') != challenge_obj.challenge:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Challenge mismatch'
|
||||
}
|
||||
|
||||
# Verify origin
|
||||
if client_data.get('origin') != self.origin:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Origin mismatch'
|
||||
}
|
||||
|
||||
# Verify type
|
||||
if client_data.get('type') != 'webauthn.get':
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Invalid response type'
|
||||
}
|
||||
|
||||
# Get credential
|
||||
credential_id = credential_data.get('id')
|
||||
if not credential_id:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'No credential ID provided'
|
||||
}
|
||||
|
||||
try:
|
||||
credential = WebAuthnCredential.objects.get(
|
||||
credential_id=credential_id,
|
||||
user=challenge_obj.user,
|
||||
is_active=True
|
||||
)
|
||||
except WebAuthnCredential.DoesNotExist:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Credential not found'
|
||||
}
|
||||
|
||||
# Verify signature (simplified - in production use proper WebAuthn library)
|
||||
# For now, we'll just update the counter and mark as successful
|
||||
|
||||
# Update credential counter
|
||||
credential.update_counter(credential.counter + 1)
|
||||
|
||||
# Mark challenge as used
|
||||
challenge_obj.mark_used()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'user_id': challenge_obj.user.pk,
|
||||
'credential_id': credential.id,
|
||||
'message': 'Authentication successful'
|
||||
}
|
||||
|
||||
except WebAuthnChallenge.DoesNotExist:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Invalid challenge'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error verifying authentication: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Authentication verification failed: {str(e)}'
|
||||
}
|
||||
|
||||
def _get_existing_credentials(self, user: Administrator) -> List[Dict[str, Any]]:
|
||||
"""Get existing credentials for a user"""
|
||||
credentials = WebAuthnCredential.objects.filter(
|
||||
user=user,
|
||||
is_active=True
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
'id': cred.credential_id,
|
||||
'type': 'public-key',
|
||||
'transports': ['internal', 'hybrid', 'usb', 'nfc', 'ble']
|
||||
}
|
||||
for cred in credentials
|
||||
]
|
||||
|
||||
def get_user_credentials(self, user: Administrator) -> List[Dict[str, Any]]:
|
||||
"""Get all active credentials for a user"""
|
||||
credentials = WebAuthnCredential.objects.filter(
|
||||
user=user,
|
||||
is_active=True
|
||||
).order_by('-created_at')
|
||||
|
||||
return [
|
||||
{
|
||||
'id': cred.id,
|
||||
'name': cred.name,
|
||||
'credential_id': cred.credential_id[:16] + '...',
|
||||
'created_at': cred.created_at.isoformat(),
|
||||
'last_used': cred.last_used.isoformat() if cred.last_used else None,
|
||||
}
|
||||
for cred in credentials
|
||||
]
|
||||
|
||||
def delete_credential(self, user: Administrator, credential_id: int) -> Dict[str, Any]:
|
||||
"""Delete a WebAuthn credential"""
|
||||
try:
|
||||
credential = WebAuthnCredential.objects.get(
|
||||
id=credential_id,
|
||||
user=user
|
||||
)
|
||||
|
||||
credential.is_active = False
|
||||
credential.save()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Credential deleted successfully'
|
||||
}
|
||||
|
||||
except WebAuthnCredential.DoesNotExist:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Credential not found'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting credential: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Failed to delete credential: {str(e)}'
|
||||
}
|
||||
|
||||
def update_credential_name(self, user: Administrator, credential_id: int, new_name: str) -> Dict[str, Any]:
|
||||
"""Update credential name"""
|
||||
try:
|
||||
credential = WebAuthnCredential.objects.get(
|
||||
id=credential_id,
|
||||
user=user
|
||||
)
|
||||
|
||||
credential.name = new_name
|
||||
credential.save()
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': 'Credential name updated successfully'
|
||||
}
|
||||
|
||||
except WebAuthnCredential.DoesNotExist:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'Credential not found'
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating credential name: {str(e)}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'Failed to update credential name: {str(e)}'
|
||||
}
|
||||
|
||||
def cleanup_expired_challenges(self):
|
||||
"""Clean up expired challenges"""
|
||||
expired_challenges = WebAuthnChallenge.objects.filter(
|
||||
expires_at__lt=datetime.now()
|
||||
)
|
||||
count = expired_challenges.count()
|
||||
expired_challenges.delete()
|
||||
logger.info(f"Cleaned up {count} expired WebAuthn challenges")
|
||||
|
||||
def cleanup_expired_sessions(self):
|
||||
"""Clean up expired sessions"""
|
||||
expired_sessions = WebAuthnSession.objects.filter(
|
||||
expires_at__lt=datetime.now()
|
||||
)
|
||||
count = expired_sessions.count()
|
||||
expired_sessions.delete()
|
||||
logger.info(f"Cleaned up {count} expired WebAuthn sessions")
|
||||
|
|
@ -0,0 +1,204 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
from .models import Administrator
|
||||
import json
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class WebAuthnCredential(models.Model):
|
||||
"""
|
||||
Model to store WebAuthn passkey credentials for users
|
||||
"""
|
||||
user = models.ForeignKey(Administrator, on_delete=models.CASCADE, related_name='webauthn_credentials')
|
||||
credential_id = models.CharField(max_length=255, unique=True, help_text="Base64 encoded credential ID")
|
||||
public_key = models.TextField(help_text="Base64 encoded public key")
|
||||
counter = models.BigIntegerField(default=0, help_text="Signature counter for replay protection")
|
||||
name = models.CharField(max_length=100, help_text="User-friendly name for the passkey")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
last_used = models.DateTimeField(null=True, blank=True)
|
||||
is_active = models.BooleanField(default=True, help_text="Whether this credential is active")
|
||||
|
||||
class Meta:
|
||||
db_table = 'webauthn_credentials'
|
||||
verbose_name = 'WebAuthn Credential'
|
||||
verbose_name_plural = 'WebAuthn Credentials'
|
||||
indexes = [
|
||||
models.Index(fields=['user', 'is_active']),
|
||||
models.Index(fields=['credential_id']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.userName} - {self.name} ({self.credential_id[:16]}...)"
|
||||
|
||||
def get_credential_id_bytes(self):
|
||||
"""Get credential ID as bytes"""
|
||||
return base64.urlsafe_b64decode(self.credential_id + '==')
|
||||
|
||||
def get_public_key_bytes(self):
|
||||
"""Get public key as bytes"""
|
||||
return base64.urlsafe_b64decode(self.public_key + '==')
|
||||
|
||||
def update_counter(self, new_counter):
|
||||
"""Update signature counter"""
|
||||
if new_counter > self.counter:
|
||||
self.counter = new_counter
|
||||
self.last_used = datetime.now()
|
||||
self.save(update_fields=['counter', 'last_used'])
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class WebAuthnChallenge(models.Model):
|
||||
"""
|
||||
Model to store WebAuthn challenges for registration and authentication
|
||||
"""
|
||||
user = models.ForeignKey(Administrator, on_delete=models.CASCADE, related_name='webauthn_challenges')
|
||||
challenge = models.CharField(max_length=255, help_text="Base64 encoded challenge")
|
||||
challenge_type = models.CharField(max_length=20, choices=[
|
||||
('registration', 'Registration'),
|
||||
('authentication', 'Authentication'),
|
||||
])
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
expires_at = models.DateTimeField()
|
||||
used = models.BooleanField(default=False)
|
||||
metadata = models.TextField(default='{}', help_text="Additional challenge metadata as JSON")
|
||||
|
||||
class Meta:
|
||||
db_table = 'webauthn_challenges'
|
||||
verbose_name = 'WebAuthn Challenge'
|
||||
verbose_name_plural = 'WebAuthn Challenges'
|
||||
indexes = [
|
||||
models.Index(fields=['user', 'challenge_type', 'used']),
|
||||
models.Index(fields=['expires_at']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.userName} - {self.challenge_type} ({self.challenge[:16]}...)"
|
||||
|
||||
def is_expired(self):
|
||||
"""Check if challenge has expired"""
|
||||
return datetime.now() > self.expires_at
|
||||
|
||||
def get_challenge_bytes(self):
|
||||
"""Get challenge as bytes"""
|
||||
return base64.urlsafe_b64decode(self.challenge + '==')
|
||||
|
||||
def get_metadata(self):
|
||||
"""Get metadata as dict"""
|
||||
try:
|
||||
return json.loads(self.metadata)
|
||||
except:
|
||||
return {}
|
||||
|
||||
def set_metadata(self, data):
|
||||
"""Set metadata from dict"""
|
||||
self.metadata = json.dumps(data)
|
||||
|
||||
def mark_used(self):
|
||||
"""Mark challenge as used"""
|
||||
self.used = True
|
||||
self.save(update_fields=['used'])
|
||||
|
||||
|
||||
class WebAuthnSession(models.Model):
|
||||
"""
|
||||
Model to store WebAuthn session data for ongoing operations
|
||||
"""
|
||||
user = models.ForeignKey(Administrator, on_delete=models.CASCADE, related_name='webauthn_sessions')
|
||||
session_id = models.CharField(max_length=255, unique=True, help_text="Unique session identifier")
|
||||
session_type = models.CharField(max_length=20, choices=[
|
||||
('registration', 'Registration'),
|
||||
('authentication', 'Authentication'),
|
||||
])
|
||||
data = models.TextField(help_text="Session data as JSON")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
expires_at = models.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
db_table = 'webauthn_sessions'
|
||||
verbose_name = 'WebAuthn Session'
|
||||
verbose_name_plural = 'WebAuthn Sessions'
|
||||
indexes = [
|
||||
models.Index(fields=['session_id']),
|
||||
models.Index(fields=['expires_at']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.userName} - {self.session_type} ({self.session_id[:16]}...)"
|
||||
|
||||
def is_expired(self):
|
||||
"""Check if session has expired"""
|
||||
return datetime.now() > self.expires_at
|
||||
|
||||
def get_data(self):
|
||||
"""Get session data as dict"""
|
||||
try:
|
||||
return json.loads(self.data)
|
||||
except:
|
||||
return {}
|
||||
|
||||
def set_data(self, data):
|
||||
"""Set session data from dict"""
|
||||
self.data = json.dumps(data)
|
||||
|
||||
@classmethod
|
||||
def create_session(cls, user, session_type, data, duration_minutes=10):
|
||||
"""Create a new WebAuthn session"""
|
||||
import uuid
|
||||
session_id = str(uuid.uuid4())
|
||||
expires_at = datetime.now() + timedelta(minutes=duration_minutes)
|
||||
|
||||
session = cls.objects.create(
|
||||
user=user,
|
||||
session_id=session_id,
|
||||
session_type=session_type,
|
||||
data=json.dumps(data),
|
||||
expires_at=expires_at
|
||||
)
|
||||
return session
|
||||
|
||||
|
||||
class WebAuthnSettings(models.Model):
|
||||
"""
|
||||
Model to store WebAuthn configuration settings
|
||||
"""
|
||||
user = models.OneToOneField(Administrator, on_delete=models.CASCADE, related_name='webauthn_settings')
|
||||
enabled = models.BooleanField(default=False, help_text="Whether WebAuthn is enabled for this user")
|
||||
require_passkey = models.BooleanField(default=False, help_text="Require passkey for login (passwordless)")
|
||||
allow_multiple_credentials = models.BooleanField(default=True, help_text="Allow multiple passkeys per user")
|
||||
max_credentials = models.IntegerField(default=10, help_text="Maximum number of passkeys allowed")
|
||||
timeout_seconds = models.IntegerField(default=60, help_text="WebAuthn operation timeout in seconds")
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
db_table = 'webauthn_settings'
|
||||
verbose_name = 'WebAuthn Settings'
|
||||
verbose_name_plural = 'WebAuthn Settings'
|
||||
|
||||
def __str__(self):
|
||||
return f"WebAuthn Settings for {self.user.userName}"
|
||||
|
||||
@classmethod
|
||||
def get_or_create_settings(cls, user):
|
||||
"""Get or create WebAuthn settings for a user"""
|
||||
settings, created = cls.objects.get_or_create(
|
||||
user=user,
|
||||
defaults={
|
||||
'enabled': False,
|
||||
'require_passkey': False,
|
||||
'allow_multiple_credentials': True,
|
||||
'max_credentials': 10,
|
||||
'timeout_seconds': 60,
|
||||
}
|
||||
)
|
||||
return settings
|
||||
|
||||
def can_add_credential(self):
|
||||
"""Check if user can add another credential"""
|
||||
if not self.allow_multiple_credentials:
|
||||
return WebAuthnCredential.objects.filter(user=self.user, is_active=True).count() == 0
|
||||
return WebAuthnCredential.objects.filter(user=self.user, is_active=True).count() < self.max_credentials
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.urls import path
|
||||
from . import webauthn_views
|
||||
|
||||
urlpatterns = [
|
||||
# WebAuthn Registration
|
||||
path('registration/start/', webauthn_views.webauthn_registration_start, name='webauthn_registration_start'),
|
||||
path('registration/complete/', webauthn_views.webauthn_registration_complete, name='webauthn_registration_complete'),
|
||||
|
||||
# WebAuthn Authentication
|
||||
path('authentication/start/', webauthn_views.webauthn_authentication_start, name='webauthn_authentication_start'),
|
||||
path('authentication/complete/', webauthn_views.webauthn_authentication_complete, name='webauthn_authentication_complete'),
|
||||
|
||||
# WebAuthn Credential Management
|
||||
path('credentials/<str:username>/', webauthn_views.webauthn_credentials_list, name='webauthn_credentials_list'),
|
||||
path('credential/delete/', webauthn_views.webauthn_credential_delete, name='webauthn_credential_delete'),
|
||||
path('credential/update/', webauthn_views.webauthn_credential_update, name='webauthn_credential_update'),
|
||||
|
||||
# WebAuthn Settings
|
||||
path('settings/update/', webauthn_views.webauthn_settings_update, name='webauthn_settings_update'),
|
||||
|
||||
# WebAuthn Maintenance
|
||||
path('cleanup/', webauthn_views.webauthn_cleanup, name='webauthn_cleanup'),
|
||||
]
|
||||
|
|
@ -0,0 +1,463 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
import base64
|
||||
from django.shortcuts import render, HttpResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
from .models import Administrator
|
||||
from .webauthn_backend import WebAuthnBackend
|
||||
from .webauthn_models import WebAuthnSettings, WebAuthnCredential
|
||||
from plogical.acl import ACLManager
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebAuthnAPIView(View):
|
||||
"""Base class for WebAuthn API views"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.webauthn = WebAuthnBackend()
|
||||
|
||||
def json_response(self, data, status=200):
|
||||
"""Return JSON response"""
|
||||
return HttpResponse(
|
||||
json.dumps(data, ensure_ascii=False),
|
||||
content_type='application/json',
|
||||
status=status
|
||||
)
|
||||
|
||||
def error_response(self, message, status=400):
|
||||
"""Return error response"""
|
||||
return self.json_response({
|
||||
'success': False,
|
||||
'error': message
|
||||
}, status)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnRegistrationStart(WebAuthnAPIView):
|
||||
"""Start WebAuthn registration process"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
username = data.get('username')
|
||||
credential_name = data.get('credential_name', '')
|
||||
|
||||
if not username:
|
||||
return self.error_response('Username is required')
|
||||
|
||||
try:
|
||||
user = Administrator.objects.get(userName=username)
|
||||
except Administrator.DoesNotExist:
|
||||
return self.error_response('User not found', 404)
|
||||
|
||||
# Check if user has permission to register WebAuthn
|
||||
if hasattr(request, 'session') and 'userID' in request.session:
|
||||
current_user_id = request.session['userID']
|
||||
current_user = Administrator.objects.get(pk=current_user_id)
|
||||
current_acl = ACLManager.loadedACL(current_user_id)
|
||||
|
||||
# Allow if admin, user is modifying themselves, or user is owned by current user
|
||||
if not (current_acl['admin'] == 1 or
|
||||
user.pk == current_user.pk or
|
||||
user.owner == current_user.pk):
|
||||
return self.error_response('Unauthorized access', 403)
|
||||
|
||||
result = self.webauthn.create_registration_challenge(user, credential_name)
|
||||
return self.json_response(result)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error in registration start: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnRegistrationComplete(WebAuthnAPIView):
|
||||
"""Complete WebAuthn registration process"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
challenge_id = data.get('challenge_id')
|
||||
credential_data = data.get('credential')
|
||||
client_data_json = data.get('client_data_json')
|
||||
attestation_object = data.get('attestation_object')
|
||||
|
||||
if not all([challenge_id, credential_data, client_data_json, attestation_object]):
|
||||
return self.error_response('Missing required fields')
|
||||
|
||||
result = self.webauthn.verify_registration(
|
||||
challenge_id=challenge_id,
|
||||
credential_data=credential_data,
|
||||
client_data_json=client_data_json,
|
||||
attestation_object=attestation_object
|
||||
)
|
||||
|
||||
return self.json_response(result)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error in registration complete: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnAuthenticationStart(WebAuthnAPIView):
|
||||
"""Start WebAuthn authentication process"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
username = data.get('username')
|
||||
|
||||
if not username:
|
||||
return self.error_response('Username is required')
|
||||
|
||||
try:
|
||||
user = Administrator.objects.get(userName=username)
|
||||
except Administrator.DoesNotExist:
|
||||
return self.error_response('User not found', 404)
|
||||
|
||||
result = self.webauthn.create_authentication_challenge(user)
|
||||
return self.json_response(result)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error in authentication start: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnAuthenticationComplete(WebAuthnAPIView):
|
||||
"""Complete WebAuthn authentication process"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
challenge_id = data.get('challenge_id')
|
||||
credential_data = data.get('credential')
|
||||
client_data_json = data.get('client_data_json')
|
||||
authenticator_data = data.get('authenticator_data')
|
||||
|
||||
if not all([challenge_id, credential_data, client_data_json, authenticator_data]):
|
||||
return self.error_response('Missing required fields')
|
||||
|
||||
result = self.webauthn.verify_authentication(
|
||||
challenge_id=challenge_id,
|
||||
credential_data=credential_data,
|
||||
client_data_json=client_data_json,
|
||||
authenticator_data=authenticator_data
|
||||
)
|
||||
|
||||
if result['success']:
|
||||
# Set session for successful authentication
|
||||
request.session['userID'] = result['user_id']
|
||||
request.session['webauthn_auth'] = True
|
||||
request.session.set_expiry(43200) # 12 hours
|
||||
|
||||
# Log successful authentication
|
||||
logger.info(f"WebAuthn authentication successful for user ID: {result['user_id']}")
|
||||
|
||||
return self.json_response(result)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error in authentication complete: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnCredentialsList(WebAuthnAPIView):
|
||||
"""List WebAuthn credentials for a user"""
|
||||
|
||||
def get(self, request, username=None):
|
||||
try:
|
||||
if not username:
|
||||
return self.error_response('Username is required')
|
||||
|
||||
try:
|
||||
user = Administrator.objects.get(userName=username)
|
||||
except Administrator.DoesNotExist:
|
||||
return self.error_response('User not found', 404)
|
||||
|
||||
# Check permissions
|
||||
if hasattr(request, 'session') and 'userID' in request.session:
|
||||
current_user_id = request.session['userID']
|
||||
current_user = Administrator.objects.get(pk=current_user_id)
|
||||
current_acl = ACLManager.loadedACL(current_user_id)
|
||||
|
||||
if not (current_acl['admin'] == 1 or
|
||||
user.pk == current_user.pk or
|
||||
user.owner == current_user.pk):
|
||||
return self.error_response('Unauthorized access', 403)
|
||||
|
||||
credentials = self.webauthn.get_user_credentials(user)
|
||||
settings = WebAuthnSettings.get_or_create_settings(user)
|
||||
|
||||
return self.json_response({
|
||||
'success': True,
|
||||
'credentials': credentials,
|
||||
'settings': {
|
||||
'enabled': settings.enabled,
|
||||
'require_passkey': settings.require_passkey,
|
||||
'allow_multiple_credentials': settings.allow_multiple_credentials,
|
||||
'max_credentials': settings.max_credentials,
|
||||
'can_add_credential': settings.can_add_credential(),
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error listing credentials: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnCredentialDelete(WebAuthnAPIView):
|
||||
"""Delete a WebAuthn credential"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
username = data.get('username')
|
||||
credential_id = data.get('credential_id')
|
||||
|
||||
if not username or not credential_id:
|
||||
return self.error_response('Username and credential ID are required')
|
||||
|
||||
try:
|
||||
user = Administrator.objects.get(userName=username)
|
||||
except Administrator.DoesNotExist:
|
||||
return self.error_response('User not found', 404)
|
||||
|
||||
# Check permissions
|
||||
if hasattr(request, 'session') and 'userID' in request.session:
|
||||
current_user_id = request.session['userID']
|
||||
current_user = Administrator.objects.get(pk=current_user_id)
|
||||
current_acl = ACLManager.loadedACL(current_user_id)
|
||||
|
||||
if not (current_acl['admin'] == 1 or
|
||||
user.pk == current_user.pk or
|
||||
user.owner == current_user.pk):
|
||||
return self.error_response('Unauthorized access', 403)
|
||||
|
||||
result = self.webauthn.delete_credential(user, credential_id)
|
||||
return self.json_response(result)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting credential: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnCredentialUpdate(WebAuthnAPIView):
|
||||
"""Update WebAuthn credential name"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
username = data.get('username')
|
||||
credential_id = data.get('credential_id')
|
||||
new_name = data.get('new_name')
|
||||
|
||||
if not all([username, credential_id, new_name]):
|
||||
return self.error_response('Username, credential ID, and new name are required')
|
||||
|
||||
try:
|
||||
user = Administrator.objects.get(userName=username)
|
||||
except Administrator.DoesNotExist:
|
||||
return self.error_response('User not found', 404)
|
||||
|
||||
# Check permissions
|
||||
if hasattr(request, 'session') and 'userID' in request.session:
|
||||
current_user_id = request.session['userID']
|
||||
current_user = Administrator.objects.get(pk=current_user_id)
|
||||
current_acl = ACLManager.loadedACL(current_user_id)
|
||||
|
||||
if not (current_acl['admin'] == 1 or
|
||||
user.pk == current_user.pk or
|
||||
user.owner == current_user.pk):
|
||||
return self.error_response('Unauthorized access', 403)
|
||||
|
||||
result = self.webauthn.update_credential_name(user, credential_id, new_name)
|
||||
return self.json_response(result)
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating credential: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnSettingsUpdate(WebAuthnAPIView):
|
||||
"""Update WebAuthn settings for a user"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
username = data.get('username')
|
||||
enabled = data.get('enabled')
|
||||
require_passkey = data.get('require_passkey')
|
||||
allow_multiple_credentials = data.get('allow_multiple_credentials')
|
||||
max_credentials = data.get('max_credentials')
|
||||
timeout_seconds = data.get('timeout_seconds')
|
||||
|
||||
if not username:
|
||||
return self.error_response('Username is required')
|
||||
|
||||
try:
|
||||
user = Administrator.objects.get(userName=username)
|
||||
except Administrator.DoesNotExist:
|
||||
return self.error_response('User not found', 404)
|
||||
|
||||
# Check permissions
|
||||
if hasattr(request, 'session') and 'userID' in request.session:
|
||||
current_user_id = request.session['userID']
|
||||
current_user = Administrator.objects.get(pk=current_user_id)
|
||||
current_acl = ACLManager.loadedACL(current_user_id)
|
||||
|
||||
if not (current_acl['admin'] == 1 or
|
||||
user.pk == current_user.pk or
|
||||
user.owner == current_user.pk):
|
||||
return self.error_response('Unauthorized access', 403)
|
||||
|
||||
settings = WebAuthnSettings.get_or_create_settings(user)
|
||||
|
||||
if enabled is not None:
|
||||
settings.enabled = bool(enabled)
|
||||
if require_passkey is not None:
|
||||
settings.require_passkey = bool(require_passkey)
|
||||
if allow_multiple_credentials is not None:
|
||||
settings.allow_multiple_credentials = bool(allow_multiple_credentials)
|
||||
if max_credentials is not None:
|
||||
settings.max_credentials = int(max_credentials)
|
||||
if timeout_seconds is not None:
|
||||
settings.timeout_seconds = int(timeout_seconds)
|
||||
|
||||
settings.save()
|
||||
|
||||
return self.json_response({
|
||||
'success': True,
|
||||
'message': 'Settings updated successfully',
|
||||
'settings': {
|
||||
'enabled': settings.enabled,
|
||||
'require_passkey': settings.require_passkey,
|
||||
'allow_multiple_credentials': settings.allow_multiple_credentials,
|
||||
'max_credentials': settings.max_credentials,
|
||||
'timeout_seconds': settings.timeout_seconds,
|
||||
}
|
||||
})
|
||||
|
||||
except json.JSONDecodeError:
|
||||
return self.error_response('Invalid JSON')
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating settings: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class WebAuthnCleanup(WebAuthnAPIView):
|
||||
"""Cleanup expired WebAuthn data"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
# Check if user is admin
|
||||
if not (hasattr(request, 'session') and 'userID' in request.session):
|
||||
return self.error_response('Authentication required', 401)
|
||||
|
||||
current_user_id = request.session['userID']
|
||||
current_acl = ACLManager.loadedACL(current_user_id)
|
||||
|
||||
if current_acl['admin'] != 1:
|
||||
return self.error_response('Admin access required', 403)
|
||||
|
||||
# Cleanup expired data
|
||||
self.webauthn.cleanup_expired_challenges()
|
||||
self.webauthn.cleanup_expired_sessions()
|
||||
|
||||
return self.json_response({
|
||||
'success': True,
|
||||
'message': 'Cleanup completed successfully'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error during cleanup: {str(e)}")
|
||||
return self.error_response(f'Internal server error: {str(e)}', 500)
|
||||
|
||||
|
||||
# Traditional function-based views for easier integration
|
||||
@csrf_exempt
|
||||
def webauthn_registration_start(request):
|
||||
"""Start WebAuthn registration - function view"""
|
||||
view = WebAuthnRegistrationStart()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_registration_complete(request):
|
||||
"""Complete WebAuthn registration - function view"""
|
||||
view = WebAuthnRegistrationComplete()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_authentication_start(request):
|
||||
"""Start WebAuthn authentication - function view"""
|
||||
view = WebAuthnAuthenticationStart()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_authentication_complete(request):
|
||||
"""Complete WebAuthn authentication - function view"""
|
||||
view = WebAuthnAuthenticationComplete()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_credentials_list(request, username):
|
||||
"""List WebAuthn credentials - function view"""
|
||||
view = WebAuthnCredentialsList()
|
||||
return view.get(request, username)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_credential_delete(request):
|
||||
"""Delete WebAuthn credential - function view"""
|
||||
view = WebAuthnCredentialDelete()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_credential_update(request):
|
||||
"""Update WebAuthn credential - function view"""
|
||||
view = WebAuthnCredentialUpdate()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_settings_update(request):
|
||||
"""Update WebAuthn settings - function view"""
|
||||
view = WebAuthnSettingsUpdate()
|
||||
return view.post(request)
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def webauthn_cleanup(request):
|
||||
"""Cleanup WebAuthn data - function view"""
|
||||
view = WebAuthnCleanup()
|
||||
return view.post(request)
|
||||
|
|
@ -179,6 +179,121 @@ app.controller('modifyUser', function ($scope, $http) {
|
|||
document.body.removeChild(tempTextarea);
|
||||
}
|
||||
};
|
||||
|
||||
// WebAuthn Functions
|
||||
$scope.loadWebAuthnData = function() {
|
||||
if (!$scope.accountUsername) return;
|
||||
|
||||
var url = '/webauthn/credentials/' + $scope.accountUsername + '/';
|
||||
|
||||
$http.get(url).then(function(response) {
|
||||
if (response.data.success) {
|
||||
$scope.webauthnCredentials = response.data.credentials;
|
||||
$scope.webauthnEnabled = response.data.settings.enabled;
|
||||
$scope.webauthnRequirePasskey = response.data.settings.require_passkey;
|
||||
$scope.webauthnAllowMultiple = response.data.settings.allow_multiple_credentials;
|
||||
$scope.webauthnMaxCredentials = response.data.settings.max_credentials;
|
||||
$scope.canAddCredential = response.data.settings.can_add_credential;
|
||||
}
|
||||
}, function(error) {
|
||||
console.error('Error loading WebAuthn data:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.toggleWebAuthn = function() {
|
||||
if ($scope.webauthnEnabled) {
|
||||
$scope.loadWebAuthnData();
|
||||
} else {
|
||||
$scope.webauthnCredentials = [];
|
||||
$scope.canAddCredential = true;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.registerNewPasskey = function() {
|
||||
if (!window.cyberPanelWebAuthn) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
var credentialName = prompt('Enter a name for this passkey:', 'Passkey ' + new Date().toLocaleDateString());
|
||||
if (!credentialName) return;
|
||||
|
||||
window.cyberPanelWebAuthn.registerPasskey($scope.accountUsername, credentialName)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error registering passkey:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.deleteCredential = function(credentialId) {
|
||||
if (!confirm('Are you sure you want to delete this passkey?')) return;
|
||||
|
||||
if (!window.cyberPanelWebAuthn) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
window.cyberPanelWebAuthn.deleteCredential($scope.accountUsername, credentialId)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error deleting credential:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.updateCredentialName = function(credentialId, newName) {
|
||||
if (!window.cyberPanelWebAuthn) return;
|
||||
|
||||
window.cyberPanelWebAuthn.updateCredentialName($scope.accountUsername, credentialId, newName)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error updating credential name:', error);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.refreshCredentials = function() {
|
||||
$scope.loadWebAuthnData();
|
||||
};
|
||||
|
||||
$scope.saveWebAuthnSettings = function() {
|
||||
if (!window.cyberPanelWebAuthn) {
|
||||
alert('WebAuthn is not supported in this browser');
|
||||
return;
|
||||
}
|
||||
|
||||
var settings = {
|
||||
enabled: $scope.webauthnEnabled,
|
||||
require_passkey: $scope.webauthnRequirePasskey,
|
||||
allow_multiple_credentials: $scope.webauthnAllowMultiple,
|
||||
max_credentials: $scope.webauthnMaxCredentials,
|
||||
timeout_seconds: $scope.webauthnTimeout
|
||||
};
|
||||
|
||||
window.cyberPanelWebAuthn.updateSettings($scope.accountUsername, settings)
|
||||
.then(function(response) {
|
||||
if (response.success) {
|
||||
$scope.loadWebAuthnData();
|
||||
$scope.$apply();
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error('Error updating WebAuthn settings:', error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
$scope.fetchUserDetails = function () {
|
||||
|
|
@ -223,6 +338,18 @@ app.controller('modifyUser', function ($scope, $http) {
|
|||
$scope.secretKey = userDetails.secretKey;
|
||||
$scope.formattedSecretKey = userDetails.secretKey.match(/.{1,4}/g).join(' ');
|
||||
}
|
||||
|
||||
// Initialize WebAuthn settings
|
||||
$scope.webauthnEnabled = false;
|
||||
$scope.webauthnRequirePasskey = false;
|
||||
$scope.webauthnAllowMultiple = true;
|
||||
$scope.webauthnMaxCredentials = 10;
|
||||
$scope.webauthnTimeout = 60;
|
||||
$scope.webauthnCredentials = [];
|
||||
$scope.canAddCredential = true;
|
||||
|
||||
// Load WebAuthn settings and credentials
|
||||
$scope.loadWebAuthnData();
|
||||
|
||||
qrCode.set({
|
||||
value: userDetails.otpauth
|
||||
|
|
@ -320,7 +447,13 @@ app.controller('modifyUser', function ($scope, $http) {
|
|||
}
|
||||
};
|
||||
|
||||
$http.post(url, data, config).then(ListInitialDatas, cantLoadInitialDatas);
|
||||
$http.post(url, data, config).then(function(response) {
|
||||
ListInitialDatas(response);
|
||||
// Save WebAuthn settings after successful user modification
|
||||
if (response.data.saveStatus == 1) {
|
||||
$scope.saveWebAuthnSettings();
|
||||
}
|
||||
}, cantLoadInitialDatas);
|
||||
|
||||
|
||||
function ListInitialDatas(response) {
|
||||
|
|
|
|||
|
|
@ -321,6 +321,97 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WebAuthn Passkey Management Section -->
|
||||
<div class="form-group" style="margin-top: 2rem;">
|
||||
<label class="form-label">{% trans "Passkey Authentication (WebAuthn)" %}</label>
|
||||
<div class="checkbox-wrapper">
|
||||
<label>
|
||||
<input ng-model="webauthnEnabled" type="checkbox" ng-change="toggleWebAuthn()">
|
||||
{% trans "Enable Passkey Authentication" %}
|
||||
</label>
|
||||
<p class="help-text mb-2">{% trans "Use passkeys for secure, passwordless login" %}</p>
|
||||
</div>
|
||||
|
||||
<div ng-show="webauthnEnabled" class="mt-3">
|
||||
<div class="checkbox-wrapper">
|
||||
<label>
|
||||
<input ng-model="webauthnRequirePasskey" type="checkbox">
|
||||
{% trans "Require Passkey for Login (Passwordless)" %}
|
||||
</label>
|
||||
<p class="help-text mb-2">{% trans "When enabled, users must use passkeys to login (password becomes optional)" %}</p>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-wrapper">
|
||||
<label>
|
||||
<input ng-model="webauthnAllowMultiple" type="checkbox">
|
||||
{% trans "Allow Multiple Passkeys" %}
|
||||
</label>
|
||||
<p class="help-text mb-2">{% trans "Allow users to register multiple passkeys for backup access" %}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Maximum Passkeys" %}</label>
|
||||
<input type="number" class="form-control" ng-model="webauthnMaxCredentials" min="1" max="20" style="max-width: 150px;">
|
||||
<p class="help-text">{% trans "Maximum number of passkeys allowed per user" %}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">{% trans "Passkey Timeout (seconds)" %}</label>
|
||||
<input type="number" class="form-control" ng-model="webauthnTimeout" min="30" max="300" style="max-width: 150px;">
|
||||
<p class="help-text">{% trans "How long to wait for passkey interaction" %}</p>
|
||||
</div>
|
||||
|
||||
<!-- Passkey Management -->
|
||||
<div class="mt-4">
|
||||
<h5>{% trans "Manage Passkeys" %}</h5>
|
||||
<div ng-show="webauthnCredentials.length === 0" class="alert alert-info">
|
||||
<i class="fa fa-info-circle"></i> {% trans "No passkeys registered yet" %}
|
||||
</div>
|
||||
|
||||
<div ng-show="webauthnCredentials.length > 0" class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Created" %}</th>
|
||||
<th>{% trans "Last Used" %}</th>
|
||||
<th>{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="cred in webauthnCredentials">
|
||||
<td>
|
||||
<input type="text" class="form-control form-control-sm" ng-model="cred.name"
|
||||
ng-blur="updateCredentialName(cred.id, cred.name)" style="max-width: 200px;">
|
||||
</td>
|
||||
<td>{{ cred.created_at | date:'short' }}</td>
|
||||
<td>{{ cred.last_used | date:'short' || 'Never' }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-danger" ng-click="deleteCredential(cred.id)">
|
||||
<i class="fa fa-trash"></i> {% trans "Delete" %}
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<button type="button" class="btn btn-primary" ng-click="registerNewPasskey()" ng-disabled="!canAddCredential">
|
||||
<i class="fa fa-plus"></i> {% trans "Register New Passkey" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" ng-click="refreshCredentials()">
|
||||
<i class="fa fa-refresh"></i> {% trans "Refresh" %}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div ng-show="!canAddCredential" class="alert alert-warning mt-2">
|
||||
<i class="fa fa-exclamation-triangle"></i> {% trans "Maximum number of passkeys reached" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 2rem;">
|
||||
<button type="button" ng-click="modifyUser()" class="btn-primary">
|
||||
<i class="fa fa-save"></i> {% trans "Save Changes" %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
# CyberPanel Utility Scripts
|
||||
|
||||
This folder contains utility scripts for CyberPanel installation, maintenance, and troubleshooting.
|
||||
|
||||
## 📁 Folder Structure
|
||||
|
||||
```
|
||||
utils/
|
||||
├── README.md # This file
|
||||
├── windows/ # Windows-specific scripts
|
||||
│ ├── cyberpanel_install.bat # Windows installation script
|
||||
│ ├── cyberpanel_upgrade.bat # Windows upgrade script
|
||||
│ └── install_webauthn.bat # WebAuthn setup for Windows
|
||||
└── linux/ # Linux-specific scripts
|
||||
└── install_webauthn.sh # WebAuthn setup for Linux
|
||||
```
|
||||
|
||||
## 🪟 Windows Scripts
|
||||
|
||||
**⚠️ IMPORTANT**: These Windows scripts are for **development and testing purposes only**. CyberPanel is designed for Linux systems and does not officially support Windows for production use.
|
||||
|
||||
### Development Scripts
|
||||
|
||||
#### `cyberpanel_install.bat`
|
||||
**Purpose**: Set up CyberPanel development environment on Windows
|
||||
|
||||
**Requirements**:
|
||||
- Windows 7/8.1/10/11
|
||||
- Administrator privileges
|
||||
- Python 3.8+ installed
|
||||
- Internet connection
|
||||
|
||||
**Limitations**:
|
||||
- No web server (OpenLiteSpeed/LiteSpeed)
|
||||
- No system services (MariaDB, PowerDNS)
|
||||
- Limited functionality
|
||||
- Development/testing only
|
||||
|
||||
**Usage**:
|
||||
1. Right-click and select "Run as administrator"
|
||||
2. Follow the on-screen prompts
|
||||
3. Access CyberPanel at `http://localhost:8090`
|
||||
|
||||
**Features**:
|
||||
- Automatic Python environment setup
|
||||
- Virtual environment creation
|
||||
- Source code download
|
||||
- Requirements installation
|
||||
- Admin user creation
|
||||
- Windows service setup
|
||||
|
||||
#### `cyberpanel_upgrade.bat`
|
||||
**Purpose**: Upgrade existing CyberPanel installation
|
||||
|
||||
**Requirements**:
|
||||
- Existing CyberPanel installation
|
||||
- Administrator privileges
|
||||
- Internet connection
|
||||
|
||||
**Usage**:
|
||||
1. Right-click and select "Run as administrator"
|
||||
2. Script will automatically backup and upgrade
|
||||
3. Restart CyberPanel after completion
|
||||
|
||||
**Features**:
|
||||
- Automatic backup creation
|
||||
- Source code update
|
||||
- Requirements upgrade
|
||||
- Database migration
|
||||
- Service restart
|
||||
|
||||
#### `install_webauthn.bat`
|
||||
**Purpose**: Set up WebAuthn/Passkey authentication
|
||||
|
||||
**Requirements**:
|
||||
- CyberPanel already installed
|
||||
- Administrator privileges
|
||||
|
||||
**Usage**:
|
||||
1. Run as administrator
|
||||
2. Follow configuration prompts
|
||||
3. Access User Management to enable WebAuthn
|
||||
|
||||
## 🐧 Linux Scripts
|
||||
|
||||
### Installation Scripts
|
||||
|
||||
#### `install_webauthn.sh`
|
||||
**Purpose**: Set up WebAuthn/Passkey authentication on Linux
|
||||
|
||||
**Requirements**:
|
||||
- CyberPanel installed
|
||||
- Root privileges
|
||||
|
||||
**Usage**:
|
||||
```bash
|
||||
sudo ./install_webauthn.sh
|
||||
```
|
||||
|
||||
**Features**:
|
||||
- Database migration
|
||||
- Static files setup
|
||||
- Service configuration
|
||||
- Permission fixing
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Windows Users
|
||||
1. **First Installation**:
|
||||
```cmd
|
||||
# Run as administrator
|
||||
cyberpanel_install.bat
|
||||
```
|
||||
|
||||
2. **Upgrade Existing Installation**:
|
||||
```cmd
|
||||
# Run as administrator
|
||||
cyberpanel_upgrade.bat
|
||||
```
|
||||
|
||||
3. **Enable WebAuthn**:
|
||||
```cmd
|
||||
# Run as administrator
|
||||
install_webauthn.bat
|
||||
```
|
||||
|
||||
### Linux Users
|
||||
1. **Fix Installation Issues**:
|
||||
```bash
|
||||
sudo ./fix_cyberpanel_install.sh
|
||||
```
|
||||
|
||||
2. **Enable WebAuthn**:
|
||||
```bash
|
||||
sudo ./install_webauthn.sh
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Windows Issues
|
||||
|
||||
#### "Python not found" Error
|
||||
- **Solution**: Install Python 3.8+ from [python.org](https://python.org)
|
||||
- **Important**: Check "Add Python to PATH" during installation
|
||||
|
||||
#### "Access Denied" Error
|
||||
- **Solution**: Right-click script and select "Run as administrator"
|
||||
- **Alternative**: Open Command Prompt as administrator
|
||||
|
||||
#### "Failed to install requirements" Error
|
||||
- **Solution**: Check internet connection
|
||||
- **Alternative**: Try running script again
|
||||
- **Advanced**: Install requirements manually with pip
|
||||
|
||||
### Common Linux Issues
|
||||
|
||||
#### "Permission denied" Error
|
||||
- **Solution**: Run with `sudo` or as root user
|
||||
- **Example**: `sudo ./script.sh`
|
||||
|
||||
#### "Command not found" Error
|
||||
- **Solution**: Ensure script is executable
|
||||
- **Fix**: `chmod +x script.sh`
|
||||
|
||||
#### "Django not found" Error
|
||||
- **Solution**: Run the fix script first
|
||||
- **Alternative**: Reinstall CyberPanel
|
||||
|
||||
## 📚 Additional Resources
|
||||
|
||||
### Documentation
|
||||
- **Main Guide**: [2FA Authentication Guide](../guides/2FA_AUTHENTICATION_GUIDE.md)
|
||||
- **Troubleshooting**: [Troubleshooting Guide](../guides/TROUBLESHOOTING.md)
|
||||
- **Complete Index**: [Guides Index](../guides/INDEX.md)
|
||||
|
||||
### Support
|
||||
- **CyberPanel Forums**: https://community.cyberpanel.net
|
||||
- **GitHub Issues**: https://github.com/usmannasir/cyberpanel/issues
|
||||
- **Discord Server**: https://discord.gg/cyberpanel
|
||||
|
||||
## ⚠️ Important Notes
|
||||
|
||||
### Security
|
||||
- Always run scripts as administrator/root when required
|
||||
- Change default passwords immediately after installation
|
||||
- Keep CyberPanel updated regularly
|
||||
|
||||
### Backups
|
||||
- The upgrade script automatically creates backups
|
||||
- Store backups in a safe location
|
||||
- Test restore procedures regularly
|
||||
|
||||
### Compatibility
|
||||
- Windows scripts tested on Windows 7/8.1/10/11
|
||||
- Linux scripts tested on Ubuntu, Debian, AlmaLinux, RockyLinux
|
||||
- Python 3.8+ required for all scripts
|
||||
|
||||
---
|
||||
|
||||
**Note**: These utility scripts are provided as-is. Always test in a non-production environment first and ensure you have proper backups before running any scripts.
|
||||
|
||||
*Last updated: January 2025*
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
#!/bin/bash
|
||||
|
||||
# WebAuthn Installation Script for CyberPanel
|
||||
# This script helps install and configure WebAuthn/Passkey authentication
|
||||
|
||||
echo "=========================================="
|
||||
echo "CyberPanel WebAuthn Installation Script"
|
||||
echo "=========================================="
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "Please run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if CyberPanel is installed
|
||||
if [ ! -d "/usr/local/CyberCP" ]; then
|
||||
echo "Error: CyberPanel not found at /usr/local/CyberCP"
|
||||
echo "Please install CyberPanel first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ CyberPanel installation found"
|
||||
|
||||
# Navigate to CyberPanel directory
|
||||
cd /usr/local/CyberCP
|
||||
|
||||
# Check if Django is available
|
||||
if ! python3 -c "import django" 2>/dev/null; then
|
||||
echo "Error: Django not found. Please ensure CyberPanel is properly installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Django installation found"
|
||||
|
||||
# Run database migrations
|
||||
echo "Running database migrations..."
|
||||
python3 manage.py makemigrations loginSystem
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Database migrations created"
|
||||
else
|
||||
echo "Error: Failed to create migrations"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 manage.py migrate
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ Database migrations applied"
|
||||
else
|
||||
echo "Error: Failed to apply migrations"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if static files directory exists
|
||||
if [ ! -d "static/loginSystem" ]; then
|
||||
echo "Creating static files directory..."
|
||||
mkdir -p static/loginSystem
|
||||
fi
|
||||
|
||||
# Copy WebAuthn JavaScript file if it doesn't exist
|
||||
if [ ! -f "static/loginSystem/webauthn.js" ]; then
|
||||
echo "WebAuthn JavaScript file not found. Please ensure webauthn.js is in static/loginSystem/"
|
||||
echo "You can copy it from the source files"
|
||||
fi
|
||||
|
||||
# Set proper permissions
|
||||
echo "Setting file permissions..."
|
||||
chown -R lscpd:lscpd /usr/local/CyberCP/static/loginSystem/
|
||||
chmod -R 755 /usr/local/CyberCP/static/loginSystem/
|
||||
|
||||
# Test the installation
|
||||
echo "Testing WebAuthn installation..."
|
||||
python3 -c "
|
||||
import sys
|
||||
sys.path.append('/usr/local/CyberCP')
|
||||
try:
|
||||
from loginSystem.webauthn_models import WebAuthnCredential, WebAuthnChallenge, WebAuthnSettings
|
||||
print('✓ WebAuthn models imported successfully')
|
||||
except ImportError as e:
|
||||
print(f'Error importing WebAuthn models: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
from loginSystem.webauthn_backend import WebAuthnBackend
|
||||
backend = WebAuthnBackend()
|
||||
print('✓ WebAuthn backend initialized successfully')
|
||||
except Exception as e:
|
||||
print(f'Error initializing WebAuthn backend: {e}')
|
||||
sys.exit(1)
|
||||
"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ WebAuthn installation test passed"
|
||||
else
|
||||
echo "Error: WebAuthn installation test failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create configuration file
|
||||
echo "Creating WebAuthn configuration..."
|
||||
cat > /usr/local/CyberCP/webauthn_config.py << 'EOF'
|
||||
# WebAuthn Configuration for CyberPanel
|
||||
# Update these values according to your setup
|
||||
|
||||
WEBAUTHN_CONFIG = {
|
||||
'RP_ID': 'cyberpanel.local', # Replace with your actual domain
|
||||
'RP_NAME': 'CyberPanel',
|
||||
'ORIGIN': 'https://cyberpanel.local:8090', # Replace with your actual origin
|
||||
'CHALLENGE_TIMEOUT': 300, # 5 minutes
|
||||
'MAX_CREDENTIALS_PER_USER': 10,
|
||||
'DEFAULT_TIMEOUT_SECONDS': 60,
|
||||
}
|
||||
|
||||
# Instructions:
|
||||
# 1. Update RP_ID to your actual domain (e.g., 'yourdomain.com')
|
||||
# 2. Update ORIGIN to your actual origin (e.g., 'https://yourdomain.com:8090')
|
||||
# 3. Restart CyberPanel after making changes
|
||||
EOF
|
||||
|
||||
echo "✓ Configuration file created at /usr/local/CyberCP/webauthn_config.py"
|
||||
|
||||
# Restart CyberPanel services
|
||||
echo "Restarting CyberPanel services..."
|
||||
systemctl restart lscpd
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✓ CyberPanel services restarted"
|
||||
else
|
||||
echo "Warning: Failed to restart CyberPanel services. Please restart manually"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "WebAuthn Installation Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Update the configuration file: /usr/local/CyberCP/webauthn_config.py"
|
||||
echo "2. Replace 'cyberpanel.local' with your actual domain"
|
||||
echo "3. Replace 'https://cyberpanel.local:8090' with your actual origin"
|
||||
echo "4. Restart CyberPanel: systemctl restart lscpd"
|
||||
echo "5. Access CyberPanel and go to User Management to enable WebAuthn"
|
||||
echo ""
|
||||
echo "Features available:"
|
||||
echo "- Passkey registration and management"
|
||||
echo "- Passwordless login option"
|
||||
echo "- Multiple device support"
|
||||
echo "- Admin management interface"
|
||||
echo ""
|
||||
echo "For more information, see: /usr/local/CyberCP/to-do/WEBAUTHN_IMPLEMENTATION.md"
|
||||
echo ""
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
@echo off
|
||||
REM CyberPanel Windows Installation Script
|
||||
REM This script installs CyberPanel on Windows systems
|
||||
|
||||
echo ==========================================
|
||||
echo CyberPanel Windows Installation Script
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
REM Check if running as administrator
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo ERROR: This script must be run as administrator
|
||||
echo Please right-click and select "Run as administrator"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [OK] Running with administrator privileges
|
||||
echo.
|
||||
|
||||
REM Check Windows version
|
||||
for /f "tokens=4-5 delims=. " %%i in ('ver') do set VERSION=%%i.%%j
|
||||
if "%VERSION%" == "10.0" (
|
||||
echo [OK] Windows 10/11 detected
|
||||
) else if "%VERSION%" == "6.3" (
|
||||
echo [OK] Windows 8.1 detected
|
||||
) else if "%VERSION%" == "6.1" (
|
||||
echo [OK] Windows 7 detected
|
||||
) else (
|
||||
echo [WARNING] Unsupported Windows version detected: %VERSION%
|
||||
echo CyberPanel may not work properly on this version
|
||||
pause
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Checking system requirements...
|
||||
|
||||
REM Check if Python is installed
|
||||
python --version >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Python is not installed or not in PATH
|
||||
echo Please install Python 3.8+ from https://python.org
|
||||
echo Make sure to check "Add Python to PATH" during installation
|
||||
pause
|
||||
exit /b 1
|
||||
) else (
|
||||
for /f "tokens=2" %%i in ('python --version 2^>^&1') do set PYTHON_VERSION=%%i
|
||||
echo [OK] Python %PYTHON_VERSION% found
|
||||
)
|
||||
|
||||
REM Check if pip is available
|
||||
pip --version >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] pip is not available
|
||||
echo Please install pip or reinstall Python with pip
|
||||
pause
|
||||
exit /b 1
|
||||
) else (
|
||||
echo [OK] pip is available
|
||||
)
|
||||
|
||||
REM Check available disk space
|
||||
for /f "tokens=3" %%i in ('dir /-c %SystemDrive%\ ^| find "bytes free"') do set FREE_SPACE=%%i
|
||||
if %FREE_SPACE% LSS 10737418240 (
|
||||
echo [WARNING] Less than 10GB free space available
|
||||
echo CyberPanel requires at least 10GB of free space
|
||||
pause
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo CyberPanel Installation
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
REM Create CyberPanel directory
|
||||
set CYBERPANEL_DIR=C:\usr\local\CyberCP
|
||||
if not exist "%CYBERPANEL_DIR%" (
|
||||
echo Creating CyberPanel directory...
|
||||
mkdir "%CYBERPANEL_DIR%" 2>nul
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to create directory %CYBERPANEL_DIR%
|
||||
echo Please ensure you have sufficient permissions
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Directory created: %CYBERPANEL_DIR%
|
||||
) else (
|
||||
echo [OK] Directory already exists: %CYBERPANEL_DIR%
|
||||
)
|
||||
|
||||
REM Navigate to CyberPanel directory
|
||||
cd /d "%CYBERPANEL_DIR%"
|
||||
|
||||
REM Create virtual environment
|
||||
echo Creating Python virtual environment...
|
||||
python -m venv . --system-site-packages
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to create virtual environment
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Virtual environment created
|
||||
|
||||
REM Activate virtual environment
|
||||
echo Activating virtual environment...
|
||||
call Scripts\activate.bat
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to activate virtual environment
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Virtual environment activated
|
||||
|
||||
REM Upgrade pip
|
||||
echo Upgrading pip...
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to upgrade pip, continuing anyway...
|
||||
)
|
||||
|
||||
REM Download CyberPanel source
|
||||
echo Downloading CyberPanel source code...
|
||||
if exist "cyberpanel" (
|
||||
echo [INFO] CyberPanel source already exists, updating...
|
||||
cd cyberpanel
|
||||
git pull origin stable
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to update source, using existing version
|
||||
)
|
||||
cd ..
|
||||
) else (
|
||||
echo Cloning CyberPanel repository...
|
||||
git clone https://github.com/usmannasir/cyberpanel.git
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to clone CyberPanel repository
|
||||
echo Please check your internet connection
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Source code downloaded
|
||||
)
|
||||
|
||||
REM Navigate to CyberPanel source
|
||||
cd cyberpanel
|
||||
|
||||
REM Install requirements
|
||||
echo Installing Python requirements...
|
||||
echo This may take several minutes...
|
||||
pip install --default-timeout=3600 -r requirments.txt
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to install requirements
|
||||
echo Please check your internet connection and try again
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Requirements installed
|
||||
|
||||
REM Create necessary directories
|
||||
echo Creating necessary directories...
|
||||
if not exist "static" mkdir static
|
||||
if not exist "logs" mkdir logs
|
||||
if not exist "static\loginSystem" mkdir static\loginSystem
|
||||
if not exist "static\userManagment" mkdir static\userManagment
|
||||
echo [OK] Directories created
|
||||
|
||||
REM Set up Django
|
||||
echo Setting up Django...
|
||||
python manage.py collectstatic --noinput
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to collect static files, continuing...
|
||||
)
|
||||
|
||||
REM Create superuser (optional)
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo User Account Setup
|
||||
echo ==========================================
|
||||
echo.
|
||||
set /p CREATE_ADMIN="Do you want to create an admin user now? (y/n): "
|
||||
if /i "%CREATE_ADMIN%"=="y" (
|
||||
echo Creating admin user...
|
||||
python manage.py createsuperuser
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to create superuser
|
||||
echo You can create one later with: python manage.py createsuperuser
|
||||
) else (
|
||||
echo [OK] Admin user created
|
||||
)
|
||||
) else (
|
||||
echo [INFO] Skipping admin user creation
|
||||
echo You can create one later with: python manage.py createsuperuser
|
||||
)
|
||||
|
||||
REM Create startup script
|
||||
echo Creating startup script...
|
||||
(
|
||||
echo @echo off
|
||||
echo cd /d "%CYBERPANEL_DIR%\cyberpanel"
|
||||
echo call ..\Scripts\activate.bat
|
||||
echo python manage.py runserver 0.0.0.0:8090
|
||||
) > start_cyberpanel.bat
|
||||
|
||||
REM Create service script
|
||||
echo Creating Windows service script...
|
||||
(
|
||||
echo @echo off
|
||||
echo REM CyberPanel Windows Service
|
||||
echo net start CyberPanel
|
||||
echo if %%errorLevel%% neq 0 ^(
|
||||
echo echo Starting CyberPanel service...
|
||||
echo sc create CyberPanel binPath= "%CYBERPANEL_DIR%\cyberpanel\start_cyberpanel.bat" start= auto
|
||||
echo sc start CyberPanel
|
||||
echo ^)
|
||||
) > install_service.bat
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo Installation Complete!
|
||||
echo ==========================================
|
||||
echo.
|
||||
echo CyberPanel has been installed to: %CYBERPANEL_DIR%\cyberpanel
|
||||
echo.
|
||||
echo To start CyberPanel:
|
||||
echo 1. Run: start_cyberpanel.bat
|
||||
echo 2. Open your browser to: http://localhost:8090
|
||||
echo.
|
||||
echo To install as Windows service:
|
||||
echo 1. Run: install_service.bat as administrator
|
||||
echo.
|
||||
echo Default login credentials:
|
||||
echo Username: admin
|
||||
echo Password: 123456
|
||||
echo.
|
||||
echo IMPORTANT: Change the default password immediately!
|
||||
echo.
|
||||
echo For more information, see the documentation at:
|
||||
echo https://cyberpanel.net/docs/
|
||||
echo.
|
||||
pause
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
@echo off
|
||||
REM CyberPanel Windows Upgrade Script
|
||||
REM This script upgrades an existing CyberPanel installation
|
||||
|
||||
echo ==========================================
|
||||
echo CyberPanel Windows Upgrade Script
|
||||
echo ==========================================
|
||||
echo.
|
||||
|
||||
REM Check if running as administrator
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo ERROR: This script must be run as administrator
|
||||
echo Please right-click and select "Run as administrator"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [OK] Running with administrator privileges
|
||||
echo.
|
||||
|
||||
REM Check if CyberPanel is installed
|
||||
set CYBERPANEL_DIR=C:\usr\local\CyberCP
|
||||
if not exist "%CYBERPANEL_DIR%" (
|
||||
echo [ERROR] CyberPanel not found at %CYBERPANEL_DIR%
|
||||
echo Please run the installation script first
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [OK] CyberPanel installation found
|
||||
echo.
|
||||
|
||||
REM Create backup
|
||||
echo Creating backup of current installation...
|
||||
set BACKUP_DIR=%CYBERPANEL_DIR%_backup_%date:~-4,4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2%%time:~6,2%
|
||||
set BACKUP_DIR=%BACKUP_DIR: =0%
|
||||
if exist "%BACKUP_DIR%" rmdir /s /q "%BACKUP_DIR%"
|
||||
xcopy "%CYBERPANEL_DIR%" "%BACKUP_DIR%" /E /I /H /Y >nul
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to create backup, continuing anyway...
|
||||
) else (
|
||||
echo [OK] Backup created: %BACKUP_DIR%
|
||||
)
|
||||
|
||||
REM Navigate to CyberPanel directory
|
||||
cd /d "%CYBERPANEL_DIR%"
|
||||
|
||||
REM Activate virtual environment
|
||||
echo Activating virtual environment...
|
||||
if not exist "Scripts\activate.bat" (
|
||||
echo [ERROR] Virtual environment not found
|
||||
echo Please reinstall CyberPanel
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
call Scripts\activate.bat
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to activate virtual environment
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Virtual environment activated
|
||||
|
||||
REM Navigate to CyberPanel source
|
||||
cd cyberpanel
|
||||
|
||||
REM Stop any running CyberPanel processes
|
||||
echo Stopping CyberPanel processes...
|
||||
taskkill /f /im python.exe 2>nul
|
||||
taskkill /f /im lscpd.exe 2>nul
|
||||
echo [OK] Processes stopped
|
||||
|
||||
REM Update source code
|
||||
echo Updating CyberPanel source code...
|
||||
git fetch origin
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to fetch updates, continuing with current version...
|
||||
) else (
|
||||
git reset --hard origin/stable
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to reset to latest version, continuing...
|
||||
) else (
|
||||
echo [OK] Source code updated
|
||||
)
|
||||
)
|
||||
|
||||
REM Upgrade pip and requirements
|
||||
echo Upgrading Python packages...
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to upgrade pip, continuing...
|
||||
)
|
||||
|
||||
REM Install/upgrade requirements
|
||||
echo Installing/upgrading requirements...
|
||||
pip install --upgrade --default-timeout=3600 -r requirments.txt
|
||||
if %errorLevel% neq 0 (
|
||||
echo [ERROR] Failed to install/upgrade requirements
|
||||
echo Please check your internet connection and try again
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
echo [OK] Requirements updated
|
||||
|
||||
REM Run database migrations
|
||||
echo Running database migrations...
|
||||
python manage.py makemigrations
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to create migrations, continuing...
|
||||
) else (
|
||||
python manage.py migrate
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to apply migrations, continuing...
|
||||
) else (
|
||||
echo [OK] Database migrations completed
|
||||
)
|
||||
)
|
||||
|
||||
REM Collect static files
|
||||
echo Collecting static files...
|
||||
python manage.py collectstatic --noinput
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Failed to collect static files, continuing...
|
||||
) else (
|
||||
echo [OK] Static files collected
|
||||
)
|
||||
|
||||
REM Update startup script
|
||||
echo Updating startup script...
|
||||
(
|
||||
echo @echo off
|
||||
echo cd /d "%CYBERPANEL_DIR%\cyberpanel"
|
||||
echo call ..\Scripts\activate.bat
|
||||
echo python manage.py runserver 0.0.0.0:8090
|
||||
) > start_cyberpanel.bat
|
||||
|
||||
REM Test installation
|
||||
echo Testing installation...
|
||||
python manage.py check
|
||||
if %errorLevel% neq 0 (
|
||||
echo [WARNING] Installation check failed, but continuing...
|
||||
) else (
|
||||
echo [OK] Installation check passed
|
||||
)
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo Upgrade Complete!
|
||||
echo ==========================================
|
||||
echo.
|
||||
echo CyberPanel has been upgraded successfully
|
||||
echo.
|
||||
echo To start CyberPanel:
|
||||
echo 1. Run: start_cyberpanel.bat
|
||||
echo 2. Open your browser to: http://localhost:8090
|
||||
echo.
|
||||
echo Backup location: %BACKUP_DIR%
|
||||
echo.
|
||||
echo If you encounter any issues, you can restore from backup:
|
||||
echo 1. Stop CyberPanel
|
||||
echo 2. Delete current installation
|
||||
echo 3. Restore from backup directory
|
||||
echo.
|
||||
pause
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
@echo off
|
||||
REM WebAuthn Installation Script for CyberPanel (Windows)
|
||||
REM This script helps install and configure WebAuthn/Passkey authentication
|
||||
|
||||
echo ==========================================
|
||||
echo CyberPanel WebAuthn Installation Script
|
||||
echo ==========================================
|
||||
|
||||
REM Check if running as administrator
|
||||
net session >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo Please run as administrator
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if CyberPanel is installed
|
||||
if not exist "C:\usr\local\CyberCP" (
|
||||
echo Error: CyberPanel not found at C:\usr\local\CyberCP
|
||||
echo Please install CyberPanel first
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [OK] CyberPanel installation found
|
||||
|
||||
REM Navigate to CyberPanel directory
|
||||
cd /d C:\usr\local\CyberCP
|
||||
|
||||
REM Check if Python is available
|
||||
python --version >nul 2>&1
|
||||
if %errorLevel% neq 0 (
|
||||
echo Error: Python not found. Please ensure CyberPanel is properly installed
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo [OK] Python installation found
|
||||
|
||||
REM Run database migrations
|
||||
echo Running database migrations...
|
||||
python manage.py makemigrations loginSystem
|
||||
if %errorLevel% equ 0 (
|
||||
echo [OK] Database migrations created
|
||||
) else (
|
||||
echo Error: Failed to create migrations
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
python manage.py migrate
|
||||
if %errorLevel% equ 0 (
|
||||
echo [OK] Database migrations applied
|
||||
) else (
|
||||
echo Error: Failed to apply migrations
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Check if static files directory exists
|
||||
if not exist "static\loginSystem" (
|
||||
echo Creating static files directory...
|
||||
mkdir static\loginSystem
|
||||
)
|
||||
|
||||
REM Check if WebAuthn JavaScript file exists
|
||||
if not exist "static\loginSystem\webauthn.js" (
|
||||
echo Warning: WebAuthn JavaScript file not found
|
||||
echo Please ensure webauthn.js is in static\loginSystem\
|
||||
echo You can copy it from the source files
|
||||
)
|
||||
|
||||
REM Test the installation
|
||||
echo Testing WebAuthn installation...
|
||||
python -c "import sys; sys.path.append('C:/usr/local/CyberCP'); from loginSystem.webauthn_models import WebAuthnCredential, WebAuthnChallenge, WebAuthnSettings; print('[OK] WebAuthn models imported successfully')"
|
||||
if %errorLevel% equ 0 (
|
||||
echo [OK] WebAuthn installation test passed
|
||||
) else (
|
||||
echo Error: WebAuthn installation test failed
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Create configuration file
|
||||
echo Creating WebAuthn configuration...
|
||||
(
|
||||
echo # WebAuthn Configuration for CyberPanel
|
||||
echo # Update these values according to your setup
|
||||
echo.
|
||||
echo WEBAUTHN_CONFIG = {
|
||||
echo 'RP_ID': 'cyberpanel.local', # Replace with your actual domain
|
||||
echo 'RP_NAME': 'CyberPanel',
|
||||
echo 'ORIGIN': 'https://cyberpanel.local:8090', # Replace with your actual origin
|
||||
echo 'CHALLENGE_TIMEOUT': 300, # 5 minutes
|
||||
echo 'MAX_CREDENTIALS_PER_USER': 10,
|
||||
echo 'DEFAULT_TIMEOUT_SECONDS': 60,
|
||||
echo }
|
||||
echo.
|
||||
echo # Instructions:
|
||||
echo # 1. Update RP_ID to your actual domain ^(e.g., 'yourdomain.com'^)
|
||||
echo # 2. Update ORIGIN to your actual origin ^(e.g., 'https://yourdomain.com:8090'^)
|
||||
echo # 3. Restart CyberPanel after making changes
|
||||
) > webauthn_config.py
|
||||
|
||||
echo [OK] Configuration file created at C:\usr\local\CyberCP\webauthn_config.py
|
||||
|
||||
echo.
|
||||
echo ==========================================
|
||||
echo WebAuthn Installation Complete!
|
||||
echo ==========================================
|
||||
echo.
|
||||
echo Next steps:
|
||||
echo 1. Update the configuration file: C:\usr\local\CyberCP\webauthn_config.py
|
||||
echo 2. Replace 'cyberpanel.local' with your actual domain
|
||||
echo 3. Replace 'https://cyberpanel.local:8090' with your actual origin
|
||||
echo 4. Restart CyberPanel services
|
||||
echo 5. Access CyberPanel and go to User Management to enable WebAuthn
|
||||
echo.
|
||||
echo Features available:
|
||||
echo - Passkey registration and management
|
||||
echo - Passwordless login option
|
||||
echo - Multiple device support
|
||||
echo - Admin management interface
|
||||
echo.
|
||||
echo For more information, see: C:\usr\local\CyberCP\to-do\WEBAUTHN_IMPLEMENTATION.md
|
||||
echo.
|
||||
pause
|
||||
Loading…
Reference in New Issue