- En la firma visible, mostrar el nombre de organización del certificado.
- Ajustar versiones
This commit is contained in:
parent
1bd5bb48d3
commit
5ab10cf6c8
@ -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",
|
||||||
|
]
|
||||||
@ -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(
|
||||||
|
|||||||
@ -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.mkdir(parents=True, exist_ok=True)
|
log_dir = Path(settings.state_path)
|
||||||
logger = create_logger(
|
log_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
app_logger = create_logger(
|
||||||
name="factuges-document-signing-service",
|
name="factuges-document-signing-service",
|
||||||
# Solo lo genera en producción
|
|
||||||
log_path=log_dir / "factuges-document-signing-service.log",
|
log_path=log_dir / "factuges-document-signing-service.log",
|
||||||
|
)
|
||||||
|
|
||||||
)
|
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("")
|
||||||
|
|
||||||
logger.info("")
|
return app_logger
|
||||||
logger.info("============================================================")
|
|
||||||
logger.info("FactuGES Document Signing Service - START ")
|
|
||||||
logger.info("Version: %s", version)
|
logger = build_logger()
|
||||||
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("")
|
|
||||||
|
|
||||||
|
|
||||||
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):
|
||||||
app.add_event_handler("startup", lambda: logger.info(
|
"""Gestiona el ciclo de vida de la aplicación."""
|
||||||
"Application startup complete"))
|
logger.info("Starting signing service")
|
||||||
|
yield
|
||||||
app.add_event_handler("shutdown", lambda: logger.info(
|
logger.info("Stopping signing service")
|
||||||
"Application shutdown complete"))
|
|
||||||
|
|
||||||
app.add_exception_handler(HTTPException, http_exception_handler)
|
|
||||||
|
|
||||||
# Add middleware to the app
|
|
||||||
app.add_middleware(LoggingMiddleware)
|
|
||||||
|
|
||||||
|
|
||||||
# Register routers
|
async def healthcheck() -> dict[str, str]:
|
||||||
app.include_router(sign_router)
|
"""Endpoint simple de healthcheck."""
|
||||||
logger.info("API routes registered from sign_document router")
|
return {"status": "ok"}
|
||||||
|
|
||||||
app.add_api_route("/health", lambda: {"status": "ok"}, methods=["GET"])
|
|
||||||
logger.info("Health check endpoint registered at /health")
|
|
||||||
|
|
||||||
app.add_api_route("/version", lambda: {"version": version}, methods=["GET"])
|
async def get_version() -> dict[str, str]:
|
||||||
logger.info("Health check endpoint registered at /version")
|
"""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_middleware(LoggingMiddleware)
|
||||||
|
app.include_router(sign_router)
|
||||||
|
logger.info("API routes registered from sign_document router")
|
||||||
|
|
||||||
|
app.add_api_route("/health", healthcheck, methods=["GET"])
|
||||||
|
logger.info("Health check endpoint registered at /health")
|
||||||
|
|
||||||
|
app.add_api_route("/version", get_version, methods=["GET"])
|
||||||
|
logger.info("Version endpoint registered at /version")
|
||||||
|
return app
|
||||||
|
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user