From d850800a5fa4089a6c0f56658f08f14f20af2d35 Mon Sep 17 00:00:00 2001 From: marko-kraemer Date: Sun, 6 Jul 2025 01:32:00 +0200 Subject: [PATCH] admin api key, send welcome mail admin action --- backend/knowledge_base/api.py | 2 +- .../file_processor.py | 0 backend/services/email_api.py | 40 ++++--------------- backend/utils/auth_utils.py | 36 ++++++++++++++++- backend/utils/config.py | 3 ++ frontend/src/app/auth/actions.ts | 13 +++++- 6 files changed, 58 insertions(+), 36 deletions(-) rename backend/{services => knowledge_base}/file_processor.py (100%) diff --git a/backend/knowledge_base/api.py b/backend/knowledge_base/api.py index 99e932c9..2ad1373c 100644 --- a/backend/knowledge_base/api.py +++ b/backend/knowledge_base/api.py @@ -4,7 +4,7 @@ from fastapi import APIRouter, HTTPException, Depends, UploadFile, File, Form, B from pydantic import BaseModel, Field, HttpUrl from utils.auth_utils import get_current_user_id_from_jwt from services.supabase import DBConnection -from services.file_processor import FileProcessor +from knowledge_base.file_processor import FileProcessor from utils.logger import logger from flags.flags import is_enabled diff --git a/backend/services/file_processor.py b/backend/knowledge_base/file_processor.py similarity index 100% rename from backend/services/file_processor.py rename to backend/knowledge_base/file_processor.py diff --git a/backend/services/email_api.py b/backend/services/email_api.py index 9834c7ba..2f1ebdfe 100644 --- a/backend/services/email_api.py +++ b/backend/services/email_api.py @@ -4,6 +4,7 @@ from typing import Optional import asyncio from services.email import email_service from utils.logger import logger +from utils.auth_utils import verify_admin_api_key router = APIRouter() @@ -16,36 +17,11 @@ class EmailResponse(BaseModel): message: str @router.post("/send-welcome-email", response_model=EmailResponse) -async def send_welcome_email(request: SendWelcomeEmailRequest): +async def send_welcome_email( + request: SendWelcomeEmailRequest, + _: bool = Depends(verify_admin_api_key) +): try: - logger.info(f"Sending welcome email to {request.email}") - success = email_service.send_welcome_email( - user_email=request.email, - user_name=request.name - ) - - if success: - return EmailResponse( - success=True, - message="Welcome email sent successfully" - ) - else: - return EmailResponse( - success=False, - message="Failed to send welcome email" - ) - - except Exception as e: - logger.error(f"Error sending welcome email to {request.email}: {str(e)}") - raise HTTPException( - status_code=500, - detail="Internal server error while sending email" - ) - -@router.post("/send-welcome-email-background", response_model=EmailResponse) -async def send_welcome_email_background(request: SendWelcomeEmailRequest): - try: - logger.info(f"Queuing welcome email for {request.email}") def send_email(): return email_service.send_welcome_email( @@ -59,12 +35,12 @@ async def send_welcome_email_background(request: SendWelcomeEmailRequest): return EmailResponse( success=True, - message="Welcome email queued for sending" + message="Welcome email sent" ) except Exception as e: - logger.error(f"Error queuing welcome email for {request.email}: {str(e)}") + logger.error(f"Error sending welcome email for {request.email}: {str(e)}") raise HTTPException( status_code=500, - detail="Internal server error while queuing email" + detail="Internal server error while sending welcome email" ) diff --git a/backend/utils/auth_utils.py b/backend/utils/auth_utils.py index 62de8205..7d2ea34a 100644 --- a/backend/utils/auth_utils.py +++ b/backend/utils/auth_utils.py @@ -1,9 +1,10 @@ import sentry -from fastapi import HTTPException, Request +from fastapi import HTTPException, Request, Header from typing import Optional import jwt from jwt.exceptions import PyJWTError from utils.logger import structlog +from utils.config import config # This function extracts the user ID from Supabase JWT async def get_current_user_id_from_jwt(request: Request) -> str: @@ -229,3 +230,36 @@ async def get_optional_user_id(request: Request) -> Optional[str]: return user_id except PyJWTError: return None + +async def verify_admin_api_key(x_admin_api_key: Optional[str] = Header(None)): + """ + Verify admin API key for server-side operations. + + Args: + x_admin_api_key: Admin API key from X-Admin-Api-Key header + + Returns: + bool: True if the API key is valid + + Raises: + HTTPException: If the API key is missing, invalid, or not configured + """ + if not config.ADMIN_API_KEY: + raise HTTPException( + status_code=500, + detail="Admin API key not configured on server" + ) + + if not x_admin_api_key: + raise HTTPException( + status_code=401, + detail="Admin API key required. Include X-Admin-Api-Key header." + ) + + if x_admin_api_key != config.ADMIN_API_KEY: + raise HTTPException( + status_code=403, + detail="Invalid admin API key" + ) + + return True diff --git a/backend/utils/config.py b/backend/utils/config.py index 1bc42f81..4128668e 100644 --- a/backend/utils/config.py +++ b/backend/utils/config.py @@ -228,6 +228,9 @@ class Configuration: LANGFUSE_SECRET_KEY: Optional[str] = None LANGFUSE_HOST: str = "https://cloud.langfuse.com" + # Admin API key for server-side operations + ADMIN_API_KEY: Optional[str] = None + @property def STRIPE_PRODUCT_ID(self) -> str: if self.ENV_MODE == EnvMode.STAGING: diff --git a/frontend/src/app/auth/actions.ts b/frontend/src/app/auth/actions.ts index f9ed1074..6eb000d3 100644 --- a/frontend/src/app/auth/actions.ts +++ b/frontend/src/app/auth/actions.ts @@ -6,10 +6,18 @@ import { redirect } from 'next/navigation'; async function sendWelcomeEmail(email: string, name?: string) { try { const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL; - const response = await fetch(`${backendUrl}/send-welcome-email-background`, { + const adminApiKey = process.env.ADMIN_API_KEY; + + if (!adminApiKey) { + console.error('ADMIN_API_KEY not configured'); + return; + } + + const response = await fetch(`${backendUrl}/api/send-welcome-email`, { method: 'POST', headers: { 'Content-Type': 'application/json', + 'X-Admin-Api-Key': adminApiKey, }, body: JSON.stringify({ email, @@ -20,7 +28,8 @@ async function sendWelcomeEmail(email: string, name?: string) { if (response.ok) { console.log(`Welcome email queued for ${email}`); } else { - console.error(`Failed to queue welcome email for ${email}`); + const errorData = await response.json().catch(() => ({})); + console.error(`Failed to queue welcome email for ${email}:`, errorData); } } catch (error) { console.error('Error sending welcome email:', error);