secshare-cli 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: secshare-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: CLI for SecShare — zero-knowledge secret sharing
5
5
  Project-URL: Homepage, https://secshare.link
6
6
  Project-URL: Source, https://github.com/rdney/secshare
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "secshare-cli"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "CLI for SecShare — zero-knowledge secret sharing"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -13,12 +13,18 @@ def _headers(auth: bool = False) -> dict:
13
13
  return h
14
14
 
15
15
 
16
- def login(email: str, password: str) -> dict:
17
- r = httpx.post(
18
- f"{get_api_base()}/auth/login",
19
- json={"email": email, "password": password, "turnstile_token": ""},
20
- timeout=TIMEOUT,
21
- )
16
+ def cli_init() -> dict:
17
+ r = httpx.post(f"{get_api_base()}/auth/cli-init", timeout=TIMEOUT)
18
+ r.raise_for_status()
19
+ return r.json()
20
+
21
+
22
+ def cli_poll(state: str) -> dict | None:
23
+ r = httpx.get(f"{get_api_base()}/auth/cli-token?state={state}", timeout=TIMEOUT)
24
+ if r.status_code == 202:
25
+ return None
26
+ if r.status_code == 410:
27
+ raise RuntimeError("Session expired. Run: secshare login")
22
28
  r.raise_for_status()
23
29
  return r.json()
24
30
 
@@ -1,11 +1,11 @@
1
1
  import json
2
- import sys
2
+ import time
3
+ import webbrowser
3
4
  from urllib.parse import urlparse
4
5
 
5
6
  import click
6
7
 
7
- from .api import create_secret, fetch_secret
8
- from .api import login as api_login
8
+ from .api import cli_init, cli_poll, create_secret, fetch_secret
9
9
  from .api import whoami as api_whoami
10
10
  from .config import get_token, get_api_base, load as load_config, save as save_config
11
11
  from .crypto import decrypt, encrypt
@@ -62,18 +62,45 @@ def cli():
62
62
 
63
63
  @cli.command()
64
64
  def login():
65
- """Authenticate with SecShare."""
66
- email = click.prompt("Email")
67
- password = click.prompt("Password", hide_input=True)
65
+ """Authenticate via browser."""
68
66
  try:
69
- data = api_login(email, password)
67
+ session = cli_init()
70
68
  except Exception as e:
71
- raise click.ClickException(f"Login failed: {e}")
72
- cfg = load_config()
73
- cfg["token"] = data["access_token"]
74
- cfg["email"] = email
75
- save_config(cfg)
76
- click.echo(f"Logged in as {email}")
69
+ raise click.ClickException(f"Could not start login: {e}")
70
+
71
+ state = session["state"]
72
+ api_host = get_api_base().replace("/api/v1", "").replace("api.", "")
73
+ url = f"{api_host}/cli-auth?state={state}"
74
+
75
+ click.echo(f"Opening browser to authorize...\n {url}")
76
+ webbrowser.open(url)
77
+ click.echo("Waiting for authorization (Ctrl+C to cancel)...")
78
+
79
+ deadline = time.time() + 300
80
+ while time.time() < deadline:
81
+ time.sleep(2)
82
+ try:
83
+ result = cli_poll(state)
84
+ except RuntimeError as e:
85
+ raise click.ClickException(str(e))
86
+ except Exception:
87
+ continue
88
+ if result is None:
89
+ continue
90
+ cfg = load_config()
91
+ cfg["token"] = result["access_token"]
92
+ try:
93
+ import httpx as _httpx
94
+ from .config import get_api_base as _base
95
+ me = _httpx.get(f"{_base()}/auth/me", headers={"Authorization": f"Bearer {result['access_token']}"}, timeout=10)
96
+ cfg["email"] = me.json().get("email", "")
97
+ except Exception:
98
+ pass
99
+ save_config(cfg)
100
+ click.echo(f"Logged in as {cfg.get('email', 'unknown')}")
101
+ return
102
+
103
+ raise click.ClickException("Timed out waiting for browser authorization.")
77
104
 
78
105
 
79
106
  @cli.command()
File without changes
File without changes