Add complete NexaMFA push MFA system with: - FastAPI backend with PostgreSQL, Redis, OIDC provider, and Prometheus metrics - React TypeScript admin console - Android Kotlin/Jetpack Compose app with biometric authentication - Docker Compose deployment configuration - Gitea CI workflow for backend, frontend, and Android builds - Environment configuration template with security settings - Documentation for security model, deployment
45 lines
1.7 KiB
Python
45 lines
1.7 KiB
Python
import json
|
|
import logging
|
|
|
|
from google.oauth2 import service_account
|
|
from google.auth.transport.requests import Request as GoogleAuthRequest
|
|
import httpx
|
|
|
|
from app.core.config import Settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PushService:
|
|
def __init__(self, settings: Settings):
|
|
self.settings = settings
|
|
|
|
async def send_challenge(self, fcm_token: str | None, challenge_id: str) -> None:
|
|
if not fcm_token:
|
|
logger.info("Skipping push: device has no FCM token for challenge %s", challenge_id)
|
|
return
|
|
if not self.settings.fcm_project_id or not self.settings.fcm_service_account_json:
|
|
logger.info("Skipping push: FCM credentials not configured for challenge %s", challenge_id)
|
|
return
|
|
|
|
credentials = service_account.Credentials.from_service_account_info(
|
|
json.loads(self.settings.fcm_service_account_json),
|
|
scopes=["https://www.googleapis.com/auth/firebase.messaging"],
|
|
)
|
|
credentials.refresh(GoogleAuthRequest())
|
|
payload = {
|
|
"message": {
|
|
"token": fcm_token,
|
|
"data": {"challenge_id": challenge_id},
|
|
"android": {"priority": "high"},
|
|
}
|
|
}
|
|
logger.debug("Prepared FCM payload: %s", json.dumps(payload))
|
|
async with httpx.AsyncClient(timeout=10) as client:
|
|
response = await client.post(
|
|
f"https://fcm.googleapis.com/v1/projects/{self.settings.fcm_project_id}/messages:send",
|
|
json=payload,
|
|
headers={"Authorization": f"Bearer {credentials.token}"},
|
|
)
|
|
response.raise_for_status()
|