VPN/main.py

118 lines
4.5 KiB
Python

"""
FastAPI server for the VPN Access Server.
"""
import uvicorn
import os
import logging
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from pydantic import BaseModel
from util.client.generate_client import generate_client_config
from util.email import send_vpn_config
from access.config import config
from access.utils import setup_logging
app = FastAPI(
title="VPN Access Server API",
description="API for managing VPN clients and server operations.",
version="1.0.0",
)
# Set up logging - use local logs directory if system log path is not writable
log_file = config.server.log_file
if log_file and not os.path.exists(os.path.dirname(log_file)):
log_file = "logs/vpn-access-server.log"
logger = setup_logging(
log_level=config.server.log_level,
log_file=log_file
)
class ClientRequest(BaseModel):
username: str
email: str
@app.post("/api/client/generate", summary="Generate a new VPN client configuration")
def generate_client(request: ClientRequest):
"""
Generates a new OpenVPN client configuration (.ovpn) file and sends it via email.
This endpoint will:
1. Trigger `easyrsa` to generate a new client certificate and key.
2. Assemble the `.ovpn` file with the new certificate/key and the server's CA and TA keys.
3. Save the file to the server's client configuration directory.
4. Send the .ovpn file and user guide via email to the provided email address.
The system automatically handles certificate conflicts by reusing existing certificates
or resolving conflicts transparently.
"""
logger.info(f"Client generation request for user: {request.username}, email: {request.email}")
success, message = generate_client_config(request.username, request.email)
if not success:
logger.error(f"Client generation failed for user {request.username}: {message}")
raise HTTPException(status_code=500, detail=message)
logger.info(f"Client generation successful for user {request.username}: {message}")
# Check if email configuration is valid
if not config.email.smtp_username or not config.email.smtp_password:
logger.warning(f"Email not configured - skipping email send for user {request.username}")
return {
"message": f"{message} Note: Email not sent - SMTP credentials not configured in .env file.",
"email_sent": False,
"warning": "Configure SMTP_USERNAME and SMTP_PASSWORD in .env to enable email notifications"
}
# Send email with VPN config and user guide
vpn_config_path = os.path.join("generated-clients", f"{request.username}.ovpn")
user_guide_path = "material/sample-ppt.pptx"
logger.info(f"Attempting to send email to {request.email} for user {request.username}")
try:
email_success = send_vpn_config(
to_email=request.email,
username=request.username,
vpn_config_path=vpn_config_path,
user_guide_path=user_guide_path
)
except Exception as e:
# Email sending failed with exception
logger.error(f"Email sending failed for user {request.username}: {str(e)}")
return {
"message": f"{message} Warning: Failed to send email - {str(e)}",
"email_sent": False
}
if not email_success:
# Configuration was generated successfully, but email failed
logger.warning(f"Email sending returned false for user {request.username}")
return {
"message": f"{message} Warning: Failed to send email with configuration files.",
"email_sent": False
}
logger.info(f"Email sent successfully to {request.email} for user {request.username}")
return {
"message": f"{message} Configuration files sent to {request.email}",
"email_sent": True
}
@app.get("/api/client/get-config/{username}", summary="Download a client configuration file")
def get_client_config(username: str, email: str):
"""
Downloads the .ovpn configuration file for a specific client.
The file is sought in the `generated-clients` directory.
"""
file_path = os.path.join("generated-clients", f"{username}.ovpn")
if not os.path.isfile(file_path):
raise HTTPException(status_code=404, detail="Configuration file not found for this user. Please generate it first.")
return FileResponse(path=file_path, filename=f"{username}.ovpn", media_type='application/octet-stream')
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8443)