Compare commits

..

No commits in common. "master" and "v0.12.6" have entirely different histories.

19 changed files with 971 additions and 1099 deletions

View file

@ -5,13 +5,12 @@ on:
- cron: "32 5 */3 * *"
push:
branches: [ master, main ]
workflow_dispatch:
jobs:
test-install:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v2
- name: Install AUR package
run: ./scripts/check-aur.sh

View file

@ -15,9 +15,9 @@ jobs:
steps:
- run: rustup component add clippy rustfmt
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Rust Cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@v2.5.0
- name: Check format
run: cargo fmt --all -- --check
- name: Check
@ -34,13 +34,13 @@ jobs:
matrix:
target: [x86_64-unknown-linux-musl, x86_64-pc-windows-gnu]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Rust Cache
uses: Swatinem/rust-cache@v2
uses: Swatinem/rust-cache@v2.5.0
with:
prefix-key: v0-rust-${{ matrix.target }}
- name: Install Cross
uses: baptiste0928/cargo-install@v2
uses: baptiste0928/cargo-install@v1
with:
crate: cross
- name: Check

1851
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -4,9 +4,9 @@ members = ["steamguard"]
[package]
name = "steamguard-cli"
version = "0.14.0"
version = "0.12.6"
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."
keywords = ["steam", "2fa", "steamguard", "authentication", "cli"]
categories = ["command-line-utilities"]
@ -29,10 +29,10 @@ path = "src/main.rs"
[dependencies]
anyhow = "^1.0"
base64 = "0.22.1"
base64 = "0.21.2"
text_io = "0.1.8"
rpassword = "7.2.0"
reqwest = { version = "0.12", default-features = false, features = [
reqwest = { version = "0.11", default-features = false, features = [
"blocking",
"json",
"cookies",
@ -43,20 +43,21 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rsa = "0.9.2"
rand = "0.8.5"
clap = { version = "4.5.4", features = ["derive", "cargo", "env"] }
clap_complete = "4.5.2"
standback = "0.2.17" # required to fix a compilation error on a transient dependency
clap = { version = "3.1.18", features = ["derive", "cargo", "env"] }
clap_complete = "3.2.1"
log = "0.4.19"
stderrlog = "0.6"
cookie = "0.18"
cookie = "0.14"
regex = "1"
lazy_static = "1.4.0"
uuid = { version = "1.8", features = ["v4"] }
steamguard = { version = "^0.14.0", path = "./steamguard" }
dirs = "5.0.1"
uuid = { version = "0.8", features = ["v4"] }
steamguard = { version = "^0.12.6", path = "./steamguard" }
dirs = "3.0.2"
aes = { version = "0.8.3", features = ["zeroize"] }
thiserror = "1.0.61"
thiserror = "1.0.26"
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"
secrecy = { version = "0.8", features = ["serde"] }
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"] }
sha1 = "0.10.5"
rayon = "1.7.0"
rqrr = "0.7.1"
image = "0.25"
[dev-dependencies]
tempfile = "3"

View file

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

View file

@ -3,7 +3,7 @@
[![Lint, Build, Test](https://github.com/dyc3/steamguard-cli/actions/workflows/rust.yml/badge.svg)](https://github.com/dyc3/steamguard-cli/actions/workflows/rust.yml)
[![AUR Tester](https://github.com/dyc3/steamguard-cli/actions/workflows/aur-checker.yml/badge.svg)](https://github.com/dyc3/steamguard-cli/actions/workflows/aur-checker.yml)
A command line utility for setting up and using Steam Mobile Authenticator (AKA Steam 2FA). It can also be used to respond to trade, market, and any other steam mobile confirmations that you would normally get in the app.
A command line utility for setting up and using Steam Mobile Authenticator (AKA Steam 2FA). It can also be used to respond to trade and market confirmations.
**The only legitimate place to download steamguard-cli binaries is through this repo's releases, or by any package manager that is linked in this document.**

View file

@ -54,18 +54,7 @@ This will do everything needed to release a new version:
"""
echo "Previewing changes..."
params=()
if [[ $BUMP != "" ]]; then
params+=(--bump "$BUMP")
params+=(--bump-dependencies "$BUMP")
fi
if [[ $SKIP_CRATE_PUBLISH == true ]]; then
params+=(--no-publish)
fi
if [[ $ALLOW_DIRTY == true ]]; then
params+=(--allow-dirty)
fi
cargo smart-release --update-crates-index --no-changelog --no-tag --no-push --no-publish "${params[@]}"
cargo smart-release --update-crates-index --no-changelog --no-tag --no-push --no-publish
if [ "$DRY_RUN" = true ]; then
echo "This is a dry run, nothing will be done. Artifacts will be built, but not published. Use --execute to do it for real."
@ -75,10 +64,20 @@ fi
echo "Press any key to continue..."
read -n 1 -s -r
params=()
if [[ $DRY_RUN == false ]]; then
params+=(--execute)
fi
if [[ $BUMP != "" ]]; then
params+=(--bump "$BUMP")
params+=(--bump-dependencies "$BUMP")
fi
if [[ $SKIP_CRATE_PUBLISH == true ]]; then
params+=(--no-publish)
fi
if [[ $ALLOW_DIRTY == true ]]; then
params+=(--allow-dirty)
fi
cargo smart-release --update-crates-index --no-changelog --no-tag --no-push --no-publish "${params[@]}"
#echo "Verify that the publish succeeded, and Press any key to continue..."

View file

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

View file

@ -1,6 +1,6 @@
use std::sync::{Arc, Mutex};
use clap::{Parser, Subcommand, ValueEnum};
use clap::{clap_derive::ArgEnum, Parser};
use clap_complete::Shell;
use secrecy::SecretString;
use std::str::FromStr;
@ -10,7 +10,6 @@ use crate::AccountManager;
pub mod code;
pub mod completions;
pub mod confirm;
pub mod debug;
pub mod decrypt;
pub mod encrypt;
@ -20,10 +19,10 @@ pub mod qr;
pub mod qr_login;
pub mod remove;
pub mod setup;
pub mod trade;
pub use code::CodeCommand;
pub use completions::CompletionsCommand;
pub use confirm::ConfirmCommand;
pub use debug::DebugCommand;
pub use decrypt::DecryptCommand;
pub use encrypt::EncryptCommand;
@ -33,6 +32,7 @@ pub use qr::QrCommand;
pub use qr_login::QrLoginCommand;
pub use remove::RemoveCommand;
pub use setup::SetupCommand;
pub use trade::TradeCommand;
/// A command that does not operate on the manifest or individual accounts.
pub(crate) trait ConstCommand {
@ -127,7 +127,7 @@ pub(crate) struct GlobalArgs {
help = "Specify your encryption passkey."
)]
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,
#[cfg(feature = "updater")]
@ -160,14 +160,13 @@ pub(crate) struct GlobalArgs {
pub danger_accept_invalid_certs: bool,
}
#[derive(Debug, Clone, Subcommand)]
#[derive(Debug, Clone, Parser)]
pub(crate) enum Subcommands {
Debug(DebugCommand),
Completion(CompletionsCommand),
Setup(SetupCommand),
Import(ImportCommand),
#[clap(alias = "trade")]
Confirm(ConfirmCommand),
Trade(TradeCommand),
Remove(RemoveCommand),
Encrypt(EncryptCommand),
Decrypt(DecryptCommand),
@ -177,7 +176,7 @@ pub(crate) enum Subcommands {
QrLogin(QrLoginCommand),
}
#[derive(Debug, Clone, Copy, ValueEnum)]
#[derive(Debug, Clone, Copy, ArgEnum)]
pub(crate) enum Verbosity {
Error = 0,
Warn = 1,
@ -223,14 +222,3 @@ impl From<Args> for CodeCommand {
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)]
#[clap(about = "Generate shell completions")]
pub struct CompletionsCommand {
#[clap(
short,
long,
value_enum,
help = "The shell to generate completions for."
)]
#[clap(short, long, arg_enum, help = "The shell to generate completions for.")]
pub shell: Shell,
}

View file

@ -1,10 +1,6 @@
use std::{
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use std::sync::{Arc, Mutex};
use log::*;
use rqrr::PreparedImage;
use steamguard::{QrApprover, QrApproverError};
use crate::AccountManager;
@ -14,8 +10,11 @@ use super::*;
#[derive(Debug, Clone, Parser)]
#[clap(about = "Log in to Steam on another device using the QR code that it's displaying.")]
pub struct QrLoginCommand {
#[clap(flatten)]
login_url_source: LoginUrlSource,
#[clap(
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
@ -42,8 +41,6 @@ where
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 {
let Some(tokens) = account.tokens.as_ref() else {
error!(
@ -54,7 +51,7 @@ where
};
let mut approver = QrApprover::new(transport.clone(), tokens);
match approver.approve(&account, url.to_owned()) {
match approver.approve(&account, &self.url) {
Ok(_) => {
info!("Login approved.");
break;
@ -73,56 +70,3 @@ where
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",
status.state()
);
debug!("full status: {:#?}", status);
manager.remove_account(&account_name);
manager.save()?;
bail!("Authenticator finalization was unsuccessful. You may have entered the wrong confirm code in the previous step. Try again.");

View file

@ -9,12 +9,12 @@ use crate::{tui, AccountManager};
use super::*;
#[derive(Debug, Clone, Parser)]
#[clap(about = "Interactive interface for steam mobile confirmations")]
pub struct ConfirmCommand {
#[clap(about = "Interactive interface for trade confirmations")]
pub struct TradeCommand {
#[clap(
short,
long,
help = "Accept all open mobile confirmations. Does not open interactive interface."
help = "Accept all open trade confirmations. Does not open interactive interface."
)]
pub accept_all: bool,
#[clap(
@ -25,7 +25,7 @@ pub struct ConfirmCommand {
pub fail_fast: bool,
}
impl<T> AccountCommand<T> for ConfirmCommand
impl<T> AccountCommand<T> for TradeCommand
where
T: Transport + Clone,
{
@ -44,12 +44,12 @@ where
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
}
info!("{}: Checking for confirmations", account.account_name);
info!("{}: Checking for trade confirmations", account.account_name);
let confirmations: Vec<Confirmation>;
loop {
let confirmer = Confirmer::new(transport.clone(), &account);
match confirmer.get_confirmations() {
match confirmer.get_trade_confirmations() {
Ok(confs) => {
confirmations = confs;
break;
@ -59,7 +59,7 @@ where
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
}
Err(err) => {
error!("Failed to get confirmations: {}", err);
error!("Failed to get trade confirmations: {}", err);
return Err(err.into());
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -86,7 +86,7 @@ fn run(args: commands::Args) -> anyhow::Result<()> {
Subcommands::Import(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Encrypt(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Decrypt(args) => CommandType::Manifest(Box::new(args)),
Subcommands::Confirm(args) => CommandType::Account(Box::new(args)),
Subcommands::Trade(args) => CommandType::Account(Box::new(args)),
Subcommands::Remove(args) => CommandType::Account(Box::new(args)),
Subcommands::Code(args) => CommandType::Account(Box::new(args)),
#[cfg(feature = "qr")]

View file

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

View file

@ -137,7 +137,10 @@ where
let mut req = CTwoFactor_Status_Request::new();
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())
}

View file

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

View file

@ -82,7 +82,7 @@ where
cookies
}
pub fn get_confirmations(&self) -> Result<Vec<Confirmation>, ConfirmerError> {
pub fn get_trade_confirmations(&self) -> Result<Vec<Confirmation>, ConfirmerError> {
let cookies = self.build_cookie_jar();
let client = self.transport.innner_http_client()?;