gamesheet_sdk.common.auth

Authentication flows and token management for GameSheet.

Functions

extract_firebase_error(body, status)

Extract a human-readable error string from a Firebase Auth response body.

load_access_token(config)

Read the SPA's access token from the saved browser storage state.

load_refresh_token(config)

Read the SPA's refresh token from the saved browser storage state.

login(session[, email, password, timeout, ...])

Log into the GameSheet dashboard, leaving the session authenticated.

refresh_access_token(refresh_token, *[, ...])

Exchange refresh_token for a fresh {access, refresh, roles} bundle.

resolve_email(cfg, email)

Resolve the login email from explicit argument or config.

resolve_password(cfg, password)

Resolve the login password from explicit argument or config.

save_tokens(config, *, access[, refresh, roles])

Persist new token values back into the saved browser storage state.

Classes

AdminLoginFlow

Browser-based LoginFlow for the admin dashboard.

AuthenticatedSession

Admin-pillar session that refreshes via the admin token endpoint.

BaseAuthenticatedSession

Abstract base for sessions that auto-refresh their bearer on 401 or 403.

LoginFlow

Structural interface for authentication flows.

class gamesheet_sdk.common.auth.AdminLoginFlow[source]

Bases: object

Browser-based LoginFlow for the admin dashboard.

Wraps the headless-browser login() flow in a class that conforms to the LoginFlow protocol. After the browser session closes (persisting localStorage to disk), the access and refresh tokens are read back and returned so callers can use them without touching the state file directly.

Example:

from gamesheet_sdk.common.auth.login import AdminLoginFlow
from gamesheet_sdk.common.config import Config

config = Config()
flow = AdminLoginFlow(config)
tokens = flow.authenticate(email="user@example.com")
print(tokens["access"])

Store the configuration for later browser-session creation.

Parameters:

config (Config) – SDK configuration (credentials, URLs, storage paths).

__init__(config)[source]

Store the configuration for later browser-session creation.

Parameters:

config (Config) – SDK configuration (credentials, URLs, storage paths).

authenticate(email=None, password=None, *, timeout=None)[source]

Run the browser-based admin login and return tokens.

Opens a BrowserSession, drives the Firebase login form via login(), and reads the persisted tokens from the saved browser state file.

Parameters:
  • email (str | None) – Login email, or None to resolve from config/env.

  • password (str | None) – Login password, or None to resolve from config/env.

  • timeout (float | None) – Auth round-trip timeout in seconds, or None for the default.

Returns:

Token bundle with "access" and "refresh" keys.

Return type:

dict[str, str]

Raises:

AuthenticationError – If credentials are missing, the auth backend rejects them, or tokens are not found in the saved state after login.

class gamesheet_sdk.common.auth.AuthenticatedSession[source]

Bases: BaseAuthenticatedSession

Admin-pillar session that refreshes via the admin token endpoint.

Delegates to refresh_access_token(), forwarding the session’s User-Agent header so the admin gateway can log the calling client.

Example:

from gamesheet_sdk.common.auth import load_access_token, load_refresh_token, save_tokens
from gamesheet_sdk.common.auth.session import AuthenticatedSession
from gamesheet_sdk.admin.associations import list_associations
from gamesheet_sdk.common.config import Config
config = Config()
with AuthenticatedSession(
    config,
    access_token=load_access_token(config),
    refresh_token=load_refresh_token(config),
    on_refresh=lambda tokens: save_tokens(config, **tokens),
) as s:
    for assoc in list_associations(s):
        print(assoc.name)

Initialize an authenticated session with token auto-refresh on 401/403.

Parameters:
  • config (Config | None) – Optional configuration object. If None, a default Config is created.

  • access_token (str) – Current access token to use as the bearer in the Authorization header.

  • refresh_token (str) – Refresh token used to renew the access token on 401/403 responses.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked with a token dict after a successful token refresh. Use this to persist the new tokens to disk (e.g. via save_tokens()).

__init__(config=None, *, access_token, refresh_token, on_refresh=None)

Initialize an authenticated session with token auto-refresh on 401/403.

Parameters:
  • config (Config | None) – Optional configuration object. If None, a default Config is created.

  • access_token (str) – Current access token to use as the bearer in the Authorization header.

  • refresh_token (str) – Refresh token used to renew the access token on 401/403 responses.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked with a token dict after a successful token refresh. Use this to persist the new tokens to disk (e.g. via save_tokens()).

close()

Persist cookies and release the underlying HTTP connection pool.

property cookies: RequestsCookieJar

Underlying cookie jar.

Mutating this affects subsequent requests. :returns: Return value. :rtype: RequestsCookieJar

delete(url, **kwargs)

Send a DELETE request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

get(url, **kwargs)

Send a GET request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

property headers: MutableMapping[str, str | bytes]

Default headers attached to every request from this session.

The underlying mapping is a case-insensitive dict (as supplied by requests.Session), but the declared return type matches the stub for requests.Session.headers. :returns: Return value. :rtype: MutableMapping[str, str | bytes]

patch(url, **kwargs)

Send a PATCH request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

post(url, **kwargs)

Send a POST request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

put(url, **kwargs)

Send a PUT request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

request(method, url, *, timeout=None, **kwargs)

Send a request, refreshing the bearer and retrying once on 401 or 403.

Parameters:
  • method (str) – HTTP method (GET, POST, PUT, DELETE, etc.).

  • url (str) – Target URL for the request.

  • timeout (float | None) – Request timeout in seconds. If None, uses the timeout from config.

  • kwargs (Any) – Additional keyword arguments forwarded to the parent request().

Returns:

HTTP response object from the request. If token refresh fails, returns the original 401/403 response without raising an exception.

Return type:

requests.Response

save()

Persist the current cookie state to Config.session_path.

The on-disk format preserves the full cookie attribute set (domain, path, secure, expires) so that reloaded cookies are sent against the correct scopes.

set_bearer_token(token)

Attach Authorization: Bearer <token> to all subsequent requests.

Convenience for s.headers["Authorization"] = f"Bearer {token}". :param token: The bearer token to attach :type token: str

class gamesheet_sdk.common.auth.BaseAuthenticatedSession[source]

Bases: Session, ABC

Abstract base for sessions that auto-refresh their bearer on 401 or 403.

Subclasses provide _do_refresh() to perform the actual token refresh HTTP call against their pillar’s endpoint. Everything else — constructor, retry logic, callback notification — is shared.

On any 401 or 403 response the session calls _do_refresh(), updates its bearer, optionally invokes on_refresh with the new token bundle, and retries the original request once. Both 401 and 403 are treated as authentication failures because different GameSheet API endpoints use different status codes for expired tokens. If the refresh itself fails the original response propagates to the caller.

Initialize an authenticated session with token auto-refresh on 401/403.

Parameters:
  • config (Config | None) – Optional configuration object. If None, a default Config is created.

  • access_token (str) – Current access token to use as the bearer in the Authorization header.

  • refresh_token (str) – Refresh token used to renew the access token on 401/403 responses.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked with a token dict after a successful token refresh. Use this to persist the new tokens to disk (e.g. via save_tokens()).

__init__(config=None, *, access_token, refresh_token, on_refresh=None)[source]

Initialize an authenticated session with token auto-refresh on 401/403.

Parameters:
  • config (Config | None) – Optional configuration object. If None, a default Config is created.

  • access_token (str) – Current access token to use as the bearer in the Authorization header.

  • refresh_token (str) – Refresh token used to renew the access token on 401/403 responses.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked with a token dict after a successful token refresh. Use this to persist the new tokens to disk (e.g. via save_tokens()).

request(method, url, *, timeout=None, **kwargs)[source]

Send a request, refreshing the bearer and retrying once on 401 or 403.

Parameters:
  • method (str) – HTTP method (GET, POST, PUT, DELETE, etc.).

  • url (str) – Target URL for the request.

  • timeout (float | None) – Request timeout in seconds. If None, uses the timeout from config.

  • kwargs (Any) – Additional keyword arguments forwarded to the parent request().

Returns:

HTTP response object from the request. If token refresh fails, returns the original 401/403 response without raising an exception.

Return type:

requests.Response

close()

Persist cookies and release the underlying HTTP connection pool.

property cookies: RequestsCookieJar

Underlying cookie jar.

Mutating this affects subsequent requests. :returns: Return value. :rtype: RequestsCookieJar

delete(url, **kwargs)

Send a DELETE request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

get(url, **kwargs)

Send a GET request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

property headers: MutableMapping[str, str | bytes]

Default headers attached to every request from this session.

The underlying mapping is a case-insensitive dict (as supplied by requests.Session), but the declared return type matches the stub for requests.Session.headers. :returns: Return value. :rtype: MutableMapping[str, str | bytes]

patch(url, **kwargs)

Send a PATCH request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

post(url, **kwargs)

Send a POST request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

put(url, **kwargs)

Send a PUT request.

See request(). :param url: Absolute URL, or a path relative to Config.base_url. :type url: str :param kwargs: Additional keyword arguments forwarded to request(). :type kwargs: Any :returns: Return value. :rtype: requests.Response

Return type:

Response

save()

Persist the current cookie state to Config.session_path.

The on-disk format preserves the full cookie attribute set (domain, path, secure, expires) so that reloaded cookies are sent against the correct scopes.

set_bearer_token(token)

Attach Authorization: Bearer <token> to all subsequent requests.

Convenience for s.headers["Authorization"] = f"Bearer {token}". :param token: The bearer token to attach :type token: str

class gamesheet_sdk.common.auth.LoginFlow[source]

Bases: Protocol

Structural interface for authentication flows.

Any class that exposes an authenticate method with the signature below is a valid LoginFlow — no explicit inheritance needed (structural subtyping via typing.Protocol).

Implementations hold whatever state they need (a Config, a BrowserSession, HTTP clients, etc.) and expose only the common authenticate entry point.

The returned dict contains at minimum "access" and "refresh" keys. Admin flows may also include "roles". The shape matches refresh_access_token()’s return value and can be unpacked directly into save_tokens().

authenticate(email=None, password=None, *, timeout=None)[source]

Authenticate with the GameSheet platform and return tokens.

Credential resolution follows a standard fallback chain: explicit arguments → GAMESHEET_USERNAME / GAMESHEET_PASSWORD env vars → Config fields. Implementations raise AuthenticationError when credentials are missing or authentication is rejected.

Parameters:
  • email (str | None) – Login email, or None to resolve from config/env.

  • password (str | None) – Login password, or None to resolve from config/env.

  • timeout (float | None) – Auth round-trip timeout in seconds, or None for the implementation’s default.

Returns:

Token bundle with at least "access" and "refresh" keys.

Return type:

dict[str, str]

Raises:

AuthenticationError – If credentials are missing or the auth backend rejects them.

__init__(*args, **kwargs)
gamesheet_sdk.common.auth.extract_firebase_error(body, status)[source]

Extract a human-readable error string from a Firebase Auth response body.

Firebase returns {"error": {"code": N, "message": "CODE_NAME", ...}}; the message is a stable identifier like EMAIL_NOT_FOUND that we surface verbatim so callers can react programmatically.

Parameters:
  • body (dict[str, Any]) – Parsed JSON body from the Firebase response.

  • status (int) – HTTP status code, used as the fallback message.

Returns:

The Firebase error code string, or "HTTP <status>" if the body does not contain a usable error message.

Return type:

str

gamesheet_sdk.common.auth.load_access_token(config)[source]

Read the SPA’s access token from the saved browser storage state.

Returns the value of the accessToken localStorage entry for the SPA’s origin (Config.base_url), or None if the storage state file is missing, unreadable, or does not contain a token. The access token is used for authenticating API requests and typically has a short lifetime (10 minutes).

Example:

from gamesheet_sdk.common.auth.tokens import load_access_token
from gamesheet_sdk.common.config import Config

config = Config()
token = load_access_token(config)
if token:
    print("Access token loaded successfully")
Parameters:

config (Config) – Configuration object containing the base URL and browser state path.

Returns:

The access token string if found, otherwise None.

Return type:

str | None

gamesheet_sdk.common.auth.load_refresh_token(config)[source]

Read the SPA’s refresh token from the saved browser storage state.

Companion to load_access_token(). Returns the value of the refreshToken localStorage entry. Used to drive refresh_access_token() and AuthenticatedSession.

Parameters:

config (Config) – Configuration object containing the base URL and browser state path.

Returns:

The refresh token string if found, otherwise None.

Return type:

str | None

gamesheet_sdk.common.auth.login(session, email=None, password=None, *, timeout=None, post_login_path='/associations')[source]

Log into the GameSheet dashboard, leaving the session authenticated.

Success is determined by:

  • HTTP 200 from the Firebase signInWithPassword call, and

  • HTTP 200 from the subsequent /api/token exchange.

On failure the Firebase error.message (e.g. EMAIL_NOT_FOUND, INVALID_LOGIN_CREDENTIALS, TOO_MANY_ATTEMPTS_TRY_LATER) is surfaced verbatim in the raised AuthenticationError so callers know exactly what was wrong.

Parameters:
  • session (BrowserSession) – An open BrowserSession.

  • email (str | None) – Login email; falls back to session.config.username.

  • password (str | None) – Login password; falls back to session.config.password.get_secret_value().

  • timeout (float | None) – Seconds to wait for the auth backend round-trip (default 15).

  • post_login_path (str | None) – Path to navigate to after the auth round-trip succeeds. The SPA performs its real routing and post-login data fetches when it reaches this page, so the saved storage state afterwards captures a fully-settled session (cookies + any SPA-cached state) rather than just the bare auth cookie. Pass None to skip the post-login navigation entirely. Default is POST_LOGIN_PATH.

Raises:

AuthenticationError – If authentication fails (missing credentials, Firebase rejection, token exchange failure, or timeout).

gamesheet_sdk.common.auth.refresh_access_token(refresh_token, *, user_agent=None, timeout=30.0)[source]

Exchange refresh_token for a fresh {access, refresh, roles} bundle.

POSTs to REFRESH_URL with Authorization: Bearer <refresh_token> and an empty JSON body. The gateway returns a new access token (10-min TTL), a new refresh token (long TTL, replaces the one you sent), and a roles token.

This is a standalone HTTP call that does not use a Session, so it can be used from inside AuthenticatedSession’s retry path without recursing.

Example:

Refresh an expired access token:

from gamesheet_sdk.common.auth.tokens import (
    load_refresh_token,
    refresh_access_token,
    save_tokens,
)
from gamesheet_sdk.common.config import Config

config = Config()
refresh = load_refresh_token(config)
if refresh:
    try:
        tokens = refresh_access_token(refresh)
        save_tokens(
            config,
            access=tokens["access"],
            refresh=tokens["refresh"],
            roles=tokens["roles"],
        )
    except AuthenticationError:
        print("Refresh token expired, please log in again")
Parameters:
  • refresh_token (str) – The refresh token to exchange for new tokens.

  • user_agent (str | None) – Optional User-Agent header value for the request.

  • timeout (float) – Request timeout in seconds. Defaults to REFRESH_TIMEOUT_S.

Returns:

Dictionary with keys access, refresh, and roles, each containing the corresponding token string.

Return type:

dict[str, str]

Raises:
  • AuthenticationError – If the refresh token is rejected (HTTP 401). This typically means the token has expired and the user needs to re-authenticate via gamesheet-admin login.

  • GameSheetError – For any other non-2xx HTTP response from the token refresh endpoint.

gamesheet_sdk.common.auth.resolve_email(cfg, email)[source]

Resolve the login email from explicit argument or config.

Falls through: explicit email argument → GAMESHEET_USERNAME env var → Config.username.

Parameters:
  • cfg (Config) – Configuration object containing username from env/defaults.

  • email (str | None) – Explicit email address, or None to fall back to config.

Returns:

The resolved email address.

Return type:

str

Raises:

AuthenticationError – If no email is available from any source.

gamesheet_sdk.common.auth.resolve_password(cfg, password)[source]

Resolve the login password from explicit argument or config.

Falls through: explicit password argument → GAMESHEET_PASSWORD env var → Config.password.

Parameters:
  • cfg (Config) – Configuration object containing password from env/defaults.

  • password (str | None) – Explicit password, or None to fall back to config.

Returns:

The resolved password string.

Return type:

str

Raises:

AuthenticationError – If no password is available from any source.

gamesheet_sdk.common.auth.save_tokens(config, *, access, refresh=None, roles=None)[source]

Persist new token values back into the saved browser storage state.

Reads Config.browser_state_path (or starts with an empty state if it is missing or malformed), updates the localStorage entries for config.base_url in place, and writes the file back. Only the keys that were passed are written; unspecified ones are left alone. Both BrowserSession and direct token loading use this same storage format, making them mutually compatible.

Example:

Save tokens after a successful refresh:

from gamesheet_sdk.common.config import Config
from gamesheet_sdk.common.auth.tokens import save_tokens

config = Config()
save_tokens(
    config,
    access="new_access_token",
    refresh="new_refresh_token",
    roles="roles_token",
)
Parameters:
  • config (Config) – Configuration object containing the base URL and browser state path where tokens will be saved.

  • access (str) – The access token to save (required).

  • refresh (str | None) – The refresh token to save, or None to leave unchanged.

  • roles (str | None) – The roles token to save, or None to leave unchanged.