Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 7 additions & 5 deletions githubkit/auth/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from githubkit.utils import UNSET, Unset, exclude_unset

from ._url import require_app_auth, require_basic_auth, require_bypass
from .base import BaseAuthStrategy
from .base import BaseAuthStrategy, build_request_with_options
from .oauth import OAuthAppAuthStrategy

try:
Expand Down Expand Up @@ -104,7 +104,7 @@ async def aget_jwt(self) -> str:
await cache.aset(cache_key, token, timedelta(minutes=8))
return token

def _build_installation_auth_request(self) -> httpx.Request:
def _build_installation_auth_request(self, request: httpx.Request) -> httpx.Request:
from githubkit_schemas.latest.models import (
AppInstallationsInstallationIdAccessTokensPostBody,
)
Expand All @@ -130,9 +130,11 @@ def _build_installation_auth_request(self) -> httpx.Request:
},
)
)
return httpx.Request(
return build_request_with_options(
self.github,
"POST",
url,
request=request,
json=exclude_unset(body),
headers={
"User-Agent": self.github.config.user_agent,
Expand Down Expand Up @@ -207,7 +209,7 @@ def sync_auth_flow(
cache = self.github.config.cache_strategy.get_cache_storage()
key = self._get_installation_cache_key()
if not (token := cache.get(key)):
token_request = self._build_installation_auth_request()
token_request = self._build_installation_auth_request(request)
token_request.headers["Authorization"] = f"Bearer {self.get_jwt()}"
response = yield token_request
response.read()
Expand Down Expand Up @@ -249,7 +251,7 @@ async def async_auth_flow(
cache = await self.github.config.cache_strategy.get_async_cache_storage()
key = self._get_installation_cache_key()
if not (token := await cache.aget(key)):
token_request = self._build_installation_auth_request()
token_request = self._build_installation_auth_request(request)
token_request.headers["Authorization"] = f"Bearer {await self.aget_jwt()}"
response = yield token_request
await response.aread()
Expand Down
22 changes: 21 additions & 1 deletion githubkit/auth/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import abc
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any

import httpx

Expand All @@ -11,3 +11,23 @@ class BaseAuthStrategy(abc.ABC):
@abc.abstractmethod
def get_auth_flow(self, github: "GitHubCore") -> httpx.Auth:
raise NotImplementedError


def build_request_with_options(
github: "GitHubCore",
method: str,
url: httpx.URL,
*,
request: httpx.Request | None = None,
**kwargs: Any,
) -> httpx.Request:
"""Build a request used inside auth flow, keeping the client timeout.

httpx only sets the timeout extension on the request passed to `send`,
not on requests yielded by an auth flow, so copy it from the original
request, or fall back to the client config.
"""
timeout = (
request.extensions.get("timeout") if request is not None else None
) or github.config.timeout.as_dict()
return httpx.Request(method, url, extensions={"timeout": timeout}, **kwargs)
41 changes: 30 additions & 11 deletions githubkit/auth/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from githubkit.utils import is_async

from ._url import get_oauth_base_url, require_basic_auth, require_bypass
from .base import BaseAuthStrategy
from .base import BaseAuthStrategy, build_request_with_options

try:
import anyio
Expand All @@ -29,17 +29,22 @@


def create_device_code(
github: "GitHubCore", client_id: str, scopes: Sequence[str] | None = None
github: "GitHubCore",
client_id: str,
scopes: Sequence[str] | None = None,
request: httpx.Request | None = None,
) -> Generator[httpx.Request, httpx.Response, dict[str, Any]]:
"""Create a device code for OAuth."""
base_url = get_oauth_base_url(github.config.base_url)
url = base_url.copy_with(raw_path=base_url.raw_path + b"login/device/code")
body = {"client_id": client_id}
if scopes:
body["scope"] = " ".join(scopes)
response = yield httpx.Request(
response = yield build_request_with_options(
github,
"POST",
url,
request=request,
json=body,
headers={
"User-Agent": github.config.user_agent,
Expand All @@ -56,6 +61,7 @@ def exchange_web_flow_code(
client_secret: str,
code: str,
redirect_uri: str | None = None,
request: httpx.Request | None = None,
) -> Generator[httpx.Request, httpx.Response, dict[str, Any]]:
"""Exchange web flow code for token."""
base_url = get_oauth_base_url(github.config.base_url)
Expand All @@ -67,9 +73,11 @@ def exchange_web_flow_code(
}
if redirect_uri:
body["redirect_uri"] = redirect_uri
response = yield httpx.Request(
response = yield build_request_with_options(
github,
"POST",
url,
request=request,
json=body,
headers={
"User-Agent": github.config.user_agent,
Expand All @@ -81,14 +89,19 @@ def exchange_web_flow_code(


def exchange_device_code(
github: "GitHubCore", client_id: str, device_code: str
github: "GitHubCore",
client_id: str,
device_code: str,
request: httpx.Request | None = None,
) -> Generator[httpx.Request, httpx.Response, dict[str, Any]]:
"""Exchange device code for token."""
base_url = get_oauth_base_url(github.config.base_url)
url = base_url.copy_with(raw_path=base_url.raw_path + b"login/oauth/access_token")
response = yield httpx.Request(
response = yield build_request_with_options(
github,
"POST",
url,
request=request,
json={
"client_id": client_id,
"grant_type": "urn:ietf:params:oauth:grant-type:device_code",
Expand All @@ -108,13 +121,16 @@ def refresh_token(
client_id: str,
client_secret: str | None, # client secret is optional in device flow
refresh_token: str,
request: httpx.Request | None = None,
) -> Generator[httpx.Request, httpx.Response, dict[str, Any]]:
"""Refresh token."""
base_url = get_oauth_base_url(github.config.base_url)
url = base_url.copy_with(raw_path=base_url.raw_path + b"login/oauth/access_token")
response = yield httpx.Request(
response = yield build_request_with_options(
github,
"POST",
url,
request=request,
json={
"client_id": client_id,
"client_secret": client_secret,
Expand Down Expand Up @@ -287,6 +303,7 @@ def auth_flow(
self.client_id,
self.client_secret,
cast(str, self.refresh_token),
request,
)
result = _parse_token_exchange_response(data)
self.token = token = result["token"]
Expand Down Expand Up @@ -343,6 +360,7 @@ def sync_auth_flow(
self.client_secret,
self.code,
self.redirect_uri,
request,
)
exchange_request = next(flow)
while True:
Expand Down Expand Up @@ -374,6 +392,7 @@ async def async_auth_flow(
self.client_secret,
self.code,
self.redirect_uri,
request,
)
exchange_request = next(flow)
while True:
Expand Down Expand Up @@ -442,7 +461,7 @@ def sync_auth_flow(
# exchange token for the first time
if (auth_strategy := self._token_auth_strategy) is None:
# create device code
flow = create_device_code(self.github, self.client_id, self.scopes)
flow = create_device_code(self.github, self.client_id, self.scopes, request)
create_request = next(flow)
while True:
response = yield create_request
Expand All @@ -462,7 +481,7 @@ def sync_auth_flow(
if datetime.now(timezone.utc) > result["expire_time"]:
raise AuthExpiredError("Device code expired.")
flow = exchange_device_code(
self.github, self.client_id, result["device_code"]
self.github, self.client_id, result["device_code"], request
)
auth_request = next(flow)
while True:
Expand Down Expand Up @@ -503,7 +522,7 @@ async def async_auth_flow(
# exchange token for the first time
if (auth_strategy := self._token_auth_strategy) is None:
# create device code
flow = create_device_code(self.github, self.client_id, self.scopes)
flow = create_device_code(self.github, self.client_id, self.scopes, request)
create_request = next(flow)
while True:
response = yield create_request
Expand All @@ -523,7 +542,7 @@ async def async_auth_flow(
if datetime.now(timezone.utc) > result["expire_time"]:
raise AuthExpiredError("Device code expired.")
flow = exchange_device_code(
self.github, self.client_id, result["device_code"]
self.github, self.client_id, result["device_code"], request
)
auth_request = next(flow)
while True:
Expand Down
Loading