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

Authenticating with the admin dashboard:

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"])
Parameters:

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

Initialize AdminLoginFlow with SDK config.

Parameters:

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

__init__(config)[source]

Initialize AdminLoginFlow with SDK config.

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:

dict[str, str] – Token bundle with "access" and "refresh" keys.

Raises:

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

Return type:

dict[str, str]

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 BaseAuthenticatedSession instance.

Parameters:
  • config (Config | None) – Optional SDK configuration.

  • access_token (str) – Access token.

  • refresh_token (str) – Refresh token.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked when tokens refresh.

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

Initialize BaseAuthenticatedSession instance.

Parameters:
  • config (Config | None) – Optional SDK configuration.

  • access_token (str) – Access token.

  • refresh_token (str) – Refresh token.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked when tokens refresh.

close()

Persist cookies and release the underlying HTTP connection pool.

property cookies: RequestsCookieJar

Underlying cookie jar.

Mutating this affects subsequent requests.

Returns:

RequestsCookieJar – Return value.

delete(url, **kwargs)

Send a DELETE request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

Return type:

Response

get(url, **kwargs)

Send a GET request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

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:

MutableMapping[str, str | bytes] – Return value.

patch(url, **kwargs)

Send a PATCH request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

Return type:

Response

post(url, **kwargs)

Send a POST request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

Return type:

Response

put(url, **kwargs)

Send a PUT request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

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:

requests.Response

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}".

Parameters:

token (str) – The bearer token to attach

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.

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()).

Initialize BaseAuthenticatedSession instance.

Parameters:
  • config (Config | None) – Optional SDK configuration.

  • access_token (str) – Access token.

  • refresh_token (str) – Refresh token.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked when tokens refresh.

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

Initialize BaseAuthenticatedSession instance.

Parameters:
  • config (Config | None) – Optional SDK configuration.

  • access_token (str) – Access token.

  • refresh_token (str) – Refresh token.

  • on_refresh (OnRefreshCallback | None) – Optional callback invoked when tokens refresh.

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:

requests.Response

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:

RequestsCookieJar – Return value.

delete(url, **kwargs)

Send a DELETE request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

Return type:

Response

get(url, **kwargs)

Send a GET request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

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:

MutableMapping[str, str | bytes] – Return value.

patch(url, **kwargs)

Send a PATCH request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

Return type:

Response

post(url, **kwargs)

Send a POST request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

Return type:

Response

put(url, **kwargs)

Send a PUT request.

See request().

Parameters:
  • url (str) – Absolute URL, or a path relative to Config.base_url.

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

Returns:

requests.Response – The HTTP response from the server.

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}".

Parameters:

token (str) – The bearer token to attach

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:

dict[str, str] – Token bundle with at least "access" and "refresh" keys.

Return type:

dict[str, str]

__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:

str

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:

str | None – 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:

str | None – 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.

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:

dict[str, str]

Dictionary with keys access, refresh, and roles, each containing the

corresponding token string.

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.

Return type:

dict[str, str]

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:

str – The resolved email address.

Raises:

AuthenticationError – If no email is available from any source.

Return type:

str

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:

str – The resolved password string.

Raises:

AuthenticationError – If no password is available from any source.

Return type:

str

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: attr: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.