0.3 #146

Merged
edraft merged 359 commits from 0.3 into master 2023-01-12 07:04:40 +01:00
7 changed files with 114 additions and 9 deletions
Showing only changes of commit 3fe8e1503c - Show all commits

View File

@ -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()

View File

@ -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

View 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)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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')