92 lines
2.9 KiB
Python
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
|