Add authentication system - User model, JWT service, auth routes

This commit is contained in:
oguz ozturk 2026-01-10 16:07:57 +03:00
parent 6fa9e5c43b
commit 87611a284b
8 changed files with 959 additions and 2 deletions

201
ARCHITECTURE.md Normal file
View File

@ -0,0 +1,201 @@
# 🏗️ Hosting Platform - Modular Architecture
## 📋 Project Overview
Professional WordPress hosting platform with container infrastructure and automation.
---
## 🎯 Architecture Principles
1. **Modularity** - Each feature is a separate module
2. **Scalability** - Easy to add new features without breaking existing code
3. **Separation of Concerns** - Clear boundaries between layers
4. **Security First** - Authentication, encryption, and access control
5. **Clean Code** - Readable, maintainable, and well-documented
---
## 🏛️ System Architecture
### Frontend Architecture
```
frontend/
├── customer-portal/ # argeict.net
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Landing.jsx # Register/Login page
│ │ │ ├── Dashboard.jsx # Customer dashboard
│ │ │ ├── DNS/ # DNS module
│ │ │ ├── Container/ # Container module
│ │ │ ├── Network/ # Network module
│ │ │ └── Security/ # Security module
│ │ ├── components/
│ │ │ ├── auth/ # Auth components
│ │ │ ├── layout/ # Layout components
│ │ │ └── shared/ # Shared components
│ │ ├── services/
│ │ │ ├── api.js # API client
│ │ │ ├── auth.js # Auth service
│ │ │ └── storage.js # Local storage
│ │ ├── hooks/ # Custom React hooks
│ │ ├── context/ # React context
│ │ └── utils/ # Utility functions
│ └── public/
├── admin-portal/ # adminpanel.argeict.net
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Login.jsx # Admin login
│ │ │ ├── Dashboard.jsx # Admin dashboard
│ │ │ ├── CFAccounts/ # CF management
│ │ │ ├── Customers/ # Customer management
│ │ │ └── Settings/ # System settings
│ │ ├── components/
│ │ ├── services/
│ │ └── utils/
│ └── public/
└── shared/ # Shared code between portals
├── components/
├── utils/
└── styles/
```
### Backend Architecture
```
backend/
├── app/
│ ├── __init__.py
│ ├── main.py # Application entry point
│ ├── config.py # Configuration
│ │
│ ├── models/ # Database models
│ │ ├── __init__.py
│ │ ├── user.py # User/Customer model
│ │ ├── domain.py # Domain model
│ │ ├── cloudflare.py # CF Account model
│ │ ├── container.py # Container model
│ │ └── base.py # Base model
│ │
│ ├── routes/ # API routes (blueprints)
│ │ ├── __init__.py
│ │ ├── auth.py # Authentication routes
│ │ ├── customer.py # Customer routes
│ │ ├── dns.py # DNS routes
│ │ ├── container.py # Container routes
│ │ ├── admin.py # Admin routes
│ │ └── webhook.py # Webhook routes
│ │
│ ├── services/ # Business logic
│ │ ├── __init__.py
│ │ ├── auth_service.py # Authentication logic
│ │ ├── cloudflare_service.py # Cloudflare API
│ │ ├── nameserver_service.py # DNS/NS logic
│ │ ├── container_service.py # Container management
│ │ └── email_service.py # Email notifications
│ │
│ ├── middleware/ # Middleware
│ │ ├── __init__.py
│ │ ├── auth.py # Auth middleware
│ │ ├── rate_limit.py # Rate limiting
│ │ └── cors.py # CORS configuration
│ │
│ ├── utils/ # Utilities
│ │ ├── __init__.py
│ │ ├── encryption.py # Encryption helpers
│ │ ├── validators.py # Input validation
│ │ └── helpers.py # General helpers
│ │
│ └── migrations/ # Database migrations
├── tests/ # Tests
│ ├── unit/
│ ├── integration/
│ └── e2e/
└── requirements.txt
```
---
## 🗄️ Database Schema
### Users Table
- id, email, password_hash, full_name
- is_active, is_verified, created_at, updated_at
- role (customer, admin)
### Customers Table (extends Users)
- user_id (FK), company_name, phone
- billing_info, subscription_plan
### CloudflareAccounts Table
- id, name, email, api_token_encrypted
- is_active, max_domains, current_domain_count
### Domains Table
- id, domain_name, customer_id (FK)
- cf_account_id (FK), cf_zone_id
- status, created_at
### Containers Table (Future)
- id, customer_id (FK), domain_id (FK)
- container_id, status, resources
---
## 🎨 Design System
### Brand Colors (from logo)
- Primary: #0066CC (Blue)
- Secondary: #00A3E0 (Light Blue)
- Accent: #FF6B35 (Orange)
- Dark: #1A1A1A
- Light: #F5F5F5
### Typography
- Headings: Inter, sans-serif
- Body: Inter, sans-serif
---
## 🔐 Security
1. **Authentication**: JWT tokens
2. **Password**: bcrypt hashing
3. **API Tokens**: Fernet encryption
4. **HTTPS**: All communications
5. **Rate Limiting**: Per endpoint
6. **CORS**: Configured per domain
---
## 📦 Technology Stack
### Frontend
- React 18 + Vite
- React Router (multi-app routing)
- TailwindCSS
- Axios
- React Query (data fetching)
### Backend
- Flask 3.0
- SQLAlchemy 2.0
- PostgreSQL 16
- Redis 7.0
- JWT authentication
### DevOps
- Docker (containers)
- Nginx (reverse proxy)
- Supervisor (process management)
- Gitea (version control)
---
**Next Steps**: Implementation in phases

176
IMPLEMENTATION_PLAN.md Normal file
View File

@ -0,0 +1,176 @@
# 🚀 Implementation Plan - Hosting Platform
## Phase 1: Foundation & Authentication (Week 1)
### Backend Tasks
- [ ] 1.1 Create User model with authentication
- [ ] 1.2 Implement JWT authentication service
- [ ] 1.3 Create auth routes (register, login, logout, verify)
- [ ] 1.4 Add auth middleware for protected routes
- [ ] 1.5 Database migrations for users table
- [ ] 1.6 Password reset functionality
### Frontend Tasks
- [ ] 1.7 Setup project structure (customer-portal, admin-portal)
- [ ] 1.8 Create Landing page with animations
- [ ] 1.9 Create Register/Login forms
- [ ] 1.10 Implement auth context and hooks
- [ ] 1.11 Protected route wrapper
- [ ] 1.12 Brand colors and design system
### Testing
- [ ] 1.13 Unit tests for auth service
- [ ] 1.14 Integration tests for auth endpoints
- [ ] 1.15 E2E tests for registration flow
---
## Phase 2: Customer Dashboard & DNS Module (Week 2)
### Backend Tasks
- [ ] 2.1 Refactor existing DNS routes to use auth
- [ ] 2.2 Link domains to customer_id
- [ ] 2.3 Customer-specific domain listing
- [ ] 2.4 Update CF account selection logic
### Frontend Tasks
- [ ] 2.5 Create customer dashboard layout
- [ ] 2.6 Sidebar navigation (DNS, Container, Network, Security)
- [ ] 2.7 DNS management page (refactor existing)
- [ ] 2.8 Project creation wizard
- [ ] 2.9 Domain list with filters
- [ ] 2.10 Responsive design
### Testing
- [ ] 2.11 Test DNS operations with auth
- [ ] 2.12 Test customer isolation
---
## Phase 3: Admin Portal (Week 3)
### Backend Tasks
- [ ] 3.1 Admin role and permissions
- [ ] 3.2 Admin-only middleware
- [ ] 3.3 Customer management endpoints
- [ ] 3.4 System statistics endpoints
### Frontend Tasks
- [ ] 3.5 Separate admin portal app
- [ ] 3.6 Admin login page
- [ ] 3.7 Admin dashboard
- [ ] 3.8 CF Accounts management (refactor existing)
- [ ] 3.9 Customer management interface
- [ ] 3.10 System settings page
### Deployment
- [ ] 3.11 Configure adminpanel.argeict.net subdomain
- [ ] 3.12 Nginx configuration for multi-app
- [ ] 3.13 Build and deploy both portals
---
## Phase 4: Container Module (Week 4-5)
### Backend Tasks
- [ ] 4.1 Container model and database schema
- [ ] 4.2 Docker integration service
- [ ] 4.3 WordPress container templates
- [ ] 4.4 Container lifecycle management
- [ ] 4.5 Resource monitoring
### Frontend Tasks
- [ ] 4.6 Container management page
- [ ] 4.7 Container creation wizard
- [ ] 4.8 Container status dashboard
- [ ] 4.9 Resource usage charts
---
## Phase 5: Network & Security Modules (Week 6)
### Backend Tasks
- [ ] 5.1 Network configuration endpoints
- [ ] 5.2 SSL certificate management
- [ ] 5.3 Firewall rules API
- [ ] 5.4 Security scanning integration
### Frontend Tasks
- [ ] 5.5 Network management interface
- [ ] 5.6 Security dashboard
- [ ] 5.7 SSL certificate viewer
- [ ] 5.8 Security recommendations
---
## Phase 6: Polish & Production (Week 7-8)
### Features
- [ ] 6.1 Email notifications
- [ ] 6.2 Billing integration
- [ ] 6.3 Usage analytics
- [ ] 6.4 API documentation
- [ ] 6.5 User documentation
### Testing & QA
- [ ] 6.6 Full system testing
- [ ] 6.7 Performance optimization
- [ ] 6.8 Security audit
- [ ] 6.9 Load testing
### Deployment
- [ ] 6.10 Production deployment
- [ ] 6.11 Monitoring setup
- [ ] 6.12 Backup system
- [ ] 6.13 CI/CD pipeline
---
## Current Sprint: Phase 1 - Foundation & Authentication
### Immediate Tasks (Today)
1. **Backend: User Model & Auth**
- Create User model
- Implement JWT service
- Create auth routes
2. **Frontend: Project Structure**
- Setup customer-portal
- Setup admin-portal
- Create Landing page
3. **Design System**
- Extract brand colors from logo
- Create TailwindCSS config
- Design component library
---
## Success Criteria
### Phase 1
- ✅ Users can register and login
- ✅ JWT authentication working
- ✅ Landing page with animations
- ✅ Brand identity applied
### Phase 2
- ✅ Customer dashboard functional
- ✅ DNS management integrated
- ✅ Customer isolation working
### Phase 3
- ✅ Admin portal deployed
- ✅ CF account management
- ✅ Customer management
### Phase 4-6
- ✅ Container management
- ✅ Full feature set
- ✅ Production ready
---
**Let's start with Phase 1!**

View File

@ -6,10 +6,11 @@ import redis
import os import os
from app.config import Config from app.config import Config
from app.models.domain import db, Domain, DNSRecord, CloudflareAccount from app.models import db, Domain, DNSRecord, CloudflareAccount, User, Customer
from app.services.cloudflare_service import CloudflareService from app.services.cloudflare_service import CloudflareService
# Import blueprints # Import blueprints
from app.routes.auth import auth_bp
from app.routes.admin import admin_bp from app.routes.admin import admin_bp
from app.routes.dns import dns_bp from app.routes.dns import dns_bp
@ -26,6 +27,7 @@ db.init_app(app)
migrate = Migrate(app, db) migrate = Migrate(app, db)
# Register blueprints # Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(admin_bp) app.register_blueprint(admin_bp)
app.register_blueprint(dns_bp) app.register_blueprint(dns_bp)

View File

@ -1,3 +1,4 @@
from app.models.domain import db, CloudflareAccount, Domain, DNSRecord from app.models.domain import db, CloudflareAccount, Domain, DNSRecord
from app.models.user import User, Customer
__all__ = ['db', 'CloudflareAccount', 'Domain', 'DNSRecord'] __all__ = ['db', 'CloudflareAccount', 'Domain', 'DNSRecord', 'User', 'Customer']

123
backend/app/models/user.py Normal file
View File

@ -0,0 +1,123 @@
"""
User and Customer models for authentication and customer management
"""
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from app.models.domain import db
class User(db.Model):
"""Base user model for authentication"""
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
password_hash = db.Column(db.String(255), nullable=False)
full_name = db.Column(db.String(255), nullable=False)
# Account status
is_active = db.Column(db.Boolean, default=True)
is_verified = db.Column(db.Boolean, default=False)
role = db.Column(db.String(20), default='customer') # 'customer' or 'admin'
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
last_login = db.Column(db.DateTime, nullable=True)
# Verification
verification_token = db.Column(db.String(255), nullable=True)
reset_token = db.Column(db.String(255), nullable=True)
reset_token_expires = db.Column(db.DateTime, nullable=True)
# Relationships
customer = db.relationship('Customer', backref='user', uselist=False, cascade='all, delete-orphan')
def set_password(self, password):
"""Hash and set password"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Verify password"""
return check_password_hash(self.password_hash, password)
def to_dict(self, include_sensitive=False):
"""Convert to dictionary"""
data = {
'id': self.id,
'email': self.email,
'full_name': self.full_name,
'role': self.role,
'is_active': self.is_active,
'is_verified': self.is_verified,
'created_at': self.created_at.isoformat() if self.created_at else None,
'last_login': self.last_login.isoformat() if self.last_login else None
}
if include_sensitive:
data['verification_token'] = self.verification_token
data['reset_token'] = self.reset_token
return data
def __repr__(self):
return f'<User {self.email}>'
class Customer(db.Model):
"""Customer profile extending User"""
__tablename__ = "customers"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False, unique=True)
# Company info
company_name = db.Column(db.String(255), nullable=True)
phone = db.Column(db.String(50), nullable=True)
# Billing
billing_address = db.Column(db.Text, nullable=True)
billing_city = db.Column(db.String(100), nullable=True)
billing_country = db.Column(db.String(100), nullable=True)
billing_postal_code = db.Column(db.String(20), nullable=True)
# Subscription
subscription_plan = db.Column(db.String(50), default='free') # free, basic, pro, enterprise
subscription_status = db.Column(db.String(20), default='active') # active, suspended, cancelled
subscription_started = db.Column(db.DateTime, default=datetime.utcnow)
subscription_expires = db.Column(db.DateTime, nullable=True)
# Limits
max_domains = db.Column(db.Integer, default=5)
max_containers = db.Column(db.Integer, default=3)
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
domains = db.relationship('Domain', backref='customer', lazy='dynamic', foreign_keys='Domain.customer_id')
def to_dict(self):
"""Convert to dictionary"""
return {
'id': self.id,
'user_id': self.user_id,
'company_name': self.company_name,
'phone': self.phone,
'billing_address': self.billing_address,
'billing_city': self.billing_city,
'billing_country': self.billing_country,
'billing_postal_code': self.billing_postal_code,
'subscription_plan': self.subscription_plan,
'subscription_status': self.subscription_status,
'subscription_started': self.subscription_started.isoformat() if self.subscription_started else None,
'subscription_expires': self.subscription_expires.isoformat() if self.subscription_expires else None,
'max_domains': self.max_domains,
'max_containers': self.max_containers,
'created_at': self.created_at.isoformat() if self.created_at else None
}
def __repr__(self):
return f'<Customer {self.company_name or self.user.email}>'

220
backend/app/routes/auth.py Normal file
View File

@ -0,0 +1,220 @@
"""
Authentication routes - Register, Login, Logout, Profile
"""
from flask import Blueprint, request, jsonify
from app.services.auth_service import AuthService, token_required
from app.models.user import User, Customer
from app.models.domain import db
auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')
@auth_bp.route('/register', methods=['POST'])
def register():
"""
Register new customer
Request body:
{
"email": "user@example.com",
"password": "password123",
"password_confirm": "password123",
"full_name": "John Doe",
"company_name": "Acme Inc" (optional)
}
"""
try:
data = request.json
# Validate required fields
required_fields = ['email', 'password', 'password_confirm', 'full_name']
for field in required_fields:
if not data.get(field):
return jsonify({
'status': 'error',
'message': f'{field} is required'
}), 400
# Validate email format
email = data['email'].lower().strip()
if '@' not in email or '.' not in email:
return jsonify({
'status': 'error',
'message': 'Invalid email format'
}), 400
# Validate password match
if data['password'] != data['password_confirm']:
return jsonify({
'status': 'error',
'message': 'Passwords do not match'
}), 400
# Validate password strength
password = data['password']
if len(password) < 8:
return jsonify({
'status': 'error',
'message': 'Password must be at least 8 characters'
}), 400
# Register user
user, customer, error = AuthService.register_user(
email=email,
password=password,
full_name=data['full_name'].strip(),
company_name=data.get('company_name', '').strip() or None
)
if error:
return jsonify({
'status': 'error',
'message': error
}), 400
# Generate token
token = AuthService.generate_token(user.id, user.role)
return jsonify({
'status': 'success',
'message': 'Registration successful',
'token': token,
'user': user.to_dict(),
'customer': customer.to_dict()
}), 201
except Exception as e:
db.session.rollback()
return jsonify({
'status': 'error',
'message': f'Registration failed: {str(e)}'
}), 500
@auth_bp.route('/login', methods=['POST'])
def login():
"""
Login user
Request body:
{
"email": "user@example.com",
"password": "password123"
}
"""
try:
data = request.json
# Validate required fields
if not data.get('email') or not data.get('password'):
return jsonify({
'status': 'error',
'message': 'Email and password are required'
}), 400
# Login user
user, token, error = AuthService.login_user(
email=data['email'].lower().strip(),
password=data['password']
)
if error:
return jsonify({
'status': 'error',
'message': error
}), 401
# Get customer profile if customer role
customer_data = None
if user.role == 'customer' and user.customer:
customer_data = user.customer.to_dict()
return jsonify({
'status': 'success',
'message': 'Login successful',
'token': token,
'user': user.to_dict(),
'customer': customer_data
}), 200
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Login failed: {str(e)}'
}), 500
@auth_bp.route('/me', methods=['GET'])
@token_required
def get_profile(current_user):
"""
Get current user profile
Headers:
Authorization: Bearer <token>
"""
try:
customer_data = None
if current_user.role == 'customer' and current_user.customer:
customer_data = current_user.customer.to_dict()
return jsonify({
'status': 'success',
'user': current_user.to_dict(),
'customer': customer_data
}), 200
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Failed to get profile: {str(e)}'
}), 500
@auth_bp.route('/verify-token', methods=['POST'])
def verify_token():
"""
Verify if token is valid
Request body:
{
"token": "jwt_token_here"
}
"""
try:
data = request.json
token = data.get('token')
if not token:
return jsonify({
'status': 'error',
'message': 'Token is required',
'valid': False
}), 400
payload = AuthService.verify_token(token)
if not payload:
return jsonify({
'status': 'error',
'message': 'Invalid or expired token',
'valid': False
}), 401
return jsonify({
'status': 'success',
'message': 'Token is valid',
'valid': True,
'payload': {
'user_id': payload['user_id'],
'role': payload['role']
}
}), 200
except Exception as e:
return jsonify({
'status': 'error',
'message': str(e),
'valid': False
}), 500

View File

@ -0,0 +1,233 @@
"""
Authentication service - JWT token generation and validation
"""
import jwt
import secrets
from datetime import datetime, timedelta
from functools import wraps
from flask import request, jsonify, current_app
from app.models.user import User, Customer
from app.models.domain import db
class AuthService:
"""Authentication service for JWT tokens"""
@staticmethod
def generate_token(user_id, role='customer', expires_in=24):
"""
Generate JWT token
Args:
user_id: User ID
role: User role (customer/admin)
expires_in: Token expiration in hours (default 24)
Returns:
JWT token string
"""
payload = {
'user_id': user_id,
'role': role,
'exp': datetime.utcnow() + timedelta(hours=expires_in),
'iat': datetime.utcnow()
}
token = jwt.encode(
payload,
current_app.config['SECRET_KEY'],
algorithm='HS256'
)
return token
@staticmethod
def verify_token(token):
"""
Verify JWT token
Args:
token: JWT token string
Returns:
dict: Decoded payload or None if invalid
"""
try:
payload = jwt.decode(
token,
current_app.config['SECRET_KEY'],
algorithms=['HS256']
)
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
@staticmethod
def register_user(email, password, full_name, company_name=None):
"""
Register new user
Args:
email: User email
password: User password
full_name: User full name
company_name: Optional company name
Returns:
tuple: (user, customer, error)
"""
# Check if user exists
existing_user = User.query.filter_by(email=email).first()
if existing_user:
return None, None, "Email already registered"
# Create user
user = User(
email=email,
full_name=full_name,
role='customer',
is_active=True,
is_verified=False,
verification_token=secrets.token_urlsafe(32)
)
user.set_password(password)
db.session.add(user)
db.session.flush() # Get user.id
# Create customer profile
customer = Customer(
user_id=user.id,
company_name=company_name,
subscription_plan='free',
subscription_status='active',
max_domains=5,
max_containers=3
)
db.session.add(customer)
db.session.commit()
return user, customer, None
@staticmethod
def login_user(email, password):
"""
Login user
Args:
email: User email
password: User password
Returns:
tuple: (user, token, error)
"""
user = User.query.filter_by(email=email).first()
if not user:
return None, None, "Invalid email or password"
if not user.check_password(password):
return None, None, "Invalid email or password"
if not user.is_active:
return None, None, "Account is deactivated"
# Update last login
user.last_login = datetime.utcnow()
db.session.commit()
# Generate token
token = AuthService.generate_token(user.id, user.role)
return user, token, None
@staticmethod
def get_current_user(token):
"""
Get current user from token
Args:
token: JWT token
Returns:
User object or None
"""
payload = AuthService.verify_token(token)
if not payload:
return None
user = User.query.get(payload['user_id'])
return user
# Decorators for route protection
def token_required(f):
"""Decorator to require valid JWT token"""
@wraps(f)
def decorated(*args, **kwargs):
token = None
# Get token from header
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
try:
token = auth_header.split(' ')[1] # Bearer <token>
except IndexError:
return jsonify({'error': 'Invalid token format'}), 401
if not token:
return jsonify({'error': 'Token is missing'}), 401
# Verify token
payload = AuthService.verify_token(token)
if not payload:
return jsonify({'error': 'Token is invalid or expired'}), 401
# Get user
current_user = User.query.get(payload['user_id'])
if not current_user or not current_user.is_active:
return jsonify({'error': 'User not found or inactive'}), 401
return f(current_user, *args, **kwargs)
return decorated
def admin_required(f):
"""Decorator to require admin role"""
@wraps(f)
def decorated(*args, **kwargs):
token = None
# Get token from header
if 'Authorization' in request.headers:
auth_header = request.headers['Authorization']
try:
token = auth_header.split(' ')[1]
except IndexError:
return jsonify({'error': 'Invalid token format'}), 401
if not token:
return jsonify({'error': 'Token is missing'}), 401
# Verify token
payload = AuthService.verify_token(token)
if not payload:
return jsonify({'error': 'Token is invalid or expired'}), 401
# Check admin role
if payload.get('role') != 'admin':
return jsonify({'error': 'Admin access required'}), 403
# Get user
current_user = User.query.get(payload['user_id'])
if not current_user or not current_user.is_active:
return jsonify({'error': 'User not found or inactive'}), 401
return f(current_user, *args, **kwargs)
return decorated

View File

@ -23,6 +23,7 @@ dnspython==2.4.2
# Security # Security
cryptography==41.0.7 cryptography==41.0.7
PyJWT==2.8.0
# Development # Development
pytest==7.4.3 pytest==7.4.3