- En la firma visible, mostrar el nombre de organización del certificado.

- Ajustar versiones
This commit is contained in:
David Arranz 2026-04-02 12:26:44 +02:00
parent 1bd5bb48d3
commit 5ab10cf6c8
3 changed files with 97 additions and 64 deletions

View File

@ -4,25 +4,31 @@ build-backend = "setuptools.build_meta"
[project]
name = "factuges-document-signing-service"
version = "0.3.1"
version = "0.3.4"
description = "FastAPI service for signing PDF documents using external secret managers"
requires-python = ">=3.11"
dependencies = [
"python-multipart",
"python-dotenv>=1.0",
"fastapi>=0.110",
"uvicorn[standard]>=0.27",
"python-multipart==0.0.9",
"python-dotenv==1.0.1",
"pyhanko>=0.32",
"cryptography>=42",
"fastapi==0.128.0",
"starlette==0.50.0",
"uvicorn[standard]==0.40.0",
# Infisical SDK for secret management
"infisicalsdk",
"pyHanko==0.32.0",
"cryptography==46.0.4",
"infisicalsdk==1.0.15",
# Google Cloud Secret Manager SDK for secret management
# "google-cloud-secret-manager>=2.18",
]
[project.optional-dependencies]
dev = ["pytest>=8.0", "pytest-cov", "ruff", "mypy"]
dev = [
"pytest>=8.0",
"pytest-cov",
"ruff",
"mypy",
]

View File

@ -11,6 +11,7 @@ from signing_service.domain.ports.pdf_signer import PDFSignerPort
from signing_service.infrastructure.pdf.pdf_validator import PDFValidator
SIGNATURE_FIELD_NAME = "Signature1"
CERTIFICATE_FIELD_NAME = "organization_name" # "common_name"
class PyHankoPDFSigner(PDFSignerPort):
@ -38,7 +39,8 @@ class PyHankoPDFSigner(PDFSignerPort):
# 3⃣ Extract certificate info (from signer)
cert = signer.signing_cert
subject = cert.subject.native.get('common_name', 'Desconocido')
subject = cert.subject.native.get(
CERTIFICATE_FIELD_NAME, 'Desconocido')
# 5⃣ Define caja del campo de firma (AcroForm)
fields.append_signature_field(

View File

@ -1,13 +1,14 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from dotenv import load_dotenv
from pathlib import Path
from datetime import datetime
from datetime import datetime, UTC
from dateutil import tz
from signing_service.application.settings.container import get_settings
from signing_service.application.settings.setup_logger import create_logger
from signing_service.api.routes.sign_document import router as sign_router
@ -18,37 +19,45 @@ load_dotenv()
# 👇 FAIL FAST: load settings at startup
settings = get_settings()
version = get_package_version()
local_tz = tz.gettz(settings.local_tz)
state_path = Path(settings.state_path)
# Logging
log_dir = state_path
def build_logger():
"""Crea el logger de aplicación y asegura el directorio de logs."""
log_dir = Path(settings.state_path)
log_dir.mkdir(parents=True, exist_ok=True)
logger = create_logger(
name="factuges-document-signing-service",
# Solo lo genera en producción
log_path=log_dir / "factuges-document-signing-service.log",
app_logger = create_logger(
name="factuges-document-signing-service",
log_path=log_dir / "factuges-document-signing-service.log",
)
logger.info("")
logger.info("============================================================")
logger.info("FactuGES Document Signing Service - START ")
logger.info("Version: %s", version)
logger.info("UTC Now: %s", datetime.utcnow().isoformat())
logger.info("Environment: %s", settings.app_env)
logger.info("")
logger.info("Log Level: %s", settings.log_level)
logger.info("Log: %s", log_dir / "factuges-document-signing-service.log")
logger.info("")
logger.info("Secret Provider: %s", settings.secret_provider)
logger.info("Infisical project ID: %s", settings.infisical_project_id)
logger.info("Infisical environment: %s", settings.infisical_env_slug)
logger.info("")
app_logger.info("")
app_logger.info(
"============================================================")
app_logger.info("FactuGES Document Signing Service - START")
app_logger.info("Version: %s", version)
app_logger.info("UTC Now: %s", datetime.now(UTC).isoformat())
app_logger.info("Environment: %s", settings.app_env)
app_logger.info("")
app_logger.info("Log Level: %s", settings.log_level)
app_logger.info(
"Log: %s",
log_dir / "factuges-document-signing-service.log",
)
app_logger.info("")
app_logger.info("Secret Provider: %s", settings.secret_provider)
app_logger.info("Infisical project ID: %s", settings.infisical_project_id)
app_logger.info("Infisical environment: %s", settings.infisical_env_slug)
app_logger.info("")
return app_logger
logger = build_logger()
async def http_exception_handler(request: Request, exc: HTTPException):
"""Registra y devuelve errores HTTP controlados."""
logger.error(
"HTTPException %s %s | status=%s | detail=%s",
request.method,
@ -63,47 +72,63 @@ async def http_exception_handler(request: Request, exc: HTTPException):
)
# Define logging middleware
class LoggingMiddleware(BaseHTTPMiddleware):
"""Registra request y response para trazabilidad básica."""
async def dispatch(self, request: Request, call_next):
# Log request details
client_ip = request.client.host
client_ip = request.client.host if request.client else "unknown"
method = request.method
url = request.url.path
logger.info(f"Request: {method} {url} from {client_ip}")
logger.info("Request: %s %s from %s", method, url, client_ip)
# Process the request
response = await call_next(request)
# Log response details
status_code = response.status_code
logger.info(
f"Response: {method} {url} returned {status_code} to {client_ip}")
"Response: %s %s returned %s to %s",
method,
url,
response.status_code,
client_ip,
)
return response
app = FastAPI(title="FactuGES Document Signing Service", version=version)
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Gestiona el ciclo de vida de la aplicación."""
logger.info("Starting signing service")
yield
logger.info("Stopping signing service")
app.add_event_handler("startup", lambda: logger.info(
"Application startup complete"))
app.add_event_handler("shutdown", lambda: logger.info(
"Application shutdown complete"))
async def healthcheck() -> dict[str, str]:
"""Endpoint simple de healthcheck."""
return {"status": "ok"}
async def get_version() -> dict[str, str]:
"""Devuelve la versión publicada del servicio."""
return {"version": version}
def create_app() -> FastAPI:
app = FastAPI(lifespan=lifespan,
title="FactuGES Document Signing Service",
version=version)
app.add_exception_handler(HTTPException, http_exception_handler)
# Add middleware to the app
app.add_middleware(LoggingMiddleware)
# Register routers
app.include_router(sign_router)
logger.info("API routes registered from sign_document router")
app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
app.add_api_route("/health", healthcheck, methods=["GET"])
logger.info("Health check endpoint registered at /health")
app.add_api_route("/version", lambda: {"version": version}, methods=["GET"])
logger.info("Health check endpoint registered at /version")
app.add_api_route("/version", get_version, methods=["GET"])
logger.info("Version endpoint registered at /version")
return app
app = create_app()