steam-user 0.2.4

Steam User web client for Rust - HTTP-based Steam Community interactions
Documentation
//! Remote credential login.
//!
//! Unlike every other remote service — which proxies a call made with an
//! *existing* session — this performs a full credential login on the server so
//! the Steam-facing handshake runs from the proxy node's IP, not the caller's.
//! Steam rate-limits logins per source IP (`AccountLoginDeniedThrottle`); a
//! caller that logs many accounts in from one host trips that throttle. Routing
//! the login through the proxy fleet spreads it across many IPs, and because a
//! throttle reply is a retryable status, the client retries on the next node.
//!
//! Two-step flow:
//!
//! * [`RemoteSteamUser::begin_login`] completes inline for codeless,
//!   machine-token-trusted, or TOTP logins (pass the precomputed code). A login
//!   that needs an **emailed** code instead returns [`BeginLogin::NeedsEmailCode`]
//!   with a `session_id`.
//! * [`RemoteSteamUser::submit_guard`] submits the emailed code(s) the caller
//!   fetched against that parked session and finishes.
//!
//! IMPORTANT: the parked session lives on **one** node. The caller must send
//! `submit_guard` to the same node that served `begin_login` — construct a
//! single-URL [`RemoteSteamUser`] for the login sequence rather than a
//! multi-URL (round-robin) one. This crate never fetches emailed codes; the
//! caller supplies them.

use serde::{Deserialize, Serialize};

use super::super::{RemoteSteamUser, RemoteSteamUserError};

/// Tokens and cookies minted by a completed credential login.
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RemoteLoginResult {
    /// SteamID64 as a 17-digit decimal string, when the server resolved it.
    #[serde(default)]
    pub steam_id: Option<String>,
    /// The account name Steam echoed back on a completed login.
    #[serde(default)]
    pub account_name: Option<String>,
    /// Fresh OAuth access token (JWT).
    pub access_token: String,
    /// Fresh OAuth refresh token (JWT).
    pub refresh_token: String,
    /// Web session cookies (`steamLoginSecure=…`, `sessionid=…`, …).
    pub cookies: Vec<String>,
    /// Steam Guard machine token Steam minted during this login, if any. Persist
    /// it and pass it to [`RemoteSteamUser::begin_login`] next time so an
    /// email-guarded account logs in codeless (Steam trusts the machine).
    #[serde(default)]
    pub new_guard_data: Option<String>,
}

/// Outcome of [`RemoteSteamUser::begin_login`].
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "status", rename_all = "snake_case")]
pub enum BeginLogin {
    /// The login finished without an emailed code (codeless / trusted machine /
    /// TOTP).
    Completed(RemoteLoginResult),
    /// Steam requires an emailed Steam Guard code. The live session is parked on
    /// the node that served this call; fetch the code and pass `session_id` to
    /// [`RemoteSteamUser::submit_guard`] **on that same node**.
    NeedsEmailCode {
        session_id: String,
        #[serde(default)]
        steam_id: Option<String>,
    },
}

impl RemoteSteamUser {
    /// Begin a credential login via a proxy node.
    ///
    /// `steam_guard_code` is a precomputed TOTP code (or `None`).
    /// `steam_guard_machine_token` is a previously-stored token that lets Steam
    /// skip the guard challenge entirely (or `None`).
    ///
    /// Returns [`BeginLogin::Completed`] when no emailed code is needed, or
    /// [`BeginLogin::NeedsEmailCode`] when the caller must supply an emailed
    /// code via [`submit_guard`](Self::submit_guard).
    pub async fn begin_login(&self, account_name: &str, password: &str, steam_guard_code: Option<&str>, steam_guard_machine_token: Option<&str>) -> Result<BeginLogin, RemoteSteamUserError> {
        self.call_typed(
            "/api/auth/begin_login",
            serde_json::json!({
                "account_name": account_name,
                "password": password,
                "steam_guard_code": steam_guard_code,
                "steam_guard_machine_token": steam_guard_machine_token,
            }),
        )
        .await
    }

    /// Submit emailed Steam Guard code(s) against a session parked by
    /// [`begin_login`](Self::begin_login) and finish the login.
    ///
    /// `codes` are the codes the caller fetched (e.g. every unread code in the
    /// inbox); the server tries them in order and the first accepted one wins.
    /// Must be sent to the **same node** that served `begin_login`.
    pub async fn submit_guard(&self, session_id: &str, codes: &[&str]) -> Result<RemoteLoginResult, RemoteSteamUserError> {
        self.call_typed(
            "/api/auth/submit_guard",
            serde_json::json!({
                "session_id": session_id,
                "codes": codes,
            }),
        )
        .await
    }
}