aeat.adapters.outbound.aeat.auth._authenticator module¶
Certificate-backed live-AEAT authenticator.
This module implements the certificate concrete for the application
application.auth.AuthProvider contract. It composes
CertificateBundle loading, mTLS handshake checks, a
CertificateContextProvisioner-backed browser context, and a
post-auth login probe into a narrow async provider surface.
The provider returns the imported AeatSession and
AeatLoginAssertion records owned by
adapters.outbound.aeat.auth._authenticator_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()accepts an optional injectable browser session factory. Unit tests pass an in-process factory that produces a stand-in context honouring the_aeat_certificate_thumbprintmarker contract. This lets the whole authenticator exercise run under@pytest.mark.unitwithout importing Playwright.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, *, browser_session_factory=None, handshake_verifier=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).TLS handshake verification (via the existing
verify_handshake).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) as auth: session = await auth.authenticate() assertion = await auth.verify_login(session)
Returned sessions use
CertificateSessionDetail, login probes useCertificateLoginAssertionDetail, and persisted resume state is validated againstPersistedSessionMetadata. Callers that only need synchronous health, handshake, or NIF extraction can instantiate without entering the async context.- Parameters:
settings (Settings)
browser_session_factory (BrowserSessionFactory | None)
handshake_verifier (Callable[[LoadedCertificate, str], HandshakeResult] | 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)
- verify_handshake(*, url=None)[source]¶
Run the mTLS smoke probe against
url.- Parameters:
url (
str|None) – Optional override. When omitted, the authenticator usesSettings.aeat_certificate_verify_url.- Return type:
- Returns:
A
HandshakeResultwith the probe outcome.
- async authenticate(*, browser_session=None, target_url=None)[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 handshake plus browser login flow. Fresh contexts are created throughCertificateContextProvisionerso the AEAT origin receives the configured client certificate.- Parameters:
browser_session (
BrowserSessionLike|None) – Optional existing browser session to reuse.target_url (
str|None) – Optional override URL for the authentication target.
- Return type:
- Returns:
An authenticated
AeatSessionready for downstream use.- Raises:
AeatLoginAssertionError – When the browser session factory returns a context missing the thumbprint marker, or when the login probe 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 handshake fails, or
verify_loginstill returnscertificate_recognised=False— MUST raiseAeatSessionExpiredErrorupwards 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_login(session, *, target_url=None)[source]¶
Navigate the authenticated context to
target_url.The assertion record captures three independent signals:
handshake_success— the TLS handshake attached to the session completed successfully.certificate_recognised— the post-auth navigation returned a non-challenge HTTP response.parsed_nif— the NIF / NIE extracted from the certificate subject (always populated when the session carries a cert;Noneonly in exceptional structural failures).
- Parameters:
session (
AeatSession) – TheAeatSessionreturned fromauthenticate().target_url (
str|None) – Optional override. Defaults toSettings.aeat_certificate_verify_url.
- 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).
- async verify(session, *, target_url=None)[source]¶
Provider-protocol alias for
verify_login().- Return type:
- Returns:
A
AeatLoginAssertiondescribing the verification outcome.- Parameters:
session (AeatSession)
target_url (str | None)
- async capture_storage_state(session)[source]¶
Persist the active Playwright state and
PersistedSessionMetadata.- Return type:
Path- Parameters:
session (AeatSession)
- async resume_from_storage_state(path, *, browser_session=None, target_url=None)[source]¶
Resume a certificate
AeatSessionfrom encrypted storage.The persisted browser state and
PersistedSessionMetadataare validated before aCertificateContextProvisioneropens a context with the restored storage state. A successful live probe refreshesauthenticated_atandidle_deadlinebefore the session is returned.- Return type:
- Parameters:
path (Path)
browser_session (BrowserSessionLike | None)
target_url (str | None)
- 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_login()call to finish its navigation before tearing down the browser context, so a page cannot be closed out from under a running probe. A one-way_closinglatch is set under the lock before the drain wait so that a newverify_logincannot slip in between the wait returning and the teardown acquiring the lock — the latch forces any arriving probe to raise.After
close()returns, the authenticator is re-usable (the latch is reset, the browser session is nulled, the context is nulled).reauthenticate()depends on this re-use path.- Return type: