Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file removed backend/api/Auth-service/app.py
Empty file.
65 changes: 65 additions & 0 deletions backend/api/Auth-service/auth_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# auth_service.py

from utils.jwt_manager import JWTManager
from utils.db import get_user_by_username
from passlib.context import CryptContext

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


class AuthService:
"""
Service class for handling authentication-related operations.

This class provides methods for user login, token validation, and logout.
"""
def __init__(self):
"""
Initializes the AuthService with a JWTManager instance.
"""
self.jwt_manager = JWTManager()

def login(self, username: str, password: str) -> str | None:
"""
Authenticates a user and generates a JWT token if credentials are valid

Args:
username (str): The username of the user.
password (str): The password of the user.

Returns:
str None: A JWT token if authentication is successful, none otherwise.
"""
user = get_user_by_username(username)
if not user:
return None

if not pwd_context.verify(password, user["password_hash"]):
return None

token = self.jwt_manager.generate_token({"sub": username})
return token

def validate_token(self, token: str) -> dict | None:
"""
Validates a JWT token and decodes its payload.

Args:
token (str): The JWT token to validate.

Returns:
dict None: The decoded payload if the token is valid, or None otherwise
"""
return self.jwt_manager.verify_token(token)

def logout(self, token: str) -> bool:
"""
Logs out a user by invalidating their token.

Args:
token (str): The token to invalidate.

Returns:
bool: True if the logout process is successful.
"""
return True
63 changes: 63 additions & 0 deletions backend/api/Auth-service/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from fastapi import APIRouter, HTTPException, Depends
from auth_service import AuthService
from models import LoginRequest, TokenResponse
from utils.jwt_manager import get_current_user

router = APIRouter()
auth_service = AuthService()


@router.post("/login", response_model=TokenResponse)
def login_route(request: LoginRequest):
"""
Endpoint for user login.

Args:
request (LoginRequest): The login request containing username and password.

Returns:
TokenResponse: A response containing the access token if login is done.

Raises:
HTTPException: If the credentials are invalid.
"""
token = auth_service.login(request.username, request.password)
if not token:
raise HTTPException(status_code=401, detail="Invalid credentials")
return TokenResponse(access_token=token)


@router.get("/validate")
def validate_route(user=Depends(get_current_user)):
"""
Endpoint to validate a JWT token.

Args:
user: The user information extracted from the token (injected by Depends).

Returns:
dict: A message indicating the token is valid and the user information.
"""
return {"message": f"Token válido. Usuario: {user['sub']}"}

return {"message": f"Token válido. Usuario: {user['sub']}"}


@router.post("/logout")
def logout_route(token: str):
"""
Endpoint for user logout.

Args:
token (str): The token to invalidate.

Returns:
dict: A message indicating the session was closed successfully.

Raises:
HTTPException: If the logout process fails.
"""
success = auth_service.logout(token)
if not success:
raise HTTPException(status_code=400, detail="Logout failed")
return {"message": "Sesión cerrada correctamente"}
35 changes: 35 additions & 0 deletions backend/api/Auth-service/models/schemas
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pydantic import BaseModel


class LoginRequest(BaseModel):
"""
Schema for a login request.

Attributes:
username (str): The username of the user.
password (str): The password of the user.
"""
username: str
password: str


class TokenResponse(BaseModel):
"""
Schema for a token response.

Attributes:
access_token (str): The access token issued to the user.
token_type (str): The type of the token, default is "bearer".
"""
access_token: str
token_type: str = "bearer"


class TokenValidationRequest(BaseModel):
"""
Schema for a token validation request.

Attributes:
token (str): The token to be validated.
"""
token: str
44 changes: 44 additions & 0 deletions backend/api/Auth-service/utils/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import psycopg2
import os
from dotenv import load_dotenv

load_dotenv()


def get_connection():
"""
Establishes a connection to the PostgreSQL database.

Returns:
psycopg2.extensions.connection: A connection object to interact with db.
"""
return psycopg2.connect(
host=os.getenv("DB_HOST"),
port=os.getenv("DB_PORT"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PASSWORD"),
dbname=os.getenv("DB_NAME")
)


def get_user_by_username(username: str) -> dict | None:
"""
Retrieves a user's details from the database by their username.

Args:
username (str): The username of the user to retrieve.

Returns:
dict None: A dictionary containing the usernames and passwords.
"""
conn = get_connection()
try:
with conn.cursor() as cur:
cur.execute(
"SELECT username, password_hash FROM users = %s", (username,))
row = cur.fetchone()
if row:
return {"username": row[0], "password_hash": row[1]}
finally:
conn.close()
return None
52 changes: 52 additions & 0 deletions backend/api/Auth-service/utils/jwt_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import jwt
from datetime import datetime, timedelta
from dotenv import load_dotenv
import os


load_dotenv()

SECRET_KEY = os.getenv("JWT_SECRET", "secretkey")
ALGORITHM = "HS256"
TOKEN_EXPIRE_MINUTES = 60


class JWTManager:
"""
A utility class for managing JSON Web Tokens (JWT).

This class provides methods to generate and verify JWTs using a secret key
and specified algorithm.
"""
def generate_token(self, data: dict) -> str:
"""
Generates a JWT with the given data and expiration time.

Args:
data (dict): The payload data to include in the token.

Returns:
str: The encoded JWT as a string.
"""
to_encode = data.copy()
expire = datetime.utcnow() + timedelta(minutes=TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(self, token: str) -> dict | None:
"""
Verifies and decodes a JWT.

Args:
token (str): The JWT to verify.

Returns:
dict None:The decoded payload if the token is valid, or None if no.
"""
try:
return jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
except jwt.ExpiredSignatureError:
print("Expired Token")
except jwt.InvalidTokenError:
print("Invalid token")
return None
Empty file.
74 changes: 74 additions & 0 deletions backend/api/Notifications-service/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
Main module for the Notifications service API.

This module defines the FastAPI application and its routes for sending emails
and push notifications. It uses the NotificationService to handle the actual
sending of notifications.

Routes:
- POST /email: Sends an email notification.
- POST /push: Sends a push notification.
"""

from fastapi import FastAPI, APIRouter, HTTPException
from notification_service import NotificationService
from models import EmailRequest, PushRequest

app = FastAPI()
router = APIRouter()
service = NotificationService()


@router.post("/email")
def send_email(request: EmailRequest):
"""
Endpoint to send an email notification.

Args:
request (EmailRequest): The email request containing subject, and body.

Returns:
dict: A success message if the email is sent successfully.

Raises:
HTTPException: If the email fails to send.
"""
success = service.send_email(request.to, request.subject, request.body)
if not success:
raise HTTPException(status_code=500, detail="Failed to send email")
return {"message": "Email sent"}


@router.post("/push")
def send_push(request: PushRequest):
"""
Endpoint to send a push notification.

Args:
request(PushRequest): The push request containing user ID and message.

Returns:
dict: A success message if the push notification is sent successfully.

Raises:
HTTPException: If the push notification fails to send.
"""
success = service.send_push(
request.user_id, request.title, request.message)
if not success:
raise HTTPException(
status_code=500, detail="Failed to send push notification")
return {"message": "Push notification sent"}


app.include_router(router)


if __name__ == "_main_":
"""
Entry point for running the FastAPI application.

The application is served using Uvicorn on host 0.0.0.0 and port 8000.
"""
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
29 changes: 29 additions & 0 deletions backend/api/Notifications-service/models/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from pydantic import BaseModel


class EmailRequest(BaseModel):
"""
Schema for an email request.

Attributes:
to (str): The recipient's email address.
subject (str): The subject of the email.
body (str): The body content of the email.
"""
to: str
subject: str
body: str


class PushRequest(BaseModel):
"""
Schema for a push notification request.

Attributes:
user_id (str): The ID of the user to receive the notification.
title (str): The title of the push notification.
message (str): The message content of the push notification.
"""
user_id: str
title: str
message: str
Loading