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 json
import logging import logging
import uuid import uuid
import aiohttp
import asyncio import asyncio
import requests
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
from datetime import timedelta from datetime import timedelta
from typing import Dict, Any, List, Optional, Union from typing import Dict, Any, List, Optional, Union
from dataclasses import dataclass from dataclasses import dataclass
import sqlite3 import sqlite3
from authlib.integrations.flask_client import OAuth
from tapo import ApiClient from tapo import ApiClient
from dotenv import load_dotenv 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['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(days=7) 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-Konfiguration
TAPO_USERNAME = os.environ.get('TAPO_USERNAME') TAPO_USERNAME = os.environ.get('TAPO_USERNAME')
TAPO_PASSWORD = os.environ.get('TAPO_PASSWORD') TAPO_PASSWORD = os.environ.get('TAPO_PASSWORD')
@ -66,32 +56,27 @@ app.logger.info('MYP Backend starting up')
db = SQLAlchemy(app) db = SQLAlchemy(app)
migrate = Migrate(app, db) migrate = Migrate(app, db)
# OAuth Setup # Lokale Authentifizierung statt OAuth
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'},
)
# Models # Models
class User(db.Model): class User(db.Model):
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid.uuid4())) 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) username = db.Column(db.String(64), index=True, unique=True)
password_hash = db.Column(db.String(128))
display_name = db.Column(db.String(100)) display_name = db.Column(db.String(100))
email = db.Column(db.String(120), index=True, unique=True) 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') 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): def to_dict(self):
return { return {
'id': self.id, 'id': self.id,
'github_id': self.github_id,
'username': self.username, 'username': self.username,
'displayName': self.display_name, 'displayName': self.display_name,
'email': self.email, 'email': self.email,
@ -258,57 +243,72 @@ def create_session(user):
return session_id return session_id
# Authentifizierungs-Routen # Authentifizierungs-Routen
@app.route('/auth/login', methods=['GET']) @app.route('/auth/register', methods=['POST'])
def login(): def register():
redirect_uri = url_for('github_callback', _external=True) data = request.get_json()
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()
# GitHub-User-Informationen if not data or not data.get('username') or not data.get('password'):
github_id = github_user['id'] return jsonify({'message': 'Benutzername und Passwort sind erforderlich!'}), 400
username = github_user['login']
display_name = github_user.get('name', username)
# E-Mail abrufen username = data.get('username')
emails_resp = github.get('user/emails', token=token) password = data.get('password')
emails = emails_resp.json() display_name = data.get('displayName', username)
primary_email = next((email['email'] for email in emails if email.get('primary')), None) email = data.get('email', '')
if not primary_email and emails: if User.query.filter_by(username=username).first():
primary_email = emails[0]['email'] return jsonify({'message': 'Benutzername bereits vergeben!'}), 400
# Benutzer suchen oder erstellen if email and User.query.filter_by(email=email).first():
user = User.query.filter_by(github_id=github_id).first() return jsonify({'message': 'E-Mail-Adresse bereits registriert!'}), 400
if not user: # Prüfen, ob es bereits einen Admin gibt
user = User( admin_exists = User.query.filter_by(role='admin').first() is not None
github_id=github_id,
username=username, # Falls kein Admin existiert, wird der erste Benutzer zum Admin
display_name=display_name, role = 'admin' if not admin_exists else 'user'
email=primary_email,
role='guest' # Standardrolle für neue Benutzer user = User(
) username=username,
db.session.add(user) display_name=display_name,
db.session.commit() email=email,
app.logger.info(f'Neuer Benutzer über GitHub registriert: {username}') role=role
else: )
# Aktualisiere Benutzerdaten, falls sie sich geändert haben user.set_password(password)
user.username = username
user.display_name = display_name db.session.add(user)
if primary_email: db.session.commit()
user.email = primary_email app.logger.info(f'Neuer Benutzer registriert: {username} (Rolle: {role})')
db.session.commit()
# Session erstellen # Session erstellen
create_session(user) create_session(user)
# Weiterleitung zur Frontend-App return jsonify({
return redirect('/') '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']) @app.route('/auth/logout', methods=['POST'])
def logout(): def logout():
@ -681,6 +681,40 @@ def check_jobs():
def test(): def test():
return jsonify({'message': 'MYP Backend API funktioniert!'}) 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 # Error Handler
@app.errorhandler(404) @app.errorhandler(404)
def not_found(error): def not_found(error):