Skip to content

Commit 7fea52d

Browse files
authored
refactor: modularize the sdk codebase (#47)
* refactor: modularize the sdk codebase Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * fix: do not fallback to any verifier on empty provider Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> * fix: close file after read Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com> --------- Signed-off-by: Árpád Csepi <csepi.arpad@outlook.com>
1 parent 260d298 commit 7fea52d

29 files changed

Lines changed: 1309 additions & 1637 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ client = Client(config)
154154
# Client construction does not start browser login automatically.
155155
# Opens the system browser and completes PKCE on loopback:
156156
try:
157-
client.authenticate_oauth_pkce()
157+
if not client.has_cached_oauth_token():
158+
client.authenticate_oauth_pkce()
158159
except OAuthPkceError as e:
159160
print(f"Login failed: {e}")
160161
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
2+
# SPDX-License-Identifier: Apache-2.0

dir-sdk-python/agntcy/dir_sdk/client/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33

44
from agntcy.dir_sdk.client.client import Client
55
from agntcy.dir_sdk.client.config import Config
6-
from agntcy.dir_sdk.client.oauth_pkce import OAuthPkceError as OAuthPkceError
6+
from agntcy.dir_sdk.client.auth.oauth_pkce import OAuthPkceError as OAuthPkceError
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""Authentication/session helpers for the Directory client."""
5+
6+
from agntcy.dir_sdk.client.auth.oauth_pkce import OAuthTokenHolder
7+
from agntcy.dir_sdk.client.auth.session import OAuthSessionManager, cached_token_from_response
8+
from agntcy.dir_sdk.client.auth.token_cache import CachedToken, TokenCache
9+
10+
__all__ = ["CachedToken", "OAuthSessionManager", "OAuthTokenHolder", "TokenCache", "cached_token_from_response"]
File renamed without changes.
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Copyright AGNTCY Contributors (https://github.com/agntcy)
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
"""OAuth session management and token cache integration."""
5+
6+
from __future__ import annotations
7+
8+
from datetime import UTC, datetime, timedelta
9+
10+
from agntcy.dir_sdk.client.config import Config
11+
from agntcy.dir_sdk.client.auth.oauth_pkce import (
12+
OAuthTokenHolder,
13+
fetch_openid_configuration,
14+
run_loopback_pkce_login,
15+
)
16+
from agntcy.dir_sdk.client.auth.token_cache import CachedToken, TokenCache
17+
18+
19+
def cached_token_from_response(config: Config, payload: dict[str, object]) -> CachedToken:
20+
expires_at = None
21+
expires_in = payload.get("expires_in")
22+
if expires_in is not None:
23+
expires_at = datetime.now(UTC) + timedelta(seconds=int(expires_in))
24+
25+
refresh_token = payload.get("refresh_token")
26+
token_type = payload.get("token_type")
27+
return CachedToken(
28+
access_token=str(payload["access_token"]),
29+
token_type=str(token_type) if isinstance(token_type, str) else "",
30+
provider="oidc",
31+
issuer=config.oidc_issuer,
32+
refresh_token=str(refresh_token) if isinstance(refresh_token, str) else "",
33+
expires_at=expires_at,
34+
created_at=datetime.now(UTC),
35+
)
36+
37+
38+
class OAuthSessionManager:
39+
"""Coordinates OIDC token state with interactive PKCE flow and cache."""
40+
41+
def __init__(
42+
self,
43+
config: Config,
44+
token_cache: TokenCache | None = None,
45+
) -> None:
46+
self.config = config
47+
self._token_cache = token_cache or TokenCache()
48+
self._oauth_holder: OAuthTokenHolder | None = None
49+
50+
if self.config.auth_mode == "oidc":
51+
self._oauth_holder = OAuthTokenHolder()
52+
if self.config.auth_token:
53+
self._oauth_holder.set_tokens(self.config.auth_token)
54+
else:
55+
cached_token = self._token_cache.get_valid_token()
56+
if cached_token is not None:
57+
self._oauth_holder.set_tokens(cached_token.access_token)
58+
59+
@property
60+
def oauth_holder(self) -> OAuthTokenHolder | None:
61+
return self._oauth_holder
62+
63+
def has_access_token(self) -> bool:
64+
if self._oauth_holder is None:
65+
return False
66+
try:
67+
self._oauth_holder.get_access_token()
68+
return True
69+
except RuntimeError:
70+
return False
71+
72+
def authenticate(self) -> None:
73+
if self.config.auth_mode != "oidc":
74+
msg = "authenticate_oauth_pkce() requires auth_mode='oidc'"
75+
raise ValueError(msg)
76+
if not self.config.oidc_issuer:
77+
msg = "oidc_issuer is required for authenticate_oauth_pkce()"
78+
raise ValueError(msg)
79+
if not self.config.oidc_client_id:
80+
msg = "oidc_client_id is required for authenticate_oauth_pkce()"
81+
raise ValueError(msg)
82+
if self._oauth_holder is None:
83+
msg = "OAuth token holder not initialized"
84+
raise RuntimeError(msg)
85+
86+
meta = fetch_openid_configuration(
87+
self.config.oidc_issuer,
88+
verify=not self.config.tls_skip_verify,
89+
timeout=min(30.0, self.config.oidc_auth_timeout),
90+
)
91+
payload = run_loopback_pkce_login(self.config, metadata=meta)
92+
self._oauth_holder.update_from_token_response(payload)
93+
self._token_cache.save(cached_token_from_response(self.config, payload))

dir-sdk-python/agntcy/dir_sdk/client/token_cache.py renamed to dir-sdk-python/agntcy/dir_sdk/client/auth/token_cache.py

File renamed without changes.

0 commit comments

Comments
 (0)