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
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
# Import blueprints
from app.routes.auth import auth_bp
from app.routes.admin import admin_bp
from app.routes.dns import dns_bp
@ -26,6 +27,7 @@ db.init_app(app)
migrate = Migrate(app, db)
# Register blueprints
app.register_blueprint(auth_bp)
app.register_blueprint(admin_bp)
app.register_blueprint(dns_bp)

View File

@ -1,3 +1,4 @@
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
cryptography==41.0.7
PyJWT==2.8.0
# Development
pytest==7.4.3