0.3 #146
@ -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>')
|
||||
@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):
|
||||
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
|
||||
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