| 6 | |
| 7 | |
| 8 | class GitHubService: |
| 9 | def __init__( |
| 10 | self, client_id: str, client_secret: str, app_id: str, private_key: str |
| 11 | ): |
| 12 | self.client_id = client_id |
| 13 | self.client_secret = client_secret |
| 14 | self.app_id = app_id |
| 15 | self.private_key = private_key |
| 16 | self._jwt_token = None |
| 17 | self._jwt_expires_at = None |
| 18 | self._time_offset = None |
| 19 | self._time_offset_checked_at = None |
| 20 | |
| 21 | def _check_time_offset(self) -> int: |
| 22 | """Check time offset between server and GitHub. Returns offset in seconds.""" |
| 23 | now = int(time.time()) |
| 24 | |
| 25 | if ( |
| 26 | self._time_offset is not None |
| 27 | and self._time_offset_checked_at is not None |
| 28 | and now - self._time_offset_checked_at < 3600 |
| 29 | ): |
| 30 | return self._time_offset |
| 31 | |
| 32 | try: |
| 33 | response = httpx.get("https://api.github.com", timeout=5.0) |
| 34 | if "Date" in response.headers: |
| 35 | github_time = parsedate_to_datetime(response.headers["Date"]) |
| 36 | github_timestamp = int(github_time.timestamp()) |
| 37 | self._time_offset = github_timestamp - now |
| 38 | self._time_offset_checked_at = now |
| 39 | return self._time_offset |
| 40 | except Exception: |
| 41 | pass |
| 42 | |
| 43 | return self._time_offset or 0 |
| 44 | |
| 45 | @property |
| 46 | def jwt_token(self) -> str: |
| 47 | """Get a valid JWT token, generating a new one if needed.""" |
| 48 | now = int(time.time()) |
| 49 | |
| 50 | # Generate new token if none exists or current one is expiring soon |
| 51 | if ( |
| 52 | not self._jwt_token |
| 53 | or not self._jwt_expires_at |
| 54 | or self._jwt_expires_at - now < 60 |
| 55 | ): |
| 56 | offset = self._check_time_offset() |
| 57 | adjusted_now = now + offset |
| 58 | adjusted_exp = adjusted_now + (10 * 60) |
| 59 | |
| 60 | self._jwt_expires_at = now + (10 * 60) |
| 61 | self._jwt_token = jwt.encode( |
| 62 | {"iat": adjusted_now, "exp": adjusted_exp, "iss": self.app_id}, |
| 63 | self.private_key, |
| 64 | algorithm="RS256", |
| 65 | ) |
no outgoing calls
no test coverage detected