Compare commits

..

No commits in common. "master" and "steamguard-v0.13.0" have entirely different histories.

12 changed files with 935 additions and 1060 deletions

1843
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,9 @@ members = ["steamguard"]
[package] [package]
name = "steamguard-cli" name = "steamguard-cli"
version = "0.14.0" version = "0.13.0"
authors = ["dyc3 (Carson McManus) <carson.mcmanus1@gmail.com>"] authors = ["dyc3 (Carson McManus) <carson.mcmanus1@gmail.com>"]
edition = "2021" edition = "2018"
description = "A command line utility to generate Steam 2FA codes and respond to confirmations." description = "A command line utility to generate Steam 2FA codes and respond to confirmations."
keywords = ["steam", "2fa", "steamguard", "authentication", "cli"] keywords = ["steam", "2fa", "steamguard", "authentication", "cli"]
categories = ["command-line-utilities"] categories = ["command-line-utilities"]
@ -29,10 +29,10 @@ path = "src/main.rs"
[dependencies] [dependencies]
anyhow = "^1.0" anyhow = "^1.0"
base64 = "0.22.1" base64 = "0.21.2"
text_io = "0.1.8" text_io = "0.1.8"
rpassword = "7.2.0" rpassword = "7.2.0"
reqwest = { version = "0.12", default-features = false, features = [ reqwest = { version = "0.11", default-features = false, features = [
"blocking", "blocking",
"json", "json",
"cookies", "cookies",
@ -43,20 +43,21 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
rsa = "0.9.2" rsa = "0.9.2"
rand = "0.8.5" rand = "0.8.5"
clap = { version = "4.5.4", features = ["derive", "cargo", "env"] } standback = "0.2.17" # required to fix a compilation error on a transient dependency
clap_complete = "4.5.2" clap = { version = "3.1.18", features = ["derive", "cargo", "env"] }
clap_complete = "3.2.1"
log = "0.4.19" log = "0.4.19"
stderrlog = "0.6" stderrlog = "0.6"
cookie = "0.18" cookie = "0.14"
regex = "1" regex = "1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
uuid = { version = "1.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
steamguard = { version = "^0.14.0", path = "./steamguard" } steamguard = { version = "^0.13.0", path = "./steamguard" }
dirs = "5.0.1" dirs = "3.0.2"
aes = { version = "0.8.3", features = ["zeroize"] } aes = { version = "0.8.3", features = ["zeroize"] }
thiserror = "1.0.61" thiserror = "1.0.26"
crossterm = { version = "0.23.2", features = ["event-stream"] } crossterm = { version = "0.23.2", features = ["event-stream"] }
qrcode = { version = "0.14.0", optional = true } qrcode = { version = "0.12.0", optional = true }
gethostname = "0.4.3" gethostname = "0.4.3"
secrecy = { version = "0.8", features = ["serde"] } secrecy = { version = "0.8", features = ["serde"] }
zeroize = { version = "^1.6.0", features = ["std", "zeroize_derive"] } zeroize = { version = "^1.6.0", features = ["std", "zeroize_derive"] }
@ -72,8 +73,6 @@ argon2 = { version = "0.5.0", features = ["std", "zeroize"] }
pbkdf2 = { version = "0.12.1", features = ["parallel"] } pbkdf2 = { version = "0.12.1", features = ["parallel"] }
sha1 = "0.10.5" sha1 = "0.10.5"
rayon = "1.7.0" rayon = "1.7.0"
rqrr = "0.7.1"
image = "0.25"
[dev-dependencies] [dev-dependencies]
tempfile = "3" tempfile = "3"

View file

@ -3,7 +3,7 @@
_pkgname=steamguard-cli _pkgname=steamguard-cli
pkgname=${_pkgname}-git pkgname=${_pkgname}-git
pkgver=0.14.0.r1.602acc66 pkgver=0.8.1.r1.fe0d6e9a
pkgrel=1 pkgrel=1
pkgdesc="A command line utility to generate Steam 2FA codes and respond to confirmations." pkgdesc="A command line utility to generate Steam 2FA codes and respond to confirmations."
arch=('i686' 'x86_64' 'armv6h' 'armv7h') arch=('i686' 'x86_64' 'armv6h' 'armv7h')
@ -12,7 +12,6 @@ license=('GPL3')
makedepends=('rust' 'cargo' 'git') makedepends=('rust' 'cargo' 'git')
source=("git+https://github.com/dyc3/steamguard-cli.git") source=("git+https://github.com/dyc3/steamguard-cli.git")
sha256sums=('SKIP') sha256sums=('SKIP')
options=(!lto)
pkgver() { pkgver() {
cd "${srcdir}/${_pkgname}" cd "${srcdir}/${_pkgname}"

View file

@ -339,9 +339,8 @@ impl AccountManager {
debug!("Adding missing account names"); debug!("Adding missing account names");
for i in 0..self.manifest.entries.len() { for i in 0..self.manifest.entries.len() {
let account = self.load_account_by_entry(&self.manifest.entries[i].clone())?; let account = self.load_account_by_entry(&self.manifest.entries[i].clone())?;
self.manifest.entries[i] self.manifest.entries[i].account_name =
.account_name account.lock().unwrap().account_name.clone();
.clone_from(&account.lock().unwrap().account_name);
} }
upgraded = true; upgraded = true;
} }

View file

@ -1,6 +1,6 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use clap::{Parser, Subcommand, ValueEnum}; use clap::{clap_derive::ArgEnum, Parser};
use clap_complete::Shell; use clap_complete::Shell;
use secrecy::SecretString; use secrecy::SecretString;
use std::str::FromStr; use std::str::FromStr;
@ -127,7 +127,7 @@ pub(crate) struct GlobalArgs {
help = "Specify your encryption passkey." help = "Specify your encryption passkey."
)] )]
pub passkey: Option<SecretString>, pub passkey: Option<SecretString>,
#[clap(short, long, value_enum, default_value_t=Verbosity::Info, help = "Set the log level. Be warned, trace is capable of printing sensitive data.")] #[clap(short, long, arg_enum, default_value_t=Verbosity::Info, help = "Set the log level. Be warned, trace is capable of printing sensitive data.")]
pub verbosity: Verbosity, pub verbosity: Verbosity,
#[cfg(feature = "updater")] #[cfg(feature = "updater")]
@ -160,7 +160,7 @@ pub(crate) struct GlobalArgs {
pub danger_accept_invalid_certs: bool, pub danger_accept_invalid_certs: bool,
} }
#[derive(Debug, Clone, Subcommand)] #[derive(Debug, Clone, Parser)]
pub(crate) enum Subcommands { pub(crate) enum Subcommands {
Debug(DebugCommand), Debug(DebugCommand),
Completion(CompletionsCommand), Completion(CompletionsCommand),
@ -177,7 +177,7 @@ pub(crate) enum Subcommands {
QrLogin(QrLoginCommand), QrLogin(QrLoginCommand),
} }
#[derive(Debug, Clone, Copy, ValueEnum)] #[derive(Debug, Clone, Copy, ArgEnum)]
pub(crate) enum Verbosity { pub(crate) enum Verbosity {
Error = 0, Error = 0,
Warn = 1, Warn = 1,
@ -223,14 +223,3 @@ impl From<Args> for CodeCommand {
args.code args.code
} }
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_cli() {
use clap::CommandFactory;
Args::command().debug_assert()
}
}

View file

@ -5,12 +5,7 @@ use super::*;
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
#[clap(about = "Generate shell completions")] #[clap(about = "Generate shell completions")]
pub struct CompletionsCommand { pub struct CompletionsCommand {
#[clap( #[clap(short, long, arg_enum, help = "The shell to generate completions for.")]
short,
long,
value_enum,
help = "The shell to generate completions for."
)]
pub shell: Shell, pub shell: Shell,
} }

View file

@ -1,10 +1,6 @@
use std::{ use std::sync::{Arc, Mutex};
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use log::*; use log::*;
use rqrr::PreparedImage;
use steamguard::{QrApprover, QrApproverError}; use steamguard::{QrApprover, QrApproverError};
use crate::AccountManager; use crate::AccountManager;
@ -14,8 +10,11 @@ use super::*;
#[derive(Debug, Clone, Parser)] #[derive(Debug, Clone, Parser)]
#[clap(about = "Log in to Steam on another device using the QR code that it's displaying.")] #[clap(about = "Log in to Steam on another device using the QR code that it's displaying.")]
pub struct QrLoginCommand { pub struct QrLoginCommand {
#[clap(flatten)] #[clap(
login_url_source: LoginUrlSource, long,
help = "The URL that would normally open in the Steam app. This is the URL that the QR code is displaying. It should start with \"https://s.team/...\""
)]
pub url: String,
} }
impl<T> AccountCommand<T> for QrLoginCommand impl<T> AccountCommand<T> for QrLoginCommand
@ -42,8 +41,6 @@ where
crate::do_login(transport.clone(), &mut account, args.password.clone())?; crate::do_login(transport.clone(), &mut account, args.password.clone())?;
} }
let url = self.login_url_source.url()?;
debug!("Using login URL to approve: {}", url);
loop { loop {
let Some(tokens) = account.tokens.as_ref() else { let Some(tokens) = account.tokens.as_ref() else {
error!( error!(
@ -54,7 +51,7 @@ where
}; };
let mut approver = QrApprover::new(transport.clone(), tokens); let mut approver = QrApprover::new(transport.clone(), tokens);
match approver.approve(&account, url.to_owned()) { match approver.approve(&account, &self.url) {
Ok(_) => { Ok(_) => {
info!("Login approved."); info!("Login approved.");
break; break;
@ -73,56 +70,3 @@ where
Ok(()) Ok(())
} }
} }
#[derive(Debug, Clone, clap::Args)]
#[group(required = true, multiple = false)]
pub struct LoginUrlSource {
/// The URL that would normally open in the Steam app. This is the URL that the QR code is displaying. It should start with \"https://s.team/...\"
#[clap(long)]
url: Option<String>,
/// Path to an image file containing the QR code. The QR code will be scanned from this image.
#[clap(long)]
image: Option<PathBuf>,
}
impl LoginUrlSource {
fn url(&self) -> anyhow::Result<String> {
match self {
Self { url: Some(url), .. } => Ok(url.clone()),
Self {
image: Some(path), ..
} => read_qr_image(path),
_ => Err(anyhow!(
"You must provide either a URL with --url or an image file with --image."
)),
}
}
}
fn read_qr_image(path: &Path) -> anyhow::Result<String> {
use image::io::Reader as ImageReader;
let image = ImageReader::open(path)?.decode()?.to_luma8();
let mut img = PreparedImage::prepare(image);
let grids = img.detect_grids();
for grid in grids {
let (_meta, text) = grid.decode()?;
// a rough validation that the QR code is a Steam login code
if text.contains("s.team") {
return Ok(text);
}
}
Err(anyhow!("No Steam login url found in the QR code"))
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_read_qr_image() {
let path = Path::new("src/fixtures/qr-codes/login-qr.png");
let url = read_qr_image(path).unwrap();
assert_eq!(url, "https://s.team/q/1/2372462679780599330");
}
}

View file

@ -193,7 +193,6 @@ impl SetupCommand {
"authenticator state: {} -- did not actually finalize", "authenticator state: {} -- did not actually finalize",
status.state() status.state()
); );
debug!("full status: {:#?}", status);
manager.remove_account(&account_name); manager.remove_account(&account_name);
manager.save()?; manager.save()?;
bail!("Authenticator finalization was unsuccessful. You may have entered the wrong confirm code in the previous step. Try again."); bail!("Authenticator finalization was unsuccessful. You may have entered the wrong confirm code in the previous step. Try again.");

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,8 +1,8 @@
[package] [package]
name = "steamguard" name = "steamguard"
version = "0.14.0" version = "0.13.0"
authors = ["Carson McManus <carson.mcmanus1@gmail.com>"] authors = ["Carson McManus <carson.mcmanus1@gmail.com>"]
edition = "2021" edition = "2018"
description = "Library for generating 2fa codes for Steam and responding to mobile confirmations." description = "Library for generating 2fa codes for Steam and responding to mobile confirmations."
keywords = ["steam", "2fa", "steamguard", "authentication"] keywords = ["steam", "2fa", "steamguard", "authentication"]
repository = "https://github.com/dyc3/steamguard-cli/tree/master/steamguard" repository = "https://github.com/dyc3/steamguard-cli/tree/master/steamguard"
@ -11,8 +11,8 @@ license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
anyhow = "^1.0" anyhow = "^1.0"
sha1 = "^0.10" sha1 = "^0.10"
base64 = "^0.22.1" base64 = "^0.21"
reqwest = { version = "0.12", default-features = false, features = [ reqwest = { version = "0.11", default-features = false, features = [
"blocking", "blocking",
"json", "json",
"cookies", "cookies",
@ -24,11 +24,13 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
rsa = "0.9.2" rsa = "0.9.2"
rand = "0.8.4" rand = "0.8.4"
cookie = "0.18" standback = "0.2.17" # required to fix a compilation error on a transient dependency
cookie = "0.14"
regex = "1" regex = "1"
lazy_static = "1.4.0" lazy_static = "1.4.0"
uuid = { version = "1.8", features = ["v4"] } uuid = { version = "0.8", features = ["v4"] }
log = "0.4.19" log = "0.4.19"
scraper = "0.12.0"
maplit = "1.0.2" maplit = "1.0.2"
thiserror = "1.0.26" thiserror = "1.0.26"
secrecy = { version = "0.8", features = ["serde"] } secrecy = { version = "0.8", features = ["serde"] }

View file

@ -137,7 +137,10 @@ where
let mut req = CTwoFactor_Status_Request::new(); let mut req = CTwoFactor_Status_Request::new();
req.set_steamid(account.steam_id); req.set_steamid(account.steam_id);
let resp = self.client.query_status(req, self.tokens.access_token())?; let resp = self
.client
.query_status(req, self.tokens.access_token())
.unwrap();
Ok(resp.into_response_data()) Ok(resp.into_response_data())
} }

View file

@ -1,7 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
#[allow(dead_code)]
pub struct OAuthData { pub struct OAuthData {
pub oauth_token: String, pub oauth_token: String,
pub steamid: String, pub steamid: String,