Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pyoverkiz/auth/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ def is_expired(self, *, skew_seconds: int = 5) -> bool:
if not self.expires_at:
return False

return datetime.datetime.now() >= self.expires_at - datetime.timedelta(
seconds=skew_seconds
)
return datetime.datetime.now(
datetime.UTC
) >= self.expires_at - datetime.timedelta(seconds=skew_seconds)


class AuthStrategy(Protocol):
Expand Down
28 changes: 22 additions & 6 deletions pyoverkiz/auth/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ async def _post_login(self, data: Mapping[str, Any]) -> None:
f"Login failed for {self.server.name}: {response.status}"
)

# A 204 No Content response cannot have a body, so skip JSON parsing.
if response.status == 204:
return

result = await response.json()
if not result.get("success"):
raise BadCredentialsException("Login failed: bad credentials")
Expand Down Expand Up @@ -200,9 +204,9 @@ async def _request_access_token(
self.context.refresh_token = token.get("refresh_token")
expires_in = token.get("expires_in")
if expires_in:
self.context.expires_at = datetime.datetime.now() + datetime.timedelta(
seconds=cast(int, expires_in) - 5
)
self.context.expires_at = datetime.datetime.now(
datetime.UTC
) + datetime.timedelta(seconds=cast(int, expires_in) - 5)


class CozytouchAuthStrategy(SessionLoginStrategy):
Expand Down Expand Up @@ -394,6 +398,18 @@ async def _exchange_token(self, payload: Mapping[str, str]) -> None:
) as response:
token = await response.json()

# Handle OAuth error responses explicitly before accessing the access token.
error = token.get("error")
if error:
description = token.get("error_description") or token.get("message")
if description:
raise InvalidTokenException(
f"Error retrieving Rexel access token: {description}"
)
raise InvalidTokenException(
f"Error retrieving Rexel access token: {error}"
)

access_token = token.get("access_token")
if not access_token:
raise InvalidTokenException("No Rexel access token provided.")
Expand All @@ -403,9 +419,9 @@ async def _exchange_token(self, payload: Mapping[str, str]) -> None:
self.context.refresh_token = token.get("refresh_token")
expires_in = token.get("expires_in")
if expires_in:
self.context.expires_at = datetime.datetime.now() + datetime.timedelta(
seconds=cast(int, expires_in) - 5
)
self.context.expires_at = datetime.datetime.now(
datetime.UTC
) + datetime.timedelta(seconds=cast(int, expires_in) - 5)

@staticmethod
def _ensure_consent(access_token: str) -> None:
Expand Down
3 changes: 2 additions & 1 deletion pyoverkiz/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,8 @@ def __init__(
if self.server_config.type == APIType.LOCAL and verify_ssl:
# To avoid security issues while authentication to local API, we add the following authority to
# our HTTPS client trust store: https://ca.overkiz.com/overkiz-root-ca-2048.crt
self._ssl = SSL_CONTEXT_LOCAL_API
# Create a new SSL context to avoid mutating the shared global context
self._ssl = _create_local_ssl_context()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be a blocking call that should not happen here during runtime, that is the reason why it is moved out of this. can you revert this change?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot This will be a blocking call that should not happen here during runtime, that is the reason why it is moved out of this. can you revert this change?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted in commit 33afeeb. The SSL context is now created from the shared SSL_CONTEXT_LOCAL_API again to avoid blocking I/O during runtime.


# Disable strict validation introduced in Python 3.13, which doesn't
# work with Overkiz self-signed gateway certificates
Expand Down
9 changes: 5 additions & 4 deletions pyoverkiz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,16 @@ def create_server_config(
configuration_url: str | None = None,
) -> ServerConfig:
"""Generate server configuration with the provided endpoint and metadata."""
# ServerConfig.__init__ handles the enum conversion, but mypy doesn't recognize
# this due to attrs @define decorator generating __init__ with stricter signatures,
# so we need type: ignore comments.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a #todo fix here

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Add a #todo fix here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added TODO fix comment in commit 33afeeb.

return ServerConfig(
server=server
if isinstance(server, Server) or server is None
else Server(server),
server=server, # type: ignore[arg-type]
name=name,
endpoint=endpoint,
manufacturer=manufacturer,
configuration_url=configuration_url,
type=type if isinstance(type, APIType) else APIType(type),
type=type, # type: ignore[arg-type]
)


Expand Down
Loading