"""nova auth login — session/PAT → OIDC token, store locally (REQ-344, C-7.3).""" from __future__ import annotations import json import os import sys from core.auth_store import store_credential, credentials_path def _vend(pat: str, env: str, endpoint: str) -> dict: """Call the token-vend Lambda (locally or via the function URL).""" if endpoint and endpoint.startswith("http"): import urllib.request body = json.dumps({"token": pat, "environment": env}).encode() req = urllib.request.Request(endpoint, data=body, headers={"Content-Type": "application/json"}) with urllib.request.urlopen(req, timeout=30) as r: return json.loads(r.read()) # Local: invoke the Lambda in-process. import importlib.util from pathlib import Path p = Path(__import__("core").__file__).parent / "lambda" / "nova_idp_token_vend.py" spec = importlib.util.spec_from_file_location("nova_idp_token_vend", p) mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod) os.environ.setdefault("NOVA_LAMBDA_LOCAL_BYPASS", "1") resp = mod.lambda_handler({"body": json.dumps({"token": pat, "environment": env})}, None) return json.loads(resp["body"]) def add_parser(subparsers): p = subparsers.add_parser("login", help="exchange a PAT/session for an OIDC token") p.add_argument("--pat", default=None, help="PAT JWT (prompted if absent)") p.add_argument("--session", default=None, help="session token (alias for --pat)") p.add_argument("--environment", default="dev", help="target environment") p.add_argument("--endpoint", default=os.environ.get("NOVA_TOKEN_VEND_URL", ""), help="token-vend function URL (empty = local)") p.set_defaults(_run=run) def run(args) -> int: pat = args.pat or args.session or os.environ.get("NOVA_PAT") if not pat: pat = sys.stdin.readline().strip() if not pat: print("error: no PAT/session provided", file=sys.stderr); return 1 result = _vend(pat, args.environment, args.endpoint) if "token" not in result: print(f"error: {result.get('error', result)}", file=sys.stderr); return 2 import base64 payload = json.loads(base64.urlsafe_b64decode(result["token"].split(".")[1] + "==")) store_credential( jti=payload.get("jti", ""), cred_type=payload.get("typ", "nova_oidc_token"), exp=payload.get("exp", 0), oidc_token=result["token"], ) print(f"logged in: jti={payload.get('jti')} exp={payload.get('exp')} " f"file={credentials_path()}") return 0