""" auth/oauth_flow.py — Google OAuth2 flow for klient-onboarding. Klienten trykker /auth/login?client_id=X → redirect til Google OAuth2 consent screen → Google redirecter til /auth/callback?code=...&state=... → token lagres i Secret Manager under klient-ID → klienten redirectes til /static/billing-dashboard.html Scopes (read-only): - bigquery.readonly - cloud-billing.readonly Krever env-vars: OAUTH_CLIENT_ID — fra GCP OAuth2 credentials OAUTH_CLIENT_SECRET — fra GCP OAuth2 credentials OAUTH_REDIRECT_URI — f.eks. https://osvauco-agent-....run.app/auth/callback PROJECT_ID — GCP project for Secret Manager """ import os import json import secrets from google_auth_oauthlib.flow import Flow SCOPES = [ "https://www.googleapis.com/auth/bigquery.readonly", "https://www.googleapis.com/auth/cloud-billing.readonly", "openid", "https://www.googleapis.com/auth/userinfo.email", ] _CLIENT_CONFIG = { "web": { "client_id": os.environ.get("OAUTH_CLIENT_ID", ""), "client_secret": os.environ.get("OAUTH_CLIENT_SECRET", ""), "redirect_uris": [os.environ.get("OAUTH_REDIRECT_URI", "http://localhost:8080/auth/callback")], "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", } } # In-memory state store (nonce → client_id). For prod: bytt til Firestore/Redis. _STATE_STORE: dict[str, str] = {} def get_authorization_url(client_id: str) -> str: """ Genererer Google OAuth2 autoriseringsURL for gitt client_id. Returnerer URL klienten skal redirectes til. """ flow = Flow.from_client_config(_CLIENT_CONFIG, scopes=SCOPES) flow.redirect_uri = os.environ.get( "OAUTH_REDIRECT_URI", "http://localhost:8080/auth/callback" ) state = secrets.token_urlsafe(32) _STATE_STORE[state] = client_id auth_url, _ = flow.authorization_url( access_type="offline", include_granted_scopes="true", state=state, prompt="consent", ) return auth_url def exchange_code_for_token(code: str, state: str) -> tuple[str, dict]: """ Bytter OAuth2 code mot token. Returnerer (client_id, token_dict). Kaster ValueError hvis state er ukjent. """ client_id = _STATE_STORE.pop(state, None) if client_id is None: raise ValueError(f"Ukjent OAuth2 state: {state}") flow = Flow.from_client_config(_CLIENT_CONFIG, scopes=SCOPES, state=state) flow.redirect_uri = os.environ.get( "OAUTH_REDIRECT_URI", "http://localhost:8080/auth/callback" ) flow.fetch_token(code=code) creds = flow.credentials token_dict = { "token": creds.token, "refresh_token": creds.refresh_token, "token_uri": creds.token_uri, "client_id": creds.client_id, "client_secret": creds.client_secret, "scopes": list(creds.scopes or []), } return client_id, token_dict