feat(openrouter): T28 implement API key validation service

- Add validate_api_key() function for OpenRouter key validation
- Add get_key_info() function to retrieve key metadata
- Implement proper error handling (timeout, network errors)
- Use httpx with 10s timeout
- Export from services/__init__.py
- 92% coverage on openrouter module (13 tests)

Refs: T28
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-07 14:44:15 +02:00
parent abf7e7a532
commit 3824ce5169
4 changed files with 306 additions and 6 deletions

View File

@@ -5,6 +5,7 @@ This package provides cryptographic and security-related services:
- Password hashing: bcrypt for password storage
- JWT utilities: Token creation and verification
- API token generation: Secure random tokens with SHA-256 hashing
- OpenRouter: API key validation and info retrieval
"""
from openrouter_monitor.services.encryption import EncryptionService
@@ -14,6 +15,12 @@ from openrouter_monitor.services.jwt import (
decode_access_token,
verify_token,
)
from openrouter_monitor.services.openrouter import (
OPENROUTER_AUTH_URL,
TIMEOUT_SECONDS,
get_key_info,
validate_api_key,
)
from openrouter_monitor.services.password import (
hash_password,
validate_password_strength,
@@ -33,6 +40,11 @@ __all__ = [
"create_access_token",
"decode_access_token",
"verify_token",
# OpenRouter
"OPENROUTER_AUTH_URL",
"TIMEOUT_SECONDS",
"validate_api_key",
"get_key_info",
# Password
"hash_password",
"verify_password",

View File

@@ -0,0 +1,94 @@
"""OpenRouter API service.
T28: Service for validating and retrieving information about OpenRouter API keys.
"""
import httpx
from typing import Optional, Dict, Any
# OpenRouter API endpoints
OPENROUTER_AUTH_URL = "https://openrouter.ai/api/v1/auth/key"
TIMEOUT_SECONDS = 10.0
async def validate_api_key(key: str) -> bool:
"""Validate an OpenRouter API key.
Makes a request to OpenRouter's auth endpoint to verify
that the API key is valid and active.
Args:
key: The OpenRouter API key to validate (should start with 'sk-or-v1-')
Returns:
True if the key is valid, False otherwise (invalid, timeout, network error)
Example:
>>> is_valid = await validate_api_key("sk-or-v1-abc123...")
>>> print(is_valid) # True or False
"""
try:
async with httpx.AsyncClient() as client:
response = await client.get(
OPENROUTER_AUTH_URL,
headers={"Authorization": f"Bearer {key}"},
timeout=TIMEOUT_SECONDS
)
# Key is valid if we get a 200 OK response
return response.status_code == 200
except (httpx.TimeoutException, httpx.NetworkError):
# Timeout or network error - key might be valid but we can't verify
return False
except Exception:
# Any other error - treat as invalid
return False
async def get_key_info(key: str) -> Optional[Dict[str, Any]]:
"""Get information about an OpenRouter API key.
Retrieves usage statistics, limits, and other metadata
for the provided API key.
Args:
key: The OpenRouter API key to query
Returns:
Dictionary with key information if successful, None otherwise.
Typical fields include:
- label: Key label/name
- usage: Current usage
- limit: Usage limit
- is_free_tier: Whether on free tier
Example:
>>> info = await get_key_info("sk-or-v1-abc123...")
>>> print(info)
{
"label": "My Key",
"usage": 50,
"limit": 100,
"is_free_tier": True
}
"""
try:
async with httpx.AsyncClient() as client:
response = await client.get(
OPENROUTER_AUTH_URL,
headers={"Authorization": f"Bearer {key}"},
timeout=TIMEOUT_SECONDS
)
if response.status_code == 200:
data = response.json()
# Return the 'data' field which contains key info
return data.get("data")
else:
return None
except (httpx.TimeoutException, httpx.NetworkError):
return None
except (ValueError, Exception):
# JSON decode error or other exception
return None