cadrumo.adapters.outbound.google.oauth_flow module¶
Google OAuth Desktop login flow for per-profile Google sessions.
Runs an operator-supplied adapters.outbound.google.records.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.active_profile.resolve_active_profile().adapters.outbound.google.oauth_flow.require_resolvable_profile_record()refuses a profile whose canonical record session cannot be opened.
See also
adapters.outbound.google.oauth_flow.run_login_flow() executes the login
path, adapters.outbound.google.oauth_flow.credentials_to_records()
produces adapters.outbound.google.records.OAuthToken and
adapters.outbound.google.records.OAuthMetadata, and
adapters.outbound.google.records.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.oauth_flow.run_login_flow()calls the local server.- Raises:
adapters.outbound.google.errors.GoogleAuthNonInteractiveError – When
sys.stdinis not attached to a terminal.- Return type:
- require_resolvable_profile_record(profile_id)[source]¶
Refuse the consent flow when the active profile cannot be resolved.
profile_idis the immutable profile identity returned byadapters.outbound.google.active_profile.resolve_active_profile(). The guard reads the profile bucket pointer throughapplication.workflow.profile_bucket_scan.read_profile_bucket_by_id()and opens the canonical user-profile record through its lifecycle service, so a profile that is committed but has no live record session is refused BEFORE any network IO rather than midway through consent.Nothing is read out of the record: existence is the whole precondition.
- Raises:
adapters.outbound.google.errors.GoogleAuthProfileUnboundError – When the profile bucket pointer or the canonical profile-record session cannot be resolved.
- Return type:
- 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.records.REQUIRED_SCOPES. The returnedadapters.outbound.google.records.OAuthTokencarries the refresh credential and the returnedadapters.outbound.google.records.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.records.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.records.OAuthToken,adapters.outbound.google.records.OAuthMetadata) ready foradapters.persistence.storage.sql.secure_objects.SecureObjectRepositorypersistence throughadapters.outbound.google.session_store. Both records validate strict pydantic invariants; metadata refuses granted-scope tuples missing anyadapters.outbound.google.records.REQUIRED_SCOPESmember.- Raises:
adapters.outbound.google.errors.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.require_resolvable_profile_record(), 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.records.OAuthClientmetadata.profile (
str) – Active profile UUID resolved byadapters.outbound.google.active_profile.resolve_active_profile().
- Return type:
- Returns:
A 2-tuple of (
adapters.outbound.google.records.OAuthToken,adapters.outbound.google.records.OAuthMetadata) ready for persistence.- Raises:
adapters.outbound.google.errors.GoogleAuthError – Any typed OAuth refusal with concrete remediation context.