🔧 Update: Enhance Guest Request Management with OTP and Name Verification
**Änderungen:** - ✅ Hinzugefügt: Neue Methode `find_by_otp_and_name` in `GuestRequest`, um Gastanfragen anhand von OTP-Code und Name zu finden. - ✅ API-Endpunkte in `admin_unified.py` für die Verwaltung von Gastanfragen mit OTP-Codes implementiert, einschließlich Generierung und Druck von Zugangsdaten. - ✅ Anpassungen in `guest.py`, um die Authentifizierung von Gastanfragen mit Name und OTP-Code zu unterstützen. **Ergebnis:** - Verbesserte Sicherheit und Benutzerfreundlichkeit bei der Verwaltung von Gastanfragen im Offline-System. - Klarere API-Responses und verbesserte Fehlerbehandlung für Gastanfragen. 🤖 Generated with [Claude Code](https://claude.ai/code)
This commit is contained in:
@ -26,7 +26,7 @@ from datetime import datetime, timedelta
|
||||
from flask import Blueprint, render_template, request, jsonify, redirect, url_for, flash, current_app
|
||||
from flask_login import login_required, current_user
|
||||
from functools import wraps
|
||||
from models import User, Printer, Job, get_cached_session, Stats, SystemLog, PlugStatusLog
|
||||
from models import User, Printer, Job, get_cached_session, Stats, SystemLog, PlugStatusLog, GuestRequest
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
# ===== BLUEPRINT-KONFIGURATION =====
|
||||
@ -1283,6 +1283,214 @@ def export_logs_api():
|
||||
admin_logger.error(f"Fehler beim Exportieren der Logs: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Exportieren der Logs"}), 500
|
||||
|
||||
# ===== GAST-OTP-MANAGEMENT FÜR OFFLINE-BETRIEB =====
|
||||
|
||||
@admin_api_blueprint.route("/guest-requests", methods=["GET"])
|
||||
@admin_required
|
||||
def get_guest_requests_api():
|
||||
"""API-Endpunkt zum Abrufen aller Gastanfragen mit OTP-Codes für Admins"""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
# Alle Gastanfragen laden
|
||||
guest_requests = db_session.query(GuestRequest).order_by(
|
||||
GuestRequest.created_at.desc()
|
||||
).all()
|
||||
|
||||
# In Dictionary konvertieren mit OTP-Codes für Admins
|
||||
requests_data = []
|
||||
for req in guest_requests:
|
||||
request_data = {
|
||||
'id': req.id,
|
||||
'name': req.name,
|
||||
'email': req.email,
|
||||
'reason': req.reason,
|
||||
'status': req.status,
|
||||
'duration_min': req.duration_min,
|
||||
'created_at': req.created_at.isoformat() if req.created_at else None,
|
||||
'processed_at': req.processed_at.isoformat() if req.processed_at else None,
|
||||
'processed_by': req.processed_by,
|
||||
'approval_notes': req.approval_notes,
|
||||
'rejection_reason': req.rejection_reason,
|
||||
'author_ip': req.author_ip
|
||||
}
|
||||
|
||||
# OTP-Code für Admins sichtbar machen (nur wenn aktiv)
|
||||
if req.status == 'approved' and req.otp_code and req.otp_expires_at:
|
||||
if req.otp_expires_at > datetime.now() and not req.otp_used_at:
|
||||
request_data['otp_code'] = req.otp_code # Klartext für Admin
|
||||
request_data['otp_expires_at'] = req.otp_expires_at.isoformat()
|
||||
request_data['otp_status'] = 'active'
|
||||
elif req.otp_used_at:
|
||||
request_data['otp_status'] = 'used'
|
||||
request_data['otp_used_at'] = req.otp_used_at.isoformat()
|
||||
else:
|
||||
request_data['otp_status'] = 'expired'
|
||||
else:
|
||||
request_data['otp_status'] = 'not_generated'
|
||||
|
||||
requests_data.append(request_data)
|
||||
|
||||
admin_logger.info(f"Gastanfragen abgerufen: {len(requests_data)} Einträge für Admin {current_user.name}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"requests": requests_data,
|
||||
"count": len(requests_data)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
admin_logger.error(f"Fehler beim Abrufen der Gastanfragen: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Laden der Gastanfragen"}), 500
|
||||
|
||||
@admin_api_blueprint.route("/guest-requests/<int:request_id>/generate-otp", methods=["POST"])
|
||||
@admin_required
|
||||
def generate_guest_otp_api(request_id):
|
||||
"""Generiert einen neuen OTP-Code für eine genehmigte Gastanfrage"""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Gastanfrage nicht gefunden"}), 404
|
||||
|
||||
if guest_request.status != 'approved':
|
||||
return jsonify({"error": "Gastanfrage muss erst genehmigt werden"}), 400
|
||||
|
||||
# Neuen OTP-Code generieren
|
||||
otp_code = guest_request.generate_otp()
|
||||
guest_request.otp_expires_at = datetime.now() + timedelta(hours=72) # 72h gültig
|
||||
guest_request.otp_used_at = None # Reset falls bereits verwendet
|
||||
|
||||
db_session.commit()
|
||||
|
||||
admin_logger.info(f"Neuer OTP-Code generiert für Gastanfrage {request_id} von Admin {current_user.name}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "Neuer OTP-Code generiert",
|
||||
"otp_code": otp_code,
|
||||
"expires_at": guest_request.otp_expires_at.isoformat(),
|
||||
"guest_name": guest_request.name
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
admin_logger.error(f"Fehler beim Generieren des OTP-Codes: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Generieren des OTP-Codes"}), 500
|
||||
|
||||
@admin_api_blueprint.route("/guest-requests/<int:request_id>/print-credentials", methods=["POST"])
|
||||
@admin_required
|
||||
def print_guest_credentials_api(request_id):
|
||||
"""Erstellt Ausdruck-Template für Gast-Zugangsdaten"""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
guest_request = db_session.query(GuestRequest).filter_by(id=request_id).first()
|
||||
|
||||
if not guest_request:
|
||||
return jsonify({"error": "Gastanfrage nicht gefunden"}), 404
|
||||
|
||||
if guest_request.status != 'approved':
|
||||
return jsonify({"error": "Gastanfrage muss erst genehmigt werden"}), 400
|
||||
|
||||
if not guest_request.otp_code or not guest_request.otp_expires_at:
|
||||
return jsonify({"error": "Kein OTP-Code verfügbar"}), 400
|
||||
|
||||
# Ausdruck-Template erstellen
|
||||
print_template = {
|
||||
"type": "guest_credentials",
|
||||
"title": "MYP GASTZUGANG GENEHMIGT",
|
||||
"subtitle": "TBA Marienfelde - Offline System",
|
||||
"guest_info": {
|
||||
"name": guest_request.name,
|
||||
"request_id": f"GAS-{guest_request.id:06d}",
|
||||
"email": guest_request.email,
|
||||
"approved_at": guest_request.processed_at.strftime("%d.%m.%Y %H:%M") if guest_request.processed_at else None,
|
||||
"approved_by": guest_request.processed_by
|
||||
},
|
||||
"access_data": {
|
||||
"otp_code": guest_request.otp_code,
|
||||
"valid_until": guest_request.otp_expires_at.strftime("%d.%m.%Y %H:%M"),
|
||||
"login_url": "http://192.168.1.100:5000/auth/guest"
|
||||
},
|
||||
"usage_rules": [
|
||||
"Max. Druckzeit pro Job: 4 Stunden",
|
||||
"Dateiformate: STL, OBJ, 3MF, GCODE",
|
||||
"Materialien: PLA, PETG",
|
||||
"Jobs benötigen Admin-Freigabe"
|
||||
],
|
||||
"pickup_info": {
|
||||
"location": "TBA Marienfelde, Raum B2.1",
|
||||
"hours": "Mo-Fr 8:00-16:00",
|
||||
"storage_days": "Max. 7 Tage"
|
||||
},
|
||||
"qr_code_data": f"http://192.168.1.100:5000/auth/guest?name={guest_request.name}&id={guest_request.id}",
|
||||
"admin_note": "An Gast aushändigen",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
admin_logger.info(f"Ausdruck-Template erstellt für Gastanfrage {request_id} von Admin {current_user.name}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"print_template": print_template
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
admin_logger.error(f"Fehler beim Erstellen des Ausdruck-Templates: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Erstellen des Ausdruck-Templates"}), 500
|
||||
|
||||
@admin_api_blueprint.route("/guest-requests/pending-otps", methods=["GET"])
|
||||
@admin_required
|
||||
def get_pending_guest_otps_api():
|
||||
"""Listet alle aktiven OTP-Codes für schnelle Admin-Übersicht"""
|
||||
try:
|
||||
with get_cached_session() as db_session:
|
||||
# Alle genehmigten Anfragen mit aktiven OTP-Codes
|
||||
active_requests = db_session.query(GuestRequest).filter(
|
||||
GuestRequest.status == 'approved',
|
||||
GuestRequest.otp_code.isnot(None),
|
||||
GuestRequest.otp_expires_at > datetime.now(),
|
||||
GuestRequest.otp_used_at.is_(None)
|
||||
).order_by(GuestRequest.otp_expires_at.asc()).all()
|
||||
|
||||
# Kompakte Liste für Admin-Dashboard
|
||||
otps_data = []
|
||||
for req in active_requests:
|
||||
time_remaining = req.otp_expires_at - datetime.now()
|
||||
hours_remaining = int(time_remaining.total_seconds() // 3600)
|
||||
|
||||
otps_data.append({
|
||||
'request_id': req.id,
|
||||
'guest_name': req.name,
|
||||
'otp_code': req.otp_code,
|
||||
'expires_at': req.otp_expires_at.isoformat(),
|
||||
'hours_remaining': hours_remaining,
|
||||
'urgency': 'critical' if hours_remaining < 2 else 'warning' if hours_remaining < 24 else 'normal'
|
||||
})
|
||||
|
||||
admin_logger.info(f"Aktive OTP-Codes abgerufen: {len(otps_data)} Codes")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"active_otps": otps_data,
|
||||
"count": len(otps_data)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
admin_logger.error(f"Fehler beim Abrufen aktiver OTP-Codes: {str(e)}")
|
||||
return jsonify({"error": "Fehler beim Laden der OTP-Codes"}), 500
|
||||
|
||||
# ===== ADMIN-UI ROUTES FÜR GAST-OTP-VERWALTUNG =====
|
||||
|
||||
@admin_blueprint.route("/guest-otps")
|
||||
@admin_required
|
||||
def guest_otps_management():
|
||||
"""Admin-UI für Gast-OTP-Verwaltung (Offline-System)"""
|
||||
admin_logger.info(f"Gast-OTP-Verwaltung aufgerufen von Admin {current_user.name}")
|
||||
|
||||
return render_template('admin_guest_otps.html',
|
||||
page_title="Gast-OTP-Verwaltung",
|
||||
current_user=current_user)
|
||||
|
||||
# ===== API-ENDPUNKTE FÜR SYSTEM-INFORMATIONEN =====
|
||||
|
||||
@admin_api_blueprint.route("/system/status", methods=["GET"])
|
||||
|
Reference in New Issue
Block a user