Added logic to create default api-key for frontend #162-3
This commit is contained in:
		| @@ -83,6 +83,10 @@ class AuthServiceABC(ABC): | |||||||
|     async def verify_login(self, token_str: str) -> bool: |     async def verify_login(self, token_str: str) -> bool: | ||||||
|         pass |         pass | ||||||
|  |  | ||||||
|  |     @abstractmethod | ||||||
|  |     async def verify_api_key(self, api_key: str) -> bool: | ||||||
|  |         pass | ||||||
|  |  | ||||||
|     @abstractmethod |     @abstractmethod | ||||||
|     async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: |     async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: | ||||||
|         pass |         pass | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ from bot_api.api import Api | |||||||
| from bot_api.api_thread import ApiThread | from bot_api.api_thread import ApiThread | ||||||
| from bot_api.controller.auth_controller import AuthController | from bot_api.controller.auth_controller import AuthController | ||||||
| from bot_api.controller.auth_discord_controller import AuthDiscordController | from bot_api.controller.auth_discord_controller import AuthDiscordController | ||||||
| from bot_api.controller.grahpql_controller import GraphQLController | from bot_api.controller.graphql_controller import GraphQLController | ||||||
| from bot_api.controller.gui_controller import GuiController | from bot_api.controller.gui_controller import GuiController | ||||||
| from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent | from bot_api.event.bot_api_on_ready_event import BotApiOnReadyEvent | ||||||
| from bot_api.service.auth_service import AuthService | from bot_api.service.auth_service import AuthService | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class GraphQLController: | |||||||
|         return PLAYGROUND_HTML, 200 |         return PLAYGROUND_HTML, 200 | ||||||
| 
 | 
 | ||||||
|     @Route.post(f"{BasePath}") |     @Route.post(f"{BasePath}") | ||||||
|     @Route.authorize |     @Route.authorize(by_api_key=True) | ||||||
|     async def graphql(self): |     async def graphql(self): | ||||||
|         data = request.get_json() |         data = request.get_json() | ||||||
| 
 | 
 | ||||||
| @@ -30,15 +30,32 @@ class Route: | |||||||
|         cls._env = env.environment_name |         cls._env = env.environment_name | ||||||
|  |  | ||||||
|     @classmethod |     @classmethod | ||||||
|     def authorize(cls, f: Callable = None, role: AuthRoleEnum = None, skip_in_dev=False): |     def authorize(cls, f: Callable = None, role: AuthRoleEnum = None, skip_in_dev=False, by_api_key=False): | ||||||
|         if f is None: |         if f is None: | ||||||
|             return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev) |             return functools.partial(cls.authorize, role=role, skip_in_dev=skip_in_dev, by_api_key=by_api_key) | ||||||
|  |  | ||||||
|         @wraps(f) |         @wraps(f) | ||||||
|         async def decorator(*args, **kwargs): |         async def decorator(*args, **kwargs): | ||||||
|             if skip_in_dev and cls._env == "development": |             if skip_in_dev and cls._env == "development": | ||||||
|                 return await f(*args, **kwargs) |                 return await f(*args, **kwargs) | ||||||
|  |  | ||||||
|  |             if "Authorization" not in request.headers and by_api_key and "API-Key" in request.headers: | ||||||
|  |                 valid = False | ||||||
|  |                 try: | ||||||
|  |                     valid = cls._auth.verify_api_key(request.headers["API-Key"]) | ||||||
|  |                 except ServiceException as e: | ||||||
|  |                     error = ErrorDTO(e.error_code, e.message) | ||||||
|  |                     return jsonify(error.to_dict()), 403 | ||||||
|  |                 except Exception as e: | ||||||
|  |                     return jsonify(e), 500 | ||||||
|  |  | ||||||
|  |                 if not valid: | ||||||
|  |                     ex = ServiceException(ServiceErrorCode.Unauthorized, f"API-Key invalid") | ||||||
|  |                     error = ErrorDTO(ex.error_code, ex.message) | ||||||
|  |                     return jsonify(error.to_dict()), 401 | ||||||
|  |  | ||||||
|  |                 return await f(*args, **kwargs) | ||||||
|  |  | ||||||
|             token = None |             token = None | ||||||
|             if "Authorization" in request.headers: |             if "Authorization" in request.headers: | ||||||
|                 bearer = request.headers.get("Authorization") |                 bearer = request.headers.get("Authorization") | ||||||
|   | |||||||
| @@ -31,9 +31,11 @@ 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_api.transformer.auth_user_transformer import AuthUserTransformer as AUT | from bot_api.transformer.auth_user_transformer import AuthUserTransformer as AUT | ||||||
|  | from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC | ||||||
| from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC | ||||||
| from bot_data.abc.server_repository_abc import ServerRepositoryABC | from bot_data.abc.server_repository_abc import ServerRepositoryABC | ||||||
| from bot_data.abc.user_repository_abc import UserRepositoryABC | from bot_data.abc.user_repository_abc import UserRepositoryABC | ||||||
|  | from bot_data.model.api_key import ApiKey | ||||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | from bot_data.model.auth_role_enum import AuthRoleEnum | ||||||
| from bot_data.model.auth_user import AuthUser | from bot_data.model.auth_user import AuthUser | ||||||
| from bot_data.model.auth_user_users_relation import AuthUserUsersRelation | from bot_data.model.auth_user_users_relation import AuthUserUsersRelation | ||||||
| @@ -49,9 +51,9 @@ class AuthService(AuthServiceABC): | |||||||
|         bot: DiscordBotServiceABC, |         bot: DiscordBotServiceABC, | ||||||
|         db: DatabaseContextABC, |         db: DatabaseContextABC, | ||||||
|         auth_users: AuthUserRepositoryABC, |         auth_users: AuthUserRepositoryABC, | ||||||
|  |         api_keys: ApiKeyRepositoryABC, | ||||||
|         users: UserRepositoryABC, |         users: UserRepositoryABC, | ||||||
|         servers: ServerRepositoryABC, |         servers: ServerRepositoryABC, | ||||||
|         # mailer: MailThread, |  | ||||||
|         mailer: EMailClientABC, |         mailer: EMailClientABC, | ||||||
|         t: TranslatePipe, |         t: TranslatePipe, | ||||||
|         auth_settings: AuthenticationSettings, |         auth_settings: AuthenticationSettings, | ||||||
| @@ -64,6 +66,7 @@ class AuthService(AuthServiceABC): | |||||||
|         self._bot = bot |         self._bot = bot | ||||||
|         self._db = db |         self._db = db | ||||||
|         self._auth_users = auth_users |         self._auth_users = auth_users | ||||||
|  |         self._api_keys = api_keys | ||||||
|         self._users = users |         self._users = users | ||||||
|         self._servers = servers |         self._servers = servers | ||||||
|         self._mailer = mailer |         self._mailer = mailer | ||||||
| @@ -82,6 +85,11 @@ class AuthService(AuthServiceABC): | |||||||
|  |  | ||||||
|         return False |         return False | ||||||
|  |  | ||||||
|  |     def _get_api_key_str(self, api_key: ApiKey) -> str: | ||||||
|  |         return hashlib.sha256( | ||||||
|  |             f"{api_key.identifier}:{api_key.key}+{self._auth_settings.secret_key}".encode("utf-8") | ||||||
|  |         ).hexdigest() | ||||||
|  |  | ||||||
|     def generate_token(self, user: AuthUser) -> str: |     def generate_token(self, user: AuthUser) -> str: | ||||||
|         token = jwt.encode( |         token = jwt.encode( | ||||||
|             payload={ |             payload={ | ||||||
| @@ -221,7 +229,12 @@ class AuthService(AuthServiceABC): | |||||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") |             raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists") | ||||||
|  |  | ||||||
|         user = AUT.to_db(user_dto) |         user = AUT.to_db(user_dto) | ||||||
|         if self._auth_users.get_all_auth_users().count() == 0: |         if ( | ||||||
|  |             self._auth_users.get_all_auth_users() | ||||||
|  |             .where(lambda x: x.name != "internal" and x.email != "internal@localhost") | ||||||
|  |             .count() | ||||||
|  |             == 0 | ||||||
|  |         ): | ||||||
|             user.auth_role = AuthRoleEnum.admin |             user.auth_role = AuthRoleEnum.admin | ||||||
|  |  | ||||||
|         user.password_salt = uuid.uuid4() |         user.password_salt = uuid.uuid4() | ||||||
| @@ -478,6 +491,18 @@ class AuthService(AuthServiceABC): | |||||||
|  |  | ||||||
|         return True |         return True | ||||||
|  |  | ||||||
|  |     def verify_api_key(self, api_key: str) -> bool: | ||||||
|  |         try: | ||||||
|  |             keys = self._api_keys.get_api_keys().select(self._get_api_key_str) | ||||||
|  |  | ||||||
|  |             if not keys.contains(api_key): | ||||||
|  |                 raise ServiceException(ServiceErrorCode.InvalidData, "API-Key invalid") | ||||||
|  |         except Exception as e: | ||||||
|  |             self._logger.error(__name__, f"Token invalid", 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") | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ class ApiKeyMigration(MigrationABC): | |||||||
|                 `Id` BIGINT NOT NULL AUTO_INCREMENT, |                 `Id` BIGINT NOT NULL AUTO_INCREMENT, | ||||||
|                 `Identifier` VARCHAR(255) NOT NULL, |                 `Identifier` VARCHAR(255) NOT NULL, | ||||||
|                 `Key` VARCHAR(255) NOT NULL, |                 `Key` VARCHAR(255) NOT NULL, | ||||||
|                 `CreatorId` BIGINT, |                 `CreatorId` BIGINT NULL, | ||||||
|                 `CreatedAt` DATETIME(6), |                 `CreatedAt` DATETIME(6), | ||||||
|                 `LastModifiedAt` DATETIME(6), |                 `LastModifiedAt` DATETIME(6), | ||||||
|                 PRIMARY KEY(`Id`), |                 PRIMARY KEY(`Id`), | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
|  | from typing import Optional | ||||||
|  |  | ||||||
| from cpl_core.database import TableABC | from cpl_core.database import TableABC | ||||||
|  |  | ||||||
| @@ -10,7 +11,7 @@ class ApiKey(TableABC): | |||||||
|         self, |         self, | ||||||
|         identifier: str, |         identifier: str, | ||||||
|         key: str, |         key: str, | ||||||
|         creator: User, |         creator: Optional[User], | ||||||
|         created_at: datetime = None, |         created_at: datetime = None, | ||||||
|         modified_at: datetime = None, |         modified_at: datetime = None, | ||||||
|         id=0, |         id=0, | ||||||
| @@ -33,7 +34,7 @@ class ApiKey(TableABC): | |||||||
|         return self._key |         return self._key | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def creator(self) -> User: |     def creator(self) -> Optional[User]: | ||||||
|         return self._creator |         return self._creator | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
| @@ -72,7 +73,7 @@ class ApiKey(TableABC): | |||||||
|             ) VALUES ( |             ) VALUES ( | ||||||
|                 '{self._identifier}', |                 '{self._identifier}', | ||||||
|                 '{self._key}', |                 '{self._key}', | ||||||
|                 '{self._creator.user_id}', |                 {"NULL" if self._creator is None else self._creator.user_id}, | ||||||
|                 '{self._created_at}', |                 '{self._created_at}', | ||||||
|                 '{self._modified_at}' |                 '{self._modified_at}' | ||||||
|             ); |             ); | ||||||
|   | |||||||
| @@ -31,10 +31,11 @@ class ApiKeyRepositoryService(ApiKeyRepositoryABC): | |||||||
|         return value |         return value | ||||||
|  |  | ||||||
|     def _api_key_from_result(self, sql_result: tuple) -> ApiKey: |     def _api_key_from_result(self, sql_result: tuple) -> ApiKey: | ||||||
|  |         creator = self._get_value_from_result(sql_result[3]) | ||||||
|         api_key = ApiKey( |         api_key = ApiKey( | ||||||
|             self._get_value_from_result(sql_result[1]), |             self._get_value_from_result(sql_result[1]), | ||||||
|             self._get_value_from_result(sql_result[2]), |             self._get_value_from_result(sql_result[2]), | ||||||
|             self._users.get_user_by_id(int(self._get_value_from_result(sql_result[3]))), |             None if creator is None else self._users.get_user_by_id(int(creator)), | ||||||
|             self._get_value_from_result(sql_result[4]), |             self._get_value_from_result(sql_result[4]), | ||||||
|             self._get_value_from_result(sql_result[5]), |             self._get_value_from_result(sql_result[5]), | ||||||
|             id=self._get_value_from_result(sql_result[0]), |             id=self._get_value_from_result(sql_result[0]), | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| from cpl_core.database.context import DatabaseContextABC | from cpl_core.database.context import DatabaseContextABC | ||||||
| from cpl_core.dependency_injection import ServiceProviderABC | from cpl_core.dependency_injection import ServiceProviderABC | ||||||
| from cpl_query.extension import List |  | ||||||
|  |  | ||||||
| from bot_core.logging.database_logger import DatabaseLogger | from bot_core.logging.database_logger import DatabaseLogger | ||||||
| from bot_data.abc.data_seeder_abc import DataSeederABC | from bot_data.abc.data_seeder_abc import DataSeederABC | ||||||
| @@ -18,12 +17,10 @@ class SeederService: | |||||||
|  |  | ||||||
|         self._db = db |         self._db = db | ||||||
|  |  | ||||||
|         self._seeder = List(type, DataSeederABC.__subclasses__()) |  | ||||||
|  |  | ||||||
|     async def seed(self): |     async def seed(self): | ||||||
|         self._logger.info(__name__, f"Seed data") |         self._logger.info(__name__, f"Seed data") | ||||||
|         for seeder in self._seeder: |         for seeder in self._services.get_services(list[DataSeederABC]): | ||||||
|             seeder_as_service: DataSeederABC = self._services.get_service(seeder) |             seeder: DataSeederABC = seeder | ||||||
|             self._logger.debug(__name__, f"Starting seeder {seeder.__name__}") |             self._logger.debug(__name__, f"Starting seeder {type(seeder).__name__}") | ||||||
|             await seeder_as_service.seed() |             await seeder.seed() | ||||||
|             self._db.save_changes() |             self._db.save_changes() | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC | |||||||
|  |  | ||||||
| from bot_core.abc.module_abc import ModuleABC | from bot_core.abc.module_abc import ModuleABC | ||||||
| from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | ||||||
|  | from bot_data.abc.data_seeder_abc import DataSeederABC | ||||||
| from modules.level.command.level_group import LevelGroup | from modules.level.command.level_group import LevelGroup | ||||||
| from modules.level.events.level_on_member_join_event import LevelOnMemberJoinEvent | from modules.level.events.level_on_member_join_event import LevelOnMemberJoinEvent | ||||||
| from modules.level.events.level_on_message_event import LevelOnMessageEvent | from modules.level.events.level_on_message_event import LevelOnMessageEvent | ||||||
| @@ -29,7 +30,7 @@ class LevelModule(ModuleABC): | |||||||
|         env.set_working_directory(cwd) |         env.set_working_directory(cwd) | ||||||
|  |  | ||||||
|     def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): |     def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): | ||||||
|         services.add_transient(LevelSeeder) |         services.add_transient(DataSeederABC, LevelSeeder) | ||||||
|         services.add_transient(LevelService) |         services.add_transient(LevelService) | ||||||
|  |  | ||||||
|         # commands |         # commands | ||||||
|   | |||||||
							
								
								
									
										44
									
								
								kdb-bot/src/modules/technician/api_key_seeder.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								kdb-bot/src/modules/technician/api_key_seeder.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | |||||||
|  | from cpl_core.configuration import ConfigurationABC | ||||||
|  | from cpl_core.database.context import DatabaseContextABC | ||||||
|  | from cpl_discord.service import DiscordBotServiceABC | ||||||
|  |  | ||||||
|  | from bot_core.logging.database_logger import DatabaseLogger | ||||||
|  | from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC | ||||||
|  | from bot_data.abc.data_seeder_abc import DataSeederABC | ||||||
|  | from bot_data.abc.user_repository_abc import UserRepositoryABC | ||||||
|  | from bot_data.model.api_key import ApiKey | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ApiKeySeeder(DataSeederABC): | ||||||
|  |     def __init__( | ||||||
|  |         self, | ||||||
|  |         logger: DatabaseLogger, | ||||||
|  |         config: ConfigurationABC, | ||||||
|  |         bot: DiscordBotServiceABC, | ||||||
|  |         db: DatabaseContextABC, | ||||||
|  |         users: UserRepositoryABC, | ||||||
|  |         api_keys: ApiKeyRepositoryABC, | ||||||
|  |     ): | ||||||
|  |         DataSeederABC.__init__(self) | ||||||
|  |  | ||||||
|  |         self._logger = logger | ||||||
|  |         self._config = config | ||||||
|  |         self._bot = bot | ||||||
|  |         self._db = db | ||||||
|  |         self._users = users | ||||||
|  |         self._api_keys = api_keys | ||||||
|  |  | ||||||
|  |     async def seed(self): | ||||||
|  |         self._logger.debug(__name__, f"API-Key seeder started") | ||||||
|  |  | ||||||
|  |         if self._api_keys.get_api_keys().count() > 0: | ||||||
|  |             self._logger.debug(__name__, f"Skip API-Key seeder") | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             frontend_key = ApiKey("frontend", "87f529fd-a32e-40b3-a1d1-7a1583cf3ff5", None) | ||||||
|  |             self._api_keys.add_api_key(frontend_key) | ||||||
|  |             self._db.save_changes() | ||||||
|  |             self._logger.info(__name__, f"Created frontend API-Key") | ||||||
|  |         except Exception as e: | ||||||
|  |             self._logger.fatal(__name__, "Cannot create frontend API-Key", e) | ||||||
| @@ -5,8 +5,10 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC | |||||||
|  |  | ||||||
| from bot_core.abc.module_abc import ModuleABC | from bot_core.abc.module_abc import ModuleABC | ||||||
| from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum | ||||||
|  | from bot_data.abc.data_seeder_abc import DataSeederABC | ||||||
| from modules.base.abc.base_helper_abc import BaseHelperABC | from modules.base.abc.base_helper_abc import BaseHelperABC | ||||||
| from modules.base.service.base_helper_service import BaseHelperService | from modules.base.service.base_helper_service import BaseHelperService | ||||||
|  | from modules.technician.api_key_seeder import ApiKeySeeder | ||||||
| from modules.technician.command.api_key_group import ApiKeyGroup | from modules.technician.command.api_key_group import ApiKeyGroup | ||||||
| from modules.technician.command.log_command import LogCommand | from modules.technician.command.log_command import LogCommand | ||||||
| from modules.technician.command.restart_command import RestartCommand | from modules.technician.command.restart_command import RestartCommand | ||||||
| @@ -21,6 +23,7 @@ class TechnicianModule(ModuleABC): | |||||||
|         pass |         pass | ||||||
|  |  | ||||||
|     def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): |     def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): | ||||||
|  |         services.add_transient(DataSeederABC, ApiKeySeeder) | ||||||
|         services.add_transient(BaseHelperABC, BaseHelperService) |         services.add_transient(BaseHelperABC, BaseHelperService) | ||||||
|         # commands |         # commands | ||||||
|         self._dc.add_command(RestartCommand) |         self._dc.add_command(RestartCommand) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user