Source code for vairified.oauth
"""
Vairified OAuth Helpers
Utilities for implementing the "Connect with Vairified" OAuth flow.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional
from urllib.parse import urlencode
# Available OAuth scopes
SCOPES = {
"profile:read": "Access your name, location, and verification status",
"profile:email": "Access your email address",
"rating:read": "View your current rating and rating splits",
"rating:history": "View your complete rating history",
"match:submit": "Submit match results on your behalf",
"webhook:subscribe": "Receive notifications when your rating changes",
}
# Default scopes requested
DEFAULT_SCOPES = ["profile:read", "rating:read"]
[docs]
@dataclass
class OAuthConfig:
"""
OAuth configuration for a partner application.
:ivar api_key: Partner API key.
:ivar redirect_uri: Your application's callback URL.
:ivar base_url: Vairified API base URL.
"""
api_key: str
redirect_uri: str
base_url: str = "https://api-next.vairified.com/api/v1"
[docs]
@dataclass
class AuthorizationResponse:
"""
Response from starting an OAuth authorization.
:ivar authorization_url: Full URL to redirect the user to.
:ivar code: Authorization code (for internal tracking).
:ivar state: CSRF state parameter.
"""
authorization_url: str
code: str
state: Optional[str] = None
[docs]
@dataclass
class TokenResponse:
"""
Response from exchanging an authorization code for tokens.
:ivar access_token: Access token for API requests.
:ivar refresh_token: Refresh token for obtaining new access tokens.
:ivar expires_in: Token expiration in seconds.
:ivar scope: Granted scopes.
:ivar player_id: Connected player's external ID.
"""
access_token: str
refresh_token: Optional[str]
expires_in: int
scope: list[str]
player_id: str
[docs]
def get_authorization_url(
config: OAuthConfig,
scopes: Optional[list[str]] = None,
state: Optional[str] = None,
) -> str:
"""
Build the URL to redirect users to for OAuth authorization.
This is a helper for building the URL manually. In most cases,
you should use the Vairified client's OAuth methods instead.
:param config: OAuth configuration.
:param scopes: Permission scopes to request.
:param state: CSRF protection state parameter.
:returns: URL to redirect the user to.
Example::
config = OAuthConfig(
api_key="vair_pk_xxx",
redirect_uri="https://myapp.com/oauth/callback",
)
url = get_authorization_url(config, scopes=["profile:read", "rating:read"])
# Redirect user to this URL
"""
if scopes is None:
scopes = DEFAULT_SCOPES
# Ensure profile:read is always included
if "profile:read" not in scopes:
scopes = ["profile:read"] + scopes
params = {
"redirect_uri": config.redirect_uri,
"scope": ",".join(scopes),
"response_type": "code",
}
if state:
params["state"] = state
# The actual authorization is done via API call, this builds the frontend URL
# Partners should POST to /partner/oauth/authorize to get the actual auth URL
return f"{config.base_url}/partner/oauth/authorize?{urlencode(params)}"
[docs]
def validate_scope(scope: str) -> bool:
"""
Check if a scope is valid.
:param scope: Scope string to validate.
:returns: True if scope is valid.
"""
return scope in SCOPES
[docs]
def describe_scope(scope: str) -> str:
"""
Get a human-readable description of a scope.
:param scope: Scope string.
:returns: Description of what the scope grants access to.
"""
return SCOPES.get(scope, f"Unknown scope: {scope}")
[docs]
def describe_scopes(scopes: list[str]) -> list[dict[str, str]]:
"""
Get descriptions for multiple scopes.
:param scopes: List of scope strings.
:returns: List of dicts with 'scope' and 'description' keys.
"""
return [{"scope": s, "description": describe_scope(s)} for s in scopes]