- 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] [project]
name = "factuges-document-signing-service" name = "factuges-document-signing-service"
version = "0.3.1" version = "0.3.4"
description = "FastAPI service for signing PDF documents using external secret managers" description = "FastAPI service for signing PDF documents using external secret managers"
requires-python = ">=3.11" requires-python = ">=3.11"
dependencies = [ dependencies = [
"python-multipart", "python-multipart==0.0.9",
"python-dotenv>=1.0", "python-dotenv==1.0.1",
"fastapi>=0.110",
"uvicorn[standard]>=0.27",
"pyhanko>=0.32", "fastapi==0.128.0",
"cryptography>=42", "starlette==0.50.0",
"uvicorn[standard]==0.40.0",
# Infisical SDK for secret management "pyHanko==0.32.0",
"infisicalsdk", "cryptography==46.0.4",
"infisicalsdk==1.0.15",
# Google Cloud Secret Manager SDK for secret management # Google Cloud Secret Manager SDK for secret management
# "google-cloud-secret-manager>=2.18", # "google-cloud-secret-manager>=2.18",
] ]
[project.optional-dependencies] [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 from signing_service.infrastructure.pdf.pdf_validator import PDFValidator
SIGNATURE_FIELD_NAME = "Signature1" SIGNATURE_FIELD_NAME = "Signature1"
CERTIFICATE_FIELD_NAME = "organization_name" # "common_name"
class PyHankoPDFSigner(PDFSignerPort): class PyHankoPDFSigner(PDFSignerPort):
@ -38,7 +39,8 @@ class PyHankoPDFSigner(PDFSignerPort):
# 3⃣ Extract certificate info (from signer) # 3⃣ Extract certificate info (from signer)
cert = signer.signing_cert 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) # 5⃣ Define caja del campo de firma (AcroForm)
fields.append_signature_field( fields.append_signature_field(

View File

@ -1,13 +1,14 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Request from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from dotenv import load_dotenv from dotenv import load_dotenv
from pathlib import Path from pathlib import Path
from datetime import datetime from datetime import datetime, UTC
from dateutil import tz from dateutil import tz
from signing_service.application.settings.container import get_settings from signing_service.application.settings.container import get_settings
from signing_service.application.settings.setup_logger import create_logger from signing_service.application.settings.setup_logger import create_logger
from signing_service.api.routes.sign_document import router as sign_router from signing_service.api.routes.sign_document import router as sign_router
@ -18,37 +19,45 @@ load_dotenv()
# 👇 FAIL FAST: load settings at startup # 👇 FAIL FAST: load settings at startup
settings = get_settings() settings = get_settings()
version = get_package_version() version = get_package_version()
local_tz = tz.gettz(settings.local_tz)
state_path = Path(settings.state_path)
# Logging def build_logger():
log_dir = state_path """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) 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("") app_logger.info("")
logger.info("============================================================") app_logger.info(
logger.info("FactuGES Document Signing Service - START ") "============================================================")
logger.info("Version: %s", version) app_logger.info("FactuGES Document Signing Service - START")
logger.info("UTC Now: %s", datetime.utcnow().isoformat()) app_logger.info("Version: %s", version)
logger.info("Environment: %s", settings.app_env) app_logger.info("UTC Now: %s", datetime.now(UTC).isoformat())
logger.info("") app_logger.info("Environment: %s", settings.app_env)
logger.info("Log Level: %s", settings.log_level) app_logger.info("")
logger.info("Log: %s", log_dir / "factuges-document-signing-service.log") app_logger.info("Log Level: %s", settings.log_level)
logger.info("") app_logger.info(
logger.info("Secret Provider: %s", settings.secret_provider) "Log: %s",
logger.info("Infisical project ID: %s", settings.infisical_project_id) log_dir / "factuges-document-signing-service.log",
logger.info("Infisical environment: %s", settings.infisical_env_slug) )
logger.info("") 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): async def http_exception_handler(request: Request, exc: HTTPException):
"""Registra y devuelve errores HTTP controlados."""
logger.error( logger.error(
"HTTPException %s %s | status=%s | detail=%s", "HTTPException %s %s | status=%s | detail=%s",
request.method, request.method,
@ -63,47 +72,63 @@ async def http_exception_handler(request: Request, exc: HTTPException):
) )
# Define logging middleware
class LoggingMiddleware(BaseHTTPMiddleware): class LoggingMiddleware(BaseHTTPMiddleware):
"""Registra request y response para trazabilidad básica."""
async def dispatch(self, request: Request, call_next): async def dispatch(self, request: Request, call_next):
# Log request details client_ip = request.client.host if request.client else "unknown"
client_ip = request.client.host
method = request.method method = request.method
url = request.url.path 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) response = await call_next(request)
# Log response details
status_code = response.status_code
logger.info( 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 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( async def healthcheck() -> dict[str, str]:
"Application shutdown complete")) """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) app.add_exception_handler(HTTPException, http_exception_handler)
# Add middleware to the app
app.add_middleware(LoggingMiddleware) app.add_middleware(LoggingMiddleware)
# Register routers
app.include_router(sign_router) app.include_router(sign_router)
logger.info("API routes registered from sign_document 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") logger.info("Health check endpoint registered at /health")
app.add_api_route("/version", lambda: {"version": version}, methods=["GET"]) app.add_api_route("/version", get_version, methods=["GET"])
logger.info("Health check endpoint registered at /version") logger.info("Version endpoint registered at /version")
return app
app = create_app()