Repository navigation
fix(auth): treat a blank api_key as unset so the OPENROUTER_API_KEY fallback applies #1117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
LukasParke
wants to merge
4
commits into
main
Choose a base branch
from
fix/blank-api-key-env-fallback
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
9108f1b
fix(auth): treat a blank api_key as unset so OPENROUTER_API_KEY fallb…
LukasParke 234be22
fix(auth): drop blank bearer Authorization header before sending
LukasParke 85c4ead
fix: fall back to OPENROUTER_API_KEY when a callable api_key returns …
LukasParke 2f9ed61
fix: accept a Bearer-prefixed OPENROUTER_API_KEY in the blank-key fal…
LukasParke File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,59 @@ | ||
| from .types import Hooks | ||
| import os | ||
|
|
||
| import httpx | ||
|
|
||
| from openrouter import components | ||
| from openrouter.sdkconfiguration import SDKConfiguration | ||
|
|
||
| from .types import BeforeRequestContext, BeforeRequestHook, Hooks, SDKInitHook | ||
|
|
||
|
|
||
| # This file is only ever generated once on the first generation and then is free to be modified. | ||
| # Any hooks you wish to add should be registered in the init_hooks function. Feel free to define them | ||
| # in this file or in separate files in the hooks folder. | ||
|
|
||
|
|
||
| class BlankAPIKeyIsUnsetHook(SDKInitHook, BeforeRequestHook): | ||
| """Treat a blank `api_key` (e.g. from `os.getenv("OPENROUTER_API_KEY", "")`) | ||
| as not supplied, so the `OPENROUTER_API_KEY` env-var fallback still applies | ||
| instead of sending an empty `Bearer ` token. | ||
|
|
||
| The env var is read per request and a callable `api_key` is resolved per | ||
| request, so either can still yield a blank token after init; a blank | ||
| bearer token is replaced with `OPENROUTER_API_KEY` when that is set, and | ||
| otherwise the `Authorization` header is dropped. | ||
| """ | ||
|
|
||
| def sdk_init(self, config: SDKConfiguration) -> SDKConfiguration: | ||
| security = config.security | ||
| if isinstance(security, components.Security) and not ( | ||
| security.api_key or "" | ||
| ).strip(): | ||
| config.security = None | ||
| return config | ||
|
|
||
| def before_request( | ||
| self, hook_ctx: BeforeRequestContext, request: httpx.Request | ||
| ) -> httpx.Request: | ||
| auth = request.headers.get("authorization") | ||
| if auth is not None: | ||
| scheme, _, token = auth.strip().partition(" ") | ||
| if not auth.strip() or (scheme.lower() == "bearer" and not token.strip()): | ||
| env_key = (os.getenv("OPENROUTER_API_KEY") or "").strip() | ||
| env_scheme, _, env_token = env_key.partition(" ") | ||
| if env_scheme.lower() == "bearer": | ||
| env_key = env_token.strip() | ||
| if env_key: | ||
| request.headers["authorization"] = f"Bearer {env_key}" | ||
|
LukasParke marked this conversation as resolved.
|
||
| else: | ||
| del request.headers["authorization"] | ||
| return request | ||
|
|
||
|
|
||
|
LukasParke marked this conversation as resolved.
|
||
| def init_hooks(hooks: Hooks): | ||
| # pylint: disable=unused-argument | ||
| """Add hooks by calling hooks.register{sdk_init/before_request/after_success/after_error}Hook | ||
| with an instance of a hook that implements that specific Hook interface | ||
| Hooks are registered per SDK instance, and are valid for the lifetime of the SDK instance""" | ||
| blank_api_key_hook = BlankAPIKeyIsUnsetHook() | ||
| hooks.register_sdk_init_hook(blank_api_key_hook) | ||
| hooks.register_before_request_hook(blank_api_key_hook) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import httpx | ||
| import pytest | ||
|
|
||
| from openrouter import OpenRouter | ||
|
|
||
|
|
||
| def _authorization_header(**kwargs): | ||
| seen = [] | ||
|
|
||
| def handler(request: httpx.Request) -> httpx.Response: | ||
| seen.append(request.headers.get("authorization")) | ||
| return httpx.Response(200, json={"data": {"count": 0}}) | ||
|
|
||
| client = httpx.Client(transport=httpx.MockTransport(handler)) | ||
| try: | ||
| OpenRouter(client=client, **kwargs).models.count() | ||
| except Exception: # pylint: disable=broad-except | ||
| pass # response shape is irrelevant; only the request header matters | ||
| assert seen, "no request was sent" | ||
| return seen[0] | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("api_key", ["", " "]) | ||
| def test_blank_api_key_falls_back_to_env_var(monkeypatch, api_key): | ||
| monkeypatch.setenv("OPENROUTER_API_KEY", "from-env") | ||
| assert _authorization_header(api_key=api_key) == "Bearer from-env" | ||
|
|
||
|
|
||
| def test_blank_api_key_without_env_var_sends_no_authorization(monkeypatch): | ||
| monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) | ||
| assert _authorization_header(api_key="") is None | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("env_value", ["", " "]) | ||
| def test_blank_api_key_and_blank_env_var_sends_no_authorization( | ||
| monkeypatch, env_value | ||
| ): | ||
| monkeypatch.setenv("OPENROUTER_API_KEY", env_value) | ||
| assert _authorization_header(api_key="") is None | ||
|
|
||
|
|
||
| @pytest.mark.parametrize("env_value", ["from-env", "Bearer from-env"]) | ||
| def test_blank_callable_api_key_falls_back_to_env_var(monkeypatch, env_value): | ||
| monkeypatch.setenv("OPENROUTER_API_KEY", env_value) | ||
| assert _authorization_header(api_key=lambda: "") == "Bearer from-env" | ||
|
|
||
|
|
||
| def test_explicit_api_key_wins_over_env_var(monkeypatch): | ||
| monkeypatch.setenv("OPENROUTER_API_KEY", "from-env") | ||
| assert _authorization_header(api_key="explicit") == "Bearer explicit" |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.