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:
@@ -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",
|
||||
|
||||
94
src/openrouter_monitor/services/openrouter.py
Normal file
94
src/openrouter_monitor/services/openrouter.py
Normal 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
|
||||
Reference in New Issue
Block a user