cadrumo.adapters.outbound.aeat.auth.authenticator module¶
Certificate-backed live-AEAT authenticator.
This module implements the certificate concrete for the application
application.auth.providers.AuthProvider contract. It composes
CertificateBundle loading, a
CertificateContextProvisioner-backed browser context, and the
canonical protected-resource probe into a narrow async provider surface.
The provider returns the imported AeatSession and
AeatLoginAssertion records owned by
application.auth.session_types. Captured
Playwright storage state is written through the encrypted session store
with PersistedSessionMetadata, then resumed only after hash,
idle-deadline, certificate thumbprint, certificate subject, and live
probe checks pass.
Design notes:
The module holds an 18-minute session idle TTL as a code-level constant. The value is deliberately not an env var — the operator surface is kept narrow, and AEAT’s observed idle window is ~20 minutes (the extra 2 minutes is safety margin).
authenticate()uses the configured browser-session factory. The authenticator owns and deterministically closes every session it creates.reauthenticate()is single-shot. Callers cap retries at ONE per downstream call-site; a second consecutive failure raisesAeatSessionExpiredErrorupwards rather than loop.
- AEAT_SESSION_IDLE_TTL: Final[timedelta]¶
Maximum idle lifetime for an authenticated AEAT Playwright session.
AEAT’s observed server-side idle window is ~20 minutes; 18 minutes leaves a 2-minute safety margin before the next downstream call would see a 401/403. Tuning this value is a code change, not an env-var change — the operator surface stays narrow.
- AEAT_LOGIN_NAVIGATION_TIMEOUT_MS: Final[int]¶
Playwright navigation timeout for post-auth verification probes.
- class AeatAuthenticator(settings, *, credentials, browser_session_factory=None, navigation_timeout_ms=30000, certificate_health_check=None)[source]¶
Bases:
objectCertificate implementation of the application
AuthProvider.The authenticator owns:
Certificate loading and health evaluation (via the existing module-level
load_certificate/healthsurface).Playwright browser-context construction with the cert wired through (via an injectable browser session factory).
Login-assertion verification.
Session lifecycle:
authenticate,reauthenticate,close.
Use as an async context manager:
async with AeatAuthenticator(settings, credentials=credentials) as auth: session = await auth.authenticate() assertion = await auth.verify(session)
Returned sessions use
CertificateSessionDetail, login probes useCertificateLoginAssertionDetail, and persisted resume state is validated againstPersistedSessionMetadata. Callers that only need synchronous health or NIF extraction can instantiate without entering the async context.- Parameters:
settings (Settings)
credentials (ActiveCertificateCredentials)
browser_session_factory (BrowserSessionFactoryPort | None)
navigation_timeout_ms (int)
certificate_health_check (CertificateHealthCheck | None)
-
kind:
AuthProviderKind¶
- load_certificate()[source]¶
Load the configured PKCS#12 bundle and return a
LoadedCertificate.- Return type:
- health(*, now=None)[source]¶
Return a
CertificateHealthfor the configured bundle.- Return type:
- Parameters:
now (datetime | None)
- async authenticate()[source]¶
Produce an authenticated
AeatSession.The method first attempts to resume a previously captured Playwright
storage_statebacked byPersistedSessionMetadata. If that persisted state is missing, malformed, stale, certificate-mismatched, or fails a live verification probe, it is deleted and the method falls back to a fresh certificate-backed browser login flow. Fresh contexts are created throughCertificateContextProvisionerso the AEAT origin receives the configured client certificate.- Return type:
- Returns:
An authenticated
AeatSessionready for downstream use.- Raises:
AeatLoginAssertionError – When the canonical protected-resource assertion does not prove a successful, exact final resource.
AuthConfigurationError – When async authentication has no browser session factory or certificate credentials are incomplete.
BrowserError – When browser launch or context construction fails.
Exception – Re-raised when storage-state capture fails after a successful context creation.
- async reauthenticate(session)[source]¶
Drop the current context and re-run
authenticate().Single-shot. The method itself does not retry; callers cap retries at one per downstream call-site. A second consecutive failure — whether the cert load fails, the protected-resource probe fails — MUST raise
AeatSessionExpiredErrorupwards rather than loop.Not atomic across the teardown + authenticate boundary. If another task calls
authenticate()between this method’sclose()completing and itsauthenticate()starting, the second call wins the “already has active session” guard check and this call raisesAeatLoginAssertionError. External serialisation is required if concurrentreauthenticate/authenticateis a real scenario for the caller.- Parameters:
session (
AeatSession) – The session to replace. Passed for traceability (logging, audit) and to document that the caller acknowledges it is discarded.- Return type:
- Returns:
A fresh
AeatSessionwith a newauthenticated_at+idle_deadline.
- async verify(session)[source]¶
Navigate the authenticated context to the canonical protected resource.
The assertion is valid only when Playwright reports a successful response and the final scheme, host, and path remain pinned to the accepted certificate resource.
- Parameters:
session (
AeatSession) – TheAeatSessionreturned fromauthenticate().- Return type:
- Returns:
A frozen
AeatLoginAssertion. Negative results (is_valid=False) are returned as records, not raised — callers may invokereauthenticate()once and re-verify.- Raises:
AeatSessionExpiredError – When the session’s idle deadline has elapsed.
AeatLoginAssertionError – When no browser context is available (authenticator was never authenticated, or
close()was called).
- describe()[source]¶
Return an
AuthProviderDescriptionwith a safe summary of the configured provider.Three distinct certificate states surface here, each with its own
health_summaryandhealth_severityso a downstream consumer can render them differently and so the loudest severity is reserved for genuine faults (round-5 B1 + minor):no path set —
configured=False, severityinfo, summaryapplication.auth.certificate.health.path_unset. An undeclared state, not a fault.path set, file missing —
configured=False, severitywarning, summaryapplication.auth.certificate.health.file_missing. The operator persisted a path that no longer resolves; the slot is operationally unusable until the file returns or a new path is supplied.path set, file present — proceeds into the password + load + health-check chain below;
configuredbecomesTrueand severity reflects the certificate’s expiry health.
- Return type:
- async close()[source]¶
Release the browser context + session. Idempotent.
Waits for any in-flight
verify()call to finish its navigation before tearing down the browser context, so a page cannot be closed out from under a running probe. The lifecycle barrier serializes the complete close sequence. Each caller registers a counted close intent before waiting for that gate, so new work remains barred until every registered closer has finished.After every registered close caller returns, the authenticator is re-usable (the browser session and context are nulled).
reauthenticate()depends on this re-use path.- Return type: