Source code for gamesheet_sdk.common.auth.flow
# Copyright (c) 2026 bdperkin
# SPDX-License-Identifier: MIT
"""Login flow protocol for pluggable authentication strategies.
Defines :class:`LoginFlow`, the structural interface that both admin
(browser-based) and teams (HTTP-only) authentication implement.
Downstream code that needs to authenticate — CLI commands, session
construction helpers — depends on this protocol rather than on a
concrete login function, so the auth mechanism can vary per pillar
without changing the consumer.
**Example — using a LoginFlow implementation:**
.. code-block:: python
from gamesheet_sdk.common.auth.flow import LoginFlow
from gamesheet_sdk.common.auth.tokens import save_tokens
from gamesheet_sdk.common.config import Config
def run_login(flow: LoginFlow, config: Config) -> None:
tokens = flow.authenticate(email="user@example.com")
save_tokens(config, **tokens)
"""
from __future__ import annotations
from typing import Protocol, runtime_checkable
[docs]
@runtime_checkable
# pylint: disable-next=too-few-public-methods
class LoginFlow(Protocol):
"""Structural interface for authentication flows.
Any class that exposes an ``authenticate`` method with the signature
below is a valid :class:`LoginFlow` — no explicit inheritance needed
(structural subtyping via :class:`typing.Protocol`).
Implementations hold whatever state they need (a :class:`~gamesheet_sdk.common.config.Config`,
a :class:`~gamesheet_sdk.common.browser.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
:func:`~gamesheet_sdk.common.auth.tokens.refresh_access_token`'s
return value and can be unpacked directly into
:func:`~gamesheet_sdk.common.auth.tokens.save_tokens`.
"""
[docs]
def authenticate(
self: LoginFlow,
email: str | None = None,
password: str | None = None,
*,
timeout: float | None = None,
) -> dict[str, str]:
"""Authenticate with the GameSheet platform and return tokens.
Credential resolution follows a standard fallback chain: explicit
arguments → ``GAMESHEET_USERNAME`` / ``GAMESHEET_PASSWORD`` env
vars → :class:`~gamesheet_sdk.common.config.Config` fields.
Implementations raise
:class:`~gamesheet_sdk.common.exceptions.AuthenticationError` when
credentials are missing or authentication is rejected.
:param email: Login email, or ``None`` to resolve from config/env.
:type email: str | None
:param password: Login password, or ``None`` to resolve from config/env.
:type password: str | None
:param timeout: Auth round-trip timeout in seconds, or ``None`` for the
implementation's default.
:type timeout: float | None
:returns: Token bundle with at least ``"access"`` and ``"refresh"`` keys.
:rtype: dict[str, str]
:raises ~gamesheet_sdk.common.exceptions.AuthenticationError:
If credentials are missing or the auth backend rejects them.
"""
_ = (email, password, timeout)
return {}