forked from sh-edraft.de/sh_discord_bot
		
	Added logic to register by discord #70
This commit is contained in:
		| @@ -43,7 +43,10 @@ class AuthServiceABC(ABC): | ||||
|     async def find_auth_user_by_email_async(self, email: str) -> AuthUserDTO: pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def add_auth_user_async(self, user_dto: AuthUserDTO) -> int: pass | ||||
|     async def add_auth_user_async(self, user_dto: AuthUserDTO): pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def add_auth_user_by_discord_async(self, user_dto: AuthUserDTO): pass | ||||
|  | ||||
|     @abstractmethod | ||||
|     async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): pass | ||||
|   | ||||
| @@ -5,13 +5,17 @@ from functools import partial | ||||
|  | ||||
| import eventlet | ||||
| from cpl_core.dependency_injection import ServiceProviderABC | ||||
| from cpl_core.utils import CredentialManager | ||||
| from eventlet import wsgi | ||||
| from flask import Flask, request, jsonify, Response, make_response | ||||
| from flask_cors import CORS | ||||
| from flask_socketio import SocketIO | ||||
| from werkzeug.exceptions import NotFound | ||||
|  | ||||
| from bot_api.configuration.api_settings import ApiSettings | ||||
| from bot_api.configuration.authentication_settings import AuthenticationSettings | ||||
| from bot_api.configuration.frontend_settings import FrontendSettings | ||||
| from bot_api.exception.service_error_code_enum import ServiceErrorCode | ||||
| from bot_api.exception.service_exception import ServiceException | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.error_dto import ErrorDTO | ||||
| @@ -26,6 +30,7 @@ class Api(Flask): | ||||
|             services: ServiceProviderABC, | ||||
|             api_settings: ApiSettings, | ||||
|             frontend_settings: FrontendSettings, | ||||
|             auth_settings: AuthenticationSettings, | ||||
|             *args, **kwargs | ||||
|     ): | ||||
|         if not args: | ||||
| @@ -36,6 +41,7 @@ class Api(Flask): | ||||
|         self._logger = logger | ||||
|         self._services = services | ||||
|         self._apt_settings = api_settings | ||||
|         self._auth_settings = auth_settings | ||||
|  | ||||
|         self._cors = CORS(self, support_credentials=True) | ||||
|  | ||||
| @@ -71,6 +77,10 @@ class Api(Flask): | ||||
|             self._logger.error(__name__, ex.get_detailed_message()) | ||||
|             error = ErrorDTO(ex.error_code, ex.message) | ||||
|             return jsonify(error.to_dict()), 500 | ||||
|         elif isinstance(e, NotFound): | ||||
|             self._logger.error(__name__, e.description) | ||||
|             error = ErrorDTO(ServiceErrorCode.NotFound, e.description) | ||||
|             return jsonify(error.to_dict()), 404 | ||||
|         else: | ||||
|             tracking_id = uuid.uuid4() | ||||
|             user_message = f'Tracking Id: {tracking_id}' | ||||
| @@ -86,6 +96,7 @@ class Api(Flask): | ||||
|     def start(self): | ||||
|         self._logger.info(__name__, f'Starting API {self._apt_settings.host}:{self._apt_settings.port}') | ||||
|         self._register_routes() | ||||
|         self.secret_key = CredentialManager.decrypt(self._auth_settings.secret_key) | ||||
|         # from waitress import serve | ||||
|         # https://docs.pylonsproject.org/projects/waitress/en/stable/arguments.html | ||||
|         # serve(self, host=self._apt_settings.host, port=self._apt_settings.port, threads=10, connection_limit=1000, channel_timeout=10) | ||||
|   | ||||
| @@ -11,6 +11,7 @@ from flask import Flask | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.api import Api | ||||
| from bot_api.api_thread import ApiThread | ||||
| from bot_api.controller.auth_discord_controller import AuthDiscordController | ||||
| from bot_api.controller.discord.server_controller import ServerController | ||||
| from bot_api.controller.gui_controller import GuiController | ||||
| from bot_api.controller.auth_controller import AuthController | ||||
| @@ -44,6 +45,7 @@ class ApiModule(ModuleABC): | ||||
|  | ||||
|         services.add_transient(AuthServiceABC, AuthService) | ||||
|         services.add_transient(AuthController) | ||||
|         services.add_transient(AuthDiscordController) | ||||
|         services.add_transient(GuiController) | ||||
|         services.add_transient(DiscordService) | ||||
|         services.add_transient(ServerController) | ||||
|   | ||||
| @@ -16,7 +16,8 @@ | ||||
|     "LicenseName": "", | ||||
|     "LicenseDescription": "", | ||||
|     "Dependencies": [ | ||||
|       "cpl-core==2022.10.0.post6" | ||||
|       "cpl-core==2022.10.0.post6", | ||||
|       "requests-oauthlib==1.3.1" | ||||
|     ], | ||||
|     "DevDependencies": [ | ||||
|       "cpl-cli==2022.10.0" | ||||
|   | ||||
| @@ -5,12 +5,22 @@ | ||||
|     "RedirectToHTTPS": false | ||||
|   }, | ||||
|   "Authentication": { | ||||
|     "SecretKey": "F3b5LDz+#Jvzg=W!@gsa%xsF", | ||||
|     "SecretKey": "RjNiNUxEeisjSnZ6Zz1XIUBnc2EleHNG", | ||||
|     "Issuer": "http://localhost:5000", | ||||
|     "Audience": "http://localhost:4200", | ||||
|     "TokenExpireTime": 1, | ||||
|     "RefreshTokenExpireTime": 7 | ||||
|   }, | ||||
|   "DiscordAuthentication": { | ||||
|     "ClientSecret": "V3FTb3JYVFBiVktEeHZxdWJDWW4xcnBCbXRwdmpwcy0=", | ||||
|     "RedirectURL": "http://localhost:5000/api/auth/discord/register", | ||||
|     "Scope": [ | ||||
|       "identify", | ||||
|       "email" | ||||
|     ], | ||||
|     "TokenURL": "https://discordapp.com/api/oauth2/token", | ||||
|     "AuthURL": "https://discordapp.com/api/oauth2/authorize" | ||||
|   }, | ||||
|   "Frontend": { | ||||
|     "URL": "http://localhost:4200/" | ||||
|   } | ||||
|   | ||||
| @@ -0,0 +1,48 @@ | ||||
| import traceback | ||||
|  | ||||
| from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC | ||||
| from cpl_core.console import Console | ||||
| from cpl_query.extension import List | ||||
|  | ||||
|  | ||||
| class DiscordAuthenticationSettings(ConfigurationModelABC): | ||||
|  | ||||
|     def __init__(self): | ||||
|         ConfigurationModelABC.__init__(self) | ||||
|  | ||||
|         self._client_secret = '' | ||||
|         self._redirect_url = '' | ||||
|         self._scope = List() | ||||
|         self._token_url = '' | ||||
|         self._auth_url = '' | ||||
|  | ||||
|     @property | ||||
|     def client_secret(self) -> str: | ||||
|         return self._client_secret | ||||
|  | ||||
|     @property | ||||
|     def redirect_url(self) -> str: | ||||
|         return self._redirect_url | ||||
|  | ||||
|     @property | ||||
|     def scope(self) -> List[str]: | ||||
|         return self._scope | ||||
|  | ||||
|     @property | ||||
|     def token_url(self) -> str: | ||||
|         return self._token_url | ||||
|  | ||||
|     @property | ||||
|     def auth_url(self) -> str: | ||||
|         return self._auth_url | ||||
|  | ||||
|     def from_dict(self, settings: dict): | ||||
|         try: | ||||
|             self._client_secret = settings['ClientSecret'] | ||||
|             self._redirect_url = settings['RedirectURL'] | ||||
|             self._scope = List(str, settings['Scope']) | ||||
|             self._token_url = settings['TokenURL'] | ||||
|             self._auth_url = settings['AuthURL'] | ||||
|         except Exception as e: | ||||
|             Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings') | ||||
|             Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}') | ||||
							
								
								
									
										83
									
								
								kdb-bot/src/bot_api/controller/auth_discord_controller.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								kdb-bot/src/bot_api/controller/auth_discord_controller.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,83 @@ | ||||
| import os | ||||
| import uuid | ||||
|  | ||||
| from cpl_core.configuration import ConfigurationABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMailClientABC, EMailClientSettings | ||||
| from cpl_core.utils import CredentialManager | ||||
| from cpl_discord.service import DiscordBotServiceABC | ||||
| from cpl_translation import TranslatePipe | ||||
| from flask import jsonify | ||||
| from flask import request, session | ||||
| from requests_oauthlib import OAuth2Session | ||||
|  | ||||
| from bot_api.abc.auth_service_abc import AuthServiceABC | ||||
| from bot_api.api import Api | ||||
| from bot_api.configuration.discord_authentication_settings import DiscordAuthenticationSettings | ||||
| from bot_api.logging.api_logger import ApiLogger | ||||
| from bot_api.model.auth_user_dto import AuthUserDTO | ||||
| from bot_api.route.route import Route | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
|  | ||||
| # Disable SSL requirement | ||||
| os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' | ||||
|  | ||||
|  | ||||
| class AuthDiscordController: | ||||
|     BasePath = '/api/auth/discord' | ||||
|  | ||||
|     def __init__( | ||||
|             self, | ||||
|             auth_settings: DiscordAuthenticationSettings, | ||||
|             config: ConfigurationABC, | ||||
|             env: ApplicationEnvironmentABC, | ||||
|             logger: ApiLogger, | ||||
|             bot: DiscordBotServiceABC, | ||||
|             t: TranslatePipe, | ||||
|             api: Api, | ||||
|             mail_settings: EMailClientSettings, | ||||
|             mailer: EMailClientABC, | ||||
|             auth_service: AuthServiceABC | ||||
|     ): | ||||
|         self._auth_settings = auth_settings | ||||
|         self._config = config | ||||
|         self._env = env | ||||
|         self._logger = logger | ||||
|         self._bot = bot | ||||
|         self._t = t | ||||
|         self._api = api | ||||
|         self._mail_settings = mail_settings | ||||
|         self._mailer = mailer | ||||
|         self._auth_service = auth_service | ||||
|  | ||||
|     @Route.get(f'{BasePath}/get-url') | ||||
|     async def get_url(self): | ||||
|         oauth = OAuth2Session(self._bot.user.id, redirect_uri=self._auth_settings.redirect_url, scope=self._auth_settings.scope) | ||||
|         login_url, state = oauth.authorization_url(self._auth_settings.auth_url) | ||||
|         session['state'] = state | ||||
|         # return jsonify({'loginURL': login_url}) | ||||
|         return '<a href="' + login_url + '">Login with Discord</a>' | ||||
|  | ||||
|     @Route.get(f'{BasePath}/register') | ||||
|     async def discord_register(self): | ||||
|         discord = OAuth2Session(self._bot.user.id, redirect_uri=self._auth_settings.redirect_url, state=session['state'], scope=self._auth_settings.scope) | ||||
|         token = discord.fetch_token( | ||||
|             self._auth_settings.token_url, | ||||
|             client_secret=CredentialManager.decrypt(self._auth_settings.client_secret), | ||||
|             authorization_response=request.url, | ||||
|         ) | ||||
|         session['discord_token'] = token | ||||
|         discord = OAuth2Session(self._bot.user.id, token=token) | ||||
|         response = discord.get('https://discordapp.com/api' + '/users/@me').json() | ||||
|  | ||||
|         await self._auth_service.add_auth_user_by_discord_async(AuthUserDTO( | ||||
|             0, | ||||
|             response['username'], | ||||
|             response['discriminator'], | ||||
|             response['email'], | ||||
|             str(uuid.uuid4()), | ||||
|             None, | ||||
|             AuthRoleEnum.normal, | ||||
|             response['id'] | ||||
|         )) | ||||
|         return '', 200 | ||||
| @@ -9,6 +9,7 @@ import jwt | ||||
| from cpl_core.database.context import DatabaseContextABC | ||||
| from cpl_core.environment import ApplicationEnvironmentABC | ||||
| from cpl_core.mailing import EMailClientABC, EMail | ||||
| from cpl_core.utils import CredentialManager | ||||
| from cpl_query.extension import List | ||||
| from cpl_translation import TranslatePipe | ||||
| from flask import request | ||||
| @@ -29,6 +30,8 @@ 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.auth_user_repository_abc import AuthUserRepositoryABC | ||||
| from bot_data.abc.user_repository_abc import UserRepositoryABC | ||||
| from bot_data.model.auth_role_enum import AuthRoleEnum | ||||
| from bot_data.model.auth_user import AuthUser | ||||
|  | ||||
| _email_regex = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' | ||||
| @@ -41,6 +44,7 @@ class AuthService(AuthServiceABC): | ||||
|             env: ApplicationEnvironmentABC, | ||||
|             logger: ApiLogger, | ||||
|             auth_users: AuthUserRepositoryABC, | ||||
|             users: UserRepositoryABC, | ||||
|             db: DatabaseContextABC, | ||||
|             mailer: MailThread, | ||||
|             t: TranslatePipe, | ||||
| @@ -53,6 +57,7 @@ class AuthService(AuthServiceABC): | ||||
|         self._environment = env | ||||
|         self._logger = logger | ||||
|         self._auth_users = auth_users | ||||
|         self._users = users | ||||
|         self._db = db | ||||
|         self._mailer = mailer | ||||
|         self._t = t | ||||
| @@ -80,7 +85,7 @@ class AuthService(AuthServiceABC): | ||||
|                 'iss': self._auth_settings.issuer, | ||||
|                 'aud': self._auth_settings.audience | ||||
|             }, | ||||
|             key=self._auth_settings.secret_key | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key) | ||||
|         ) | ||||
|  | ||||
|         return token | ||||
| @@ -88,7 +93,7 @@ class AuthService(AuthServiceABC): | ||||
|     def decode_token(self, token: str) -> dict: | ||||
|         return jwt.decode( | ||||
|             token, | ||||
|             key=self._auth_settings.secret_key, | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|             issuer=self._auth_settings.issuer, | ||||
|             audience=self._auth_settings.audience, | ||||
|             algorithms=['HS256'] | ||||
| @@ -105,7 +110,7 @@ class AuthService(AuthServiceABC): | ||||
|  | ||||
|         return jwt.decode( | ||||
|             token, | ||||
|             key=self._auth_settings.secret_key, | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|             issuer=self._auth_settings.issuer, | ||||
|             audience=self._auth_settings.audience, | ||||
|             algorithms=['HS256'] | ||||
| @@ -119,7 +124,7 @@ class AuthService(AuthServiceABC): | ||||
|  | ||||
|         return jwt.decode( | ||||
|             token, | ||||
|             key=self._auth_settings.secret_key, | ||||
|             key=CredentialManager.decrypt(self._auth_settings.secret_key), | ||||
|             issuer=self._auth_settings.issuer, | ||||
|             audience=self._auth_settings.audience, | ||||
|             algorithms=['HS256'] | ||||
| @@ -196,12 +201,15 @@ class AuthService(AuthServiceABC): | ||||
|         user = self._auth_users.find_auth_user_by_email(email) | ||||
|         return AUT.to_dto(user) if user is not None else None | ||||
|  | ||||
|     async def add_auth_user_async(self, user_dto: AuthUser): | ||||
|     async def add_auth_user_async(self, user_dto: AuthUserDTO): | ||||
|         db_user = self._auth_users.find_auth_user_by_email(user_dto.email) | ||||
|         if db_user is not None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists') | ||||
|  | ||||
|         user = AUT.to_db(user_dto) | ||||
|         if self._auth_users.get_all_auth_users().count() == 0: | ||||
|             user.auth_role = AuthRoleEnum.admin | ||||
|  | ||||
|         user.password_salt = uuid.uuid4() | ||||
|         user.password = self._hash_sha256(user_dto.password, user.password_salt) | ||||
|         if not self._is_email_valid(user.email): | ||||
| @@ -217,6 +225,25 @@ class AuthService(AuthServiceABC): | ||||
|             self._logger.error(__name__, f'Cannot add user with E-Mal {user_dto.email}', e) | ||||
|             raise ServiceException(ServiceErrorCode.UnableToAdd, "Invalid E-Mail") | ||||
|  | ||||
|     async def add_auth_user_by_discord_async(self, user_dto: AuthUserDTO): | ||||
|         db_user = self._auth_users.find_auth_user_by_email(user_dto.email) | ||||
|         # user exists | ||||
|         if db_user is not None and db_user.user_id is not None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidUser, 'User already exists') | ||||
|  | ||||
|         # user exists but discord user id not set | ||||
|         elif db_user is not None and db_user.user_id is None: | ||||
|             user = self._users.get_users_by_discord_id(user_dto.user_id).single() | ||||
|             db_user.user_id = user.user_id | ||||
|             self._auth_users.update_auth_user(db_user) | ||||
|             self._db.save_changes() | ||||
|             return AUT.to_dto(db_user) | ||||
|  | ||||
|         # user does not exists | ||||
|         await self.add_auth_user_async(user_dto) | ||||
|         db_user = self._auth_users.get_auth_user_by_email(user_dto.email) | ||||
|         return AUT.to_dto(db_user, password=db_user.password) | ||||
|  | ||||
|     async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): | ||||
|         if update_user_dto is None: | ||||
|             raise ServiceException(ServiceErrorCode.InvalidData, f'User is empty') | ||||
|   | ||||
| @@ -26,13 +26,13 @@ class AuthUserTransformer(TransformerABC): | ||||
|         ) | ||||
|  | ||||
|     @staticmethod | ||||
|     def to_dto(db: AuthUser) -> AuthUserDTO: | ||||
|     def to_dto(db: AuthUser, password: str = None) -> AuthUserDTO: | ||||
|         return AuthUserDTO( | ||||
|             db.id, | ||||
|             db.first_name, | ||||
|             db.last_name, | ||||
|             db.email, | ||||
|             '', | ||||
|             '' if password is None else password, | ||||
|             db.confirmation_id, | ||||
|             db.auth_role, | ||||
|             db.user_id | ||||
|   | ||||
		Reference in New Issue
	
	Block a user