|
@@ -1,4 +1,5 @@
|
|
|
from logging import getLogger
|
|
from logging import getLogger
|
|
|
|
|
+from math import log
|
|
|
from fastapi import APIRouter
|
|
from fastapi import APIRouter
|
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.responses import JSONResponse
|
|
|
from httpx import RequestError
|
|
from httpx import RequestError
|
|
@@ -13,6 +14,7 @@ from auth.security import generate_token
|
|
|
from services.email_service import send_email
|
|
from services.email_service import send_email
|
|
|
from config.mails import REGISTER_MAIL
|
|
from config.mails import REGISTER_MAIL
|
|
|
from config.settings import APPNAME
|
|
from config.settings import APPNAME
|
|
|
|
|
+from config.messages import ErrorResponse, SuccessResponse, UserResponse
|
|
|
fernet = Fernet(PIN_KEY.encode())
|
|
fernet = Fernet(PIN_KEY.encode())
|
|
|
logger = getLogger(__name__)
|
|
logger = getLogger(__name__)
|
|
|
user_data_service = UserDataService()
|
|
user_data_service = UserDataService()
|
|
@@ -31,9 +33,9 @@ async def exists_user(request: UserIDRequest):
|
|
|
"""Check if user exists"""
|
|
"""Check if user exists"""
|
|
|
user = user_data_service.get_by_id(request.id)
|
|
user = user_data_service.get_by_id(request.id)
|
|
|
if user:
|
|
if user:
|
|
|
- return JSONResponse(status_code=200, content={"exists": True})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=200, content={"exists": True, "message": UserResponse.USER_EXISTS})
|
|
|
else:
|
|
else:
|
|
|
- return JSONResponse(status_code=404, content={"exists": False, "data": {"message": "User does not exist."}})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=404, content={"exists": False, "message": UserResponse.USER_DOES_NOT_EXIST})
|
|
|
|
|
|
|
|
@user_router.post("/register")
|
|
@user_router.post("/register")
|
|
|
async def register_user(request: RegisterUserRequest):
|
|
async def register_user(request: RegisterUserRequest):
|
|
@@ -41,16 +43,16 @@ async def register_user(request: RegisterUserRequest):
|
|
|
pin = unique_pin_generate()
|
|
pin = unique_pin_generate()
|
|
|
userID = user_data_service.create(request.name, request.email, request.rut, pin)
|
|
userID = user_data_service.create(request.name, request.email, request.rut, pin)
|
|
|
if userID == -1:
|
|
if userID == -1:
|
|
|
- return JSONResponse(status_code=400, content={"message": "User already exists."})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=400, content={"message": UserResponse.USER_ALREADY_EXISTS})
|
|
|
user = user_data_service.get_by_id(userID)
|
|
user = user_data_service.get_by_id(userID)
|
|
|
if not user:
|
|
if not user:
|
|
|
- return JSONResponse(status_code=500, content={"message": "Error creating user."})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=500, content={"message": ErrorResponse.USER_CREATION_ERROR})
|
|
|
send_email(
|
|
send_email(
|
|
|
REGISTER_MAIL["subject"],
|
|
REGISTER_MAIL["subject"],
|
|
|
REGISTER_MAIL["body"],
|
|
REGISTER_MAIL["body"],
|
|
|
[user.email], name=user.nombre, app_name=APPNAME, pin=pin
|
|
[user.email], name=user.nombre, app_name=APPNAME, pin=pin
|
|
|
)
|
|
)
|
|
|
- return JSONResponse(status_code=201, content={"message": "User created successfully.", "data": {
|
|
|
|
|
|
|
+ return JSONResponse(status_code=201, content={"message": SuccessResponse.USER_CREATED_SUCCESS, "data": {
|
|
|
**user.model_dump(exclude={"pin_hash"}),
|
|
**user.model_dump(exclude={"pin_hash"}),
|
|
|
"token": generate_token(user.email)
|
|
"token": generate_token(user.email)
|
|
|
}})
|
|
}})
|
|
@@ -58,11 +60,21 @@ async def register_user(request: RegisterUserRequest):
|
|
|
@user_router.post("/login")
|
|
@user_router.post("/login")
|
|
|
async def login_user(request: LoginRequest):
|
|
async def login_user(request: LoginRequest):
|
|
|
"""Login user with email and PIN"""
|
|
"""Login user with email and PIN"""
|
|
|
|
|
+ redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
|
|
logger.debug(f"Login attempt for email: {request.email}")
|
|
logger.debug(f"Login attempt for email: {request.email}")
|
|
|
|
|
+
|
|
|
|
|
+ is_blocked = redis_client.get(f"blocked:{request.email}")
|
|
|
|
|
+
|
|
|
|
|
+ if is_blocked:
|
|
|
|
|
+ logger.warning(f"Login attempt for blocked user: {request.email}, locked: {is_blocked}")
|
|
|
|
|
+ return JSONResponse(status_code=403, content={"message": UserResponse.USER_FORMAT_BLOCKED.format(time=f"{int(str(redis_client.ttl(f'blocked:{request.email}'))) // 60} minutos")})
|
|
|
|
|
+ else:
|
|
|
|
|
+ logger.info(f"{redis_client.get(f'blocked:{request.email}')}")
|
|
|
|
|
+
|
|
|
user = user_data_service.login(request.email, request.pin)
|
|
user = user_data_service.login(request.email, request.pin)
|
|
|
if user:
|
|
if user:
|
|
|
# Successful login, return user data and token
|
|
# Successful login, return user data and token
|
|
|
- return JSONResponse(status_code=200, content={"message": "Login successful.", "data": {
|
|
|
|
|
|
|
+ return JSONResponse(status_code=200, content={"message": SuccessResponse.LOGIN_SUCCESS, "data": {
|
|
|
"id": user.id,
|
|
"id": user.id,
|
|
|
"name": user.nombre,
|
|
"name": user.nombre,
|
|
|
"email": user.email,
|
|
"email": user.email,
|
|
@@ -72,31 +84,33 @@ async def login_user(request: LoginRequest):
|
|
|
}})
|
|
}})
|
|
|
else:
|
|
else:
|
|
|
# Failed login: increment attempts in Redis
|
|
# Failed login: increment attempts in Redis
|
|
|
- redis_client = redis.Redis(host='localhost', port=6379, db=0)
|
|
|
|
|
redis_client.incr(f"login_attempts:{request.email}")
|
|
redis_client.incr(f"login_attempts:{request.email}")
|
|
|
redis_client.expire(f"login_attempts:{request.email}", 3600)
|
|
redis_client.expire(f"login_attempts:{request.email}", 3600)
|
|
|
redis_attempts = redis_client.get(f"login_attempts:{request.email}")
|
|
redis_attempts = redis_client.get(f"login_attempts:{request.email}")
|
|
|
attempts = int(redis_attempts) if redis_attempts else 0 # type: ignore
|
|
attempts = int(redis_attempts) if redis_attempts else 0 # type: ignore
|
|
|
- if attempts and int(attempts) > 5:
|
|
|
|
|
|
|
+ if attempts and int(attempts) >= 5:
|
|
|
# Too many attempts, block login
|
|
# Too many attempts, block login
|
|
|
- return JSONResponse(status_code=429, content={"message": "Too many login attempts. Please try again later."})
|
|
|
|
|
|
|
+ redis_client.set(f"blocked:{request.email}", "true")
|
|
|
|
|
+ redis_client.expire(f"blocked:{request.email}", 3600)
|
|
|
|
|
+ logger.warning(f"Too many login attempts for {request.email}. User blocked.")
|
|
|
|
|
+ return JSONResponse(status_code=429, content={"message": ErrorResponse.TOO_MANY_ATTEMPTS})
|
|
|
else:
|
|
else:
|
|
|
# Set flag for last failed login
|
|
# Set flag for last failed login
|
|
|
redis_client.set(f"last_failed_login:{request.email}", "true")
|
|
redis_client.set(f"last_failed_login:{request.email}", "true")
|
|
|
- redis_client.expire(f"last_failed_login:{request.email}", 3600)
|
|
|
|
|
|
|
+ redis_client.expire(f"last_failed_login:{request.email}", 1800) # 30 minutes
|
|
|
logger.warning(f"Failed login attempt for {request.email}. Attempts: {attempts}")
|
|
logger.warning(f"Failed login attempt for {request.email}. Attempts: {attempts}")
|
|
|
logger.error("Invalid email or PIN.")
|
|
logger.error("Invalid email or PIN.")
|
|
|
# Return unauthorized with attempts remaining
|
|
# Return unauthorized with attempts remaining
|
|
|
- return JSONResponse(status_code=401, content={"message": "Invalid email or PIN.", "attempts_remaining": 5 - attempts if attempts else 5})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=401, content={"message": ErrorResponse.INVALID_CREDENTIALS, "attempts_remaining": 5 - attempts if attempts else 5})
|
|
|
|
|
|
|
|
@user_router.delete("/delete")
|
|
@user_router.delete("/delete")
|
|
|
async def delete_user(request: UserIDRequest):
|
|
async def delete_user(request: UserIDRequest):
|
|
|
"""Delete a user by ID"""
|
|
"""Delete a user by ID"""
|
|
|
user = user_data_service.delete(request.id)
|
|
user = user_data_service.delete(request.id)
|
|
|
if user:
|
|
if user:
|
|
|
- return JSONResponse(status_code=200, content={"message": "User deleted successfully.", "data": user})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=200, content={"message": SuccessResponse.USER_DELETED_SUCCESS, "data": user})
|
|
|
else:
|
|
else:
|
|
|
- return JSONResponse(status_code=404, content={"message": "User not found."})
|
|
|
|
|
|
|
+ return JSONResponse(status_code=404, content={"message": UserResponse.USER_NOT_FOUND})
|
|
|
|
|
|
|
|
@user_router.get("/all")
|
|
@user_router.get("/all")
|
|
|
async def get_all_users():
|
|
async def get_all_users():
|