Massive rewrite of user and product logic. Still WIP
This commit is contained in:
1 parent
be7b8064d6
commit
c018d2fb52
49 files changed
+1471
-993
No files matched your search
@@ -0,0 +1,31 @@
|
||||
from typing import Tuple, Union
|
||||
from mysql.connector import Error as mysqlError
|
||||
|
||||
import app.db.user_db as user_db
|
||||
from app.models.user_model import User
|
||||
|
||||
import app.messages.api_responses.user_responses as response
|
||||
import app.messages.api_errors as errors
|
||||
from app.mail.mail import send_mail
|
||||
|
||||
|
||||
from app.messages.mail_responses.user_email import USER_EMAIL_SUCCESSFULLY_DELETED_ACCOUNT
|
||||
|
||||
def delete_user(user_id: str) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Deletes a user account.
|
||||
|
||||
:param user_id: User ID.
|
||||
:type user_id: str
|
||||
:return: Tuple containing a dictionary and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
user: User = user_db.fetch_by_id(user_id=user_id)
|
||||
user_db.delete_user(user)
|
||||
send_mail(USER_EMAIL_SUCCESSFULLY_DELETED_ACCOUNT, user.email)
|
||||
except mysqlError as e:
|
||||
return errors.UNKNOWN_DATABASE_ERROR(e)
|
||||
|
||||
return response.USER_DELETED_SUCCESSFULLY
|
||||
@@ -0,0 +1,45 @@
|
||||
import datetime
|
||||
from typing import Tuple, Union
|
||||
|
||||
import bcrypt
|
||||
from mysql.connector import Error as mysqlError
|
||||
from flask_jwt_extended import create_access_token
|
||||
|
||||
import app.messages.api_responses.user_responses as response
|
||||
import app.messages.api_errors as errors
|
||||
from app.db import user_db
|
||||
from app.mail.mail import send_mail
|
||||
from app.models.user_model import User
|
||||
from app.messages.mail_responses.user_email import USER_EMAIL_SUCCESSFULLY_LOGGED_IN
|
||||
|
||||
|
||||
|
||||
def login(username: str, password: str) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Authenticates a user with the provided username and password.
|
||||
|
||||
:param username: User's username.
|
||||
:type username: str
|
||||
:param password: User's password.
|
||||
:type password: str
|
||||
:return: Tuple containing a dictionary with a token and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
try:
|
||||
user: User = user_db.fetch_by_username(username)
|
||||
|
||||
if user is None:
|
||||
return response.USERNAME_NOT_FOUND
|
||||
|
||||
if not bcrypt.checkpw(password.encode("utf-8"), user.password.encode("utf-8")):
|
||||
return response.INCORRECT_PASSWORD
|
||||
|
||||
expire = datetime.timedelta(hours=1)
|
||||
token = create_access_token(identity=user.user_id, expires_delta=expire)
|
||||
|
||||
send_mail(USER_EMAIL_SUCCESSFULLY_LOGGED_IN, user.email)
|
||||
|
||||
return {"token": token}, 200
|
||||
|
||||
except mysqlError as e:
|
||||
return errors.UNKNOWN_DATABASE_ERROR(e)
|
||||
@@ -0,0 +1,31 @@
|
||||
from typing import Tuple, Union
|
||||
|
||||
import app.messages.api_responses.user_responses as response
|
||||
from app.db import user_db
|
||||
from app.models.user_model import User
|
||||
from app.mail.mail import send_mail
|
||||
from app.services.user import user_helper as helper
|
||||
from app.messages.mail_responses.user_email import USER_EMAIL_SUCCESSFULLY_LOGGED_OUT
|
||||
|
||||
|
||||
def logout(jwt_token, user_id, send_notif: bool) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Logs out a user by invalidating the provided JWT.
|
||||
|
||||
:param jti: JWT ID.
|
||||
:type jti: str
|
||||
:param exp: JWT expiration timestamp.
|
||||
:type exp: int
|
||||
:return: Tuple containing a dictionary and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
jti = jwt_token["jti"]
|
||||
exp = jwt_token["exp"]
|
||||
|
||||
user: User = user_db.fetch_by_id(user_id)
|
||||
|
||||
helper.invalidate_token(jti, exp)
|
||||
if send_notif:
|
||||
send_mail(USER_EMAIL_SUCCESSFULLY_LOGGED_OUT, user.email)
|
||||
return response.USER_LOGGED_OUT_SUCCESSFULLY
|
||||
@@ -0,0 +1,64 @@
|
||||
import bcrypt
|
||||
from typing import Tuple, Union
|
||||
from mysql.connector import Error as mysqlError
|
||||
|
||||
import app.messages.api_responses.user_responses as response
|
||||
import app.messages.api_errors as errors
|
||||
from app.db import user_db
|
||||
from app.mail.mail import send_mail
|
||||
from app.models.user_model import User
|
||||
from app.services.user import user_helper as helper
|
||||
from app.messages.mail_responses.user_email import USER_EMAIL_SUCCESSFULLY_REGISTERED
|
||||
|
||||
|
||||
def register(
|
||||
username: str, displayname: str, email: str, password: str
|
||||
) -> Tuple[Union[dict, str], int]:
|
||||
"""
|
||||
Registers a new user with the provided username, email, and password.
|
||||
|
||||
:param username: User's username.
|
||||
:type username: str
|
||||
:param email: User's email address.
|
||||
:type email: str
|
||||
:param password: User's password.
|
||||
:type password: str
|
||||
:return: Tuple containing a dictionary and an HTTP status code.
|
||||
:rtype: Tuple[Union[dict, str], int]
|
||||
"""
|
||||
|
||||
try:
|
||||
if not helper.verify_username(username):
|
||||
return response.INVALID_USERNAME_FORMAT
|
||||
|
||||
if not helper.verify_displayname(displayname):
|
||||
return response.INVALID_DISPLAYNAME_FORMAT
|
||||
|
||||
if not helper.verify_email(email):
|
||||
return response.INVALID_EMAIL_FORMAT
|
||||
|
||||
if not helper.verify_password(password):
|
||||
return response.INVALID_PASSWORD_FORMAT
|
||||
|
||||
hashed_password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
||||
|
||||
new_user: User = User(
|
||||
username=username,
|
||||
displayname=displayname,
|
||||
email=email,
|
||||
password=hashed_password,
|
||||
)
|
||||
|
||||
user_db.insert_user(new_user)
|
||||
|
||||
except mysqlError as e:
|
||||
if "email" in e.msg:
|
||||
return response.EMAIL_ALREADY_IN_USE
|
||||
if "username" in e.msg:
|
||||
return response.USERNAME_ALREADY_IN_USE
|
||||
|
||||
return errors.UNKNOWN_DATABASE_ERROR(e)
|
||||
|
||||
send_mail(USER_EMAIL_SUCCESSFULLY_REGISTERED, new_user.email)
|
||||
|
||||
return response.USER_CREATED_SUCCESSFULLY
|
||||
@@ -0,0 +1,72 @@
|
||||
import bcrypt
|
||||
from typing import Tuple, Union
|
||||
from mysql.connector import Error as mysqlError
|
||||
|
||||
from app.db import user_db
|
||||
from app.mail.mail import send_mail
|
||||
from app.models.user_model import User
|
||||
from app.services.user import user_helper as helper
|
||||
from app.messages.api_responses import user_responses as response
|
||||
import app.messages.api_errors as errors
|
||||
from app.messages.mail_responses.user_email import (
|
||||
USER_EMAIL_SUCCESSFULLY_UPDATED_ACCOUNT,
|
||||
)
|
||||
|
||||
|
||||
def update_user(
|
||||
user_id: str,
|
||||
new_username: str = None,
|
||||
new_displayname: str = None,
|
||||
new_email: str = None,
|
||||
new_password: str = None,
|
||||
) -> Tuple[Union[dict, str], int]:
|
||||
user: User = user_db.fetch_by_id(user_id)
|
||||
|
||||
updated_attributes = []
|
||||
|
||||
if user is None:
|
||||
return response.UNKNOWN_ERROR
|
||||
|
||||
if new_username:
|
||||
if not helper.verify_username(new_username):
|
||||
return response.INVALID_USERNAME_FORMAT
|
||||
|
||||
user.username = new_username
|
||||
updated_attributes.append("username")
|
||||
|
||||
if new_displayname:
|
||||
if not helper.verify_displayname(new_displayname):
|
||||
return response.INVALID_DISPLAYNAME_FORMAT
|
||||
|
||||
user.displayname = new_displayname
|
||||
updated_attributes.append("displayname")
|
||||
|
||||
if new_email:
|
||||
if not helper.verify_email(new_email):
|
||||
return response.INVALID_EMAIL_FORMAT
|
||||
|
||||
user.email = new_email
|
||||
updated_attributes.append("email")
|
||||
|
||||
if new_password:
|
||||
if not helper.verify_password(new_password):
|
||||
return response.INVALID_PASSWORD_FORMAT
|
||||
|
||||
hashed_password = bcrypt.hashpw(new_password.encode("utf-8"), bcrypt.gensalt())
|
||||
|
||||
user.password = hashed_password
|
||||
updated_attributes.append("password")
|
||||
|
||||
try:
|
||||
user_db.update_user(user)
|
||||
|
||||
except mysqlError as e:
|
||||
if "username" in e.msg:
|
||||
return response.USERNAME_ALREADY_IN_USE
|
||||
if "email" in e.msg:
|
||||
return response.EMAIL_ALREADY_IN_USE
|
||||
|
||||
return errors.UNKNOWN_DATABASE_ERROR(e)
|
||||
|
||||
send_mail(USER_EMAIL_SUCCESSFULLY_UPDATED_ACCOUNT, user.email)
|
||||
return response.USER_ACCOUNT_UPDATED_SUCCESSFULLY(updated_attributes)
|
||||
@@ -0,0 +1,74 @@
|
||||
import re
|
||||
from datetime import datetime
|
||||
|
||||
from app.extensions import jwt_redis_blocklist
|
||||
|
||||
|
||||
def invalidate_token(jti: str, exp: int):
|
||||
"""
|
||||
Invalidates a JWT by adding its JTI to the Redis blocklist.
|
||||
|
||||
:param jti: JWT ID.
|
||||
:type jti: str
|
||||
:param exp: JWT expiration timestamp.
|
||||
:type exp: int
|
||||
"""
|
||||
expiration = datetime.fromtimestamp(exp)
|
||||
now = datetime.now()
|
||||
|
||||
delta = expiration - now
|
||||
jwt_redis_blocklist.set(jti, "", ex=delta)
|
||||
|
||||
|
||||
def verify_email(email: str) -> bool:
|
||||
"""
|
||||
Verifies a given email string against a regular expression.
|
||||
|
||||
:param email: Email string.
|
||||
:type email: str
|
||||
:return: Boolean indicating whether the email successfully passed the check.
|
||||
:rtype: bool
|
||||
"""
|
||||
email_regex = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
|
||||
return re.match(email_regex, email) and len(email) <= 64
|
||||
|
||||
|
||||
def verify_displayname(displayname: str) -> bool:
|
||||
"""
|
||||
Verifies a given display name string against a regular expression.
|
||||
|
||||
:param displayname: Display name string.
|
||||
:type displayname: str
|
||||
:return: Boolean indicating whether the display name successfully passed the check.
|
||||
:rtype: bool
|
||||
"""
|
||||
displayname_regex = r"^[a-zA-Z.-_]{1,64}$"
|
||||
return re.match(displayname_regex, displayname)
|
||||
|
||||
|
||||
def verify_username(username: str) -> bool:
|
||||
"""
|
||||
Verifies a given username string against a regular expression.
|
||||
|
||||
:param username: Username string.
|
||||
:type username: str
|
||||
:return: Boolean indicating whether the username successfully passed the check.
|
||||
:rtype: bool
|
||||
"""
|
||||
username_regex = r"^[a-z]{1,64}$"
|
||||
return re.match(username_regex, username)
|
||||
|
||||
|
||||
def verify_password(password: str) -> bool:
|
||||
"""
|
||||
Verifies a given password string against a regular expression.
|
||||
|
||||
:param password: Password string.
|
||||
:type password: str
|
||||
:return: Boolean indicating whether the password successfully passed the check.
|
||||
:rtype: bool
|
||||
"""
|
||||
password_regex = (
|
||||
r"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$ %^&*-]).{8,64}$"
|
||||
)
|
||||
return re.match(password_regex, password)
|
||||
Reference in new issue
Block a user