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:
parent
73ead5939c
commit
70aeb17cdb
166
backend/app.py
166
backend/app.py
@ -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):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user