diff --git a/app/methods/user_methods.py b/app/methods/user_methods.py index b37c9f4..513e0ee 100644 --- a/app/methods/user_methods.py +++ b/app/methods/user_methods.py @@ -265,7 +265,7 @@ def load_user_obj( inc_post_comment_list: bool = False, inc_user_role_list: bool = False, ) -> User_Out_Base|dict|bool: - # log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL + log.setLevel(logging.WARNING) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(locals()) if user_id := redis_lookup_id_random(record_id_random=user_id, table_name='user'): pass diff --git a/app/routers/event_presentation.py b/app/routers/event_presentation.py index 02b0b10..99b904d 100644 --- a/app/routers/event_presentation.py +++ b/app/routers/event_presentation.py @@ -148,27 +148,27 @@ async def get_event_presentation_obj( return mk_resp(data=None, status_code=404) if event_presentation_obj := load_event_presentation_obj( - event_presentation_id = event_presentation_id, - enabled = enabled, - # review = review, - # approved = approved, - hidden = hidden, - inc_file_count = inc_file_count, - limit = limit, - inc_address = inc_address, - inc_contact = inc_contact, - inc_event_abstract_list = inc_event_abstract_list, - inc_event_badge = inc_event_badge, - # inc_event_badge_list = inc_event_badge_list, - inc_event_device_list = inc_event_device_list, - inc_event_file_list = inc_event_file_list, - inc_event_person_list = inc_event_person_list, - inc_event_presenter_list = inc_event_presenter_list, - inc_event_registration = inc_event_registration, - # inc_event_registration_list = inc_event_registration_list, - inc_person = inc_person, - inc_user = inc_user, - ): + event_presentation_id = event_presentation_id, + enabled = enabled, + # review = review, + # approved = approved, + hidden = hidden, + inc_file_count = inc_file_count, + limit = limit, + inc_address = inc_address, + inc_contact = inc_contact, + inc_event_abstract_list = inc_event_abstract_list, + inc_event_badge = inc_event_badge, + # inc_event_badge_list = inc_event_badge_list, + inc_event_device_list = inc_event_device_list, + inc_event_file_list = inc_event_file_list, + inc_event_person_list = inc_event_person_list, + inc_event_presenter_list = inc_event_presenter_list, + inc_event_registration = inc_event_registration, + # inc_event_registration_list = inc_event_registration_list, + inc_person = inc_person, + inc_user = inc_user, + ): event_presentation_dict = event_presentation_obj.dict(by_alias=by_alias, exclude_unset=exclude_unset) pass else: diff --git a/app/routers/user.py b/app/routers/user.py index c1b08aa..f4cca63 100644 --- a/app/routers/user.py +++ b/app/routers/user.py @@ -202,14 +202,17 @@ async def user_new_auth_key( # ### BEGIN ### API User Routers ### user_authenticate() ### -# Authenticate a username and password OR by authorization key +# Authenticate a username and password OR by user ID and authorization key # An authorization key can only be done once. It will be deleted if found. # A new key will need to be requested for a particular user each time. +# NOTE: Should this be divided into username/password and user ID/auth key endpoints? +# Updated 2021-10-06 @router.get('/authenticate', response_model=Resp_Body_Base) async def user_authenticate( account_id: Optional[Union[int,str]] = None, - username: Optional[str] = Query(None, min_length=2, max_length=50), - password: Optional[str] = Query(None, min_length=6, max_length=50), + user_id: Optional[str] = Query(None, min_length=11, max_length=22), + username: Optional[str] = Query(None, min_length=3, max_length=50), + password: Optional[str] = Query(None, min_length=6, max_length=100), auth_key: Optional[str] = Query(None, min_length=11, max_length=22), x_account_id: str = Header(...), inc_user_role_list: bool = False, @@ -232,7 +235,7 @@ async def user_authenticate( user_data['account_id'] = account_id user_data['username'] = username - sql_select(table_name='user', data=user_data) + # sql_select(table_name='user', data=user_data) sql = f""" SELECT `user`.id AS 'user_id', `user`.id_random AS 'user_id_random', `user`.password, `user`.enable, `user`.enable_from, `user`.enable_to @@ -247,37 +250,63 @@ async def user_authenticate( if password_hash := user_rec_result.get('password', None): if verify_secure_hash_string(string=password, string_hash=password_hash): - log.info('The username was found, and the password matched.') - #return mk_resp(data=False, status_message='The username was found, and the password matched.', response=response) + log.info(f'The username was found, and the password matched. Log in allowed if the account is enabled. Account ID: {account_id}, Username: {username}') + # The user account will be checked if it is enabled below. else: - log.info('The username was found, but the password did not match.') - return mk_resp(data=False, status_message='The username was found, but the password did not match.', response=response) + log.info(f'The username was found, but the password did not match. Not allowed to log in. Account ID: {account_id}, Username: {username}') + return mk_resp(data=False, status_message=f'The username was found, but the password did not match. Not allowed to log in. Account ID: {account_id}, Username: {username}', response=response) else: - log.error('The password has was not found. This should not happen.') - return mk_resp(data=False, status_message='The password has was not found. This should not happen.', response=response) + log.warning(f'The password hash has was not found. Not allowed to log in. Account ID: {account_id}, Username: {username}') + return mk_resp(data=False, status_code=400, status_message=f'The password hash has was not found. Not allowed to log in. Account ID: {account_id}, Username: {username}', response=response) + else: + log.info(f'A user account was not found with the account and username given. Not allowed to log in. Account ID: {account_id}, Username: {username}') + return mk_resp(data=None, status_code=404, status_message=f'A user account was not found with the account and username given. Not allowed to log in. Account ID: {account_id}, Username: {username}', response=response) # Not Found + elif user_id and auth_key: + # NOTE: Since the user_id (which is required to be unique in the DB table), the account_id is not really needed. + user_data = {} + user_data['user_id_random'] = user_id # user_id_random + user_data['auth_key'] = auth_key - else: return mk_resp(data=None, status_code=404, status_message='The user account was not found', response=response) - elif auth_key: - if user_rec_result := sql_select(table_name='user', field_name='auth_key', field_value=auth_key): + # NOTE: allow_auth_key is hardcoded in the SQL query to check that it is True + sql = f""" + SELECT `user`.id AS 'user_id', `user`.id_random AS 'user_id_random', `user`.password, `user`.enable, `user`.enable_from, `user`.enable_to + FROM `user` AS `user` + WHERE `user`.id_random = :user_id_random + AND `user`.auth_key = :auth_key + AND `user`.allow_auth_key = 1 + LIMIT 1 + """ + + if user_rec_result := sql_select(data=user_data, sql=sql): + log.info(f'The user ID and auth key combination was found. Log in allowed if the account is enabled. User ID: {user_id}') + # The user account will be checked if it is enabled below. + + # NOTE: Using the id (not id_random) value to do the SQL UPDATE faster. Probably an insignificant speed difference though. update_user_data = {} - update_user_data['id'] = user_rec_result.get('id', None) + update_user_data['id'] = user_rec_result.get('user_id', None) # Using ID, not ID Random update_user_data['auth_key'] = None if user_rec_update_result := sql_update(table_name='user', data=update_user_data): - log.info('The user record was updated with a NULL auth_key') + log.info(f'The user record was updated with a NULL auth_key. User ID: {user_id}') else: - log.info('The user record was not updated with a NULL auth_key') + log.error(f'The user record was not updated with a NULL auth_key. User ID: {user_id}') + log.debug(update_user_data) log.debug(user_rec_update_result) - user_id = user_rec_result.get('id', None) # NOTE: This us looking for "id", not "user_id" - else: return mk_resp(data=None, status_code=404, status_message='A user account with that auth key was not found', response=response) + # user_id = user_rec_result.get('id', None) # NOTE: This is looking for "id", not "user_id" + else: + log.info(f'The user ID and auth key combination was not found. Not allowed to log in. User ID: {user_id}, Auth Key: {auth_key}') + log.debug(user_data) + log.debug(sql) + return mk_resp(data=None, status_code=404, status_message=f'The user ID and auth key combination was not found. Not allowed to log in. User ID: {user_id}, Auth Key: {auth_key}', response=response) # Not Found else: return mk_resp(data=None, status_code=400, status_message='One more user account fields was missing or unexpected.', response=response) # Bad Request log.setLevel(logging.DEBUG) # DEBUG, INFO, WARNING, ERROR, EXCEPTION, CRITICAL log.debug(user_rec_result) + # Check if the SQL result is as expected and check if the user account is enabled. if isinstance(user_rec_result, dict): - + log.info(f'Checking if the user is enabled. Account ID: {account_id}, Username: {username}') current_utc_datetime = datetime.datetime.now(datetime.timezone.utc) log.debug(current_utc_datetime) @@ -287,36 +316,49 @@ async def user_authenticate( log.info('The user account is not enabled') return mk_resp(data=False, status_message='This user account is not enabled', response=response) - #if user_enable_from := user_rec_result.get('enable_from', None).astimezone(pytz.UTC): - if user_enable_from := user_rec_result.get('enable_from', None).replace(tzinfo=datetime.timezone.utc): - log.debug(user_enable_from) - if user_enable_from <= current_utc_datetime: - log.info('Enable from datetime is valid') - else: - log.info('Enable from datetime is in the future. Please wait.') - return mk_resp(data=False, status_message='This account is not yet enabled', response=response) + if user_rec_result.get('enable_from', None): + #if user_enable_from := user_rec_result.get('enable_from', None).astimezone(pytz.UTC): + if user_enable_from := user_rec_result.get('enable_from', None).replace(tzinfo=datetime.timezone.utc): + log.debug(user_enable_from) + if user_enable_from <= current_utc_datetime: + log.info('Enable from datetime is valid') + else: + log.info(f'Enable from datetime is in the future. The user account has not been enabled yet. User Enabled From: {user_enable_from}') + return mk_resp(data=False, status_message=f'This user account is not yet enabled. User Enabled From: {user_enable_from}', response=response) + else: + log.warning('The enable_from datetime was not set. Ignoring this check.') - #if user_enable_to := user_rec_result.get('enable_to', None).astimezone(pytz.UTC): - if user_enable_to := user_rec_result.get('enable_to', None).replace(tzinfo=datetime.timezone.utc): - log.debug(user_enable_to) - if user_enable_to >= current_utc_datetime: - log.info('Enable to datetime is valid') - else: - log.info('Enable to datetime is in the past. Your user account has been disabled.') - return mk_resp(data=False, status_message='This account is not enabled because the expiratation date has passed', response=response) + if user_rec_result.get('enable_to', None): + #if user_enable_to := user_rec_result.get('enable_to', None).astimezone(pytz.UTC): + if user_enable_to := user_rec_result.get('enable_to', None).replace(tzinfo=datetime.timezone.utc): + log.debug(user_enable_to) + if user_enable_to >= current_utc_datetime: + log.info('Enable to datetime is valid') + else: + log.info(f'Enable to datetime is in the past. The user account has been disabled. User Enabled To: {user_enable_to}') + return mk_resp(data=False, status_message=f'This user account is not enabled because the expiratation date has passed. User Enabled To: {user_enable_to}', response=response) + else: + log.warning('The enable_to datetime was not set. Ignoring this check.') - user_obj = load_user_obj( - user_id=user_id, - inc_user_role_list=inc_user_role_list, - inc_contact=inc_contact, - inc_organization=inc_organization, - inc_person=inc_person - ).dict(by_alias=by_alias, exclude_unset=exclude_unset) - data = user_obj - return mk_resp(data=user_obj, response=response) + # Try to load the user object + if user_obj_result := load_user_obj( + user_id = user_id, + inc_user_role_list = inc_user_role_list, + inc_contact = inc_contact, + inc_organization = inc_organization, + inc_person = inc_person, + ): + log.info(f'The user account was loaded. Account ID: {account_id} Username: {username}') + user_obj_dict = user_obj_result.dict(by_alias=by_alias, exclude_unset=exclude_unset) + return mk_resp(data=user_obj_dict, response=response) + else: + log.warning(f'Something went wrong while trying to load the user account. Account ID: {account_id} Username: {username}') + log.debug(user_obj_result) + return mk_resp(data=False, status_code=500, status_message=f'Something went wrong while trying to load the user account. Account ID: {account_id} Username: {username}', response=response) # Internal Server Error else: - log.error('SQL result was unexpected. A dict result type was expected. This should not happen.') - return mk_resp(data=False, status_code=500, response=response) + log.error(f'SQL result was unexpected. A dict result type was expected. This should not happen. Account ID: {account_id} Username: {username}') + log.debug(user_rec_result) + return mk_resp(data=False, status_code=500, status_message=f'The database lookup result was unexpected. This should not happen. Account ID: {account_id} Username: {username}', response=response) # ### END ### API User Routers ### user_authenticate() ###