|
90 | 90 |
|
91 | 91 | # Default token expiration times |
92 | 92 | DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS: Final[int] = 60 * 60 # 1 hour |
| 93 | +DEFAULT_ACCESS_TOKEN_EXPIRY_NO_REFRESH_SECONDS: Final[int] = ( |
| 94 | + 60 * 60 * 24 * 365 |
| 95 | +) # 1 year |
93 | 96 | DEFAULT_AUTH_CODE_EXPIRY_SECONDS: Final[int] = 5 * 60 # 5 minutes |
94 | 97 |
|
95 | 98 | # HTTP client timeout |
@@ -653,6 +656,8 @@ def __init__( |
653 | 656 | # Consent screen configuration |
654 | 657 | require_authorization_consent: bool = True, |
655 | 658 | consent_csp_policy: str | None = None, |
| 659 | + # Token expiry fallback |
| 660 | + fallback_access_token_expiry_seconds: int | None = None, |
656 | 661 | ): |
657 | 662 | """Initialize the OAuth proxy provider. |
658 | 663 |
|
@@ -702,6 +707,11 @@ def __init__( |
702 | 707 | If a non-empty string, uses that as the CSP policy value. |
703 | 708 | This allows organizations with their own CSP policies to override or disable |
704 | 709 | the built-in CSP directives. |
| 710 | + fallback_access_token_expiry_seconds: Expiry time to use when upstream provider |
| 711 | + doesn't return `expires_in` in the token response. If not set, uses smart |
| 712 | + defaults: 1 hour if a refresh token is available (since we can refresh), |
| 713 | + or 1 year if no refresh token (for API-key-style tokens like GitHub OAuth Apps). |
| 714 | + Set explicitly to override these defaults. |
705 | 715 | """ |
706 | 716 |
|
707 | 717 | # Always enable DCR since we implement it locally for MCP clients |
@@ -773,6 +783,11 @@ def __init__( |
773 | 783 | self._extra_authorize_params: dict[str, str] = extra_authorize_params or {} |
774 | 784 | self._extra_token_params: dict[str, str] = extra_token_params or {} |
775 | 785 |
|
| 786 | + # Token expiry fallback (None means use smart default based on refresh token) |
| 787 | + self._fallback_access_token_expiry_seconds: int | None = ( |
| 788 | + fallback_access_token_expiry_seconds |
| 789 | + ) |
| 790 | + |
776 | 791 | if jwt_signing_key is None: |
777 | 792 | jwt_signing_key = derive_jwt_key( |
778 | 793 | high_entropy_material=upstream_client_secret, |
@@ -1142,9 +1157,18 @@ async def exchange_authorization_code( |
1142 | 1157 | ) |
1143 | 1158 |
|
1144 | 1159 | # Calculate token expiry times |
1145 | | - expires_in = int( |
1146 | | - idp_tokens.get("expires_in", DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS) |
1147 | | - ) |
| 1160 | + # If upstream provides expires_in, use it. Otherwise use fallback based on: |
| 1161 | + # - User-provided fallback if set |
| 1162 | + # - 1 hour if refresh token available (can refresh when expired) |
| 1163 | + # - 1 year if no refresh token (likely API-key-style token like GitHub OAuth Apps) |
| 1164 | + if "expires_in" in idp_tokens: |
| 1165 | + expires_in = int(idp_tokens["expires_in"]) |
| 1166 | + elif self._fallback_access_token_expiry_seconds is not None: |
| 1167 | + expires_in = self._fallback_access_token_expiry_seconds |
| 1168 | + elif idp_tokens.get("refresh_token"): |
| 1169 | + expires_in = DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS |
| 1170 | + else: |
| 1171 | + expires_in = DEFAULT_ACCESS_TOKEN_EXPIRY_NO_REFRESH_SECONDS |
1148 | 1172 |
|
1149 | 1173 | # Calculate refresh token expiry if provided by upstream |
1150 | 1174 | # Some providers include refresh_expires_in, some don't |
@@ -1381,9 +1405,14 @@ async def exchange_refresh_token( |
1381 | 1405 | raise TokenError("invalid_grant", f"Upstream refresh failed: {e}") from e |
1382 | 1406 |
|
1383 | 1407 | # Update stored upstream token |
1384 | | - new_expires_in = int( |
1385 | | - token_response.get("expires_in", DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS) |
1386 | | - ) |
| 1408 | + # In refresh flow, we know there's a refresh token, so default to 1 hour |
| 1409 | + # (user override still applies if set) |
| 1410 | + if "expires_in" in token_response: |
| 1411 | + new_expires_in = int(token_response["expires_in"]) |
| 1412 | + elif self._fallback_access_token_expiry_seconds is not None: |
| 1413 | + new_expires_in = self._fallback_access_token_expiry_seconds |
| 1414 | + else: |
| 1415 | + new_expires_in = DEFAULT_ACCESS_TOKEN_EXPIRY_SECONDS |
1387 | 1416 | upstream_token_set.access_token = token_response["access_token"] |
1388 | 1417 | upstream_token_set.expires_at = time.time() + new_expires_in |
1389 | 1418 |
|
|
0 commit comments