keyrunes-python-sdk 0.3.1__tar.gz → 0.4.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.
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/CHANGELOG.md +53 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/PKG-INFO +1 -1
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/__init__.py +1 -1
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/client.py +58 -1
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/pyproject.toml +1 -2
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/LICENSE +0 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/README.md +0 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/config.py +0 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/decorators.py +0 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/exceptions.py +0 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/models.py +0 -0
- {keyrunes_python_sdk-0.3.1 → keyrunes_python_sdk-0.4.0}/keyrunes_sdk/py.typed +0 -0
|
@@ -5,6 +5,59 @@ Todas as mudanças notáveis neste projeto serão documentadas neste arquivo.
|
|
|
5
5
|
O formato é baseado em [Keep a Changelog](https://keepachangelog.com/pt-BR/1.0.0/),
|
|
6
6
|
e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `KeyrunesClient.change_password(current_password, new_password)`, wrapping
|
|
13
|
+
`POST /api/user/change-password`. The server has served this endpoint all
|
|
14
|
+
along; the SDK exposed `login`, `refresh_token`, `register_user` and the group
|
|
15
|
+
queries, and not this one. Anyone who needed it built the HTTP call by hand —
|
|
16
|
+
which is what happened downstream, after first concluding from the SDK's
|
|
17
|
+
surface that the operation did not exist at all.
|
|
18
|
+
|
|
19
|
+
Three details the endpoint carries, and each one bites if read wrong:
|
|
20
|
+
|
|
21
|
+
- it lives at `/api/user/change-password`, **not** `/api/change-password` —
|
|
22
|
+
the router registers the first, and the handler behind the second is dead
|
|
23
|
+
code marked `#[allow(dead_code)]`, so pointing at it answers 404;
|
|
24
|
+
- it answers errors as **plain text** (`e.to_string()`), not JSON, and with
|
|
25
|
+
status **400** rather than 401 — so this client raises `NetworkError`, the
|
|
26
|
+
same class it already uses for every non-401/403/404 status, and preserves
|
|
27
|
+
the server's own wording, because "invalid current password" and "password
|
|
28
|
+
too short" call for opposite fixes;
|
|
29
|
+
- changing the password clears the account's `first_login` flag server-side,
|
|
30
|
+
which is what `requires_password_change` reports at login. There is no
|
|
31
|
+
second call to make.
|
|
32
|
+
|
|
33
|
+
Without a token the client refuses before sending: the server decides whose
|
|
34
|
+
password it is from the token, and nothing in the body chooses that.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- A malformed `groups` field in a server response no longer crashes the client.
|
|
39
|
+
`get_current_user` and `check_user` called `list(data["groups"])` on whatever
|
|
40
|
+
came back; a JSON `true`, a number, or `null` raised `TypeError: 'bool' object
|
|
41
|
+
is not iterable` out of the SDK, from a line that reads like a safe coercion.
|
|
42
|
+
Anything that is not a JSON array is now read as no groups. Found by the fuzz
|
|
43
|
+
suite, and it predates this release — `git stash` confirmed it on the previous
|
|
44
|
+
tag.
|
|
45
|
+
|
|
46
|
+
### Security
|
|
47
|
+
|
|
48
|
+
- `pyjwt` 2.10.1 → 2.14.0 and `idna` 3.11 → 3.19 in the lockfile. Both are
|
|
49
|
+
runtime dependencies (`idna` arrives through `httpx`), and OSV reports known
|
|
50
|
+
advisories against the pinned versions — nine for `pyjwt`, four of them high.
|
|
51
|
+
The declared constraint was already `^2.9.0`, so this is a lockfile refresh
|
|
52
|
+
with no API change; the 194 tests pass unchanged.
|
|
53
|
+
|
|
54
|
+
### Removed
|
|
55
|
+
|
|
56
|
+
- `towncrier` left the dev dependencies. It was declared and never configured:
|
|
57
|
+
no `[tool.towncrier]` section, no `newsfragments/` directory, and this
|
|
58
|
+
changelog was written by hand. A tool nobody runs still gets installed,
|
|
59
|
+
resolved, and audited on every environment build.
|
|
60
|
+
|
|
8
61
|
## [0.3.1] - 2026-09-04
|
|
9
62
|
|
|
10
63
|
### Fixed
|
|
@@ -35,6 +35,13 @@ ENDPOINT_ME = "/api/me"
|
|
|
35
35
|
#: Exchanges a still-valid token for a fresh one.
|
|
36
36
|
ENDPOINT_REFRESH_TOKEN = "/api/refresh-token"
|
|
37
37
|
|
|
38
|
+
#: Changes the password of whoever the bearer token identifies.
|
|
39
|
+
#:
|
|
40
|
+
#: ``/api/user/change-password``, and not ``/api/change-password``: the router
|
|
41
|
+
#: registers the first, and the handler behind the second is dead code marked
|
|
42
|
+
#: ``#[allow(dead_code)]``. Pointing at it answers 404.
|
|
43
|
+
ENDPOINT_CHANGE_PASSWORD = "/api/user/change-password"
|
|
44
|
+
|
|
38
45
|
|
|
39
46
|
class KeyrunesClient:
|
|
40
47
|
"""
|
|
@@ -175,7 +182,14 @@ class KeyrunesClient:
|
|
|
175
182
|
)
|
|
176
183
|
normalized["username"] = data.get("username", "")
|
|
177
184
|
normalized["email"] = data.get("email", "")
|
|
178
|
-
|
|
185
|
+
# `or []` trata nulo e vazio, e **não** trata o resto: com
|
|
186
|
+
# ``{"groups": true}`` o valor sobrevivia como ``True``, e o
|
|
187
|
+
# ``"admins" in groups`` logo abaixo estourava ``TypeError`` — o cliente
|
|
188
|
+
# caía em vez de conter uma resposta malformada. Achado pelo fuzzer.
|
|
189
|
+
raw_groups = data.get("groups")
|
|
190
|
+
normalized["groups"] = (
|
|
191
|
+
list(raw_groups) if isinstance(raw_groups, list) else []
|
|
192
|
+
)
|
|
179
193
|
normalized["attributes"] = data.get("attributes", {})
|
|
180
194
|
normalized["is_active"] = data.get("is_active", True)
|
|
181
195
|
groups = normalized["groups"]
|
|
@@ -627,6 +641,49 @@ class KeyrunesClient:
|
|
|
627
641
|
self.set_token(refreshed.access_token)
|
|
628
642
|
return refreshed
|
|
629
643
|
|
|
644
|
+
def change_password(self, current_password: str, new_password: str) -> None:
|
|
645
|
+
"""
|
|
646
|
+
Change the password of whoever the current token identifies.
|
|
647
|
+
|
|
648
|
+
The server also clears the account's ``first_login`` flag, which is
|
|
649
|
+
what ``requires_password_change`` reports at login: changing the
|
|
650
|
+
password here puts that warning away without a second call.
|
|
651
|
+
|
|
652
|
+
Nothing in the body says whose password it is — the token does. A body
|
|
653
|
+
that chose would make "change my password" mean "change anyone's".
|
|
654
|
+
|
|
655
|
+
Args:
|
|
656
|
+
current_password: The password being replaced. The server checks
|
|
657
|
+
it before accepting the change.
|
|
658
|
+
new_password: The new password. The server requires at least 8
|
|
659
|
+
characters and answers 400 when it is shorter.
|
|
660
|
+
|
|
661
|
+
Raises:
|
|
662
|
+
InvalidTokenError: If the client has no token set
|
|
663
|
+
NetworkError: If the server refuses — wrong current password, or a
|
|
664
|
+
new password it will not accept. The server answers **400** for
|
|
665
|
+
both, which this client maps to ``NetworkError`` like any
|
|
666
|
+
other non-401/403/404 status. The server's wording survives,
|
|
667
|
+
because "invalid current password" and "password too short" call
|
|
668
|
+
for opposite fixes.
|
|
669
|
+
|
|
670
|
+
Example:
|
|
671
|
+
>>> client = KeyrunesClient("https://keyrunes.example.com")
|
|
672
|
+
>>> client.set_token("eyJhbGciOiJIUzI1NiIs...")
|
|
673
|
+
>>> client.change_password("old-secret", "a-longer-new-secret")
|
|
674
|
+
"""
|
|
675
|
+
if not self._token:
|
|
676
|
+
raise InvalidTokenError("No token available to change a password.")
|
|
677
|
+
|
|
678
|
+
self._make_request(
|
|
679
|
+
"POST",
|
|
680
|
+
ENDPOINT_CHANGE_PASSWORD,
|
|
681
|
+
data={
|
|
682
|
+
"current_password": current_password,
|
|
683
|
+
"new_password": new_password,
|
|
684
|
+
},
|
|
685
|
+
)
|
|
686
|
+
|
|
630
687
|
def clear_token(self) -> None:
|
|
631
688
|
"""
|
|
632
689
|
Clear authentication token.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "keyrunes-python-sdk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "Python SDK for Keyrunes Authorization System"
|
|
5
5
|
authors = ["keyrunes <contact@singularjourney.host>"]
|
|
6
6
|
maintainers = ["jonatasoli <contact@jonatasoli.dev>"]
|
|
@@ -47,7 +47,6 @@ isort = "^5.12.0"
|
|
|
47
47
|
flake8 = "^6.1.0"
|
|
48
48
|
mypy = "^1.5.0"
|
|
49
49
|
taskipy = "^1.12.0"
|
|
50
|
-
towncrier = "^25.8.0"
|
|
51
50
|
pre-commit = "^4.5.1"
|
|
52
51
|
safety = "^3.7.0"
|
|
53
52
|
hypothesis = "^6.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|