spawner/api.py
2026-01-30 18:00:41 +01:00

252 lines
7.8 KiB
Python

from flask import Blueprint, jsonify, request, current_app
from flask_jwt_extended import (
create_access_token,
jwt_required,
get_jwt_identity,
get_jwt
)
from datetime import timedelta
from models import db, User
from container_manager import ContainerManager
api_bp = Blueprint('api', __name__, url_prefix='/api')
# Token-Blacklist für Logout
token_blacklist = set()
@api_bp.route('/auth/login', methods=['POST'])
def api_login():
"""API-Login - gibt JWT-Token zurück"""
data = request.get_json()
if not data:
return jsonify({'error': 'Keine Daten übermittelt'}), 400
username = data.get('username')
password = data.get('password')
if not username or not password:
return jsonify({'error': 'Username und Passwort erforderlich'}), 400
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
return jsonify({'error': 'Ungültige Anmeldedaten'}), 401
# Container spawnen wenn noch nicht vorhanden
if not user.container_id:
try:
container_mgr = ContainerManager()
container_id, port = container_mgr.spawn_container(user.id, user.username)
user.container_id = container_id
user.container_port = port
db.session.commit()
except Exception as e:
current_app.logger.error(f"Container-Start fehlgeschlagen: {str(e)}")
return jsonify({'error': f'Container-Start fehlgeschlagen: {str(e)}'}), 500
# JWT-Token erstellen
expires = timedelta(seconds=current_app.config.get('JWT_ACCESS_TOKEN_EXPIRES', 3600))
access_token = create_access_token(
identity=str(user.id),
expires_delta=expires,
additional_claims={'username': user.username}
)
return jsonify({
'access_token': access_token,
'token_type': 'Bearer',
'expires_in': int(expires.total_seconds()),
'user': {
'id': user.id,
'username': user.username,
'email': user.email
}
}), 200
@api_bp.route('/auth/signup', methods=['POST'])
def api_signup():
"""API-Registrierung - erstellt User, spawnt Container, gibt JWT zurück"""
data = request.get_json()
if not data:
return jsonify({'error': 'Keine Daten übermittelt'}), 400
username = data.get('username')
email = data.get('email')
password = data.get('password')
if not username or not email or not password:
return jsonify({'error': 'Username, Email und Passwort erforderlich'}), 400
# Validierung
if len(username) < 3:
return jsonify({'error': 'Username muss mindestens 3 Zeichen lang sein'}), 400
if len(password) < 6:
return jsonify({'error': 'Passwort muss mindestens 6 Zeichen lang sein'}), 400
# Prüfe ob User existiert
if User.query.filter_by(username=username).first():
return jsonify({'error': 'Username bereits vergeben'}), 409
if User.query.filter_by(email=email).first():
return jsonify({'error': 'Email bereits registriert'}), 409
# Neuen User anlegen
user = User(username=username, email=email)
user.set_password(password)
db.session.add(user)
db.session.commit()
# Container spawnen
try:
container_mgr = ContainerManager()
container_id, port = container_mgr.spawn_container(user.id, user.username)
user.container_id = container_id
user.container_port = port
db.session.commit()
except Exception as e:
db.session.delete(user)
db.session.commit()
current_app.logger.error(f"Registrierung fehlgeschlagen: {str(e)}")
return jsonify({'error': f'Container-Erstellung fehlgeschlagen: {str(e)}'}), 500
# JWT-Token erstellen
expires = timedelta(seconds=current_app.config.get('JWT_ACCESS_TOKEN_EXPIRES', 3600))
access_token = create_access_token(
identity=str(user.id),
expires_delta=expires,
additional_claims={'username': user.username}
)
return jsonify({
'access_token': access_token,
'token_type': 'Bearer',
'expires_in': int(expires.total_seconds()),
'user': {
'id': user.id,
'username': user.username,
'email': user.email
}
}), 201
@api_bp.route('/auth/logout', methods=['POST'])
@jwt_required()
def api_logout():
"""API-Logout - invalidiert Token"""
jti = get_jwt()['jti']
token_blacklist.add(jti)
return jsonify({'message': 'Erfolgreich abgemeldet'}), 200
@api_bp.route('/user/me', methods=['GET'])
@jwt_required()
def api_user_me():
"""Gibt aktuellen User und Container-Info zurück"""
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user:
return jsonify({'error': 'User nicht gefunden'}), 404
# Service-URL berechnen
scheme = current_app.config['PREFERRED_URL_SCHEME']
spawner_domain = f"{current_app.config['SPAWNER_SUBDOMAIN']}.{current_app.config['BASE_DOMAIN']}"
service_url = f"{scheme}://{spawner_domain}/{user.username}"
# Container-Status abrufen
container_status = 'unknown'
if user.container_id:
try:
container_mgr = ContainerManager()
container_status = container_mgr.get_container_status(user.container_id)
except Exception:
container_status = 'error'
return jsonify({
'user': {
'id': user.id,
'username': user.username,
'email': user.email,
'created_at': user.created_at.isoformat() if user.created_at else None
},
'container': {
'id': user.container_id,
'port': user.container_port,
'status': container_status,
'service_url': service_url
}
}), 200
@api_bp.route('/container/status', methods=['GET'])
@jwt_required()
def api_container_status():
"""Gibt Container-Status zurück"""
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user:
return jsonify({'error': 'User nicht gefunden'}), 404
container_status = 'no_container'
if user.container_id:
try:
container_mgr = ContainerManager()
container_status = container_mgr.get_container_status(user.container_id)
except Exception as e:
container_status = f'error: {str(e)}'
return jsonify({
'container_id': user.container_id,
'status': container_status
}), 200
@api_bp.route('/container/restart', methods=['POST'])
@jwt_required()
def api_container_restart():
"""Startet Container neu"""
user_id = get_jwt_identity()
user = User.query.get(int(user_id))
if not user:
return jsonify({'error': 'User nicht gefunden'}), 404
container_mgr = ContainerManager()
# Alten Container stoppen falls vorhanden
if user.container_id:
try:
container_mgr.stop_container(user.container_id)
container_mgr.remove_container(user.container_id)
except Exception as e:
current_app.logger.warning(f"Alter Container konnte nicht gestoppt werden: {str(e)}")
# Neuen Container starten
try:
container_id, port = container_mgr.spawn_container(user.id, user.username)
user.container_id = container_id
user.container_port = port
db.session.commit()
return jsonify({
'message': 'Container erfolgreich neugestartet',
'container_id': container_id,
'status': 'running'
}), 200
except Exception as e:
current_app.logger.error(f"Container-Restart fehlgeschlagen: {str(e)}")
return jsonify({'error': f'Container-Restart fehlgeschlagen: {str(e)}'}), 500
def check_if_token_revoked(jwt_header, jwt_payload):
"""Callback für flask-jwt-extended um revoked Tokens zu prüfen"""
jti = jwt_payload['jti']
return jti in token_blacklist