@ -11,6 +11,7 @@ from bot.startup_discord_extension import StartupDiscordExtension
|
||||
from bot.startup_migration_extension import StartupMigrationExtension
|
||||
from bot.startup_module_extension import StartupModuleExtension
|
||||
from bot.startup_settings_extension import StartupSettingsExtension
|
||||
from bot_api.app_api_extension import AppApiExtension
|
||||
from modules.boot_log.boot_log_extension import BootLogExtension
|
||||
from modules.database.database_extension import DatabaseExtension
|
||||
|
||||
@ -29,6 +30,7 @@ class Program:
|
||||
.use_extension(StartupMigrationExtension) \
|
||||
.use_extension(BootLogExtension) \
|
||||
.use_extension(DatabaseExtension) \
|
||||
.use_extension(AppApiExtension) \
|
||||
.use_startup(Startup)
|
||||
self.app: Application = await app_builder.build_async()
|
||||
await self.app.run_async()
|
||||
|
@ -9,6 +9,7 @@ from bot_api.model.email_string_dto import EMailStringDTO
|
||||
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||
from bot_api.model.token_dto import TokenDTO
|
||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||
from bot_data.model.auth_user import AuthUser
|
||||
|
||||
|
||||
class AuthServiceABC(ABC):
|
||||
@ -16,6 +17,12 @@ class AuthServiceABC(ABC):
|
||||
@abstractmethod
|
||||
def __init__(self): pass
|
||||
|
||||
@abstractmethod
|
||||
def generate_token(self, user: AuthUser) -> str: pass
|
||||
|
||||
@abstractmethod
|
||||
def decode_token(self, token: str) -> dict: pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass
|
||||
|
||||
@ -43,6 +50,9 @@ class AuthServiceABC(ABC):
|
||||
@abstractmethod
|
||||
async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def verify_login(self, token_str: str) -> bool: pass
|
||||
|
||||
@abstractmethod
|
||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass
|
||||
|
||||
|
26
kdb-bot/src/bot_api/app_api_extension.py
Normal file
26
kdb-bot/src/bot_api/app_api_extension.py
Normal file
@ -0,0 +1,26 @@
|
||||
from cpl_core.application import ApplicationExtensionABC
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||
from bot_api.route.route import Route
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||
|
||||
|
||||
class AppApiExtension(ApplicationExtensionABC):
|
||||
|
||||
def __init__(self):
|
||||
ApplicationExtensionABC.__init__(self)
|
||||
|
||||
async def run(self, config: ConfigurationABC, services: ServiceProviderABC):
|
||||
feature_flags: FeatureFlagsSettings = config.get_configuration(FeatureFlagsSettings)
|
||||
if not feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
return
|
||||
|
||||
auth_settings: AuthenticationSettings = config.get_configuration(AuthenticationSettings)
|
||||
auth_users: AuthUserRepositoryABC = services.get_service(AuthUserRepositoryABC)
|
||||
auth: AuthServiceABC = services.get_service(AuthServiceABC)
|
||||
Route.init_authorize(auth_users, auth)
|
@ -39,11 +39,13 @@ class AuthController:
|
||||
self._auth_service = auth_service
|
||||
|
||||
@Route.get(f'{BasePath}/users')
|
||||
@Route.authorize
|
||||
async def get_all_users(self) -> Response:
|
||||
result = await self._auth_service.get_all_auth_users_async()
|
||||
return jsonify(result.select(lambda x: x.to_dict()))
|
||||
|
||||
@Route.post(f'{BasePath}/users/get/filtered')
|
||||
@Route.authorize
|
||||
async def get_filtered_users(self) -> Response:
|
||||
dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
|
||||
result = await self._auth_service.get_filtered_auth_users_async(dto)
|
||||
@ -51,11 +53,13 @@ class AuthController:
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.get(f'{BasePath}/users/get/<email>')
|
||||
@Route.authorize
|
||||
async def get_user_from_email(self, email: str) -> Response:
|
||||
result = await self._auth_service.get_auth_user_by_email_async(email)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.get(f'{BasePath}/users/find/<email>')
|
||||
edraft marked this conversation as resolved
|
||||
@Route.authorize
|
||||
async def find_user_from_email(self, email: str) -> Response:
|
||||
result = await self._auth_service.find_auth_user_by_email_async(email)
|
||||
return jsonify(result.to_dict())
|
||||
@ -83,36 +87,42 @@ class AuthController:
|
||||
return '', 200
|
||||
|
||||
@Route.post(f'{BasePath}/update-user')
|
||||
@Route.authorize
|
||||
async def update_user(self):
|
||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.update_user_async(dto)
|
||||
return '', 200
|
||||
|
||||
@Route.post(f'{BasePath}/update-user-as-admin')
|
||||
@Route.authorize
|
||||
async def update_user_as_admin(self):
|
||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.update_user_async(dto)
|
||||
return '', 200
|
||||
|
||||
@Route.post(f'{BasePath}/refresh')
|
||||
@Route.authorize
|
||||
async def refresh(self) -> Response:
|
||||
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
||||
result = await self._auth_service.refresh_async(dto)
|
||||
return jsonify(result.to_dict())
|
||||
|
||||
@Route.post(f'{BasePath}/revoke')
|
||||
@Route.authorize
|
||||
async def revoke(self):
|
||||
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.revoke_async(dto)
|
||||
return '', 200
|
||||
|
||||
@Route.post(f'{BasePath}/delete-user')
|
||||
@Route.authorize
|
||||
async def delete_user(self):
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Was passiert, wenn ein User eine JSON sendet mit der E-Mail-Adresse eines anderen Users? Was passiert, wenn ein User eine JSON sendet mit der E-Mail-Adresse eines anderen Users?
Also versuchen die Einstellungen eines anderen Users überscheiben.
edraft
commented
Er muss das Passwort des Users kennen Er muss das Passwort des Users kennen
|
||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||
await self._auth_service.delete_auth_user_async(dto)
|
||||
return '', 200
|
||||
|
||||
@Route.post(f'{BasePath}/delete-user-by-mail/<email>')
|
||||
@Route.authorize
|
||||
async def delete_user_by_mail(self, email: str):
|
||||
await self._auth_service.delete_auth_user_by_email_async(email)
|
||||
return '', 200
|
||||
|
@ -40,6 +40,7 @@ class GuiController:
|
||||
return VersionDTO(version.major, version.minor, version.micro).to_dict()
|
||||
|
||||
@Route.get(f'{BasePath}/settings')
|
||||
@Route.authorize
|
||||
async def settings(self):
|
||||
# TODO: Authentication
|
||||
import bot_api
|
||||
@ -61,6 +62,7 @@ class GuiController:
|
||||
).to_dict()
|
||||
|
||||
@Route.get(f'{BasePath}/send-test-mail/<email>')
|
||||
@Route.authorize
|
||||
async def send_test_mail(self, email: str):
|
||||
# TODO: Authentication
|
||||
mail = EMail()
|
||||
|
@ -1,9 +1,48 @@
|
||||
from functools import wraps
|
||||
from typing import Optional
|
||||
|
||||
from flask import request
|
||||
from flask_cors import cross_origin
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.exception.service_error_code_enum import ServiceErrorCode
|
||||
from bot_api.exception.service_exception import ServiceException
|
||||
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
|
||||
|
||||
|
||||
class Route:
|
||||
registered_routes = {}
|
||||
|
||||
_auth_users: Optional[AuthUserRepositoryABC] = None
|
||||
_auth: Optional[AuthServiceABC] = None
|
||||
|
||||
@classmethod
|
||||
def init_authorize(cls, auth_users: AuthUserRepositoryABC, auth: AuthServiceABC):
|
||||
cls._auth_users = auth_users
|
||||
cls._auth = auth
|
||||
|
||||
@classmethod
|
||||
def authorize(cls, f):
|
||||
@wraps(f)
|
||||
async def decorator(*args, **kwargs):
|
||||
token = None
|
||||
edraft marked this conversation as resolved
Ebola-Chan
commented
Vorschlag: Die HTTP-Response-Status-Codes in einem Enum packen, damit man schneller erkennen kann, um was die Response handelt. Vorschlag: Die HTTP-Response-Status-Codes in einem Enum packen, damit man schneller erkennen kann, um was die Response handelt.
edraft
commented
Kann man später mal nachbessern aber die API ist jetzt noch übersichtlich genug Kann man später mal nachbessern aber die API ist jetzt noch übersichtlich genug
|
||||
if 'Authorization' in request.headers:
|
||||
bearer = request.headers.get('Authorization')
|
||||
token = bearer.split()[1]
|
||||
|
||||
if not token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set')
|
||||
|
||||
if cls._auth_users is None or cls._auth is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidDependencies, f'Authorize is not initialized')
|
||||
|
||||
if not cls._auth.verify_login(token):
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, f'Token expired')
|
||||
|
||||
return await f(*args, **kwargs)
|
||||
|
||||
return decorator
|
||||
|
||||
@classmethod
|
||||
def route(cls, path=None, **kwargs):
|
||||
# simple decorator for class based views
|
||||
|
@ -70,7 +70,7 @@ class AuthService(AuthServiceABC):
|
||||
|
||||
return False
|
||||
|
||||
def _generate_token(self, user: AuthUser) -> str:
|
||||
def generate_token(self, user: AuthUser) -> str:
|
||||
token = jwt.encode(
|
||||
payload={
|
||||
'user_id': user.id,
|
||||
@ -85,7 +85,7 @@ class AuthService(AuthServiceABC):
|
||||
|
||||
return token
|
||||
|
||||
def _decode_token(self, token: str) -> dict:
|
||||
def decode_token(self, token: str) -> dict:
|
||||
return jwt.decode(
|
||||
token,
|
||||
key=self._auth_settings.secret_key,
|
||||
@ -292,6 +292,21 @@ class AuthService(AuthServiceABC):
|
||||
self._logger.error(__name__, f'Cannot delete user', e)
|
||||
raise ServiceException(ServiceErrorCode.UnableToDelete, f'Cannot delete user by mail {user_dto.email}')
|
||||
|
||||
def verify_login(self, token_str: str) -> bool:
|
||||
try:
|
||||
token = self.decode_token(token_str)
|
||||
if token is None or 'email' not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
||||
|
||||
user = self._auth_users.find_auth_user_by_email(token['email'])
|
||||
if user is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f'Refreshing token failed', e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def login_async(self, user_dto: AuthUser) -> TokenDTO:
|
||||
if user_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'User not set')
|
||||
@ -304,7 +319,7 @@ class AuthService(AuthServiceABC):
|
||||
if db_user.password != user_dto.password:
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, 'Wrong password')
|
||||
|
||||
token = self._generate_token(db_user)
|
||||
token = self.generate_token(db_user)
|
||||
refresh_token = self._create_and_save_refresh_token(db_user)
|
||||
if db_user.forgot_password_id is not None:
|
||||
db_user.forgot_password_id = None
|
||||
@ -316,16 +331,16 @@ class AuthService(AuthServiceABC):
|
||||
if token_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set')
|
||||
|
||||
token = self._decode_token(token_dto.token)
|
||||
if token is None or 'email' not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
||||
|
||||
try:
|
||||
token = self.decode_token(token_dto.token)
|
||||
if token is None or 'email' not in token:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
||||
|
||||
user = self._auth_users.get_auth_user_by_email(token['email'])
|
||||
if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now():
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
|
||||
|
||||
return TokenDTO(self._generate_token(user), self._create_and_save_refresh_token(user))
|
||||
return TokenDTO(self.generate_token(user), self._create_and_save_refresh_token(user))
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f'Refreshing token failed', e)
|
||||
return TokenDTO('', '')
|
||||
@ -334,8 +349,9 @@ class AuthService(AuthServiceABC):
|
||||
if token_dto is None or token_dto.token is None or token_dto.refresh_token is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token not set')
|
||||
|
||||
token = self._decode_token(token_dto.token)
|
||||
try:
|
||||
token = self.decode_token(token_dto.token)
|
||||
|
||||
user = self._auth_users.get_auth_user_by_email(token['email'])
|
||||
if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now():
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
|
||||
|
Loading…
Reference in New Issue
Block a user
Was passiert, wenn eine andere E-Mail-Adresse angegeben wird?
Wenn eine positive Antwort gegeben wird: Ist es gewollt, dass ein User eine andere E-Mail-Adresse aufrufen kann?
Wenn ja: Soll ein User eine E-Mail-Adresse abfragen können, mit dem dieser nichts zu tun hat?
was spricht dagegen?
So kannst du Profile von anderen usern sehen, kannst damit ja nichts ans pw kommen.
Naja, so könnte sich jemand registrieren und dann somit eine Liste von gültigen E-Mails erstellen für Spammers.
Wenn der User nur Profile von anderen Usern aufrufen kann, mit dem er zu tun hat, dann wäre es besser.