aeat.adapters.outbound.google._oauth_flow module¶
Google OAuth Desktop login flow for per-profile Google sessions.
Runs an operator-supplied adapters.outbound.google.OAuthClient
through Google’s loopback IP + PKCE Desktop flow using
google_auth_oauthlib.flow.InstalledAppFlow.run_local_server(port=0).
The operating system picks an ephemeral loopback port and opens the
consent screen in the operator’s default browser.
Two policy gates fire before any network IO happens:
The caller must pass a profile identity resolved by
adapters.outbound.google.resolve_active_profile().When
core.config.SecretStoreBackendis configured asUNSECUREDand that profile carries a real Spanish NIF / NIE / CIF,adapters.outbound.google._oauth_flow.check_unsecured_mode_safety()refuses withadapters.outbound.google.GoogleAuthUnsecuredModeRefusedError.
See also
adapters.outbound.google.run_login_flow() executes the login
path, adapters.outbound.google._oauth_flow.credentials_to_records()
produces adapters.outbound.google.OAuthToken and
adapters.outbound.google.OAuthMetadata, and
adapters.outbound.google.REQUIRED_SCOPES defines the consent
surface the Google account must grant.
- require_interactive_terminal()[source]¶
Refuse the consent flow when no controlling terminal can drive it.
The Desktop OAuth flow opens the consent screen in the operator’s browser and then blocks a loopback HTTP receiver until consent completes. In a non-interactive invocation (piped, redirected, cron, or another agent)
stdinis not a TTY, no operator can complete the flow, and the receiver would block forever. This guard eliminates that silent-hang failure mode beforeadapters.outbound.google.run_login_flow()calls the local server.- Raises:
adapters.outbound.google.GoogleAuthNonInteractiveError – When
sys.stdinis not attached to a terminal. The exception carries the interactive-terminal prerequisite as a suggestion.- Return type:
- check_unsecured_mode_safety(profile, tax_id)[source]¶
Refuse the OAuth flow when unsecured mode meets a real NIF.
The guard mirrors the storage substrate’s NIF-canary rule: real taxpayer identifiers must not enter OAuth token setup while
core.config.SecretStoreBackendis running in unsecured mode.- Parameters:
profile (
str) – Active profile UUID resolved byadapters.outbound.google.resolve_active_profile().tax_id (
str) – The active profile’sidentity.tax_idvalue. Empty string when the profile has no stored tax identifier.
- Raises:
adapters.outbound.google.GoogleAuthUnsecuredModeRefusedError – When
aeat_secret_store_backend=unsecuredandtax_idparses as a real Spanish tax identifier peradapters.persistence.storage.master_key.looks_like_real_tax_id().- Return type:
- resolve_active_tax_id(profile_id)[source]¶
Return the
identity.tax_idvalue for the profile UUID.profile_idis the immutable profile identity returned byadapters.outbound.google.resolve_active_profile(). The resolver loads the profile bucket pointer throughapplication.workflow.read_profile_bucket_by_id(), opens the canonical user-profile lifecycle service, and reads the tax-id fact used byadapters.outbound.google._oauth_flow.check_unsecured_mode_safety().- Return type:
- Returns:
The stored
identity.tax_idvalue, or an empty string when the profile record has no tax identifier.- Raises:
adapters.outbound.google.GoogleAuthProfileUnboundError – When the profile bucket manifest or canonical profile record cannot be resolved.
- Parameters:
profile_id (str)
- credentials_to_records(*, refresh_token, token_uri, account_email, granted_scopes, issued_at)[source]¶
Map OAuth credential fields into persisted Google session records.
The consent screen must grant every scope in
adapters.outbound.google.REQUIRED_SCOPES. The returnedadapters.outbound.google.OAuthTokencarries the refresh credential and the returnedadapters.outbound.google.OAuthMetadatacarries the linked Google account, granted scope tuple, and issuance timestamps used by the session store.- Parameters:
refresh_token (
str) – The refresh token returned by the consent screen.token_uri (
str) – The token endpoint URL mirrored fromadapters.outbound.google.OAuthClient.account_email (
str) – The Google account that completed the consent.granted_scopes (
tuple[str,...]) – Scopes the consent screen actually granted.issued_at (
datetime) – Timestamp the credential was first issued.
- Return type:
- Returns:
A 2-tuple of (
adapters.outbound.google.OAuthToken,adapters.outbound.google.OAuthMetadata) ready foradapters.persistence.storage.SecureObjectRepositorypersistence throughadapters.outbound.google._session_store. Both records validate strict pydantic invariants; metadata refuses granted-scope tuples missing anyadapters.outbound.google.REQUIRED_SCOPESmember.- Raises:
adapters.outbound.google.GoogleAuthScopeInsufficientError – When
granted_scopesomits any required scope. Re-raised separately from the pydanticValidationErrorso the CLI can surface a concrete remediation hint.
- run_login_flow(client, profile)[source]¶
Execute the loopback-IP + PKCE OAuth Desktop flow.
Always runs the real
google_auth_oauthlib.flow.InstalledAppFlow.run_local_server(port=0)againstaccounts.google.com. The flow checks profile state withadapters.outbound.google._oauth_flow.resolve_active_tax_id(), appliesadapters.outbound.google._oauth_flow.check_unsecured_mode_safety(), requiresadapters.outbound.google._oauth_flow.require_interactive_terminal(), then maps the resulting credential fields throughadapters.outbound.google._oauth_flow.credentials_to_records().- Parameters:
client (
OAuthClient) – Operator-importedadapters.outbound.google.OAuthClientmetadata.profile (
str) – Active profile UUID resolved byadapters.outbound.google.resolve_active_profile().
- Return type:
- Returns:
A 2-tuple of (
adapters.outbound.google.OAuthToken,adapters.outbound.google.OAuthMetadata) ready for persistence.- Raises:
adapters.outbound.google.GoogleAuthError – Any typed OAuth refusal with concrete remediation context.