97 async def post_login(self, request: Request) -> Response:
100 Validate provided credentials and return an authentication token on
101 success. Produces a suitable HTTP response for success, unauthorized
105 request: The incoming FastAPI request containing JSON with
106 ``email`` and ``password``.
109 Response: A FastAPI response with the result body and status.
113 self.
disp.log_debug(f
"Request body: {request_body}", title)
114 if not request_body
or not all(key
in request_body
for key
in (
"email",
"password")):
116 email = request_body[
"email"]
117 password = request_body[
"password"]
119 CONST.TAB_ACCOUNTS,
"*", f
"email='{email}'"
121 self.
disp.log_debug(f
"Retrived data: {user_info}", title)
122 if isinstance(user_info, int)
or len(user_info) == 0:
129 if data[
"status"] == self.
error:
132 message=
"Login failed.",
138 name = user_info[0][
"username"]
141 message=f
"Welcome {name}",
146 body[
"token"] = data[
"token"]
170 default: str =
"15-20"
171 self.
disp.log_debug(f
"Default age: '{default}'")
188 if isinstance(age, (int, float)):
190 self.
disp.log_debug(f
"Age provided as number: {age_num}")
191 for min_age, max_age, range_str
in age_ranges:
192 if min_age <= age_num <= max_age:
194 f
"Age {age_num} falls in range {range_str}")
197 f
"Age {age_num} doesn't fall in any defined range")
201 if isinstance(age, str):
202 age_str = age.strip()
203 self.
disp.log_debug(f
"Age provided as string: '{age_str}'")
206 valid_range_strs = [r[2]
for r
in age_ranges]
207 if age_str
in valid_range_strs:
209 f
"Age string '{age_str}' is already a valid range")
214 age_num = int(float(age_str))
215 self.
disp.log_debug(f
"Age string parsed as number: {age_num}")
216 for min_age, max_age, range_str
in age_ranges:
217 if min_age <= age_num <= max_age:
219 f
"Age {age_num} falls in range {range_str}")
222 f
"Age {age_num} doesn't fall in any defined range")
223 except (ValueError, TypeError):
225 f
"Could not parse age string '{age_str}' as number")
231 "Age is either not in the list or not provided"
236 """Register a new user account.
238 Create a new account from provided ``email`` and ``password`` and
239 automatically log the user in on success.
242 request: The incoming FastAPI request with required fields.
245 Response: A FastAPI response indicating success or a suitable
246 error code (e.g. conflict if the email already exists).
250 self.
disp.log_debug(f
"Request body: {request_body}", title)
251 if not request_body
or (
"email" not in request_body
and "password" not in request_body):
253 email: str = request_body[
"email"]
254 password: str = request_body[
"password"]
255 if "gender" not in request_body:
256 self.
disp.log_warning(
257 "The gender field was not provided during registration."
259 if "age" not in request_body:
260 self.
disp.log_warning(
261 "The age field was not provided during registration."
263 gender: str = self.
_check_gender(request_body.get(
"gender",
""))
265 if not (email
and password):
268 CONST.TAB_ACCOUNTS,
"*", f
"email='{email}'"
272 message=
"Unable to create account. Please try again later.",
273 resp=
"registration_failed",
277 if not isinstance(user_info, int):
278 for user
in user_info:
279 if email
in user[
"email"]:
280 return HCI.bad_request(account_creation_error)
282 return HCI.bad_request(account_creation_error)
285 username = email.split(
'@')[0]
286 self.
disp.log_debug(f
"Username = {username}", title)
290 data: List[Union[str, int, float,
None]] = [
291 username, email, hashed_password,
"local", gender, age, favicon, admin, deletion_date
293 self.
disp.log_debug(f
"Data list = {data}", title)
297 self.
disp.log_debug(f
"Column = {column}", title)
298 if isinstance(column, int):
300 column = CONST.clean_list(
302 CONST.TABLE_COLUMNS_TO_IGNORE_USER,
305 if self.
database_link.insert_data_into_table(CONST.TAB_ACCOUNTS, data, column) == self.
error:
310 if login_data[
"status"] == self.
error:
313 message=
"Login failed.",
315 token=login_data[
"token"],
321 message=f
"Welcome {username}",
323 token=login_data[
"token"],
326 body[
"token"] = login_data[
"token"]
329 content_type=HTTP_DEFAULT_TYPE,
334 """Send an email verification code to a user.
336 Generate and store a verification code for the provided email and
337 send it via the configured mail management subsystem.
340 request: The incoming FastAPI request containing the ``email``
344 Response: A FastAPI response indicating whether the email was
345 sent successfully or an error occurred.
347 title =
"Send e-mail verification"
349 self.
disp.log_debug(f
"Request body: {request_body}", title)
350 if not request_body
or (
"email")
not in request_body:
352 email: str = request_body[
"email"]
354 table=CONST.TAB_ACCOUNTS,
356 where=f
"email='{email}'",
359 if isinstance(data, int):
361 self.
disp.log_debug(f
"user query = {data}", title)
362 if data == self.
error or len(data) == 0:
364 email_subject =
"[Asperguide] Verification code"
366 CONST.CHECK_TOKEN_SIZE
369 CONST.EMAIL_VERIFICATION_DELAY
372 expiration_time,
False
375 new_node[
'email'] = email
376 new_node[
'code'] = code
378 CONST.TAB_VERIFICATION)
379 if isinstance(tab_column, int):
381 if tab_column == self.
error or len(tab_column) == 0:
383 tab_column = CONST.clean_list(
385 CONST.TABLE_COLUMNS_TO_IGNORE,
389 CONST.TAB_VERIFICATION,
393 table=CONST.TAB_VERIFICATION,
398 expiration_time,
False,
True
403 if status == self.
error:
405 code_style =
"background-color: lightgray;border: 2px lightgray solid;border-radius: 6px;color: black;font-weight: bold;padding: 5px;padding-top: 5px;padding-bottom: 5px;padding-top: 0px;padding-bottom: 0px;"
407 body +=
"<p>The code is: "
408 body += f
"<span style=\"{code_style}\">{code}</span></p>"
409 body +=
"<p>The code will be valid until "
410 body += f
"<span style=\"{code_style}\">"
411 body += f
"{expiration_time_str}</span>.</p>"
412 self.
disp.log_debug(f
"e-mail body: {body}", title)
414 email, email_subject, body
416 if status == self.
error:
420 message=
"Email send successfully.",
425 return HCI.success(body)
428 """Reset a user's password using a verification code.
430 Validates the provided code for the email and updates the account password to the supplied new password on success.
433 request: The incoming FastAPI request containing ``email``, ``code`` and ``password``.
436 Response: A FastAPI response indicating success or an error such as invalid verification code.
438 title =
"Reset password"
440 self.
disp.log_debug(f
"Request body: {request_body}", title)
441 if not request_body
or not all(key
in request_body
for key
in (
"email",
"code",
"password")):
443 body_email: str = request_body[
"email"]
444 body_code: str = request_body[
"code"]
445 body_password: str = request_body[
"password"]
446 verified_user: dict = {}
448 CONST.TAB_VERIFICATION,
450 where=f
"term='{body_email}'",
453 self.
disp.log_debug(f
"Current codes: {current_codes}", title)
454 nodes_of_interest = []
455 if isinstance(current_codes, int)
and current_codes == self.
error:
457 current_codes_list = []
458 if isinstance(current_codes, list):
459 current_codes_list: List[Dict[str, Any]] = current_codes
460 if len(current_codes_list) == 0:
462 for user
in current_codes_list:
463 if user.get(
"term") == body_email
and user.get(
"definition") == body_code:
465 nodes_of_interest.append(user)
466 if not verified_user:
473 data.append(hashed_password)
474 column.append(
"password")
476 CONST.TAB_ACCOUNTS, data, column, f
"email='{body_email}'"
478 if status == self.
error:
480 self.
disp.log_debug(f
"Nodes found: {nodes_of_interest}", title)
481 for line
in nodes_of_interest:
482 self.
disp.log_debug(f
"line removed: {line}", title)
484 CONST.TAB_VERIFICATION,
489 message=
"Password changed successfully.",
496 async def put_user(self, request: Request) -> Response:
497 """Replace a user's account information.
499 Requires a valid authentication token. Replaces username, email and
500 password with the submitted values.
503 request (Request): The incoming FastAPI request with ``username``,
504 ``email`` and ``password`` fields and authentication token.
507 Response: A FastAPI response indicating update success or an
508 appropriate error response.
516 token: str = token_raw
520 self.
disp.log_debug(f
"token = {token}, valid = {token_valid}", title)
521 if token_valid
is False:
524 self.
disp.log_debug(f
"Request body: {request_body}", title)
525 if not request_body
or not all(key
in request_body
for key
in (
"username",
"email",
"password")):
527 body_username: str = request_body[
"username"]
528 body_email: str = request_body[
"email"]
529 body_password: str = request_body[
"password"]
535 if isinstance(usr_id, Response):
537 user_profile_raw: Union[int, List[Dict[str, Any]]] = self.
database_link.get_data_from_table(
538 table=CONST.TAB_ACCOUNTS,
540 where=f
"id='{usr_id}'",
542 if isinstance(user_profile_raw, int):
544 user_profile: List[Dict[str, Any]] = user_profile_raw
545 self.
disp.log_debug(f
"User profile = {user_profile}", title)
546 if user_profile == self.
error or len(user_profile) == 0:
548 data: List[Optional[Union[str, int, float]]] = [
552 user_profile[0][
"method"],
553 user_profile[0][
"favicon"],
554 str(user_profile[0][
"admin"])
559 if isinstance(status, Response):
565 message=
"The account information has been updated.",
573 """Partially update a user's account information.
575 Only fields present in the request body are updated. Requires a
576 valid authentication token.
579 request (Request): The incoming FastAPI request which may include any of
580 ``username``, ``email`` or ``password``.
583 Response: A FastAPI response indicating the update result.
591 token: str = token_raw
595 self.
disp.log_debug(f
"token = {token}, valid = {token_valid}", title)
596 if token_valid
is False:
599 self.
disp.log_debug(f
"Request body: {request_body}", title)
600 body_username: str = request_body.get(
"username",
"")
601 body_email: str = request_body.get(
"email",
"")
602 body_password: str = request_body.get(
"password",
"")
606 if isinstance(usr_id, Response):
610 user_profile_raw: Union[int, List[Dict[str, Any]]] = self.
database_link.get_data_from_table(
611 table=CONST.TAB_ACCOUNTS,
613 where=f
"id='{usr_id}'",
615 if isinstance(user_profile_raw, int)
or user_profile_raw == self.
error:
617 user_profile: List[Dict[str, Any]] = user_profile_raw
618 self.
disp.log_debug(f
"User profile = {user_profile}", title)
619 if len(user_profile) == 0:
621 email: str = user_profile[0][
"email"]
622 username: str = user_profile[0][
"username"]
623 password: str = user_profile[0][
"password"]
624 msg = f
"body_username = {body_username}, body_email = {body_email}, "
625 msg += f
"body_password = {body_password}, email = {email}, "
626 msg += f
"username = {username}, password = {password}"
627 self.
disp.log_debug(msg, title)
628 if body_username
is not None:
629 username = body_username
630 self.
disp.log_debug(f
"username is now: {username}", title)
631 if body_email
is not None:
633 self.
disp.log_debug(f
"email is now: {email}", title)
634 if body_password
is not None:
638 self.
disp.log_debug(f
"password is now: {password}", title)
639 data: List[Union[str, int, float,
None]] = [
640 username, email, password,
641 user_profile[0][
"method"], user_profile[0][
"favicon"],
642 str(user_profile[0][
"admin"])
647 if isinstance(status, int)
or not status:
649 if isinstance(status, Response):
653 message=
"The account information has been updated.",
660 async def get_user(self, request: Request) -> Response:
661 """Return the authenticated user's profile data.
663 Sensitive or banned fields are removed from the returned profile.
666 request (Request): The incoming FastAPI request carrying an auth token.
669 Response: A FastAPI response with the user profile on success.
677 token: str = token_raw
681 self.
disp.log_debug(f
"token = {token}, valid = {token_valid}", title)
682 if token_valid
is False:
687 self.
disp.log_debug(f
"user_id = {usr_id}", title)
688 if isinstance(usr_id, Response):
690 user_profile_raw: Union[int, List[Dict[str, Any]]] = self.
database_link.get_data_from_table(
691 table=CONST.TAB_ACCOUNTS,
693 where=f
"id='{usr_id}'",
695 if isinstance(user_profile_raw, int):
697 user_profile: List[Dict[str, Any]] = user_profile_raw
698 self.
disp.log_debug(f
"User profile = {user_profile}", title)
699 if user_profile == self.
error or len(user_profile) == 0:
701 new_profile = user_profile[0]
702 for i
in CONST.USER_INFO_BANNED:
705 if CONST.USER_INFO_ADMIN_NODE
in new_profile:
706 new_profile[CONST.USER_INFO_ADMIN_NODE] = bool(
707 new_profile[CONST.USER_INFO_ADMIN_NODE]
719 """Delete the authenticated user's account and related data.
721 Removes the user record and cleans up related tables (services,
722 actions, connections, OAuth sessions). Requires a valid auth token.
725 request (Request): The incoming FastAPI request containing the auth token.
728 Response: A FastAPI response indicating deletion success or an
729 internal server error if cleanup fails.
731 title =
"Delete user"
737 token: str = token_raw
741 self.
disp.log_debug(f
"token = {token}, valid = {token_valid}", title)
742 if token_valid
is False:
747 self.
disp.log_debug(f
"user_id = {usr_id}", title)
748 if isinstance(usr_id, Response):
750 user_profile_raw: Union[int, List[Dict[str, Any]]] = self.
database_link.get_data_from_table(
751 table=CONST.TAB_ACCOUNTS,
753 where=f
"id='{usr_id}'",
755 if isinstance(user_profile_raw, int):
757 user_profile: List[Dict[str, Any]] = user_profile_raw
758 self.
disp.log_debug(f
"User profile = {user_profile}", title)
759 if user_profile == self.
error or len(user_profile) == 0:
761 self.
disp.log_warning(
"-------------------------------------")
762 self.
disp.log_warning(
"-------------------------------------")
763 self.
disp.log_warning(
"-------------------------------------")
764 self.
disp.log_warning(
"-------------------------------------")
765 self.
disp.log_warning(
766 "Check table section and make sure that it is up to date."
768 self.
disp.log_warning(
"-------------------------------------")
769 self.
disp.log_warning(
"-------------------------------------")
770 self.
disp.log_warning(
"-------------------------------------")
771 self.
disp.log_warning(
"-------------------------------------")
772 tables_of_interest = [
774 CONST.TAB_CONNECTIONS, CONST.TAB_ACTIVE_OAUTHS
777 f
"user_id={usr_id}", tables_of_interest
779 if isinstance(removal_status, int)
or self.
error in list(removal_status.values()):
782 CONST.TAB_ACCOUNTS, f
"id={usr_id}"
784 if status == self.
error:
788 message=
"The account has successfully been deleted.",
796 """Log out the current user by removing the session token.
798 Requires a valid token; removes the connection record associated with the token and returns an appropriate response.
801 request: The incoming FastAPI request carrying the auth token.
804 Response: A FastAPI response indicating logout success or an
805 internal server error.
808 self.
disp.log_debug(
"Checking is a token is present", title)
814 token: str = token_raw
815 self.
disp.log_debug(
"Checking if the token is still valid", title)
819 self.
disp.log_debug(f
"token = {token}, valid = {token_valid}", title)
822 self.
disp.log_debug(
"Attempting to remove data from the table", title)
824 f
"token: {token}, table: {CONST.TAB_CONNECTIONS}", title
827 CONST.TAB_CONNECTIONS,
830 if response == self.
error:
833 "Informing the user that the removal was a success", title
837 message=
"You have successfully logged out...",
845 """Return the numerical id of the authenticated user.
847 Validates the provided token and returns the user id in the response body.
850 request (Request): The incoming FastAPI request containing the auth token.
853 Response: A FastAPI response with the user's id on success.
855 title =
"Get user id"
861 token: str = token_raw
865 self.
disp.log_debug(f
"token = {token}, valid = {token_valid}", title)
866 if token_valid
is False:
871 self.
disp.log_debug(f
"user_id = {usr_id}", title)
872 if usr_id == self.
error or isinstance(usr_id, list)
and len(usr_id) == 0:
874 if isinstance(usr_id, Response):
878 message=f
"Your id is {usr_id}",