Files
acdl/nova/auth/login.py
T
Jon Chery 14809327fb feat(P04): PAT lifecycle + nova auth login/revoke/status (REQ-342..344, C-7.3, security+cli)
---ci---
project: acdl
phase: 4
milestone: v1.28
status: execute
persona: cli-engineer
---
2026-08-19 23:11:16 +00:00

58 lines
2.5 KiB
Python

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