Added logic to create default api-key for frontend #162-3
This commit is contained in:
parent
7f14aff1bd
commit
a6df06f13a
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user