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
|
async def add_auth_user_async(self, user_dto: AuthUserDTO) -> int: pass
|
||||||
|
|
||||||
@abstractmethod
|
@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
|
@abstractmethod
|
||||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass
|
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
|
@abstractmethod
|
||||||
async def forgot_password_async(self, email: str): pass
|
async def forgot_password_async(self, email: str): pass
|
||||||
|
|
||||||
@ -46,21 +64,3 @@ class AuthServiceABC(ABC):
|
|||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
async def reset_password_async(self, rp_dto: ResetPasswordDTO): pass
|
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
|
|
||||||
|
@ -32,30 +32,54 @@ class AuthUserDTO(DtoABC):
|
|||||||
@property
|
@property
|
||||||
def id(self) -> int:
|
def id(self) -> int:
|
||||||
return self._id
|
return self._id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def first_name(self) -> str:
|
def first_name(self) -> str:
|
||||||
return self._first_name
|
return self._first_name
|
||||||
|
|
||||||
|
@first_name.setter
|
||||||
|
def first_name(self, value: str):
|
||||||
|
self._first_name = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def last_name(self) -> str:
|
def last_name(self) -> str:
|
||||||
return self._last_name
|
return self._last_name
|
||||||
|
|
||||||
|
@last_name.setter
|
||||||
|
def last_name(self, value: str):
|
||||||
|
self._last_name = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def email(self) -> str:
|
def email(self) -> str:
|
||||||
return self._email
|
return self._email
|
||||||
|
|
||||||
|
@email.setter
|
||||||
|
def email(self, value: str):
|
||||||
|
self._email = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def password(self) -> str:
|
def password(self) -> str:
|
||||||
return self._password
|
return self._password
|
||||||
|
|
||||||
|
@password.setter
|
||||||
|
def password(self, value: str):
|
||||||
|
self._password = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_confirmed(self) -> bool:
|
def is_confirmed(self) -> Optional[str]:
|
||||||
return self._is_confirmed
|
return self._is_confirmed
|
||||||
|
|
||||||
|
@is_confirmed.setter
|
||||||
|
def is_confirmed(self, value: Optional[str]):
|
||||||
|
self._is_confirmed = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def auth_role(self) -> AuthRoleEnum:
|
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):
|
def from_dict(self, values: dict):
|
||||||
self._id = values['Id']
|
self._id = values['Id']
|
||||||
|
@ -7,12 +7,26 @@ from bot_api.abc.dto_abc import DtoABC
|
|||||||
|
|
||||||
class TokenDTO(DtoABC):
|
class TokenDTO(DtoABC):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, token: str, refresh_token: str):
|
||||||
DtoABC.__init__(self)
|
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):
|
def from_dict(self, values: dict):
|
||||||
pass
|
self._token = values['Token']
|
||||||
|
self._refresh_token = values['RefreshToken']
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
return {
|
return {
|
||||||
|
'Token': self._token,
|
||||||
|
'RefreshToken': self._refresh_token
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,21 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import re
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
import jwt
|
||||||
from cpl_core.database.context import DatabaseContextABC
|
from cpl_core.database.context import DatabaseContextABC
|
||||||
from cpl_core.mailing import EMailClientABC, EMail
|
from cpl_core.mailing import EMailClientABC, EMail
|
||||||
from cpl_query.extension import List
|
from cpl_query.extension import List
|
||||||
from cpl_translation import TranslatePipe
|
from cpl_translation import TranslatePipe
|
||||||
|
|
||||||
from bot_api.abc.auth_service_abc import AuthServiceABC
|
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.configuration.frontend_settings import FrontendSettings
|
||||||
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
|
from bot_api.filter.auth_user_select_criteria import AuthUserSelectCriteria
|
||||||
from bot_api.logging.api_logger import ApiLogger
|
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.auth_user_filtered_result_dto import AuthUserFilteredResultDTO
|
||||||
from bot_api.model.email_string_dto import EMailStringDTO
|
from bot_api.model.email_string_dto import EMailStringDTO
|
||||||
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
from bot_api.model.reset_password_dto import ResetPasswordDTO
|
||||||
@ -32,6 +35,7 @@ class AuthService(AuthServiceABC):
|
|||||||
db: DatabaseContextABC,
|
db: DatabaseContextABC,
|
||||||
mailer: EMailClientABC,
|
mailer: EMailClientABC,
|
||||||
t: TranslatePipe,
|
t: TranslatePipe,
|
||||||
|
auth_settings: AuthenticationSettings,
|
||||||
frontend_settings: FrontendSettings,
|
frontend_settings: FrontendSettings,
|
||||||
|
|
||||||
):
|
):
|
||||||
@ -42,6 +46,7 @@ class AuthService(AuthServiceABC):
|
|||||||
self._db = db
|
self._db = db
|
||||||
self._mailer = mailer
|
self._mailer = mailer
|
||||||
self._t = t
|
self._t = t
|
||||||
|
self._auth_settings = auth_settings
|
||||||
self._frontend_settings = frontend_settings
|
self._frontend_settings = frontend_settings
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -61,6 +66,29 @@ class AuthService(AuthServiceABC):
|
|||||||
regex = '^[a-z0-9]+[\\._]?[a-z0-9]+[@]\\w+[.]\\w{2,3}$'
|
regex = '^[a-z0-9]+[\\._]?[a-z0-9]+[@]\\w+[.]\\w{2,3}$'
|
||||||
return bool(re.search(regex, email))
|
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):
|
def _send_confirmation_id_to_user(self, user: AuthUser):
|
||||||
url = self._frontend_settings.url
|
url = self._frontend_settings.url
|
||||||
if not url.endswith('/'):
|
if not url.endswith('/'):
|
||||||
@ -109,49 +137,24 @@ class AuthService(AuthServiceABC):
|
|||||||
user = self._auth_users.find_auth_user_by_email(email)
|
user = self._auth_users.find_auth_user_by_email(email)
|
||||||
return AUT.to_dto(user) if user is not None else None
|
return AUT.to_dto(user) if user is not None else None
|
||||||
|
|
||||||
async def add_auth_user_async(self, user_dto: AuthUserDTO) -> AuthUser:
|
async def add_auth_user_async(self, user_dto: AuthUserDTO):
|
||||||
pass
|
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_dto.password = self._hash_sha256(user_dto.password)
|
||||||
user = self._auth_users.find_auth_user_by_confirmation_id(id)
|
user = AUT.to_db(user_dto)
|
||||||
if user is None:
|
if not self._is_email_valid(user.email):
|
||||||
return False
|
raise Exception('Invalid E-Mail address')
|
||||||
|
|
||||||
user.confirmation_id = None
|
try:
|
||||||
self._auth_users.update_auth_user(user)
|
user.confirmation_id = uuid.uuid4()
|
||||||
self._db.save_changes()
|
self._auth_users.update_auth_user(user)
|
||||||
return True
|
self._send_confirmation_id_to_user(user)
|
||||||
|
self._db.save_changes()
|
||||||
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO:
|
self._logger.info(__name__, f'Added auth user with E-Mail: {user_dto.email}')
|
||||||
pass
|
except Exception as e:
|
||||||
|
self._logger.error(__name__, f'Cannot add user with E-Mal {user_dto.email}', e)
|
||||||
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()
|
|
||||||
|
|
||||||
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO):
|
async def update_user_async(self, update_user_dto: UpdateAuthUserDTO):
|
||||||
if update_user_dto is None:
|
if update_user_dto is None:
|
||||||
@ -258,12 +261,6 @@ class AuthService(AuthServiceABC):
|
|||||||
|
|
||||||
self._db.save_changes()
|
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):
|
async def delete_auth_user_by_email_async(self, email: str):
|
||||||
try:
|
try:
|
||||||
user = self._auth_users.get_auth_user_by_email(email)
|
user = self._auth_users.get_auth_user_by_email(email)
|
||||||
@ -280,3 +277,95 @@ class AuthService(AuthServiceABC):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._logger.error(__name__, f'Cannot delete user', e)
|
self._logger.error(__name__, f'Cannot delete user', e)
|
||||||
raise Exception(f'Cannot delete user by mail {user_dto.email}')
|
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