graphql part3/3 #162-3 #194
@ -83,6 +83,10 @@ class AuthServiceABC(ABC):
|
||||
async def verify_login(self, token_str: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def verify_api_key(self, api_key: str) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO:
|
||||
pass
|
||||
|
@ -13,7 +13,7 @@ from bot_api.api import Api
|
||||
from bot_api.api_thread import ApiThread
|
||||
from bot_api.controller.auth_controller import AuthController
|
||||
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.event.bot_api_on_ready_event import BotApiOnReadyEvent
|
||||
from bot_api.service.auth_service import AuthService
|
||||
|
@ -33,7 +33,7 @@ class GraphQLController:
|
||||
return PLAYGROUND_HTML, 200
|
||||
|
||||
@Route.post(f"{BasePath}")
|
||||
@Route.authorize
|
||||
@Route.authorize(by_api_key=True)
|
||||
async def graphql(self):
|
||||
data = request.get_json()
|
||||
|
@ -30,15 +30,32 @@ class Route:
|
||||
cls._env = env.environment_name
|
||||
|
||||
@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:
|
||||
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)
|
||||
async def decorator(*args, **kwargs):
|
||||
if skip_in_dev and cls._env == "development":
|
||||
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
|
||||
if "Authorization" in request.headers:
|
||||
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.update_auth_user_dto import UpdateAuthUserDTO
|
||||
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.server_repository_abc import ServerRepositoryABC
|
||||
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_user import AuthUser
|
||||
from bot_data.model.auth_user_users_relation import AuthUserUsersRelation
|
||||
@ -49,9 +51,9 @@ class AuthService(AuthServiceABC):
|
||||
bot: DiscordBotServiceABC,
|
||||
db: DatabaseContextABC,
|
||||
auth_users: AuthUserRepositoryABC,
|
||||
api_keys: ApiKeyRepositoryABC,
|
||||
users: UserRepositoryABC,
|
||||
servers: ServerRepositoryABC,
|
||||
# mailer: MailThread,
|
||||
mailer: EMailClientABC,
|
||||
t: TranslatePipe,
|
||||
auth_settings: AuthenticationSettings,
|
||||
@ -64,6 +66,7 @@ class AuthService(AuthServiceABC):
|
||||
self._bot = bot
|
||||
self._db = db
|
||||
self._auth_users = auth_users
|
||||
self._api_keys = api_keys
|
||||
self._users = users
|
||||
self._servers = servers
|
||||
self._mailer = mailer
|
||||
@ -82,6 +85,11 @@ class AuthService(AuthServiceABC):
|
||||
|
||||
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:
|
||||
token = jwt.encode(
|
||||
payload={
|
||||
@ -221,7 +229,12 @@ class AuthService(AuthServiceABC):
|
||||
raise ServiceException(ServiceErrorCode.InvalidUser, "User already exists")
|
||||
|
||||
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.password_salt = uuid.uuid4()
|
||||
@ -478,6 +491,18 @@ class AuthService(AuthServiceABC):
|
||||
|
||||
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:
|
||||
if user_dto is None:
|
||||
raise ServiceException(ServiceErrorCode.InvalidData, "User not set")
|
||||
|
@ -22,7 +22,7 @@ class ApiKeyMigration(MigrationABC):
|
||||
`Id` BIGINT NOT NULL AUTO_INCREMENT,
|
||||
`Identifier` VARCHAR(255) NOT NULL,
|
||||
`Key` VARCHAR(255) NOT NULL,
|
||||
`CreatorId` BIGINT,
|
||||
`CreatorId` BIGINT NULL,
|
||||
`CreatedAt` DATETIME(6),
|
||||
`LastModifiedAt` DATETIME(6),
|
||||
PRIMARY KEY(`Id`),
|
||||
|
@ -1,4 +1,5 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from cpl_core.database import TableABC
|
||||
|
||||
@ -10,7 +11,7 @@ class ApiKey(TableABC):
|
||||
self,
|
||||
identifier: str,
|
||||
key: str,
|
||||
creator: User,
|
||||
creator: Optional[User],
|
||||
created_at: datetime = None,
|
||||
modified_at: datetime = None,
|
||||
id=0,
|
||||
@ -33,7 +34,7 @@ class ApiKey(TableABC):
|
||||
return self._key
|
||||
|
||||
@property
|
||||
def creator(self) -> User:
|
||||
def creator(self) -> Optional[User]:
|
||||
return self._creator
|
||||
|
||||
@staticmethod
|
||||
@ -72,7 +73,7 @@ class ApiKey(TableABC):
|
||||
) VALUES (
|
||||
'{self._identifier}',
|
||||
'{self._key}',
|
||||
'{self._creator.user_id}',
|
||||
{"NULL" if self._creator is None else self._creator.user_id},
|
||||
'{self._created_at}',
|
||||
'{self._modified_at}'
|
||||
);
|
||||
|
@ -31,10 +31,11 @@ class ApiKeyRepositoryService(ApiKeyRepositoryABC):
|
||||
return value
|
||||
|
||||
def _api_key_from_result(self, sql_result: tuple) -> ApiKey:
|
||||
creator = self._get_value_from_result(sql_result[3])
|
||||
api_key = ApiKey(
|
||||
self._get_value_from_result(sql_result[1]),
|
||||
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[5]),
|
||||
id=self._get_value_from_result(sql_result[0]),
|
||||
|
@ -1,6 +1,5 @@
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.dependency_injection import ServiceProviderABC
|
||||
from cpl_query.extension import List
|
||||
|
||||
from bot_core.logging.database_logger import DatabaseLogger
|
||||
from bot_data.abc.data_seeder_abc import DataSeederABC
|
||||
@ -18,12 +17,10 @@ class SeederService:
|
||||
|
||||
self._db = db
|
||||
|
||||
self._seeder = List(type, DataSeederABC.__subclasses__())
|
||||
|
||||
async def seed(self):
|
||||
self._logger.info(__name__, f"Seed data")
|
||||
for seeder in self._seeder:
|
||||
seeder_as_service: DataSeederABC = self._services.get_service(seeder)
|
||||
self._logger.debug(__name__, f"Starting seeder {seeder.__name__}")
|
||||
await seeder_as_service.seed()
|
||||
for seeder in self._services.get_services(list[DataSeederABC]):
|
||||
seeder: DataSeederABC = seeder
|
||||
self._logger.debug(__name__, f"Starting seeder {type(seeder).__name__}")
|
||||
await seeder.seed()
|
||||
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.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.events.level_on_member_join_event import LevelOnMemberJoinEvent
|
||||
from modules.level.events.level_on_message_event import LevelOnMessageEvent
|
||||
@ -29,7 +30,7 @@ class LevelModule(ModuleABC):
|
||||
env.set_working_directory(cwd)
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
services.add_transient(LevelSeeder)
|
||||
services.add_transient(DataSeederABC, LevelSeeder)
|
||||
services.add_transient(LevelService)
|
||||
|
||||
# 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.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.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.log_command import LogCommand
|
||||
from modules.technician.command.restart_command import RestartCommand
|
||||
@ -21,6 +23,7 @@ class TechnicianModule(ModuleABC):
|
||||
pass
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
services.add_transient(DataSeederABC, ApiKeySeeder)
|
||||
services.add_transient(BaseHelperABC, BaseHelperService)
|
||||
# commands
|
||||
self._dc.add_command(RestartCommand)
|
||||
|
Loading…
Reference in New Issue
Block a user