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:

  1. The caller must pass a profile identity resolved by adapters.outbound.google.active_profile.resolve_active_profile().

  2. 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) stdin is not a TTY, no operator can complete the flow, and the receiver would block forever. This guard eliminates that silent-hang failure mode before adapters.outbound.google.oauth_flow.run_login_flow() calls the local server.

Raises:

adapters.outbound.google.errors.GoogleAuthNonInteractiveError – When sys.stdin is not attached to a terminal.

Return type:

None

require_resolvable_profile_record(profile_id)[source]

Refuse the consent flow when the active profile cannot be resolved.

profile_id is the immutable profile identity returned by adapters.outbound.google.active_profile.resolve_active_profile(). The guard reads the profile bucket pointer through application.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:

None

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 returned adapters.outbound.google.records.OAuthToken carries the refresh credential and the returned adapters.outbound.google.records.OAuthMetadata carries 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 from adapters.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:

tuple[OAuthToken, OAuthMetadata]

Returns:

A 2-tuple of (adapters.outbound.google.records.OAuthToken, adapters.outbound.google.records.OAuthMetadata) ready for adapters.persistence.storage.sql.secure_objects.SecureObjectRepository persistence through adapters.outbound.google.session_store. Both records validate strict pydantic invariants; metadata refuses granted-scope tuples missing any adapters.outbound.google.records.REQUIRED_SCOPES member.

Raises:

adapters.outbound.google.errors.GoogleAuthScopeInsufficientError – When granted_scopes omits any required scope. Re-raised separately from the pydantic ValidationError so 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) against accounts.google.com. The flow checks profile state with adapters.outbound.google.oauth_flow.require_resolvable_profile_record(), requires adapters.outbound.google.oauth_flow.require_interactive_terminal(), then maps the resulting credential fields through adapters.outbound.google.oauth_flow.credentials_to_records().

Parameters:
Return type:

tuple[OAuthToken, OAuthMetadata]

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.