1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
//! Allows you to implement a 'login with steam' feature on your website.
//!
//! ## Usage
//!
//! The easiest way to use this crate is with the `reqwest-09x` feature which allows the library to
//! make HTTP requests on your behalf. Otherwise, you will need to do that manually.
//!
//! Using the `reqwest-09x` feature:
//! ```rust
//! # use steam_auth::{Redirector, Verifier};
//! # fn main() {
//! // First, create a redirector
//! let redirector = Redirector::new("http://localhost:8080", "/callback").unwrap();
//!
//! // When a user wants to log in with steam, (e.g when they land on the `/login` route),
//! // redirect them to this URL:
//! let redirect_url = redirector.url();
//!
//! // Once they've finished authenticating, they will be returned to `/callback` with some data in
//! // the query string that needs to be parsed and then verified by sending an HTTP request to the steam
//! // servers.
//! # let querystring = "openid.ns=http%3A%2F%2Fspecs.openid.net%2Fauth%2F2.0&openid.mode=id_res&openid.op_endpoint=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Flogin&openid.claimed_id=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Fid%2F92345666790633291&openid.identity=https%3A%2F%2Fsteamcommunity.com%2Fopenid%2Fid%2F12333456789000000&openid.return_to=http%3A%2F%2Flocalhost%3A8080%2Fcallback&openid.response_nonce=2019-06-15T00%3A36%3A00Z7nVIS5lDAcZe%2FT0gT4%2BQNQyexyA%3D&openid.assoc_handle=1234567890&openid.signed=signed%2Cop_endpoint%2Cclaimed_id%2Cidentity%2Creturn_to%2Cresponse_nonce%2Cassoc_handle&openid.sig=BK0zC%2F%2FKzERs7N%2BNlDO0aL06%2BBA%3D";
//! match Verifier::make_verify_request(&reqwest::Client::new(), querystring) {
//!     Ok(steam_id) => println!("Successfully logged in user with steam ID 64 {}", steam_id),
//!     Err(e) => eprintln!("There was an error authenticating: {}", e),
//! }
//! # }
//! ```
//!
//! There is an asynchronous variant: `Verifier::make_verify_request_async` which returns a
//! future. You can also deserialize the data into a `SteamLoginData` struct and construct a
//! `Verifier` from that if it is more convenient.
//!
//! If you don't want to depend on request, you'll need to send the HTTP request yourself. See the
//! [example server](https://github.com/64/steam-auth/blob/master/examples/server.rs) and the
//! `Verifier` documentation for more details on how this can be done.

#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate failure;

mod redirector;
mod verifier;

pub use redirector::Redirector;
pub use verifier::SteamLoginData;
pub use verifier::Verifier;

pub(crate) const STEAM_URL: &str = "https://steamcommunity.com/openid/login";

#[derive(Debug, Fail)]
pub enum Error {
    #[fail(display = "bad site or return url: {}", _0)]
    /// The site or return URL was incorrect
    BadUrl(url::ParseError),
    #[fail(display = "failed to parse SteamAuthRequest (please file bug): {}", _0)]
    /// Internal error serializing the query string - should never happen.
    ParseQueryString(serde_urlencoded::ser::Error),
    #[fail(display = "authentication failed")]
    /// The authentication failed because the data provided to the callback was invalid
    AuthenticationFailed,
    #[fail(display = "failed to parse steam id")]
    /// There was an error parsing the Steam ID returned to the callback
    ParseSteamId,
    #[fail(display = "failed to build HTTP request or response: {}", _0)]
    BuildHttpStruct(http::Error),
    #[fail(display = "error serializing url encoded data: {}", _0)]
    Serialize(serde_urlencoded::ser::Error),
    #[fail(display = "error deserializing url encoded data: {}", _0)]
    Deserialize(serde_urlencoded::de::Error),
    #[fail(display = "reqwest error: {}", _0)]
    #[cfg(feature = "reqwest-09x")]
    /// There was an error during the verify request
    Reqwest(reqwest::Error),
}

#[cfg(feature = "reqwest-0_9")]
pub fn verify_response_async(
    client: &reqwest::r#async::Client,
    mut form: SteamAuthResponse,
) -> impl futures::Future<Item = u64, Error = Error> {
    client
        .post(STEAM_URL)
        .form(&form)
        .send()
        .map_err(Error::Reqwest)
        .and_then(|res| res.into_body().concat2().map_err(Error::Reqwest))
        .and_then(move |body| {
            let s = std::str::from_utf8(&body)
                .map_err(|_| Error::AuthenticationFailed)?
                .to_owned();

            parse_verify_response(&form.claimed_id, s)
        })
}