kscale 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
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.
- kscale/__init__.py +1 -1
- kscale/web/clients/base.py +94 -36
- kscale/web/clients/robot_class.py +5 -1
- {kscale-0.1.2.dist-info → kscale-0.1.3.dist-info}/METADATA +1 -1
- {kscale-0.1.2.dist-info → kscale-0.1.3.dist-info}/RECORD +9 -9
- {kscale-0.1.2.dist-info → kscale-0.1.3.dist-info}/LICENSE +0 -0
- {kscale-0.1.2.dist-info → kscale-0.1.3.dist-info}/WHEEL +0 -0
- {kscale-0.1.2.dist-info → kscale-0.1.3.dist-info}/entry_points.txt +0 -0
- {kscale-0.1.2.dist-info → kscale-0.1.3.dist-info}/top_level.txt +0 -0
kscale/__init__.py
CHANGED
kscale/web/clients/base.py
CHANGED
@@ -29,14 +29,22 @@ OAUTH_PORT = 16821
|
|
29
29
|
|
30
30
|
class OAuthCallback:
|
31
31
|
def __init__(self) -> None:
|
32
|
+
self.token_type: str | None = None
|
32
33
|
self.access_token: str | None = None
|
34
|
+
self.id_token: str | None = None
|
35
|
+
self.state: str | None = None
|
36
|
+
self.expires_in: str | None = None
|
33
37
|
self.app = web.Application()
|
34
38
|
self.app.router.add_get("/token", self.handle_token)
|
35
39
|
self.app.router.add_get("/callback", self.handle_callback)
|
36
40
|
|
37
41
|
async def handle_token(self, request: web.Request) -> web.Response:
|
38
42
|
"""Handle the token extraction."""
|
43
|
+
self.token_type = request.query.get("token_type")
|
39
44
|
self.access_token = request.query.get("access_token")
|
45
|
+
self.id_token = request.query.get("id_token")
|
46
|
+
self.state = request.query.get("state")
|
47
|
+
self.expires_in = request.query.get("expires_in")
|
40
48
|
return web.Response(text="OK")
|
41
49
|
|
42
50
|
async def handle_callback(self, request: web.Request) -> web.Response:
|
@@ -45,62 +53,92 @@ class OAuthCallback:
|
|
45
53
|
text="""
|
46
54
|
<!DOCTYPE html>
|
47
55
|
<html lang="en">
|
48
|
-
|
49
56
|
<head>
|
50
57
|
<meta charset="UTF-8">
|
51
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
52
58
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
53
59
|
<title>Authentication successful</title>
|
54
60
|
<style>
|
55
61
|
body {
|
62
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
56
63
|
display: flex;
|
57
64
|
justify-content: center;
|
58
65
|
align-items: center;
|
59
66
|
min-height: 100vh;
|
60
67
|
margin: 0;
|
61
|
-
|
68
|
+
background: #f5f5f5;
|
69
|
+
color: #333;
|
70
|
+
}
|
71
|
+
.container {
|
72
|
+
background: white;
|
73
|
+
padding: 2rem;
|
74
|
+
border-radius: 8px;
|
75
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
76
|
+
max-width: 600px;
|
77
|
+
width: 90%;
|
78
|
+
}
|
79
|
+
h1 {
|
80
|
+
color: #2c3e50;
|
81
|
+
margin-bottom: 1rem;
|
62
82
|
}
|
63
|
-
|
64
|
-
|
83
|
+
.token-info {
|
84
|
+
background: #f8f9fa;
|
85
|
+
border: 1px solid #dee2e6;
|
86
|
+
border-radius: 4px;
|
87
|
+
padding: 1rem;
|
88
|
+
margin: 1rem 0;
|
89
|
+
word-break: break-all;
|
65
90
|
}
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
margin-
|
70
|
-
|
71
|
-
|
72
|
-
|
91
|
+
.token-label {
|
92
|
+
font-weight: bold;
|
93
|
+
color: #6c757d;
|
94
|
+
margin-bottom: 0.5rem;
|
95
|
+
}
|
96
|
+
.success-icon {
|
97
|
+
color: #28a745;
|
98
|
+
font-size: 48px;
|
99
|
+
margin-bottom: 1rem;
|
73
100
|
}
|
74
101
|
</style>
|
75
102
|
</head>
|
76
|
-
|
77
103
|
<body>
|
78
|
-
<div
|
104
|
+
<div class="container">
|
105
|
+
<div class="success-icon">✓</div>
|
79
106
|
<h1>Authentication successful!</h1>
|
80
|
-
<p>
|
81
|
-
|
107
|
+
<p>Your authentication tokens are shown below. You can now close this window.</p>
|
108
|
+
|
109
|
+
<div class="token-info">
|
110
|
+
<div class="token-label">Access Token:</div>
|
111
|
+
<div id="accessTokenDisplay"></div>
|
112
|
+
</div>
|
113
|
+
|
114
|
+
<div class="token-info">
|
115
|
+
<div class="token-label">ID Token:</div>
|
116
|
+
<div id="idTokenDisplay"></div>
|
117
|
+
</div>
|
82
118
|
</div>
|
119
|
+
|
83
120
|
<script>
|
84
121
|
const params = new URLSearchParams(window.location.hash.substring(1));
|
85
|
-
const
|
86
|
-
|
87
|
-
|
122
|
+
const tokenType = params.get('token_type');
|
123
|
+
const accessToken = params.get('access_token');
|
124
|
+
const idToken = params.get('id_token');
|
125
|
+
const state = params.get('state');
|
126
|
+
const expiresIn = params.get('expires_in');
|
127
|
+
|
128
|
+
// Display tokens
|
129
|
+
document.getElementById('accessTokenDisplay').textContent = accessToken || 'Not provided';
|
130
|
+
document.getElementById('idTokenDisplay').textContent = idToken || 'Not provided';
|
131
|
+
|
132
|
+
if (accessToken) {
|
133
|
+
const tokenUrl = new URL(window.location.href);
|
134
|
+
tokenUrl.pathname = '/token';
|
135
|
+
tokenUrl.searchParams.set('access_token', accessToken);
|
136
|
+
tokenUrl.searchParams.set('token_type', tokenType);
|
137
|
+
tokenUrl.searchParams.set('id_token', idToken);
|
138
|
+
tokenUrl.searchParams.set('state', state);
|
139
|
+
tokenUrl.searchParams.set('expires_in', expiresIn);
|
140
|
+
fetch(tokenUrl.toString());
|
88
141
|
}
|
89
|
-
|
90
|
-
let timeLeft = 3;
|
91
|
-
const countdownElement = document.getElementById('countdown');
|
92
|
-
const closeNotification = document.getElementById('closeNotification');
|
93
|
-
const timer = setInterval(() => {
|
94
|
-
timeLeft--;
|
95
|
-
countdownElement.textContent = timeLeft;
|
96
|
-
if (timeLeft <= 0) {
|
97
|
-
clearInterval(timer);
|
98
|
-
window.close();
|
99
|
-
setTimeout(() => {
|
100
|
-
closeNotification.style.display = 'block';
|
101
|
-
}, 500);
|
102
|
-
}
|
103
|
-
}, 1000);
|
104
142
|
</script>
|
105
143
|
</body>
|
106
144
|
</html>
|
@@ -167,8 +205,23 @@ class BaseClient:
|
|
167
205
|
oicd_info = await self._get_oicd_info()
|
168
206
|
metadata = await self._get_oicd_metadata()
|
169
207
|
auth_endpoint = metadata["authorization_endpoint"]
|
170
|
-
|
171
|
-
nonce
|
208
|
+
|
209
|
+
# Use the cached state and nonce if available, otherwise generate.
|
210
|
+
state_file = get_cache_dir() / "oauth_state.json"
|
211
|
+
state: str | None = None
|
212
|
+
nonce: str | None = None
|
213
|
+
if state_file.exists():
|
214
|
+
with open(state_file, "r") as f:
|
215
|
+
state_data = json.load(f)
|
216
|
+
state = state_data.get("state")
|
217
|
+
nonce = state_data.get("nonce")
|
218
|
+
if state is None:
|
219
|
+
state = secrets.token_urlsafe(32)
|
220
|
+
if nonce is None:
|
221
|
+
nonce = secrets.token_urlsafe(32)
|
222
|
+
|
223
|
+
# Change /oauth2/authorize to /login to use the login endpoint.
|
224
|
+
auth_endpoint = auth_endpoint.replace("/oauth2/authorize", "/login")
|
172
225
|
|
173
226
|
auth_url = str(
|
174
227
|
URL(auth_endpoint).with_query(
|
@@ -208,6 +261,11 @@ class BaseClient:
|
|
208
261
|
raise TimeoutError("Authentication timed out after 30 seconds")
|
209
262
|
await asyncio.sleep(0.1)
|
210
263
|
|
264
|
+
# Save the state and nonce to the cache.
|
265
|
+
state = callback_handler.state
|
266
|
+
state_file.parent.mkdir(parents=True, exist_ok=True)
|
267
|
+
state_file.write_text(json.dumps({"state": state, "nonce": nonce}))
|
268
|
+
|
211
269
|
return callback_handler.access_token
|
212
270
|
finally:
|
213
271
|
await runner.cleanup()
|
@@ -8,7 +8,11 @@ from pathlib import Path
|
|
8
8
|
import httpx
|
9
9
|
|
10
10
|
from kscale.web.clients.base import BaseClient
|
11
|
-
from kscale.web.gen.api import
|
11
|
+
from kscale.web.gen.api import (
|
12
|
+
RobotClass,
|
13
|
+
RobotDownloadURDFResponse,
|
14
|
+
RobotUploadURDFResponse,
|
15
|
+
)
|
12
16
|
from kscale.web.utils import get_cache_dir, should_refresh_file
|
13
17
|
|
14
18
|
logger = logging.getLogger(__name__)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
kscale/__init__.py,sha256=
|
1
|
+
kscale/__init__.py,sha256=jCPBaF41wGta1VKhadMOzjzqpwaKVoU5lWY21BAvCZY,172
|
2
2
|
kscale/api.py,sha256=jmiuFurTN_Gj_-k-6asqxw8wp-_bgJUXgMPFgJ4lqHA,230
|
3
3
|
kscale/cli.py,sha256=PMHLKR5UwdbbReVmqHXpJ-K9-mGHv_0I7KQkwxmFcUA,881
|
4
4
|
kscale/conf.py,sha256=x9jJ8pmSweUsLScN741B21SySCrnRZioV-1ZkhmERbI,1585
|
@@ -21,16 +21,16 @@ kscale/web/cli/robot_class.py,sha256=ymC5phUqofvOXv5P6f51b9lMK5eRDaavvnzS0x9rDbU
|
|
21
21
|
kscale/web/cli/token.py,sha256=1rFC8MYKtqbNsQa2KIqwW1tqpaMtFaxuNsallwejXTU,787
|
22
22
|
kscale/web/cli/user.py,sha256=qO0z2K5uA48hEiOOYEzv6BO2nOlCpITTDZFuiNl6d34,817
|
23
23
|
kscale/web/clients/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
24
|
-
kscale/web/clients/base.py,sha256=
|
24
|
+
kscale/web/clients/base.py,sha256=voOgOGlrYy-fyFJiLVNVwL4osOo53-ARBsqdhBp4BWA,14263
|
25
25
|
kscale/web/clients/client.py,sha256=QjBicdHQYNoUG9XRjAYmGu3THae9DzWa_hQox3OO1Gw,214
|
26
26
|
kscale/web/clients/robot.py,sha256=HMfJnkDxaJ_o7X2vdYYS9iob1JRoBG2qiGmQpCQZpAk,1485
|
27
|
-
kscale/web/clients/robot_class.py,sha256=
|
27
|
+
kscale/web/clients/robot_class.py,sha256=yP-GBToyTyNc9eZzsoVmQuNC98aQHmgScxwsxRiM2v8,5067
|
28
28
|
kscale/web/clients/user.py,sha256=9iv8J-ROm_yBIwi-0oqldReLkNBFktdHRv3UCOxBzjY,377
|
29
29
|
kscale/web/gen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
30
|
kscale/web/gen/api.py,sha256=SovcII36JFgK9jd2CXlLPMjiUROGB4vEnapOsYMUrkU,2188
|
31
|
-
kscale-0.1.
|
32
|
-
kscale-0.1.
|
33
|
-
kscale-0.1.
|
34
|
-
kscale-0.1.
|
35
|
-
kscale-0.1.
|
36
|
-
kscale-0.1.
|
31
|
+
kscale-0.1.3.dist-info/LICENSE,sha256=HCN2bImAzUOXldAZZI7JZ9PYq6OwMlDAP_PpX1HnuN0,1071
|
32
|
+
kscale-0.1.3.dist-info/METADATA,sha256=Aw4Q4bvhclIV1tG_1a8Gk-ZylIPe84vUUtIkQLXBa68,2340
|
33
|
+
kscale-0.1.3.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
34
|
+
kscale-0.1.3.dist-info/entry_points.txt,sha256=N_0pCpPnwGDYVzOeuaSOrbJkS5L3lS9d8CxpJF1f8UI,62
|
35
|
+
kscale-0.1.3.dist-info/top_level.txt,sha256=C2ynjYwopg6YjgttnI2dJjasyq3EKNmYp-IfQg9Xms4,7
|
36
|
+
kscale-0.1.3.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|