Email rework and additional notifications
This commit is contained in:
		
							parent
							
								
									a22d8becbb
								
							
						
					
					
						commit
						4fb09e8fd7
					
				| @ -9,7 +9,7 @@ app = Flask(__name__) | |||||||
| from app.config import FlaskTesting, FlaskProduction | from app.config import FlaskTesting, FlaskProduction | ||||||
| app.config.from_object(FlaskTesting) | app.config.from_object(FlaskTesting) | ||||||
| 
 | 
 | ||||||
| mail = Mail(app) | flask_mail = Mail(app) | ||||||
| jwt_manager = JWTManager(app) | jwt_manager = JWTManager(app) | ||||||
| swag = Swagger(app, template=main_swagger) | swag = Swagger(app, template=main_swagger) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ def unauthorized(e): | |||||||
| 
 | 
 | ||||||
| @bp_errors.app_errorhandler(403) | @bp_errors.app_errorhandler(403) | ||||||
| def forbidden(e): | def forbidden(e): | ||||||
| 	return {"Forbidden": "Forbidden from accessing this resource. Try logging in"}, 403 | 	return {"Forbidden": "You shall not pass"}, 403 | ||||||
| 
 | 
 | ||||||
| @bp_errors.app_errorhandler(404) | @bp_errors.app_errorhandler(404) | ||||||
| def not_found(e): | def not_found(e): | ||||||
|  | |||||||
| @ -43,7 +43,10 @@ def logout(): | |||||||
| 
 | 
 | ||||||
| 	jti = jwt['jti'] | 	jti = jwt['jti'] | ||||||
| 	exp = jwt['exp'] | 	exp = jwt['exp'] | ||||||
| 	result, status_code = UserService.logout(jti, exp) | 
 | ||||||
|  | 	user_id = get_jwt_identity() | ||||||
|  | 
 | ||||||
|  | 	result, status_code = UserService.logout(jti, exp, user_id) | ||||||
| 
 | 
 | ||||||
| 	return result, status_code | 	return result, status_code | ||||||
| 
 | 
 | ||||||
| @ -62,7 +65,7 @@ def update_username(): | |||||||
| 
 | 
 | ||||||
| 	jti = jwt['jti'] | 	jti = jwt['jti'] | ||||||
| 	exp = jwt['exp'] | 	exp = jwt['exp'] | ||||||
| 	UserService.logout(jti, exp) | 	UserService.logout(jti, exp, user_id) | ||||||
| 
 | 
 | ||||||
| 	return result, status_code | 	return result, status_code | ||||||
| 
 | 
 | ||||||
| @ -81,7 +84,7 @@ def update_displayname(): | |||||||
| 	 | 	 | ||||||
| 	jti = jwt['jti'] | 	jti = jwt['jti'] | ||||||
| 	exp = jwt['exp'] | 	exp = jwt['exp'] | ||||||
| 	UserService.logout(jti, exp) | 	UserService.logout(jti, exp, user_id) | ||||||
| 
 | 
 | ||||||
| 	return result, status_code | 	return result, status_code | ||||||
| 
 | 
 | ||||||
| @ -100,7 +103,7 @@ def update_email(): | |||||||
| 	 | 	 | ||||||
| 	jti = jwt['jti'] | 	jti = jwt['jti'] | ||||||
| 	exp = jwt['exp'] | 	exp = jwt['exp'] | ||||||
| 	UserService.logout(jti, exp) | 	UserService.logout(jti, exp, username) | ||||||
| 
 | 
 | ||||||
| 	return result, status_code | 	return result, status_code | ||||||
| 
 | 
 | ||||||
| @ -120,7 +123,7 @@ def update_password(): | |||||||
| 
 | 
 | ||||||
| 	jti = jwt['jti'] | 	jti = jwt['jti'] | ||||||
| 	exp = jwt['exp'] | 	exp = jwt['exp'] | ||||||
| 	UserService.logout(jti, exp) | 	UserService.logout(jti, exp, username) | ||||||
| 
 | 
 | ||||||
| 	return result, status_code | 	return result, status_code | ||||||
| 
 | 
 | ||||||
| @ -136,6 +139,6 @@ def delete_user(): | |||||||
| 
 | 
 | ||||||
| 	jti = jwt['jti'] | 	jti = jwt['jti'] | ||||||
| 	exp = jwt['exp'] | 	exp = jwt['exp'] | ||||||
| 	UserService.logout(jti, exp) | 	UserService.logout(jti, exp, user_id) | ||||||
| 
 | 
 | ||||||
| 	return result, status_code | 	return result, status_code | ||||||
							
								
								
									
										20
									
								
								app/mail/mail.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								app/mail/mail.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | from flask_mail import Message | ||||||
|  | 
 | ||||||
|  | from app import flask_mail | ||||||
|  | 
 | ||||||
|  | from app.mail.messages import messages | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def send_mail(message: str, recipient: str): | ||||||
|  | 
 | ||||||
|  | 	body = messages[message]["body"] | ||||||
|  | 	subject = messages[message]["subject"] | ||||||
|  | 
 | ||||||
|  | 	msg = Message(subject, recipients=[recipient], body=body) | ||||||
|  | 
 | ||||||
|  | 	try: | ||||||
|  | 		flask_mail.send(msg) | ||||||
|  | 		return True | ||||||
|  | 	except Exception as e: | ||||||
|  | 		print(f"Failed to send email. Error: {e}") | ||||||
|  | 		return False | ||||||
							
								
								
									
										24
									
								
								app/mail/messages.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								app/mail/messages.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | import datetime | ||||||
|  | 
 | ||||||
|  | messages = { | ||||||
|  | 	"register": { | ||||||
|  | 		"subject": "Successfully registered!", | ||||||
|  | 		"body": "Congratulations! Your account has been successfully created.\nThis mail also serves as a test that the email address is correct" | ||||||
|  | 	}, | ||||||
|  | 	"login": { | ||||||
|  | 		"subject": "New Login detected!", | ||||||
|  | 		"body": "A new login token has been created" | ||||||
|  | 	}, | ||||||
|  | 	"logout": { | ||||||
|  | 		"subject": "Successfully logged out", | ||||||
|  | 		"body": "A login has been revoked. No further action is needed." | ||||||
|  | 	}, | ||||||
|  | 	"update": { | ||||||
|  | 		"subject": "Account updated", | ||||||
|  | 		"body": "Your account has been successfully updated. This also means you have been logged out of everywhere" | ||||||
|  | 	}, | ||||||
|  | 	"delete": { | ||||||
|  | 		"subject": "Account Deleted!", | ||||||
|  | 		"body": "Your account has been deleted. No further action needed" | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -1,15 +0,0 @@ | |||||||
| from flask_mail import Message |  | ||||||
| 
 |  | ||||||
| from app import mail |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def send_mail(subject: str, recipient: str, body: str): |  | ||||||
| 	msg = Message(subject, recipients=[recipient]) |  | ||||||
| 	msg.body = body |  | ||||||
| 
 |  | ||||||
| 	try: |  | ||||||
| 		mail.send(msg) |  | ||||||
| 		return True |  | ||||||
| 	except Exception as e: |  | ||||||
| 		print(f"Failed to send email. Error: {e}") |  | ||||||
| 		return False |  | ||||||
| @ -9,7 +9,7 @@ from flask_jwt_extended import create_access_token | |||||||
| from app.extensions import db_connection | from app.extensions import db_connection | ||||||
| from app.extensions import jwt_redis_blocklist | from app.extensions import jwt_redis_blocklist | ||||||
| 
 | 
 | ||||||
| from app.mail_utils import send_mail | from app.mail.mail import send_mail | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class UserService: | class UserService: | ||||||
| @ -62,7 +62,7 @@ class UserService: | |||||||
| 			print(f"Error: {e}") | 			print(f"Error: {e}") | ||||||
| 			return {"Failed": "Failed to insert into database. Username or email are likely in use already"}, 500 | 			return {"Failed": "Failed to insert into database. Username or email are likely in use already"}, 500 | ||||||
| 
 | 
 | ||||||
| 		send_mail("Successfully registered!", email, "Congratulations! Your account has been successfully created.\nThis mail also serves as a test that the email address is correct") | 		send_mail("register", email) | ||||||
| 
 | 
 | ||||||
| 		return {"Success": "User created successfully"}, 200 | 		return {"Success": "User created successfully"}, 200 | ||||||
| 
 | 
 | ||||||
| @ -81,10 +81,11 @@ class UserService: | |||||||
| 		try: | 		try: | ||||||
| 			with db_connection.cursor(dictionary=True) as cursor: | 			with db_connection.cursor(dictionary=True) as cursor: | ||||||
| 
 | 
 | ||||||
| 				cursor.execute("select id, password from user where username = %s", (username,)) | 				cursor.execute("select id, email, password from user where username = %s", (username,)) | ||||||
| 				result = cursor.fetchone() | 				result = cursor.fetchone() | ||||||
| 
 | 
 | ||||||
| 				user_id = result['id'] | 				user_id = result['id'] | ||||||
|  | 				email = result['email'] | ||||||
| 				password_hash = result['password'] | 				password_hash = result['password'] | ||||||
| 
 | 
 | ||||||
| 				if user_id is None: | 				if user_id is None: | ||||||
| @ -97,13 +98,15 @@ class UserService: | |||||||
| 
 | 
 | ||||||
| 				token = create_access_token(identity=user_id, expires_delta=expire) | 				token = create_access_token(identity=user_id, expires_delta=expire) | ||||||
| 
 | 
 | ||||||
|  | 				send_mail("login", email) | ||||||
|  | 
 | ||||||
| 				return {"token": token}, 200 | 				return {"token": token}, 200 | ||||||
| 
 | 
 | ||||||
| 		except Error as e: | 		except Error as e: | ||||||
| 			return {"Failed": f"Failed to login. Error: {e}"}, 500 | 			return {"Failed": f"Failed to login. Error: {e}"}, 500 | ||||||
| 
 | 
 | ||||||
| 	@staticmethod | 	@staticmethod | ||||||
| 	def logout(jti, exp) -> Tuple[Union[dict, str], int]: | 	def logout(jti, exp, user_id) -> Tuple[Union[dict, str], int]: | ||||||
| 		""" | 		""" | ||||||
| 		Logs out a user by invalidating the provided JWT. | 		Logs out a user by invalidating the provided JWT. | ||||||
| 
 | 
 | ||||||
| @ -114,7 +117,9 @@ class UserService: | |||||||
| 		:return: Tuple containing a dictionary and an HTTP status code. | 		:return: Tuple containing a dictionary and an HTTP status code. | ||||||
| 		:rtype: Tuple[Union[dict, str], int] | 		:rtype: Tuple[Union[dict, str], int] | ||||||
| 		""" | 		""" | ||||||
|  | 
 | ||||||
| 		UserService.__invalidate_token(jti, exp) | 		UserService.__invalidate_token(jti, exp) | ||||||
|  | 		UserService.__send_email("logout", id=user_id) | ||||||
| 
 | 
 | ||||||
| 		return {"Success": "Successfully logged out"}, 200 | 		return {"Success": "Successfully logged out"}, 200 | ||||||
| 
 | 
 | ||||||
| @ -129,6 +134,8 @@ class UserService: | |||||||
| 		:rtype: Tuple[Union[dict, str], int] | 		:rtype: Tuple[Union[dict, str], int] | ||||||
| 		""" | 		""" | ||||||
| 
 | 
 | ||||||
|  | 		UserService.__send_email("delete", id=user_id) | ||||||
|  | 		 | ||||||
| 		try: | 		try: | ||||||
| 			with db_connection.cursor() as cursor: | 			with db_connection.cursor() as cursor: | ||||||
| 				cursor.execute("delete from user where id = %s", (user_id,)) | 				cursor.execute("delete from user where id = %s", (user_id,)) | ||||||
| @ -215,6 +222,38 @@ class UserService: | |||||||
| 
 | 
 | ||||||
| 		return {"Success": "Password successfully updated"}, 200 | 		return {"Success": "Password successfully updated"}, 200 | ||||||
| 
 | 
 | ||||||
|  | 	@staticmethod | ||||||
|  | 	def __send_email(message: str, username: str = None, id: str = None, email: str = None): | ||||||
|  | 		if email is not None: | ||||||
|  | 			send_mail(message, email) | ||||||
|  | 			return | ||||||
|  | 		 | ||||||
|  | 		if username is not None: | ||||||
|  | 			try: | ||||||
|  | 				with db_connection.cursor(dictionary=True) as cursor: | ||||||
|  | 					cursor.execute("select email from user where username = %s", (username,)) | ||||||
|  | 					result = cursor.fetchone() | ||||||
|  | 					email = result['email'] | ||||||
|  | 					send_mail("logout", email) | ||||||
|  | 
 | ||||||
|  | 			except Error as e: | ||||||
|  | 				return {"Failed": f"Failed to fetch some data. Error: {e}"}, 500 | ||||||
|  | 			return | ||||||
|  | 		 | ||||||
|  | 		if id is not None: | ||||||
|  | 			try: | ||||||
|  | 				with db_connection.cursor(dictionary=True) as cursor: | ||||||
|  | 					cursor.execute("select email from user where id = %s", (id,)) | ||||||
|  | 					result = cursor.fetchone() | ||||||
|  | 					email = result['email'] | ||||||
|  | 					send_mail("logout", email) | ||||||
|  | 
 | ||||||
|  | 			except Error as e: | ||||||
|  | 				return {"Failed": f"Failed to fetch some data. Error: {e}"}, 500 | ||||||
|  | 			return | ||||||
|  | 
 | ||||||
|  | 		raise ValueError("Invalid input data to send mail") | ||||||
|  | 
 | ||||||
| 	@staticmethod | 	@staticmethod | ||||||
| 	def __invalidate_token(jti: str, exp: int): | 	def __invalidate_token(jti: str, exp: int): | ||||||
| 		""" | 		""" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user