forked from sh-edraft.de/sh_discord_bot
Added auth service stuff with jwt #70
This commit is contained in:
parent
1090e502c2
commit
6f95d0a055
@ -33,11 +33,29 @@ class AuthServiceABC(ABC):
|
||||
async def add_auth_user_async(self, user_dto: AuthUserDTO) -> int: pass
|
||||
|
||||
@abstractmethod
|
||||
async def confirm_email_async(self, id: str) -> bool: pass
|
||||
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_auth_user_by_email_async(self, email: str): pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass
|
||||
|
||||
@abstractmethod
|
||||
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: pass
|
||||
|
||||
@abstractmethod
|
||||
async def revoke_async(self, token_dto: TokenDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def confirm_email_async(self, id: str) -> bool: pass
|
||||
|
||||
@abstractmethod
|
||||
async def forgot_password_async(self, email: str): pass
|
||||
|
||||
@ -46,21 +64,3 @@ class AuthServiceABC(ABC):
|
||||
|
||||
@abstractmethod
|
||||
async def reset_password_async(self, rp_dto: ResetPasswordDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def update_user_as_admin_async(self, update_user_dto: UpdateAuthUserDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: pass
|
||||
|
||||
@abstractmethod
|
||||
async def revoke_async(self, token_dto: TokenDTO): pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_auth_user_by_email_async(self, email: str): pass
|
||||
|
||||
@abstractmethod
|
||||
async def delete_auth_user_async(self, user_dto: AuthUserDTO): pass
|
||||
|
@ -37,25 +37,49 @@ class AuthUserDTO(DtoABC):
|
||||
def first_name(self) -> str:
|
||||
return self._first_name
|
||||
|
||||
@first_name.setter
|
||||
def first_name(self, value: str):
|
||||
self._first_name = value
|
||||
|
||||
@property
|
||||
def last_name(self) -> str:
|
||||
return self._last_name
|
||||
|
||||
@last_name.setter
|
||||
def last_name(self, value: str):
|
||||
self._last_name = value
|
||||
|
||||
@property
|
||||
def email(self) -> str:
|
||||
return self._email
|
||||
|
||||
@email.setter
|
||||
def email(self, value: str):
|
||||
self._email = value
|
||||
|
||||
@property
|
||||
def password(self) -> str:
|
||||
return self._password
|
||||
|
||||
@password.setter
|
||||
def password(self, value: str):
|
||||
self._password = value
|
||||
|
||||
@property
|
||||
def is_confirmed(self) -> bool:
|
||||
def is_confirmed(self) -> Optional[str]:
|
||||
return self._is_confirmed
|
||||
|
||||
@is_confirmed.setter
|
||||
def is_confirmed(self, value: Optional[str]):
|
||||
self._is_confirmed = value
|
||||
|
||||
@property
|
||||
def auth_role(self) -> AuthRoleEnum:
|
||||
return self._auth_role
|
||||
return self.auth_role
|
||||
|
||||
@auth_role.setter
|
||||
def auth_role(self, value: AuthRoleEnum):
|
||||
self.auth_role = value
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
self._id = values['Id']
|
||||
|
@ -7,12 +7,26 @@ from bot_api.abc.dto_abc import DtoABC
|
||||
|
||||
class TokenDTO(DtoABC):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, token: str, refresh_token: str):
|
||||
DtoABC.__init__(self)
|
||||
|
||||
self._token = token
|
||||
self._refresh_token = refresh_token
|
||||
|
||||
@property
|
||||
def token(self) -> str:
|
||||
return self._token
|
||||
|
||||
@property
|
||||
def refresh_token(self) -> str:
|
||||
return self._refresh_token
|
||||
|
||||
def from_dict(self, values: dict):
|
||||
pass
|
||||
self._token = values['Token']
|
||||
self._refresh_token = values['RefreshToken']
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
'Token': self._token,
|
||||
'RefreshToken': self._refresh_token
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
import hashlib
|
||||
import re
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
import jwt
|
||||
from cpl_core.database.context import DatabaseContextABC
|
||||
from cpl_core.mailing import EMailClientABC, EMail
|
||||
from cpl_query.extension import List
|
||||
from cpl_translation import TranslatePipe
|
||||
|
||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||
from bot_api.configuration.frontend_settings import FrontendSettings
|
||||
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
|
||||
from bot_api.logging.api_logger import ApiLogger
|
||||
from bot_api.model.auth_user import AuthUserDTO
|
||||
from bot_api.model.auth_user_dto import AuthUserDTO
|
||||
from bot_api.model.auth_user_filtered_result_dto import AuthUserFilteredResultDTO
|
||||
from bot_api.model.email_string_dto import EMailStringDTO
|
||||
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||
@ -32,6 +35,7 @@ class AuthService(AuthServiceABC):
|
||||
db: DatabaseContextABC,
|
||||
mailer: EMailClientABC,
|
||||
t: TranslatePipe,
|
||||
auth_settings: AuthenticationSettings,
|
||||
frontend_settings: FrontendSettings,
|
||||
|
||||
):
|
||||
@ -42,6 +46,7 @@ class AuthService(AuthServiceABC):
|
||||
self._db = db
|
||||
self._mailer = mailer
|
||||
self._t = t
|
||||
self._auth_settings = auth_settings
|
||||
self._frontend_settings = frontend_settings
|
||||
|
||||
@staticmethod
|
||||
@ -61,6 +66,29 @@ class AuthService(AuthServiceABC):
|
||||
regex = '^[a-z0-9]+[\\._]?[a-z0-9]+[@]\\w+[.]\\w{2,3}$'
|
||||
return bool(re.search(regex, email))
|
||||
|
||||
def _generate_token(self, user: AuthUser) -> str:
|
||||
token = jwt.encode(
|
||||
payload={
|
||||
'user_id': user.id,
|
||||
'email': user.email,
|
||||
'role': user.auth_role,
|
||||
'exp': datetime.now(tz=timezone.utc) + timedelta(days=self._auth_settings.token_expire_time),
|
||||
'iss': self._auth_settings.issuer,
|
||||
'aud': self._auth_settings.audience
|
||||
},
|
||||
key=self._auth_settings.secret_key,
|
||||
)
|
||||
|
||||
return token
|
||||
|
||||
def _create_and_save_refresh_token(self, user: AuthUser) -> str:
|
||||
token = str(uuid.uuid4())
|
||||
user.refresh_token = token
|
||||
user.refresh_token_expire_time = datetime.now(tz=timezone.utc) + timedelta(days=self._auth_settings.refresh_token_expire_time)
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
return token
|
||||
|
||||
def _send_confirmation_id_to_user(self, user: AuthUser):
|
||||
url = self._frontend_settings.url
|
||||
if not url.endswith('/'):
|
||||
@ -109,49 +137,24 @@ 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: AuthUserDTO) -> AuthUser:
|
||||
pass
|
||||
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 Exception('User already exists')
|
||||
|
||||
async def confirm_email_async(self, id: str) -> bool:
|
||||
user = self._auth_users.find_auth_user_by_confirmation_id(id)
|
||||
if user is None:
|
||||
return False
|
||||
user_dto.password = self._hash_sha256(user_dto.password)
|
||||
user = AUT.to_db(user_dto)
|
||||
if not self._is_email_valid(user.email):
|
||||
raise Exception('Invalid E-Mail address')
|
||||
|
||||
user.confirmation_id = None
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
return True
|
||||
|
||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO:
|
||||
pass
|
||||
|
||||
async def forgot_password_async(self, email: str):
|
||||
user = self._auth_users.find_auth_user_by_email(email)
|
||||
if user is None:
|
||||
return
|
||||
|
||||
user.forgot_password_id = uuid.uuid4()
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._send_forgot_password_id_to_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO:
|
||||
user = self._auth_users.find_auth_user_by_forgot_password_id(id)
|
||||
return EMailStringDTO(user.email)
|
||||
|
||||
async def reset_password_async(self, rp_dto: ResetPasswordDTO):
|
||||
user = self._auth_users.find_auth_user_by_forgot_password_id(rp_dto.id)
|
||||
if user is None:
|
||||
pass
|
||||
|
||||
if user.confirmation_id is not None:
|
||||
pass
|
||||
|
||||
if user.password is None or rp_dto.password == '':
|
||||
pass
|
||||
|
||||
user.password = self._hash_sha256(rp_dto.password)
|
||||
self._db.save_changes()
|
||||
try:
|
||||
user.confirmation_id = uuid.uuid4()
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._send_confirmation_id_to_user(user)
|
||||
self._db.save_changes()
|
||||
self._logger.info(__name__, f'Added auth user with E-Mail: {user_dto.email}')
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f'Cannot add user with E-Mal {user_dto.email}', e)
|
||||
|
||||
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO):
|
||||
if update_user_dto is None:
|
||||
@ -258,12 +261,6 @@ class AuthService(AuthServiceABC):
|
||||
|
||||
self._db.save_changes()
|
||||
|
||||
async def refresh_async(self, token_dto: TokenDTO) -> AuthUser:
|
||||
pass
|
||||
|
||||
async def revoke_async(self, token_dto: TokenDTO) -> AuthUser:
|
||||
pass
|
||||
|
||||
async def delete_auth_user_by_email_async(self, email: str):
|
||||
try:
|
||||
user = self._auth_users.get_auth_user_by_email(email)
|
||||
@ -280,3 +277,95 @@ class AuthService(AuthServiceABC):
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f'Cannot delete user', e)
|
||||
raise Exception(f'Cannot delete user by mail {user_dto.email}')
|
||||
|
||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO:
|
||||
if user_dto is None:
|
||||
raise Exception('User not set')
|
||||
|
||||
db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
|
||||
if db_user is None:
|
||||
raise Exception(f'User with E-Mail {user_dto.email} not found')
|
||||
|
||||
user_dto.password = self._hash_sha256(user_dto.password)
|
||||
if db_user.password != user_dto.password:
|
||||
raise Exception('Wrong password')
|
||||
|
||||
token = self._generate_token(user_dto)
|
||||
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
|
||||
|
||||
self._db.save_changes()
|
||||
return TokenDTO(token, refresh_token)
|
||||
|
||||
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO:
|
||||
if token_dto is None:
|
||||
raise Exception(f'Token not set')
|
||||
|
||||
token = jwt.decode(token_dto.token, key=self._auth_settings.secret_key)
|
||||
if token is None or 'email' not in token:
|
||||
raise Exception('Token invalid')
|
||||
|
||||
try:
|
||||
user = self._auth_users.get_auth_user_by_email(token)
|
||||
if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now():
|
||||
raise Exception('Token expired')
|
||||
|
||||
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('', '')
|
||||
|
||||
async def revoke_async(self, token_dto: TokenDTO):
|
||||
if token_dto is None or token_dto.token is None or token_dto.refresh_token is None:
|
||||
raise Exception('Token not set')
|
||||
|
||||
token = jwt.decode(token_dto.token, key=self._auth_settings.secret_key)
|
||||
try:
|
||||
user = self._auth_users.get_auth_user_by_email(token)
|
||||
if user is None or user.refresh_token != token_dto.refresh_token or user.refresh_token_expire_time <= datetime.now():
|
||||
raise Exception('Token expired')
|
||||
|
||||
user.refresh_token = None
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f'Refreshing token failed', e)
|
||||
|
||||
async def confirm_email_async(self, id: str) -> bool:
|
||||
user = self._auth_users.find_auth_user_by_confirmation_id(id)
|
||||
if user is None:
|
||||
return False
|
||||
|
||||
user.confirmation_id = None
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._db.save_changes()
|
||||
return True
|
||||
|
||||
async def forgot_password_async(self, email: str):
|
||||
user = self._auth_users.find_auth_user_by_email(email)
|
||||
if user is None:
|
||||
return
|
||||
|
||||
user.forgot_password_id = uuid.uuid4()
|
||||
self._auth_users.update_auth_user(user)
|
||||
self._send_forgot_password_id_to_user(user)
|
||||
self._db.save_changes()
|
||||
|
||||
async def confirm_forgot_password_async(self, id: str) -> EMailStringDTO:
|
||||
user = self._auth_users.find_auth_user_by_forgot_password_id(id)
|
||||
return EMailStringDTO(user.email)
|
||||
|
||||
async def reset_password_async(self, rp_dto: ResetPasswordDTO):
|
||||
user = self._auth_users.find_auth_user_by_forgot_password_id(rp_dto.id)
|
||||
if user is None:
|
||||
raise Exception(f'User by forgot password id {rp_dto.id} not found')
|
||||
|
||||
if user.confirmation_id is not None:
|
||||
raise Exception(f'E-Mail not confirmed')
|
||||
|
||||
if user.password is None or rp_dto.password == '':
|
||||
raise Exception(f'Password not set')
|
||||
|
||||
user.password = self._hash_sha256(rp_dto.password)
|
||||
self._db.save_changes()
|
||||
|
Loading…
Reference in New Issue
Block a user