Implementiere lokales Authentifizierungssystem

- Entferne GitHub OAuth-Abhängigkeiten
- Implementiere lokales Authentifizierungssystem mit Passwort-Hashing
- Füge Passwort-Hash zum User-Modell hinzu, entferne GitHub-ID
- Implementiere Benutzerregistrierung und Login-Endpoints
- Erstelle Endpunkt für initialen Admin-Setup
- Passe Benutzerrollenverwaltung an

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root 2025-03-11 09:29:25 +01:00
parent 73ead5939c
commit 70aeb17cdb

View File

@ -10,15 +10,12 @@ import os
import json
import logging
import uuid
import aiohttp
import asyncio
import requests
from logging.handlers import RotatingFileHandler
from datetime import timedelta
from typing import Dict, Any, List, Optional, Union
from dataclasses import dataclass
import sqlite3
from authlib.integrations.flask_client import OAuth
from tapo import ApiClient
from dotenv import load_dotenv
@ -38,13 +35,6 @@ app.config['SESSION_COOKIE_SECURE'] = os.environ.get('FLASK_ENV') == 'production
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7)
# GitHub OAuth Konfiguration
app.config['GITHUB_CLIENT_ID'] = os.environ.get('OAUTH_CLIENT_ID')
app.config['GITHUB_CLIENT_SECRET'] = os.environ.get('OAUTH_CLIENT_SECRET')
app.config['GITHUB_API_BASE_URL'] = os.environ.get('GITHUB_API_BASE_URL', 'https://api.github.com/')
app.config['GITHUB_AUTHORIZE_URL'] = os.environ.get('GITHUB_AUTHORIZE_URL', 'https://github.com/login/oauth/authorize')
app.config['GITHUB_TOKEN_URL'] = os.environ.get('GITHUB_TOKEN_URL', 'https://github.com/login/oauth/access_token')
# Tapo-Konfiguration
TAPO_USERNAME = os.environ.get('TAPO_USERNAME')
TAPO_PASSWORD = os.environ.get('TAPO_PASSWORD')
@ -66,32 +56,27 @@ app.logger.info('MYP Backend starting up')
db = SQLAlchemy(app)
migrate = Migrate(app, db)
# OAuth Setup
oauth = OAuth(app)
github = oauth.register(
name='github',
client_id=app.config['GITHUB_CLIENT_ID'],
client_secret=app.config['GITHUB_CLIENT_SECRET'],
access_token_url=app.config['GITHUB_TOKEN_URL'],
authorize_url=app.config['GITHUB_AUTHORIZE_URL'],
api_base_url=app.config['GITHUB_API_BASE_URL'],
client_kwargs={'scope': 'user:email'},
)
# Lokale Authentifizierung statt OAuth
# Models
class User(db.Model):
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
github_id = db.Column(db.Integer, unique=True)
username = db.Column(db.String(64), index=True, unique=True)
password_hash = db.Column(db.String(128))
display_name = db.Column(db.String(100))
email = db.Column(db.String(120), index=True, unique=True)
role = db.Column(db.String(20), default='guest') # admin, user, guest
role = db.Column(db.String(20), default='user') # admin, user, guest
jobs = db.relationship('PrintJob', backref='user', lazy='dynamic')
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def to_dict(self):
return {
'id': self.id,
'github_id': self.github_id,
'username': self.username,
'displayName': self.display_name,
'email': self.email,
@ -258,57 +243,72 @@ def create_session(user):
return session_id
# Authentifizierungs-Routen
@app.route('/auth/login', methods=['GET'])
def login():
redirect_uri = url_for('github_callback', _external=True)
return github.authorize_redirect(redirect_uri)
@app.route('/auth/login/callback', methods=['GET'])
def github_callback():
token = github.authorize_access_token()
resp = github.get('user', token=token)
github_user = resp.json()
@app.route('/auth/register', methods=['POST'])
def register():
data = request.get_json()
# GitHub-User-Informationen
github_id = github_user['id']
username = github_user['login']
display_name = github_user.get('name', username)
if not data or not data.get('username') or not data.get('password'):
return jsonify({'message': 'Benutzername und Passwort sind erforderlich!'}), 400
# E-Mail abrufen
emails_resp = github.get('user/emails', token=token)
emails = emails_resp.json()
primary_email = next((email['email'] for email in emails if email.get('primary')), None)
username = data.get('username')
password = data.get('password')
display_name = data.get('displayName', username)
email = data.get('email', '')
if not primary_email and emails:
primary_email = emails[0]['email']
if User.query.filter_by(username=username).first():
return jsonify({'message': 'Benutzername bereits vergeben!'}), 400
# Benutzer suchen oder erstellen
user = User.query.filter_by(github_id=github_id).first()
if email and User.query.filter_by(email=email).first():
return jsonify({'message': 'E-Mail-Adresse bereits registriert!'}), 400
if not user:
user = User(
github_id=github_id,
username=username,
display_name=display_name,
email=primary_email,
role='guest' # Standardrolle für neue Benutzer
)
db.session.add(user)
db.session.commit()
app.logger.info(f'Neuer Benutzer über GitHub registriert: {username}')
else:
# Aktualisiere Benutzerdaten, falls sie sich geändert haben
user.username = username
user.display_name = display_name
if primary_email:
user.email = primary_email
db.session.commit()
# Prüfen, ob es bereits einen Admin gibt
admin_exists = User.query.filter_by(role='admin').first() is not None
# Falls kein Admin existiert, wird der erste Benutzer zum Admin
role = 'admin' if not admin_exists else 'user'
user = User(
username=username,
display_name=display_name,
email=email,
role=role
)
user.set_password(password)
db.session.add(user)
db.session.commit()
app.logger.info(f'Neuer Benutzer registriert: {username} (Rolle: {role})')
# Session erstellen
create_session(user)
# Weiterleitung zur Frontend-App
return redirect('/')
return jsonify({
'message': 'Registrierung erfolgreich!',
'user': user.to_dict()
}), 201
@app.route('/auth/login', methods=['POST'])
def login():
data = request.get_json()
if not data or not data.get('username') or not data.get('password'):
return jsonify({'message': 'Benutzername und Passwort sind erforderlich!'}), 400
username = data.get('username')
password = data.get('password')
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
return jsonify({'message': 'Ungültiger Benutzername oder Passwort!'}), 401
# Session erstellen
create_session(user)
return jsonify({
'message': 'Anmeldung erfolgreich!',
'user': user.to_dict()
})
@app.route('/auth/logout', methods=['POST'])
def logout():
@ -681,6 +681,40 @@ def check_jobs():
def test():
return jsonify({'message': 'MYP Backend API funktioniert!'})
@app.route('/api/create-initial-admin', methods=['POST'])
def create_initial_admin():
admin_exists = User.query.filter_by(role='admin').first() is not None
if admin_exists:
return jsonify({'message': 'Es existiert bereits ein Administrator!'}), 400
data = request.get_json()
if not data or not data.get('username') or not data.get('password'):
return jsonify({'message': 'Benutzername und Passwort sind erforderlich!'}), 400
username = data.get('username')
password = data.get('password')
display_name = data.get('displayName', username)
email = data.get('email', '')
user = User(
username=username,
display_name=display_name,
email=email,
role='admin'
)
user.set_password(password)
db.session.add(user)
db.session.commit()
app.logger.info(f'Initialer Admin-Benutzer erstellt: {username}')
return jsonify({
'message': 'Administrator wurde erfolgreich erstellt!',
'user': user.to_dict()
}), 201
# Error Handler
@app.errorhandler(404)
def not_found(error):