Add authentication system - User model, JWT service, auth routes
This commit is contained in:
parent
6fa9e5c43b
commit
87611a284b
|
|
@ -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
|
||||
|
||||
|
|
@ -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!**
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
|
|
|
|||
|
|
@ -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}>'
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ dnspython==2.4.2
|
|||
|
||||
# Security
|
||||
cryptography==41.0.7
|
||||
PyJWT==2.8.0
|
||||
|
||||
# Development
|
||||
pytest==7.4.3
|
||||
|
|
|
|||
Loading…
Reference in New Issue