OSVauco/auth/oauth_flow.py
Chris Christiansen 9fcb9c354a
Some checks are pending
Check Python Version Consistency / Check Python Version (push) Waiting to run
feat(core): Fresh initialization - Deploy v3.6.1 Singularity Architecture
2026-09-03 04:03:09 +00:00

92 lines
2.9 KiB
Python

"""
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