Add some more authentication methods along with rest of notification method
This commit is contained in:
parent
1f551ad161
commit
9c4baae49e
@ -67,6 +67,16 @@ class SignInHub:
|
||||
user_id=user_id, password=password
|
||||
)
|
||||
|
||||
@log_entry_exit_async
|
||||
async def send_email_code(self, sender_id: str, email: str) -> dict[str, any]:
|
||||
result = await self.signin_manager.send_email_code(sender_id, email)
|
||||
return {"succeeded": result}
|
||||
|
||||
@log_entry_exit_async
|
||||
async def send_mobile_code(self, sender_id: str, mobile: str) -> dict[str, any]:
|
||||
result = await self.signin_manager.send_mobile_code(sender_id, mobile)
|
||||
return {"succeeded": result}
|
||||
|
||||
@log_entry_exit_async
|
||||
async def sign_out(self, identity: str) -> bool:
|
||||
# TODO: to be implemented
|
||||
|
||||
@ -16,6 +16,12 @@ from app.authentication.backend.services.code_depot.code_depot_service import (
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
from infra.utils.string import check_password_complexity
|
||||
from infra.exception.exceptions import InvalidDataError
|
||||
from app.authentication.backend.services.notification.notification_service import (
|
||||
NotificationService,
|
||||
)
|
||||
from app.authentication.backend.models.user.constants import (
|
||||
AuthType,
|
||||
)
|
||||
|
||||
|
||||
class SignInManager:
|
||||
@ -25,6 +31,7 @@ class SignInManager:
|
||||
self.user_management_service = UserManagementService()
|
||||
self.module_logger = ModuleLogger(sender_id=SignInManager)
|
||||
self.code_depot_service = CodeDepotService()
|
||||
self.notification_service = NotificationService()
|
||||
# TODO: Dax: notification service
|
||||
# self.event_service = EventService()
|
||||
|
||||
@ -319,3 +326,31 @@ class SignInManager:
|
||||
user_id, user_flid, password
|
||||
)
|
||||
return {"succeeded": True}
|
||||
|
||||
async def send_email_code(self, sender_id: str, email: str) -> bool:
|
||||
mail_code = await self.user_auth_service.generate_auth_code_for_object(
|
||||
email, AuthType.EMAIL
|
||||
)
|
||||
success = await self.notification_service.send_notification(
|
||||
sender_id=sender_id,
|
||||
channels=["email"],
|
||||
receiver_id=email,
|
||||
subject="email",
|
||||
event="authentication",
|
||||
properties={"auth_code": mail_code},
|
||||
)
|
||||
return success
|
||||
|
||||
async def send_mobile_code(self, sender_id: str, mobile: str) -> bool:
|
||||
mail_code = await self.user_auth_service.generate_auth_code_for_object(
|
||||
mobile, AuthType.MOBILE
|
||||
)
|
||||
success = await self.notification_service.send_notification(
|
||||
sender_id=sender_id,
|
||||
channels=["email"],
|
||||
receiver_id=mobile,
|
||||
subject="mobile",
|
||||
event="authentication",
|
||||
properties={"auth_code": mail_code},
|
||||
)
|
||||
return success
|
||||
|
||||
@ -6,8 +6,6 @@ from infra.utils.string import generate_auth_code
|
||||
from app.authentication.backend.infra.code_management.depot_handler import (
|
||||
CodeDepotHandler,
|
||||
)
|
||||
|
||||
|
||||
from app.authentication.backend.models.user.constants import (
|
||||
AuthType,
|
||||
)
|
||||
@ -331,18 +329,19 @@ class UserAuthHandler:
|
||||
else:
|
||||
return False
|
||||
|
||||
async def generate_auth_code(self, email: str) -> str:
|
||||
async def generate_auth_code(self, deliver_object: str, auth_type: AuthType) -> str:
|
||||
"""send auth code to email address
|
||||
|
||||
Args:
|
||||
email (str): email address
|
||||
deliver_object (str): email address, mobile, etc
|
||||
auth_type (str): authentication type
|
||||
"""
|
||||
auth_code = generate_auth_code()
|
||||
expiry = datetime.now(timezone.utc) + timedelta(minutes=5)
|
||||
auth_code_doc = AuthCodeDoc(
|
||||
auth_code=auth_code,
|
||||
method=email.lower(),
|
||||
method_type=AuthType.EMAIL,
|
||||
method=deliver_object.lower(),
|
||||
method_type=auth_type,
|
||||
expiry=expiry,
|
||||
)
|
||||
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
from app.authentication.backend.infra.auth.user_auth_handler import (
|
||||
UserAuthHandler,
|
||||
)
|
||||
from app.authentication.backend.models.user.constants import (
|
||||
AuthType,
|
||||
)
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@ -29,8 +32,12 @@ class UserAuthService:
|
||||
async def update_flid(self, user_id: str, user_flid: str) -> str:
|
||||
return await self.user_auth_handler.update_flid(user_id, user_flid)
|
||||
|
||||
async def generate_auth_code_for_email(self, email: str) -> str:
|
||||
return await self.user_auth_handler.generate_auth_code(email)
|
||||
async def generate_auth_code_for_object(
|
||||
self, deliver_object: str, auth_type: AuthType
|
||||
) -> str:
|
||||
return await self.user_auth_handler.generate_auth_code(
|
||||
deliver_object, auth_type
|
||||
)
|
||||
|
||||
async def verify_user_with_password(self, user_id: str, password: str) -> bool:
|
||||
return await self.user_auth_handler.verify_user_with_password(user_id, password)
|
||||
|
||||
@ -0,0 +1,35 @@
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
class NotificationService:
|
||||
def __init__(self, base_url: str = "http://localhost:8003"):
|
||||
self.base_url = base_url
|
||||
|
||||
async def send_notification(
|
||||
self,
|
||||
sender_id: str,
|
||||
channels: List[str],
|
||||
receiver_id: str,
|
||||
subject: str,
|
||||
event: str,
|
||||
properties: Dict,
|
||||
) -> bool:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
f"{self.base_url}/send_notification",
|
||||
json={
|
||||
"sender_id": sender_id,
|
||||
"channels": channels,
|
||||
"receiver_id": receiver_id,
|
||||
"subject": subject,
|
||||
"event": event,
|
||||
"properties": properties,
|
||||
},
|
||||
)
|
||||
if response.status_code == 200:
|
||||
return True
|
||||
else:
|
||||
# Optionally log or handle errors here
|
||||
return False
|
||||
@ -8,6 +8,7 @@ uvicorn==0.23.2
|
||||
beanie==1.21.0
|
||||
jieba==0.42.1
|
||||
aio-pika
|
||||
httpx
|
||||
pydantic-settings
|
||||
python-jose
|
||||
passlib[bcrypt]
|
||||
@ -1,8 +1,10 @@
|
||||
from fastapi import APIRouter
|
||||
from .signin import router as signin_router
|
||||
from .tokens import router as token_router
|
||||
from .auth import router as auth_router
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(signin_router, tags=["user"])
|
||||
api_router.include_router(token_router, tags=["auth"])
|
||||
api_router.include_router(token_router, tags=["token"])
|
||||
api_router.include_router(auth_router, tags=["auth"])
|
||||
websocket_router = APIRouter()
|
||||
|
||||
9
app/authentication/webapi/routes/auth/__init__.py
Normal file
9
app/authentication/webapi/routes/auth/__init__.py
Normal file
@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
from .send_email_code import router as sec_router
|
||||
from .send_mobile_code import router as smc_router
|
||||
|
||||
|
||||
router = APIRouter(prefix="/auth")
|
||||
|
||||
router.include_router(sec_router, tags=["authentication"])
|
||||
router.include_router(smc_router, tags=["authentication"])
|
||||
41
app/authentication/webapi/routes/auth/send_email_code.py
Normal file
41
app/authentication/webapi/routes/auth/send_email_code.py
Normal file
@ -0,0 +1,41 @@
|
||||
from app.authentication.backend.application.signin_hub import SignInHub
|
||||
from pydantic import BaseModel
|
||||
from infra.token.token_manager import TokenManager
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
router = APIRouter()
|
||||
token_manager = TokenManager()
|
||||
# Web API
|
||||
# send_email_code
|
||||
#
|
||||
|
||||
|
||||
class RequestIn(BaseModel):
|
||||
email: str
|
||||
sender_id: str
|
||||
|
||||
|
||||
@router.post(
|
||||
"/send-email-code",
|
||||
operation_id="send-email-code",
|
||||
summary="Send a verification code to the specified email address",
|
||||
description="This API requires an authenticated user and will send a code to the specified email. \
|
||||
The code can be used later to verify the email address.",
|
||||
response_description="Indicates success or failure of the email code send operation",
|
||||
)
|
||||
async def send_email_code(
|
||||
item: RequestIn,
|
||||
current_user: dict = Depends(token_manager.get_current_user),
|
||||
):
|
||||
user_id = current_user.get("id")
|
||||
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED, detail="Could not validate credentials"
|
||||
)
|
||||
|
||||
result = await SignInHub(user_id).send_email_code(item.sender_id, item.email)
|
||||
return JSONResponse(content=jsonable_encoder(result))
|
||||
40
app/authentication/webapi/routes/auth/send_mobile_code.py
Normal file
40
app/authentication/webapi/routes/auth/send_mobile_code.py
Normal file
@ -0,0 +1,40 @@
|
||||
from app.authentication.backend.application.signin_hub import SignInHub
|
||||
from pydantic import BaseModel
|
||||
from infra.token.token_manager import TokenManager
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from starlette.status import HTTP_401_UNAUTHORIZED
|
||||
|
||||
router = APIRouter()
|
||||
token_manager = TokenManager()
|
||||
# Web API
|
||||
# send_email_code
|
||||
#
|
||||
|
||||
|
||||
class RequestIn(BaseModel):
|
||||
email: str
|
||||
|
||||
|
||||
@router.post(
|
||||
"/send-mobile-code",
|
||||
operation_id="send-mobile-code",
|
||||
summary="Send a verification code to the specified mobile number",
|
||||
description="This API requires an authenticated user and will send a code to the specified mobile. \
|
||||
The code can be used later to verify the mobile.",
|
||||
response_description="Indicates success or failure of the mobile code send operation",
|
||||
)
|
||||
async def send_email_code(
|
||||
item: RequestIn,
|
||||
current_user: dict = Depends(token_manager.get_current_user),
|
||||
):
|
||||
user_id = current_user.get("id")
|
||||
|
||||
if not user_id:
|
||||
raise HTTPException(
|
||||
status_code=HTTP_401_UNAUTHORIZED, detail="Could not validate credentials"
|
||||
)
|
||||
|
||||
result = await SignInHub(user_id).send_mobile_code(item.email)
|
||||
return JSONResponse(content=jsonable_encoder(result))
|
||||
@ -0,0 +1,19 @@
|
||||
# Dockerfile for Python Service
|
||||
FROM python:3.10-slim
|
||||
|
||||
# Set the working directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the requirements.txt to the working directory and install dependencies
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy the application code to the working directory
|
||||
COPY . /app
|
||||
|
||||
# Expose the port used by the FastAPI app
|
||||
EXPOSE 8003
|
||||
|
||||
|
||||
# Run the application using the start script
|
||||
CMD ["uvicorn", "app.notification.webapi.main:app", "--reload", "--port=8003", "--host=0.0.0.0"]
|
||||
@ -1,5 +1,5 @@
|
||||
from app.notification.backend.models.constants import NotificationChannel
|
||||
from backend.infra.rabbitmq.async_publisher import AsyncMQPublisher
|
||||
from app.notification.backend.infra.rabbitmq.async_publisher import AsyncMQPublisher
|
||||
|
||||
|
||||
class NotificationPublisherService:
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
fastapi==0.114.0
|
||||
fastapi-jwt==0.2.0
|
||||
pika==1.3.2
|
||||
pydantic==2.9.2
|
||||
uvicorn==0.23.2
|
||||
beanie==1.21.0
|
||||
httpx==0.24.1
|
||||
loguru==0.7.2
|
||||
sendgrid
|
||||
aio-pika
|
||||
twilio
|
||||
pydantic-settings
|
||||
python-multipart
|
||||
python-jose
|
||||
@ -2,12 +2,12 @@ import logging
|
||||
from fastapi import FastAPI
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
|
||||
from webapi.providers import common
|
||||
from webapi.providers import logger
|
||||
from webapi.providers import router
|
||||
from webapi.providers import database
|
||||
from webapi.providers import scheduler
|
||||
from webapi.providers import exception_handler
|
||||
from app.notification.webapi.providers import common
|
||||
from app.notification.webapi.providers import logger
|
||||
from app.notification.webapi.providers import router
|
||||
from app.notification.webapi.providers import database
|
||||
from app.notification.webapi.providers import scheduler
|
||||
from app.notification.webapi.providers import exception_handler
|
||||
from .freeleaps_app import FreeleapsApp
|
||||
|
||||
|
||||
@ -49,11 +49,7 @@ def customize_openapi_security(app: FastAPI) -> None:
|
||||
|
||||
# Add security scheme to components
|
||||
openapi_schema["components"]["securitySchemes"] = {
|
||||
"bearerAuth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer",
|
||||
"bearerFormat": "JWT"
|
||||
}
|
||||
"bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}
|
||||
}
|
||||
|
||||
# Add security requirement globally
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
from fastapi import FastAPI
|
||||
from backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from backend.models.constants import NotificationChannel
|
||||
from webapi.utils.email_consumer import EmailMQConsumer
|
||||
from webapi.utils.sms_consumer import SmsMQConsumer
|
||||
from app.notification.backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from app.notification.backend.models.constants import NotificationChannel
|
||||
from app.notification.webapi.utils.email_consumer import EmailMQConsumer
|
||||
from app.notification.webapi.utils.sms_consumer import SmsMQConsumer
|
||||
|
||||
|
||||
class FreeleapsApp(FastAPI):
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
from webapi.bootstrap.application import create_app
|
||||
from webapi.config.site_settings import site_settings
|
||||
from app.notification.webapi.bootstrap.application import create_app
|
||||
from app.notification.webapi.config.site_settings import site_settings
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from strawberry.fastapi import GraphQLRouter
|
||||
from strawberry.fastapi.handlers import GraphQLTransportWSHandler, GraphQLWSHandler
|
||||
import uvicorn
|
||||
from typing import Any
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
@app.get("/", status_code=301)
|
||||
async def root():
|
||||
"""
|
||||
@ -19,12 +17,16 @@ async def root():
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app="main:app", host=site_settings.SERVER_HOST, port=site_settings.SERVER_PORT)
|
||||
uvicorn.run(
|
||||
app="main:app", host=site_settings.SERVER_HOST, port=site_settings.SERVER_PORT
|
||||
)
|
||||
|
||||
|
||||
def get_context() -> Any:
|
||||
# Define your context function. This is where you can set up authentication, database connections, etc.
|
||||
return {}
|
||||
|
||||
|
||||
def get_root_value() -> Any:
|
||||
# Define your root value function. This can be used to customize the root value for GraphQL operations.
|
||||
return {}
|
||||
return {}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from webapi.config.site_settings import site_settings
|
||||
from app.notification.webapi.config.site_settings import site_settings
|
||||
|
||||
|
||||
def register(app):
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from webapi.config.site_settings import site_settings
|
||||
from app.notification.webapi.config.site_settings import site_settings
|
||||
from beanie import init_beanie
|
||||
from motor.motor_asyncio import AsyncIOMotorClient
|
||||
|
||||
@ -13,5 +13,5 @@ def register(app):
|
||||
|
||||
|
||||
async def initiate_database():
|
||||
#init your database here
|
||||
# init your database here
|
||||
pass
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import logging
|
||||
import sys
|
||||
from loguru import logger
|
||||
from common.config.log_settings import log_settings
|
||||
from infra.config.log_settings import log_settings
|
||||
|
||||
|
||||
def register(app=None):
|
||||
@ -21,15 +21,8 @@ def register(app=None):
|
||||
logging.getLogger(name).propagate = True
|
||||
|
||||
# configure loguru
|
||||
logger.add(
|
||||
sink=sys.stdout
|
||||
)
|
||||
logger.add(
|
||||
sink=file_path,
|
||||
level=level,
|
||||
retention=retention,
|
||||
rotation=rotation
|
||||
)
|
||||
logger.add(sink=sys.stdout)
|
||||
logger.add(sink=file_path, level=level, retention=retention, rotation=rotation)
|
||||
|
||||
logger.disable("pika.adapters")
|
||||
logger.disable("pika.connection")
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from webapi.routes import api_router
|
||||
from app.notification.webapi.routes import api_router
|
||||
|
||||
from starlette import routing
|
||||
|
||||
|
||||
@ -4,5 +4,5 @@ import asyncio
|
||||
def register(app):
|
||||
@app.on_event("startup")
|
||||
async def start_scheduler():
|
||||
#create your scheduler here
|
||||
# create your scheduler here
|
||||
pass
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
from fastapi import APIRouter
|
||||
from .send_notification import router as sn_router
|
||||
|
||||
api_router = APIRouter()
|
||||
|
||||
api_router.include_router(sn_router, tags=["notification"])
|
||||
websocket_router = APIRouter()
|
||||
|
||||
@ -22,14 +22,14 @@ class NotificationRequest(BaseModel):
|
||||
# Web API
|
||||
# Send notification to user for a certain channel
|
||||
@router.post(
|
||||
"/enqueue_notification",
|
||||
operation_id="enqueue_notification",
|
||||
"/send_notification",
|
||||
operation_id="send_notification",
|
||||
summary="Send notification to user for a certain channel",
|
||||
description="Send notification to user for a channel (e.g., sms, email, in-app, etc.)",
|
||||
response_description="Success/failure response in processing the notification send request",
|
||||
)
|
||||
# API route to enqueue notifications
|
||||
@router.post("/enqueue_notification")
|
||||
@router.post("/send_notification")
|
||||
async def enqueue_notification(request: NotificationRequest):
|
||||
try:
|
||||
notification_hub = NotificationHub()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from backend.infra.email_handler import EmailHandler
|
||||
from app.notification.backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from app.notification.backend.infra.email_handler import EmailHandler
|
||||
|
||||
|
||||
class EmailMQConsumer:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from app.notification.common.config.app_settings import app_settings
|
||||
from backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from backend.infra.sms_handler import SmsHandler
|
||||
from app.notification.backend.infra.rabbitmq.async_subscriber import AsyncMQSubscriber
|
||||
from app.notification.backend.infra.sms_handler import SmsHandler
|
||||
from infra.log.module_logger import ModuleLogger
|
||||
|
||||
|
||||
|
||||
@ -43,6 +43,20 @@ services:
|
||||
volumes:
|
||||
- .:/app # Mount the current directory to /app in the container
|
||||
|
||||
notification:
|
||||
build:
|
||||
context: app/notification
|
||||
dockerfile: Dockerfile
|
||||
restart: always
|
||||
ports:
|
||||
- "8003:8003" # Map the central_storage service port
|
||||
networks:
|
||||
- freeleaps_service_hub_network
|
||||
env_file:
|
||||
- sites/notification/.env
|
||||
volumes:
|
||||
- .:/app # Mount the current directory to /app in the container
|
||||
|
||||
networks:
|
||||
freeleaps_service_hub_network:
|
||||
driver: bridge
|
||||
|
||||
Loading…
Reference in New Issue
Block a user