-
Notifications
You must be signed in to change notification settings - Fork 172
Expand file tree
/
Copy pathlogin_cmd.py
More file actions
131 lines (105 loc) · 4.24 KB
/
login_cmd.py
File metadata and controls
131 lines (105 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""``backend.ai v2 login`` / ``logout`` CLI commands."""
from __future__ import annotations
import asyncio
import getpass
import json
import os
import click
from .helpers import COOKIE_FILE, SESSION_DIR, create_v2_registry, load_v2_config
@click.command()
@click.option(
"--force", is_flag=True, default=False, help="Force login, invalidating existing sessions."
)
def login(force: bool) -> None:
"""Log in to a Backend.AI webserver endpoint.
Stores the session cookie in ``~/.backend.ai/session/cookie.dat``.
Requires ``endpoint-type`` to be ``session``.
"""
user_id = os.environ.get("BACKEND_USER") or input("User ID: ")
password = os.environ.get("BACKEND_PASSWORD") or getpass.getpass("Password: ")
async def _run() -> None:
config = load_v2_config()
if config.endpoint_type != "session":
click.echo(
click.style(
"Login requires endpoint-type=session. "
"Run: backend.ai v2 config set endpoint-type session",
fg="yellow",
)
)
raise SystemExit(1)
registry = await create_v2_registry(config)
try:
client = registry._client
login_url = client.build_url_raw("/server/login")
payload: dict[str, str | bool] = {"username": user_id, "password": password}
if force:
payload["force"] = True
async with client.session.post(login_url, json=payload) as resp:
data = await resp.json()
def _get_details(resp_data: dict[str, Any]) -> str:
raw = resp_data.get("data")
if isinstance(raw, dict):
details = raw.get("details")
if details:
return str(details)
elif raw:
return str(raw)
# Handle RFC 7807 problem responses (e.g., {"type": ..., "title": ...})
if "title" in resp_data:
return str(resp_data["title"])
return "Unknown error"
if not data.get("authenticated"):
details = _get_details(data)
if details == "OTP not provided":
otp = input("One-time Password: ")
payload["otp"] = otp.strip()
async with client.session.post(login_url, json=payload) as resp:
data = await resp.json()
if not data.get("authenticated"):
details = _get_details(data)
click.echo(click.style(f"Login failed: {details}", fg="red"))
raise SystemExit(1)
SESSION_DIR.mkdir(parents=True, exist_ok=True)
cookie_jar = client.session.cookie_jar
if hasattr(cookie_jar, "save"):
cookie_jar.save(COOKIE_FILE)
login_config = data.get("config", {})
if login_config:
config_path = SESSION_DIR / "config.json"
config_path.write_text(json.dumps(login_config))
click.echo(click.style("Login succeeded.", fg="green"))
finally:
await registry.close()
asyncio.run(_run())
@click.command()
def logout() -> None:
"""Log out and clear the stored session cookie."""
async def _run() -> None:
config = load_v2_config()
if config.endpoint_type != "session":
click.echo(
click.style(
"Logout requires endpoint-type=session.",
fg="yellow",
)
)
raise SystemExit(1)
registry = await create_v2_registry(config)
try:
client = registry._client
logout_url = client.build_url_raw("/server/logout")
try:
async with client.session.post(logout_url) as resp:
await resp.read()
except Exception:
pass
finally:
await registry.close()
for path in [COOKIE_FILE, SESSION_DIR / "config.json"]:
try:
path.unlink(missing_ok=True)
except OSError:
pass
click.echo(click.style("Logged out.", fg="green"))
asyncio.run(_run())