Compare commits

..

No commits in common. "master" and "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,