forked from sh-edraft.de/sh_discord_bot
Added authorize check to controller #70
This commit is contained in:
parent
651482a1b9
commit
3fe8e1503c
@ -11,6 +11,7 @@ from bot.startup_discord_extension import StartupDiscordExtension
|
|||||||
from bot.startup_migration_extension import StartupMigrationExtension
|
from bot.startup_migration_extension import StartupMigrationExtension
|
||||||
from bot.startup_module_extension import StartupModuleExtension
|
from bot.startup_module_extension import StartupModuleExtension
|
||||||
from bot.startup_settings_extension import StartupSettingsExtension
|
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.boot_log.boot_log_extension import BootLogExtension
|
||||||
from modules.database.database_extension import DatabaseExtension
|
from modules.database.database_extension import DatabaseExtension
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ class Program:
|
|||||||
.use_extension(StartupMigrationExtension) \
|
.use_extension(StartupMigrationExtension) \
|
||||||
.use_extension(BootLogExtension) \
|
.use_extension(BootLogExtension) \
|
||||||
.use_extension(DatabaseExtension) \
|
.use_extension(DatabaseExtension) \
|
||||||
|
.use_extension(AppApiExtension) \
|
||||||
.use_startup(Startup)
|
.use_startup(Startup)
|
||||||
self.app: Application = await app_builder.build_async()
|
self.app: Application = await app_builder.build_async()
|
||||||
await self.app.run_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.reset_password_dto import ResetPasswordDTO
|
||||||
from bot_api.model.token_dto import TokenDTO
|
from bot_api.model.token_dto import TokenDTO
|
||||||
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
from bot_api.model.update_auth_user_dto import UpdateAuthUserDTO
|
||||||
|
from bot_data.model.auth_user import AuthUser
|
||||||
|
|
||||||
|
|
||||||
class AuthServiceABC(ABC):
|
class AuthServiceABC(ABC):
|
||||||
@ -16,6 +17,12 @@ class AuthServiceABC(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def __init__(self): pass
|
def __init__(self): pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def generate_token(self, user: AuthUser) -> str: pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def decode_token(self, token: str) -> dict: pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass
|
async def get_all_auth_users_async(self) -> List[AuthUserDTO]: pass
|
||||||
|
|
||||||
@ -43,6 +50,9 @@ class AuthServiceABC(ABC):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass
|
async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
async def verify_login(self, token_str: str) -> bool: pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass
|
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
|
self._auth_service = auth_service
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/users')
|
@Route.get(f'{BasePath}/users')
|
||||||
|
@Route.authorize
|
||||||
async def get_all_users(self) -> Response:
|
async def get_all_users(self) -> Response:
|
||||||
result = await self._auth_service.get_all_auth_users_async()
|
result = await self._auth_service.get_all_auth_users_async()
|
||||||
return jsonify(result.select(lambda x: x.to_dict()))
|
return jsonify(result.select(lambda x: x.to_dict()))
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/users/get/filtered')
|
@Route.post(f'{BasePath}/users/get/filtered')
|
||||||
|
@Route.authorize
|
||||||
async def get_filtered_users(self) -> Response:
|
async def get_filtered_users(self) -> Response:
|
||||||
dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
|
dto: AuthUserSelectCriteria = JSONProcessor.process(AuthUserSelectCriteria, request.get_json(force=True, silent=True))
|
||||||
result = await self._auth_service.get_filtered_auth_users_async(dto)
|
result = await self._auth_service.get_filtered_auth_users_async(dto)
|
||||||
@ -51,11 +53,13 @@ class AuthController:
|
|||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/users/get/<email>')
|
@Route.get(f'{BasePath}/users/get/<email>')
|
||||||
|
@Route.authorize
|
||||||
async def get_user_from_email(self, email: str) -> Response:
|
async def get_user_from_email(self, email: str) -> Response:
|
||||||
result = await self._auth_service.get_auth_user_by_email_async(email)
|
result = await self._auth_service.get_auth_user_by_email_async(email)
|
||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/users/find/<email>')
|
@Route.get(f'{BasePath}/users/find/<email>')
|
||||||
|
@Route.authorize
|
||||||
async def find_user_from_email(self, email: str) -> Response:
|
async def find_user_from_email(self, email: str) -> Response:
|
||||||
result = await self._auth_service.find_auth_user_by_email_async(email)
|
result = await self._auth_service.find_auth_user_by_email_async(email)
|
||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
@ -83,36 +87,42 @@ class AuthController:
|
|||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/update-user')
|
@Route.post(f'{BasePath}/update-user')
|
||||||
|
@Route.authorize
|
||||||
async def update_user(self):
|
async def update_user(self):
|
||||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||||
await self._auth_service.update_user_async(dto)
|
await self._auth_service.update_user_async(dto)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/update-user-as-admin')
|
@Route.post(f'{BasePath}/update-user-as-admin')
|
||||||
|
@Route.authorize
|
||||||
async def update_user_as_admin(self):
|
async def update_user_as_admin(self):
|
||||||
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
dto: UpdateAuthUserDTO = JSONProcessor.process(UpdateAuthUserDTO, request.get_json(force=True, silent=True))
|
||||||
await self._auth_service.update_user_async(dto)
|
await self._auth_service.update_user_async(dto)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/refresh')
|
@Route.post(f'{BasePath}/refresh')
|
||||||
|
@Route.authorize
|
||||||
async def refresh(self) -> Response:
|
async def refresh(self) -> Response:
|
||||||
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
||||||
result = await self._auth_service.refresh_async(dto)
|
result = await self._auth_service.refresh_async(dto)
|
||||||
return jsonify(result.to_dict())
|
return jsonify(result.to_dict())
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/revoke')
|
@Route.post(f'{BasePath}/revoke')
|
||||||
|
@Route.authorize
|
||||||
async def revoke(self):
|
async def revoke(self):
|
||||||
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
dto: TokenDTO = JSONProcessor.process(TokenDTO, request.get_json(force=True, silent=True))
|
||||||
await self._auth_service.revoke_async(dto)
|
await self._auth_service.revoke_async(dto)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/delete-user')
|
@Route.post(f'{BasePath}/delete-user')
|
||||||
|
@Route.authorize
|
||||||
async def delete_user(self):
|
async def delete_user(self):
|
||||||
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
dto: AuthUserDTO = JSONProcessor.process(AuthUserDTO, request.get_json(force=True, silent=True))
|
||||||
await self._auth_service.delete_auth_user_async(dto)
|
await self._auth_service.delete_auth_user_async(dto)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
|
||||||
@Route.post(f'{BasePath}/delete-user-by-mail/<email>')
|
@Route.post(f'{BasePath}/delete-user-by-mail/<email>')
|
||||||
|
@Route.authorize
|
||||||
async def delete_user_by_mail(self, email: str):
|
async def delete_user_by_mail(self, email: str):
|
||||||
await self._auth_service.delete_auth_user_by_email_async(email)
|
await self._auth_service.delete_auth_user_by_email_async(email)
|
||||||
return '', 200
|
return '', 200
|
||||||
|
@ -40,6 +40,7 @@ class GuiController:
|
|||||||
return VersionDTO(version.major, version.minor, version.micro).to_dict()
|
return VersionDTO(version.major, version.minor, version.micro).to_dict()
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/settings')
|
@Route.get(f'{BasePath}/settings')
|
||||||
|
@Route.authorize
|
||||||
async def settings(self):
|
async def settings(self):
|
||||||
# TODO: Authentication
|
# TODO: Authentication
|
||||||
import bot_api
|
import bot_api
|
||||||
@ -61,6 +62,7 @@ class GuiController:
|
|||||||
).to_dict()
|
).to_dict()
|
||||||
|
|
||||||
@Route.get(f'{BasePath}/send-test-mail/<email>')
|
@Route.get(f'{BasePath}/send-test-mail/<email>')
|
||||||
|
@Route.authorize
|
||||||
async def send_test_mail(self, email: str):
|
async def send_test_mail(self, email: str):
|
||||||
# TODO: Authentication
|
# TODO: Authentication
|
||||||
mail = EMail()
|
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 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:
|
class Route:
|
||||||
registered_routes = {}
|
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
|
||||||
|
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
|
@classmethod
|
||||||
def route(cls, path=None, **kwargs):
|
def route(cls, path=None, **kwargs):
|
||||||
# simple decorator for class based views
|
# simple decorator for class based views
|
||||||
|
@ -70,7 +70,7 @@ class AuthService(AuthServiceABC):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _generate_token(self, user: AuthUser) -> str:
|
def generate_token(self, user: AuthUser) -> str:
|
||||||
token = jwt.encode(
|
token = jwt.encode(
|
||||||
payload={
|
payload={
|
||||||
'user_id': user.id,
|
'user_id': user.id,
|
||||||
@ -85,7 +85,7 @@ class AuthService(AuthServiceABC):
|
|||||||
|
|
||||||
return token
|
return token
|
||||||
|
|
||||||
def _decode_token(self, token: str) -> dict:
|
def decode_token(self, token: str) -> dict:
|
||||||
return jwt.decode(
|
return jwt.decode(
|
||||||
token,
|
token,
|
||||||
key=self._auth_settings.secret_key,
|
key=self._auth_settings.secret_key,
|
||||||
@ -292,6 +292,21 @@ class AuthService(AuthServiceABC):
|
|||||||
self._logger.error(__name__, f'Cannot delete user', e)
|
self._logger.error(__name__, f'Cannot delete user', e)
|
||||||
raise ServiceException(ServiceErrorCode.UnableToDelete, f'Cannot delete user by mail {user_dto.email}')
|
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:
|
async def login_async(self, user_dto: AuthUser) -> TokenDTO:
|
||||||
if user_dto is None:
|
if user_dto is None:
|
||||||
raise ServiceException(ServiceErrorCode.InvalidData, 'User not set')
|
raise ServiceException(ServiceErrorCode.InvalidData, 'User not set')
|
||||||
@ -304,7 +319,7 @@ class AuthService(AuthServiceABC):
|
|||||||
if db_user.password != user_dto.password:
|
if db_user.password != user_dto.password:
|
||||||
raise ServiceException(ServiceErrorCode.InvalidUser, 'Wrong 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)
|
refresh_token = self._create_and_save_refresh_token(db_user)
|
||||||
if db_user.forgot_password_id is not None:
|
if db_user.forgot_password_id is not None:
|
||||||
db_user.forgot_password_id = None
|
db_user.forgot_password_id = None
|
||||||
@ -316,16 +331,16 @@ class AuthService(AuthServiceABC):
|
|||||||
if token_dto is None:
|
if token_dto is None:
|
||||||
raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set')
|
raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set')
|
||||||
|
|
||||||
token = self._decode_token(token_dto.token)
|
try:
|
||||||
|
token = self.decode_token(token_dto.token)
|
||||||
if token is None or 'email' not in token:
|
if token is None or 'email' not in token:
|
||||||
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
raise ServiceException(ServiceErrorCode.InvalidData, 'Token invalid')
|
||||||
|
|
||||||
try:
|
|
||||||
user = self._auth_users.get_auth_user_by_email(token['email'])
|
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():
|
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')
|
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:
|
except Exception as e:
|
||||||
self._logger.error(__name__, f'Refreshing token failed', e)
|
self._logger.error(__name__, f'Refreshing token failed', e)
|
||||||
return TokenDTO('', '')
|
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:
|
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')
|
raise ServiceException(ServiceErrorCode.InvalidData, 'Token not set')
|
||||||
|
|
||||||
token = self._decode_token(token_dto.token)
|
|
||||||
try:
|
try:
|
||||||
|
token = self.decode_token(token_dto.token)
|
||||||
|
|
||||||
user = self._auth_users.get_auth_user_by_email(token['email'])
|
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():
|
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')
|
raise ServiceException(ServiceErrorCode.InvalidData, 'Token expired')
|
||||||
|
Loading…
Reference in New Issue
Block a user