Skip to content

Commit 45fc31c

Browse files
committed
server/integrations/github: cleanup GitHub OAuth login
1 parent c81d8c3 commit 45fc31c

File tree

5 files changed

+58
-396
lines changed

5 files changed

+58
-396
lines changed
Lines changed: 0 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,15 @@
1-
import time
21
from enum import StrEnum
32
from typing import Any
43

5-
import httpx
6-
import structlog
74
from githubkit import (
85
AppAuthStrategy,
96
AppInstallationAuthStrategy,
107
GitHub,
118
Response,
129
TokenAuthStrategy,
13-
utils,
14-
webhooks,
1510
)
16-
from githubkit.typing import Missing
17-
from pydantic import BaseModel, Field
1811

1912
from polar.config import settings
20-
from polar.locker import Locker
21-
from polar.models.user import OAuthAccount, OAuthPlatform, User
22-
from polar.postgres import AsyncSession
23-
from polar.user.oauth_service import oauth_account_service
24-
25-
log = structlog.get_logger()
2613

2714

2815
class UnexpectedStatusCode(Exception): ...
@@ -72,123 +59,6 @@ def ensure_expected_response(
7259
###############################################################################
7360

7461

75-
class RefreshAccessToken(BaseModel):
76-
access_token: str = Field(default=...)
77-
# The number of seconds until access_token expires (will always be 28800)
78-
expires_in: int = Field(default=...)
79-
# A new refresh token (is only set if the app is using expiring refresh tokens)
80-
refresh_token: str | None = Field(default=...)
81-
# The value will always be 15897600 (6 months) unless token expiration is disabled
82-
refresh_token_expires_in: int | None = Field(default=...)
83-
# Always an empty string
84-
scope: str = Field(default=...)
85-
# Always "bearer"
86-
token_type: str = Field(default=...)
87-
88-
89-
async def get_user_client(
90-
session: AsyncSession, locker: Locker, user: User
91-
) -> GitHub[TokenAuthStrategy]:
92-
oauth = await oauth_account_service.get_by_platform_and_user_id(
93-
session, OAuthPlatform.github, user.id
94-
)
95-
if not oauth:
96-
raise Exception("no github oauth account found")
97-
98-
return await get_refreshed_oauth_client(session, locker, oauth)
99-
100-
101-
async def refresh_oauth_account(
102-
session: AsyncSession, locker: Locker, oauth: OAuthAccount
103-
) -> OAuthAccount:
104-
if oauth.platform != OAuthPlatform.github:
105-
raise Exception("unexpected platform")
106-
107-
if not oauth.should_refresh_access_token():
108-
return oauth
109-
110-
async with locker.lock(
111-
f"oauth_refresh:{oauth.id}",
112-
timeout=10.0,
113-
blocking_timeout=10.0,
114-
):
115-
# first, reload from DB, a concurrent process might have already refreshed this token
116-
# (and used the refresh token).
117-
oauth_db = await oauth_account_service.get(session, oauth.id)
118-
119-
if not oauth_db:
120-
raise Exception("oauth account not found")
121-
122-
# token is already refreshed
123-
if not oauth_db.should_refresh_access_token():
124-
return oauth_db
125-
126-
# refresh token
127-
async with httpx.AsyncClient() as http_client:
128-
response = await http_client.post(
129-
"https://github.com/login/oauth/access_token",
130-
params={
131-
"client_id": settings.GITHUB_CLIENT_ID,
132-
"client_secret": settings.GITHUB_CLIENT_SECRET,
133-
"refresh_token": oauth.refresh_token,
134-
"grant_type": "refresh_token",
135-
},
136-
headers={"Accept": "application/json"},
137-
)
138-
if response.status_code != 200:
139-
log.error(
140-
"github.auth.refresh.error",
141-
user_id=oauth_db.user_id,
142-
oauth_id=oauth_db.id,
143-
http_code=response.status_code,
144-
)
145-
return oauth_db
146-
147-
data = response.json()
148-
# GitHub returns 200 in case of errors, but with an error payload
149-
error = data.get("error", None)
150-
if error:
151-
log.error(
152-
"github.auth.refresh.error",
153-
user_id=oauth_db.user_id,
154-
oauth_id=oauth_db.id,
155-
http_code=response.status_code,
156-
error=error,
157-
error_description=data.get("error_description", None),
158-
)
159-
return oauth_db
160-
161-
refreshed = RefreshAccessToken.model_validate(data)
162-
163-
# update
164-
epoch_now = int(time.time())
165-
oauth_db.access_token = refreshed.access_token
166-
oauth_db.expires_at = epoch_now + refreshed.expires_in
167-
if refreshed.refresh_token:
168-
oauth_db.refresh_token = refreshed.refresh_token
169-
170-
if refreshed.refresh_token_expires_in:
171-
oauth_db.refresh_token_expires_at = (
172-
epoch_now + refreshed.refresh_token_expires_in
173-
)
174-
175-
log.info(
176-
"github.auth.refresh.succeeded",
177-
user_id=oauth.user_id,
178-
platform=oauth.platform,
179-
)
180-
session.add(oauth_db)
181-
await session.flush()
182-
return oauth_db
183-
184-
185-
async def get_refreshed_oauth_client(
186-
session: AsyncSession, locker: Locker, oauth: OAuthAccount
187-
) -> GitHub[TokenAuthStrategy]:
188-
refreshed_oauth = await refresh_oauth_account(session, locker, oauth)
189-
return get_client(refreshed_oauth.access_token)
190-
191-
19262
def get_client(access_token: str) -> GitHub[TokenAuthStrategy]:
19363
return GitHub(access_token, http_cache=False)
19464

@@ -231,12 +101,8 @@ def get_app_installation_client(
231101
"get_client",
232102
"get_app_client",
233103
"get_app_installation_client",
234-
"get_user_client",
235104
"GitHub",
236-
"Missing",
237105
"AppInstallationAuthStrategy",
238106
"TokenAuthStrategy",
239-
"utils",
240107
"Response",
241-
"webhooks",
242108
]

server/polar/integrations/github/endpoints.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
from httpx_oauth.clients.github import GitHubOAuth2
77
from httpx_oauth.integrations.fastapi import OAuth2AuthorizeCallback
88
from httpx_oauth.oauth2 import OAuth2Token
9-
from pydantic import ValidationError
109

1110
from polar.auth.dependencies import WebUserOrAnonymous
1211
from polar.auth.models import is_user
@@ -16,14 +15,12 @@
1615
from polar.integrations.loops.service import loops as loops_service
1716
from polar.kit import jwt
1817
from polar.kit.http import ReturnTo
19-
from polar.locker import Locker, get_locker
2018
from polar.openapi import APITag
2119
from polar.postgres import AsyncSession, get_db_session
2220
from polar.posthog import posthog
2321
from polar.routing import APIRouter
2422
from polar.user.schemas import UserSignupAttribution, UserSignupAttributionQuery
2523

26-
from .schemas import OAuthAccessToken
2724
from .service.secret_scanning import secret_scanning as secret_scanning_service
2825
from .service.user import GithubUserServiceError, github_user
2926

@@ -86,7 +83,6 @@ async def github_callback(
8683
access_token_state: tuple[OAuth2Token, str | None] = Depends(
8784
oauth2_authorize_callback
8885
),
89-
locker: Locker = Depends(get_locker),
9086
) -> RedirectResponse:
9187
token_data, state = access_token_state
9288
error_description = token_data.get("error_description")
@@ -103,12 +99,6 @@ async def github_callback(
10399
raise OAuthCallbackError("Invalid state") from e
104100

105101
return_to = state_data.get("return_to", None)
106-
107-
try:
108-
tokens = OAuthAccessToken(**token_data)
109-
except ValidationError as e:
110-
raise OAuthCallbackError("Invalid token data", return_to=return_to) from e
111-
112102
state_user_id = state_data.get("user_id")
113103

114104
state_signup_attribution = state_data.get("signup_attribution")
@@ -124,14 +114,13 @@ async def github_callback(
124114
and auth_subject.subject.id == UUID(state_user_id)
125115
):
126116
is_signup = False
127-
user = await github_user.link_existing_user(
128-
session, user=auth_subject.subject, tokens=tokens
117+
user = await github_user.link_user(
118+
session, user=auth_subject.subject, token=token_data
129119
)
130120
else:
131121
user, is_signup = await github_user.get_updated_or_create(
132122
session,
133-
locker,
134-
tokens=tokens,
123+
token=token_data,
135124
signup_attribution=state_signup_attribution,
136125
)
137126

server/polar/integrations/github/schemas.py

Lines changed: 0 additions & 22 deletions
This file was deleted.

0 commit comments

Comments
 (0)