borecli 1.0.0__tar.gz → 1.4.1__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.
Files changed (35) hide show
  1. {borecli-1.0.0/borecli.egg-info → borecli-1.4.1}/PKG-INFO +1 -1
  2. borecli-1.4.1/bore/auth.py +176 -0
  3. {borecli-1.0.0 → borecli-1.4.1}/bore/cli.py +41 -6
  4. {borecli-1.0.0 → borecli-1.4.1/borecli.egg-info}/PKG-INFO +1 -1
  5. borecli-1.4.1/borecli.egg-info/top_level.txt +2 -0
  6. {borecli-1.0.0 → borecli-1.4.1}/pyproject.toml +1 -1
  7. borecli-1.0.0/bore/auth.py +0 -104
  8. borecli-1.0.0/borecli.egg-info/top_level.txt +0 -1
  9. {borecli-1.0.0 → borecli-1.4.1}/LICENSE +0 -0
  10. {borecli-1.0.0 → borecli-1.4.1}/README.md +0 -0
  11. {borecli-1.0.0 → borecli-1.4.1}/bore/__init__.py +0 -0
  12. {borecli-1.0.0 → borecli-1.4.1}/bore/config.py +0 -0
  13. {borecli-1.0.0 → borecli-1.4.1}/bore/frames.py +0 -0
  14. {borecli-1.0.0 → borecli-1.4.1}/bore/handlers/__init__.py +0 -0
  15. {borecli-1.0.0 → borecli-1.4.1}/bore/handlers/error_pages.py +0 -0
  16. {borecli-1.0.0 → borecli-1.4.1}/bore/handlers/http_handler.py +0 -0
  17. {borecli-1.0.0 → borecli-1.4.1}/bore/handlers/websocket_handler.py +0 -0
  18. {borecli-1.0.0 → borecli-1.4.1}/bore/parse_duration.py +0 -0
  19. {borecli-1.0.0 → borecli-1.4.1}/bore/protocol.py +0 -0
  20. {borecli-1.0.0 → borecli-1.4.1}/bore/tunnel/__init__.py +0 -0
  21. {borecli-1.0.0 → borecli-1.4.1}/bore/tunnel/client.py +0 -0
  22. {borecli-1.0.0 → borecli-1.4.1}/bore/tunnel/dispatcher.py +0 -0
  23. {borecli-1.0.0 → borecli-1.4.1}/bore/tunnel/heartbeat.py +0 -0
  24. {borecli-1.0.0 → borecli-1.4.1}/bore/tunnel.py +0 -0
  25. {borecli-1.0.0 → borecli-1.4.1}/bore/websocket/__init__.py +0 -0
  26. {borecli-1.0.0 → borecli-1.4.1}/bore/websocket/connection.py +0 -0
  27. {borecli-1.0.0 → borecli-1.4.1}/bore/websocket/frames.py +0 -0
  28. {borecli-1.0.0 → borecli-1.4.1}/bore/websocket/manager.py +0 -0
  29. {borecli-1.0.0 → borecli-1.4.1}/bore/websocket/proxy.py +0 -0
  30. {borecli-1.0.0 → borecli-1.4.1}/bore/websocket_client.py +0 -0
  31. {borecli-1.0.0 → borecli-1.4.1}/borecli.egg-info/SOURCES.txt +0 -0
  32. {borecli-1.0.0 → borecli-1.4.1}/borecli.egg-info/dependency_links.txt +0 -0
  33. {borecli-1.0.0 → borecli-1.4.1}/borecli.egg-info/entry_points.txt +0 -0
  34. {borecli-1.0.0 → borecli-1.4.1}/borecli.egg-info/requires.txt +0 -0
  35. {borecli-1.0.0 → borecli-1.4.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: borecli
3
- Version: 1.0.0
3
+ Version: 1.4.1
4
4
  Summary: Official BoreHook CLI for securely exposing localhost through BoreHook.
5
5
  Author-email: Chinonso Egwuaka <chinonso_patrick@yahoo.com>
6
6
  License: MIT
@@ -0,0 +1,176 @@
1
+ import requests
2
+
3
+ from .config import (
4
+ DEFAULT_TOKEN_LIFETIME,
5
+ clear_credentials,
6
+ save_credentials,
7
+ )
8
+
9
+
10
+ API_URL = "https://api.borehook.com"
11
+
12
+
13
+ class BoreAuthError(Exception):
14
+ """Raised when BoreHook authentication fails."""
15
+
16
+
17
+ def _get_error_message(response):
18
+ """
19
+ Extract a safe, user-facing error message from an API response.
20
+
21
+ This deliberately does not include the API URL or raw HTTP exception.
22
+ """
23
+
24
+ try:
25
+ data = response.json()
26
+ except ValueError:
27
+ data = {}
28
+
29
+ if isinstance(data, dict):
30
+ message = (
31
+ data.get("message")
32
+ or data.get("detail")
33
+ or data.get("error")
34
+ )
35
+
36
+ if isinstance(message, str) and message.strip():
37
+ return message.strip()
38
+
39
+ if response.status_code in {401, 403}:
40
+ return (
41
+ "Login failed. Your email/password may be incorrect, "
42
+ "or your account may be suspended or unverified."
43
+ )
44
+
45
+ if response.status_code == 429:
46
+ return "Too many login attempts. Please try again later."
47
+
48
+ if response.status_code >= 500:
49
+ return (
50
+ "BoreHook is temporarily unavailable. "
51
+ "Please try again later."
52
+ )
53
+
54
+ return "Login failed. Please check your details and try again."
55
+
56
+
57
+ def login(
58
+ email,
59
+ password,
60
+ lifetime=DEFAULT_TOKEN_LIFETIME,
61
+ ):
62
+ """
63
+ Authenticate a user and save the session locally.
64
+
65
+ Raises:
66
+ BoreAuthError: If authentication fails.
67
+ """
68
+
69
+ try:
70
+ response = requests.post(
71
+ f"{API_URL}/api/accounts/login/",
72
+ json={
73
+ "email": email,
74
+ "password": password,
75
+ },
76
+ timeout=30,
77
+ )
78
+
79
+ except requests.Timeout as exc:
80
+ raise BoreAuthError(
81
+ "Unable to reach BoreHook. The request timed out. "
82
+ "Please try again."
83
+ ) from exc
84
+
85
+ except requests.ConnectionError as exc:
86
+ raise BoreAuthError(
87
+ "Unable to connect to BoreHook. "
88
+ "Please check your internet connection and try again."
89
+ ) from exc
90
+
91
+ except requests.RequestException as exc:
92
+ raise BoreAuthError(
93
+ "An unexpected network error occurred while logging in."
94
+ ) from exc
95
+
96
+ if not response.ok:
97
+ raise BoreAuthError(_get_error_message(response))
98
+
99
+ try:
100
+ data = response.json()
101
+ except ValueError as exc:
102
+ raise BoreAuthError(
103
+ "BoreHook returned an invalid login response."
104
+ ) from exc
105
+
106
+ token = data.get("token")
107
+
108
+ if not token:
109
+ raise BoreAuthError(
110
+ "Login succeeded but no authentication token was returned."
111
+ )
112
+
113
+ save_credentials(
114
+ email=email,
115
+ token=token,
116
+ lifetime=lifetime,
117
+ )
118
+
119
+ return {
120
+ "email": email,
121
+ "token": token,
122
+ "lifetime": lifetime,
123
+ }
124
+
125
+
126
+ def verify_token(token):
127
+ """
128
+ Verify token validity against the API.
129
+
130
+ Returns:
131
+ True if the token is valid.
132
+ False otherwise.
133
+ """
134
+
135
+ try:
136
+ response = requests.get(
137
+ f"{API_URL}/api/accounts/me/",
138
+ headers={
139
+ "Authorization": f"Token {token}",
140
+ },
141
+ timeout=30,
142
+ )
143
+
144
+ return response.status_code == 200
145
+
146
+ except requests.RequestException:
147
+ return False
148
+
149
+
150
+ def logout():
151
+ """
152
+ Remove locally stored credentials.
153
+ """
154
+
155
+ clear_credentials()
156
+
157
+
158
+ def refresh_session(email, token):
159
+ """
160
+ Refresh the saved session timestamp.
161
+ """
162
+
163
+ save_credentials(
164
+ email=email,
165
+ token=token,
166
+ )
167
+
168
+
169
+ def get_authenticated_headers(token):
170
+ """
171
+ Return authorization headers for API requests.
172
+ """
173
+
174
+ return {
175
+ "Authorization": f"Token {token}",
176
+ }
@@ -21,6 +21,8 @@ from bore.parse_duration import parse_duration
21
21
  from bore.auth import (
22
22
  login,
23
23
  logout,
24
+ BoreAuthError,
25
+
24
26
  )
25
27
 
26
28
  from bore.tunnel.client import TunnelClient
@@ -54,7 +56,6 @@ def cli():
54
56
  pass
55
57
 
56
58
 
57
-
58
59
  @cli.command(name="login")
59
60
  @click.option("--email", prompt=True)
60
61
  @click.option(
@@ -76,7 +77,6 @@ def login_cmd(email, password, time):
76
77
  return
77
78
 
78
79
  try:
79
-
80
80
  if time:
81
81
  lifetime = parse_duration(time)
82
82
  else:
@@ -92,11 +92,46 @@ def login_cmd(email, password, time):
92
92
  click.echo(f"Account: {session['email']}")
93
93
 
94
94
  except ValueError as exc:
95
- click.echo(f"\n❌ Invalid time: {exc}")
95
+ click.echo(
96
+ f"\n❌ Invalid time: {exc}",
97
+ err=True,
98
+ )
99
+ raise click.exceptions.Exit(1)
100
+
101
+
102
+ except BoreAuthError as exc:
103
+ click.echo(
104
+ f"\n❌ {exc}",
105
+ err=True,
106
+ )
107
+ raise click.exceptions.Exit(1)
108
+
109
+ except Exception:
110
+ # Never print the raw exception because it may contain
111
+ # the API URL, internal paths, or other implementation details.
112
+ click.echo(
113
+ "\n❌ Unable to log in. Please try again later.",
114
+ err=True,
115
+ )
116
+ raise click.exceptions.Exit(1)
117
+
118
+
96
119
 
97
- except Exception as exc:
98
- click.echo(f"\n❌ {exc}")
120
+ except BoreAuthError as exc:
121
+ click.echo(
122
+ f"\n❌ {exc}",
123
+ err=True,
124
+ )
125
+ raise click.exceptions.Exit(1)
99
126
 
127
+ except Exception:
128
+ # Never print the raw exception because it may contain
129
+ # the API URL, internal paths, or other implementation details.
130
+ click.echo(
131
+ "\n❌ Unable to log in. Please try again later.",
132
+ err=True,
133
+ )
134
+ raise click.exceptions.Exit(1)
100
135
 
101
136
 
102
137
  @cli.command()
@@ -375,4 +410,4 @@ def connect():
375
410
  )
376
411
 
377
412
  if __name__ == "__main__":
378
- cli()
413
+ cli()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: borecli
3
- Version: 1.0.0
3
+ Version: 1.4.1
4
4
  Summary: Official BoreHook CLI for securely exposing localhost through BoreHook.
5
5
  Author-email: Chinonso Egwuaka <chinonso_patrick@yahoo.com>
6
6
  License: MIT
@@ -0,0 +1,2 @@
1
+ bore
2
+ borecli-npm
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "borecli"
7
- version = "1.0.0"
7
+ version = "1.4.1"
8
8
  description = "Official BoreHook CLI for securely exposing localhost through BoreHook."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,104 +0,0 @@
1
- #borecli/bore/auth.py
2
- import requests
3
-
4
- from .config import (DEFAULT_TOKEN_LIFETIME, clear_credentials, save_credentials,)
5
-
6
- API_URL = "https://api.borehook.com"
7
-
8
-
9
-
10
- def login(
11
- email,
12
- password,
13
- lifetime=DEFAULT_TOKEN_LIFETIME,
14
- ):
15
- """
16
- Authenticate a user and save the session locally.
17
- """
18
-
19
- response = requests.post(
20
- f"{API_URL}/api/accounts/login/",
21
- json={
22
- "email": email,
23
- "password": password,
24
- },
25
- timeout=30,
26
- )
27
-
28
- response.raise_for_status()
29
-
30
- data = response.json()
31
-
32
- token = data.get("token")
33
-
34
- if not token:
35
- raise Exception(
36
- "Login succeeded but no token was returned."
37
- )
38
-
39
- save_credentials(
40
- email=email,
41
- token=token,
42
- lifetime=lifetime,
43
- )
44
-
45
- return {
46
- "email": email,
47
- "token": token,
48
- "lifetime": lifetime,
49
- }
50
-
51
-
52
- def verify_token(token):
53
- """
54
- Verify token validity against the API.
55
-
56
- Returns:
57
- True if valid.
58
- False if invalid.
59
- """
60
-
61
- try:
62
- response = requests.get(
63
- f"{API_URL}/api/accounts/me/",
64
- headers={
65
- "Authorization": (
66
- f"Token {token}"
67
- )
68
- },
69
- timeout=30,
70
- )
71
-
72
- return response.status_code == 200
73
-
74
- except Exception:
75
- return False
76
-
77
-
78
- def logout():
79
- """
80
- Remove locally stored credentials.
81
- """
82
-
83
- clear_credentials()
84
-
85
-
86
- def refresh_session(email, token):
87
- """
88
- Refresh the saved session timestamp.
89
- """
90
-
91
- save_credentials(
92
- email=email,
93
- token=token,
94
- )
95
-
96
-
97
- def get_authenticated_headers(token):
98
- """
99
- Return authorization headers for API requests.
100
- """
101
-
102
- return {
103
- "Authorization": f"Token {token}",
104
- }
@@ -1 +0,0 @@
1
- bore
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes