Compare commits
30 commits
steamguard
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
96a30c6150 | ||
|
dd153a617c | ||
|
28c7a797cf | ||
|
c2a72fee6c | ||
|
602acc6641 | ||
|
b4564b7d5e | ||
|
d30ba017c9 | ||
|
37ae7c76a6 | ||
|
ef72bd898c | ||
|
52044c95bb | ||
|
b24a7415f6 | ||
|
8d54a1254a | ||
|
ab5cb00ee3 | ||
|
c7fefc1452 | ||
|
a64f7d4134 | ||
|
0a41e8b042 | ||
|
d3c1dfb3d5 | ||
|
a8d3f3fc99 | ||
|
5e24031719 | ||
|
d67e925a47 | ||
|
4c9f494ed3 | ||
|
46a1c2ed04 | ||
|
ad146c97c9 | ||
|
d96b6e65a6 | ||
|
84da432b2d | ||
|
2629b0b715 | ||
|
f7a4ce6133 | ||
|
c5d51b5174 | ||
|
8394baa157 | ||
|
25280abc62 |
21 changed files with 1262 additions and 1131 deletions
3
.github/workflows/aur-checker.yml
vendored
3
.github/workflows/aur-checker.yml
vendored
|
@ -5,12 +5,13 @@ on:
|
||||||
- cron: "32 5 */3 * *"
|
- cron: "32 5 */3 * *"
|
||||||
push:
|
push:
|
||||||
branches: [ master, main ]
|
branches: [ master, main ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-install:
|
test-install:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
- name: Install AUR package
|
- name: Install AUR package
|
||||||
run: ./scripts/check-aur.sh
|
run: ./scripts/check-aur.sh
|
10
.github/workflows/rust.yml
vendored
10
.github/workflows/rust.yml
vendored
|
@ -15,9 +15,9 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- run: rustup component add clippy rustfmt
|
- run: rustup component add clippy rustfmt
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.5.0
|
uses: Swatinem/rust-cache@v2
|
||||||
- name: Check format
|
- name: Check format
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
- name: Check
|
- name: Check
|
||||||
|
@ -34,13 +34,13 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
target: [x86_64-unknown-linux-musl, x86_64-pc-windows-gnu]
|
target: [x86_64-unknown-linux-musl, x86_64-pc-windows-gnu]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2.5.0
|
uses: Swatinem/rust-cache@v2
|
||||||
with:
|
with:
|
||||||
prefix-key: v0-rust-${{ matrix.target }}
|
prefix-key: v0-rust-${{ matrix.target }}
|
||||||
- name: Install Cross
|
- name: Install Cross
|
||||||
uses: baptiste0928/cargo-install@v1
|
uses: baptiste0928/cargo-install@v2
|
||||||
with:
|
with:
|
||||||
crate: cross
|
crate: cross
|
||||||
- name: Check
|
- name: Check
|
||||||
|
|
2103
Cargo.lock
generated
2103
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
43
Cargo.toml
43
Cargo.toml
|
@ -4,9 +4,9 @@ members = ["steamguard"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "steamguard-cli"
|
name = "steamguard-cli"
|
||||||
version = "0.12.5"
|
version = "0.14.0"
|
||||||
authors = ["dyc3 (Carson McManus) <carson.mcmanus1@gmail.com>"]
|
authors = ["dyc3 (Carson McManus) <carson.mcmanus1@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
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"]
|
||||||
|
@ -27,38 +27,43 @@ keyring = ["dep:keyring"]
|
||||||
name = "steamguard"
|
name = "steamguard"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "^1.0"
|
anyhow = "^1.0"
|
||||||
base64 = "0.21.2"
|
base64 = "0.22.1"
|
||||||
text_io = "0.1.8"
|
text_io = "0.1.8"
|
||||||
rpassword = "7.2.0"
|
rpassword = "7.2.0"
|
||||||
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "cookies", "gzip", "rustls-tls"] }
|
reqwest = { version = "0.12", default-features = false, features = [
|
||||||
|
"blocking",
|
||||||
|
"json",
|
||||||
|
"cookies",
|
||||||
|
"gzip",
|
||||||
|
"rustls-tls",
|
||||||
|
] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
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"
|
||||||
standback = "0.2.17" # required to fix a compilation error on a transient dependency
|
clap = { version = "4.5.4", features = ["derive", "cargo", "env"] }
|
||||||
clap = { version = "3.1.18", features = ["derive", "cargo", "env"] }
|
clap_complete = "4.5.2"
|
||||||
clap_complete = "3.2.1"
|
|
||||||
log = "0.4.19"
|
log = "0.4.19"
|
||||||
stderrlog = "0.4"
|
stderrlog = "0.6"
|
||||||
cookie = "0.14"
|
cookie = "0.18"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "1.8", features = ["v4"] }
|
||||||
steamguard = { version = "^0.12.5", path = "./steamguard" }
|
steamguard = { version = "^0.14.0", path = "./steamguard" }
|
||||||
dirs = "3.0.2"
|
dirs = "5.0.1"
|
||||||
aes = { version = "0.8.3", features = ["zeroize"] }
|
aes = { version = "0.8.3", features = ["zeroize"] }
|
||||||
thiserror = "1.0.26"
|
thiserror = "1.0.61"
|
||||||
crossterm = { version = "0.23.2", features = ["event-stream"] }
|
crossterm = { version = "0.23.2", features = ["event-stream"] }
|
||||||
qrcode = { version = "0.12.0", optional = true }
|
qrcode = { version = "0.14.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"] }
|
||||||
serde_path_to_error = "0.1.11"
|
serde_path_to_error = "0.1.11"
|
||||||
update-informer = { version = "1.0.0", optional = true, default-features = false, features = ["github"] }
|
update-informer = { version = "1.0.0", optional = true, default-features = false, features = [
|
||||||
|
"github",
|
||||||
|
] }
|
||||||
phonenumber = "0.3"
|
phonenumber = "0.3"
|
||||||
cbc = { version = "0.1.2", features = ["std", "zeroize"] }
|
cbc = { version = "0.1.2", features = ["std", "zeroize"] }
|
||||||
inout = { version = "0.1.3", features = ["std"] }
|
inout = { version = "0.1.3", features = ["std"] }
|
||||||
|
@ -67,9 +72,11 @@ 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]
|
||||||
tempdir = "0.3"
|
tempfile = "3"
|
||||||
proptest = "1"
|
proptest = "1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|
3
PKGBUILD
3
PKGBUILD
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
_pkgname=steamguard-cli
|
_pkgname=steamguard-cli
|
||||||
pkgname=${_pkgname}-git
|
pkgname=${_pkgname}-git
|
||||||
pkgver=0.8.1.r1.fe0d6e9a
|
pkgver=0.14.0.r1.602acc66
|
||||||
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,6 +12,7 @@ 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}"
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
[](https://github.com/dyc3/steamguard-cli/actions/workflows/rust.yml)
|
[](https://github.com/dyc3/steamguard-cli/actions/workflows/rust.yml)
|
||||||
[](https://github.com/dyc3/steamguard-cli/actions/workflows/aur-checker.yml)
|
[](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 and market confirmations.
|
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.
|
||||||
|
|
||||||
**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.**
|
**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.**
|
||||||
|
|
||||||
|
|
|
@ -54,20 +54,7 @@ This will do everything needed to release a new version:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
echo "Previewing changes..."
|
echo "Previewing changes..."
|
||||||
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."
|
|
||||||
else
|
|
||||||
echo "This is not a dry run. This is the real deal!"
|
|
||||||
fi
|
|
||||||
echo "Press any key to continue..."
|
|
||||||
read -n 1 -s -r
|
|
||||||
|
|
||||||
params=()
|
params=()
|
||||||
if [[ $DRY_RUN == false ]]; then
|
|
||||||
params+=(--execute)
|
|
||||||
fi
|
|
||||||
if [[ $BUMP != "" ]]; then
|
if [[ $BUMP != "" ]]; then
|
||||||
params+=(--bump "$BUMP")
|
params+=(--bump "$BUMP")
|
||||||
params+=(--bump-dependencies "$BUMP")
|
params+=(--bump-dependencies "$BUMP")
|
||||||
|
@ -80,6 +67,20 @@ if [[ $ALLOW_DIRTY == true ]]; then
|
||||||
fi
|
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 "${params[@]}"
|
||||||
|
|
||||||
|
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."
|
||||||
|
else
|
||||||
|
echo "This is not a dry run. This is the real deal!"
|
||||||
|
fi
|
||||||
|
echo "Press any key to continue..."
|
||||||
|
read -n 1 -s -r
|
||||||
|
|
||||||
|
|
||||||
|
if [[ $DRY_RUN == false ]]; then
|
||||||
|
params+=(--execute)
|
||||||
|
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..."
|
#echo "Verify that the publish succeeded, and Press any key to continue..."
|
||||||
# read -n 1 -s -r
|
# read -n 1 -s -r
|
||||||
|
|
||||||
|
|
|
@ -339,8 +339,9 @@ 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].account_name =
|
self.manifest.entries[i]
|
||||||
account.lock().unwrap().account_name.clone();
|
.account_name
|
||||||
|
.clone_from(&account.lock().unwrap().account_name);
|
||||||
}
|
}
|
||||||
upgraded = true;
|
upgraded = true;
|
||||||
}
|
}
|
||||||
|
@ -470,11 +471,11 @@ pub enum ManifestAccountImportError {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use steamguard::ExposeSecret;
|
use steamguard::ExposeSecret;
|
||||||
use tempdir::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_save_new_manifest() {
|
fn test_should_save_new_manifest() {
|
||||||
let tmp_dir = TempDir::new("steamguard-cli-test").unwrap();
|
let tmp_dir = TempDir::new().unwrap();
|
||||||
let manifest_path = tmp_dir.path().join("manifest.json");
|
let manifest_path = tmp_dir.path().join("manifest.json");
|
||||||
let manager = AccountManager::new(manifest_path.as_path());
|
let manager = AccountManager::new(manifest_path.as_path());
|
||||||
assert!(manager.save().is_ok());
|
assert!(manager.save().is_ok());
|
||||||
|
@ -482,7 +483,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_save_and_load_manifest() -> anyhow::Result<()> {
|
fn test_should_save_and_load_manifest() -> anyhow::Result<()> {
|
||||||
let tmp_dir = TempDir::new("steamguard-cli-test")?;
|
let tmp_dir = TempDir::new()?;
|
||||||
let manifest_path = tmp_dir.path().join("manifest.json");
|
let manifest_path = tmp_dir.path().join("manifest.json");
|
||||||
println!("tempdir: {}", manifest_path.display());
|
println!("tempdir: {}", manifest_path.display());
|
||||||
let mut manager = AccountManager::new(manifest_path.as_path());
|
let mut manager = AccountManager::new(manifest_path.as_path());
|
||||||
|
@ -518,7 +519,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_save_and_load_manifest_encrypted() -> anyhow::Result<()> {
|
fn test_should_save_and_load_manifest_encrypted() -> anyhow::Result<()> {
|
||||||
let passkey = Some(SecretString::new("password".into()));
|
let passkey = Some(SecretString::new("password".into()));
|
||||||
let tmp_dir = TempDir::new("steamguard-cli-test")?;
|
let tmp_dir = TempDir::new()?;
|
||||||
let manifest_path = tmp_dir.path().join("manifest.json");
|
let manifest_path = tmp_dir.path().join("manifest.json");
|
||||||
let mut manager = AccountManager::new(manifest_path.as_path());
|
let mut manager = AccountManager::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
|
@ -567,7 +568,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_save_and_load_manifest_encrypted_longer() -> anyhow::Result<()> {
|
fn test_should_save_and_load_manifest_encrypted_longer() -> anyhow::Result<()> {
|
||||||
let passkey = Some(SecretString::new("password".into()));
|
let passkey = Some(SecretString::new("password".into()));
|
||||||
let tmp_dir = TempDir::new("steamguard-cli-test")?;
|
let tmp_dir = TempDir::new()?;
|
||||||
let manifest_path = tmp_dir.path().join("manifest.json");
|
let manifest_path = tmp_dir.path().join("manifest.json");
|
||||||
let mut manager = AccountManager::new(manifest_path.as_path());
|
let mut manager = AccountManager::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
|
@ -614,7 +615,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_import() -> anyhow::Result<()> {
|
fn test_should_import() -> anyhow::Result<()> {
|
||||||
let tmp_dir = TempDir::new("steamguard-cli-test")?;
|
let tmp_dir = TempDir::new()?;
|
||||||
let manifest_path = tmp_dir.path().join("manifest.json");
|
let manifest_path = tmp_dir.path().join("manifest.json");
|
||||||
let mut manager = AccountManager::new(manifest_path.as_path());
|
let mut manager = AccountManager::new(manifest_path.as_path());
|
||||||
let mut account = SteamGuardAccount::new();
|
let mut account = SteamGuardAccount::new();
|
||||||
|
|
|
@ -319,6 +319,8 @@ impl From<ExternalAccount> for SteamGuardAccount {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
use crate::{accountmanager::CURRENT_MANIFEST_VERSION, AccountManager};
|
use crate::{accountmanager::CURRENT_MANIFEST_VERSION, AccountManager};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -477,7 +479,7 @@ mod tests {
|
||||||
];
|
];
|
||||||
for case in cases {
|
for case in cases {
|
||||||
eprintln!("testing: {:?}", case);
|
eprintln!("testing: {:?}", case);
|
||||||
let temp = tempdir::TempDir::new("steamguard-cli-test")?;
|
let temp = TempDir::new()?;
|
||||||
for file in std::fs::read_dir(case.dir)? {
|
for file in std::fs::read_dir(case.dir)? {
|
||||||
let file = file?;
|
let file = file?;
|
||||||
let path = file.path();
|
let path = file.path();
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use clap::{clap_derive::ArgEnum, Parser};
|
use clap::{Parser, Subcommand, ValueEnum};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
use secrecy::SecretString;
|
use secrecy::SecretString;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
@ -10,6 +10,7 @@ use crate::AccountManager;
|
||||||
|
|
||||||
pub mod code;
|
pub mod code;
|
||||||
pub mod completions;
|
pub mod completions;
|
||||||
|
pub mod confirm;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod decrypt;
|
pub mod decrypt;
|
||||||
pub mod encrypt;
|
pub mod encrypt;
|
||||||
|
@ -19,10 +20,10 @@ pub mod qr;
|
||||||
pub mod qr_login;
|
pub mod qr_login;
|
||||||
pub mod remove;
|
pub mod remove;
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
pub mod trade;
|
|
||||||
|
|
||||||
pub use code::CodeCommand;
|
pub use code::CodeCommand;
|
||||||
pub use completions::CompletionsCommand;
|
pub use completions::CompletionsCommand;
|
||||||
|
pub use confirm::ConfirmCommand;
|
||||||
pub use debug::DebugCommand;
|
pub use debug::DebugCommand;
|
||||||
pub use decrypt::DecryptCommand;
|
pub use decrypt::DecryptCommand;
|
||||||
pub use encrypt::EncryptCommand;
|
pub use encrypt::EncryptCommand;
|
||||||
|
@ -32,7 +33,6 @@ pub use qr::QrCommand;
|
||||||
pub use qr_login::QrLoginCommand;
|
pub use qr_login::QrLoginCommand;
|
||||||
pub use remove::RemoveCommand;
|
pub use remove::RemoveCommand;
|
||||||
pub use setup::SetupCommand;
|
pub use setup::SetupCommand;
|
||||||
pub use trade::TradeCommand;
|
|
||||||
|
|
||||||
/// A command that does not operate on the manifest or individual accounts.
|
/// A command that does not operate on the manifest or individual accounts.
|
||||||
pub(crate) trait ConstCommand {
|
pub(crate) trait ConstCommand {
|
||||||
|
@ -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, arg_enum, default_value_t=Verbosity::Info, help = "Set the log level. Be warned, trace is capable of printing sensitive data.")]
|
#[clap(short, long, value_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,13 +160,14 @@ pub(crate) struct GlobalArgs {
|
||||||
pub danger_accept_invalid_certs: bool,
|
pub danger_accept_invalid_certs: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Subcommand)]
|
||||||
pub(crate) enum Subcommands {
|
pub(crate) enum Subcommands {
|
||||||
Debug(DebugCommand),
|
Debug(DebugCommand),
|
||||||
Completion(CompletionsCommand),
|
Completion(CompletionsCommand),
|
||||||
Setup(SetupCommand),
|
Setup(SetupCommand),
|
||||||
Import(ImportCommand),
|
Import(ImportCommand),
|
||||||
Trade(TradeCommand),
|
#[clap(alias = "trade")]
|
||||||
|
Confirm(ConfirmCommand),
|
||||||
Remove(RemoveCommand),
|
Remove(RemoveCommand),
|
||||||
Encrypt(EncryptCommand),
|
Encrypt(EncryptCommand),
|
||||||
Decrypt(DecryptCommand),
|
Decrypt(DecryptCommand),
|
||||||
|
@ -176,7 +177,7 @@ pub(crate) enum Subcommands {
|
||||||
QrLogin(QrLoginCommand),
|
QrLogin(QrLoginCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, ArgEnum)]
|
#[derive(Debug, Clone, Copy, ValueEnum)]
|
||||||
pub(crate) enum Verbosity {
|
pub(crate) enum Verbosity {
|
||||||
Error = 0,
|
Error = 0,
|
||||||
Warn = 1,
|
Warn = 1,
|
||||||
|
@ -222,3 +223,14 @@ 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,12 @@ 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(short, long, arg_enum, help = "The shell to generate completions for.")]
|
#[clap(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
value_enum,
|
||||||
|
help = "The shell to generate completions for."
|
||||||
|
)]
|
||||||
pub shell: Shell,
|
pub shell: Shell,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,12 @@ use crate::{tui, AccountManager};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Parser)]
|
#[derive(Debug, Clone, Parser)]
|
||||||
#[clap(about = "Interactive interface for trade confirmations")]
|
#[clap(about = "Interactive interface for steam mobile confirmations")]
|
||||||
pub struct TradeCommand {
|
pub struct ConfirmCommand {
|
||||||
#[clap(
|
#[clap(
|
||||||
short,
|
short,
|
||||||
long,
|
long,
|
||||||
help = "Accept all open trade confirmations. Does not open interactive interface."
|
help = "Accept all open mobile confirmations. Does not open interactive interface."
|
||||||
)]
|
)]
|
||||||
pub accept_all: bool,
|
pub accept_all: bool,
|
||||||
#[clap(
|
#[clap(
|
||||||
|
@ -25,7 +25,7 @@ pub struct TradeCommand {
|
||||||
pub fail_fast: bool,
|
pub fail_fast: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> AccountCommand<T> for TradeCommand
|
impl<T> AccountCommand<T> for ConfirmCommand
|
||||||
where
|
where
|
||||||
T: Transport + Clone,
|
T: Transport + Clone,
|
||||||
{
|
{
|
||||||
|
@ -44,12 +44,12 @@ where
|
||||||
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
|
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("{}: Checking for trade confirmations", account.account_name);
|
info!("{}: Checking for confirmations", account.account_name);
|
||||||
let confirmations: Vec<Confirmation>;
|
let confirmations: Vec<Confirmation>;
|
||||||
loop {
|
loop {
|
||||||
let confirmer = Confirmer::new(transport.clone(), &account);
|
let confirmer = Confirmer::new(transport.clone(), &account);
|
||||||
|
|
||||||
match confirmer.get_trade_confirmations() {
|
match confirmer.get_confirmations() {
|
||||||
Ok(confs) => {
|
Ok(confs) => {
|
||||||
confirmations = confs;
|
confirmations = confs;
|
||||||
break;
|
break;
|
||||||
|
@ -59,7 +59,7 @@ where
|
||||||
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
|
crate::do_login(transport.clone(), &mut account, args.password.clone())?;
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Failed to get trade confirmations: {}", err);
|
error!("Failed to get confirmations: {}", err);
|
||||||
return Err(err.into());
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
use std::sync::{Arc, Mutex};
|
use std::{
|
||||||
|
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;
|
||||||
|
@ -10,11 +14,8 @@ 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(
|
#[clap(flatten)]
|
||||||
long,
|
login_url_source: LoginUrlSource,
|
||||||
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
|
||||||
|
@ -41,6 +42,8 @@ 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!(
|
||||||
|
@ -51,7 +54,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut approver = QrApprover::new(transport.clone(), tokens);
|
let mut approver = QrApprover::new(transport.clone(), tokens);
|
||||||
match approver.approve(&account, &self.url) {
|
match approver.approve(&account, url.to_owned()) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
info!("Login approved.");
|
info!("Login approved.");
|
||||||
break;
|
break;
|
||||||
|
@ -70,3 +73,56 @@ 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -193,6 +193,7 @@ 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.");
|
||||||
|
|
BIN
src/fixtures/qr-codes/login-qr.png
Normal file
BIN
src/fixtures/qr-codes/login-qr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -86,7 +86,7 @@ fn run(args: commands::Args) -> anyhow::Result<()> {
|
||||||
Subcommands::Import(args) => CommandType::Manifest(Box::new(args)),
|
Subcommands::Import(args) => CommandType::Manifest(Box::new(args)),
|
||||||
Subcommands::Encrypt(args) => CommandType::Manifest(Box::new(args)),
|
Subcommands::Encrypt(args) => CommandType::Manifest(Box::new(args)),
|
||||||
Subcommands::Decrypt(args) => CommandType::Manifest(Box::new(args)),
|
Subcommands::Decrypt(args) => CommandType::Manifest(Box::new(args)),
|
||||||
Subcommands::Trade(args) => CommandType::Account(Box::new(args)),
|
Subcommands::Confirm(args) => CommandType::Account(Box::new(args)),
|
||||||
Subcommands::Remove(args) => CommandType::Account(Box::new(args)),
|
Subcommands::Remove(args) => CommandType::Account(Box::new(args)),
|
||||||
Subcommands::Code(args) => CommandType::Account(Box::new(args)),
|
Subcommands::Code(args) => CommandType::Account(Box::new(args)),
|
||||||
#[cfg(feature = "qr")]
|
#[cfg(feature = "qr")]
|
||||||
|
|
|
@ -1,31 +1,34 @@
|
||||||
[package]
|
[package]
|
||||||
name = "steamguard"
|
name = "steamguard"
|
||||||
version = "0.12.5"
|
version = "0.14.0"
|
||||||
authors = ["Carson McManus <carson.mcmanus1@gmail.com>"]
|
authors = ["Carson McManus <carson.mcmanus1@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2021"
|
||||||
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"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "^1.0"
|
anyhow = "^1.0"
|
||||||
sha1 = "^0.10"
|
sha1 = "^0.10"
|
||||||
base64 = "^0.21"
|
base64 = "^0.22.1"
|
||||||
reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "cookies", "gzip", "rustls-tls", "multipart"] }
|
reqwest = { version = "0.12", default-features = false, features = [
|
||||||
|
"blocking",
|
||||||
|
"json",
|
||||||
|
"cookies",
|
||||||
|
"gzip",
|
||||||
|
"rustls-tls",
|
||||||
|
"multipart",
|
||||||
|
] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
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"
|
||||||
standback = "0.2.17" # required to fix a compilation error on a transient dependency
|
cookie = "0.18"
|
||||||
cookie = "0.14"
|
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
uuid = { version = "0.8", features = ["v4"] }
|
uuid = { version = "1.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"] }
|
||||||
|
@ -36,6 +39,7 @@ phonenumber = "0.3"
|
||||||
serde_path_to_error = "0.1.11"
|
serde_path_to_error = "0.1.11"
|
||||||
hmac = "^0.12"
|
hmac = "^0.12"
|
||||||
sha2 = "^0.10"
|
sha2 = "^0.10"
|
||||||
|
num_enum = "0.7.2"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
anyhow = "^1.0"
|
anyhow = "^1.0"
|
||||||
|
|
|
@ -137,10 +137,7 @@ 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
|
let resp = self.client.query_status(req, self.tokens.access_token())?;
|
||||||
.client
|
|
||||||
.query_status(req, self.tokens.access_token())
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(resp.into_response_data())
|
Ok(resp.into_response_data())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
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,
|
||||||
|
|
|
@ -3,5 +3,3 @@ mod login;
|
||||||
mod phone_ajax;
|
mod phone_ajax;
|
||||||
|
|
||||||
pub use i_authentication_service::*;
|
pub use i_authentication_service::*;
|
||||||
pub use login::*;
|
|
||||||
pub use phone_ajax::*;
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ where
|
||||||
cookies
|
cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_trade_confirmations(&self) -> Result<Vec<Confirmation>, ConfirmerError> {
|
pub fn get_confirmations(&self) -> Result<Vec<Confirmation>, ConfirmerError> {
|
||||||
let cookies = self.build_cookie_jar();
|
let cookies = self.build_cookie_jar();
|
||||||
let client = self.transport.innner_http_client()?;
|
let client = self.transport.innner_http_client()?;
|
||||||
|
|
||||||
|
@ -367,34 +367,27 @@ impl Confirmation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, num_enum::FromPrimitive)]
|
||||||
#[repr(u32)]
|
#[repr(u32)]
|
||||||
#[serde(from = "u32")]
|
#[serde(from = "u32")]
|
||||||
/// Source: <https://github.com/SteamDatabase/SteamTracking/blob/6e7797e69b714c59f4b5784780b24753c17732ba/Structs/enums.steamd#L1607-L1616>
|
/// Source: https://github.com/SteamDatabase/SteamTracking/blob/6e7797e69b714c59f4b5784780b24753c17732ba/Structs/enums.steamd#L1607-L1616
|
||||||
|
/// There are also some additional undocumented types.
|
||||||
pub enum ConfirmationType {
|
pub enum ConfirmationType {
|
||||||
Test = 1,
|
Test = 1,
|
||||||
|
/// Occurs when sending a trade offer or accepting a received trade offer, only when there is items on the user's side
|
||||||
Trade = 2,
|
Trade = 2,
|
||||||
|
/// Occurs when selling an item on the Steam community market
|
||||||
MarketSell = 3,
|
MarketSell = 3,
|
||||||
FeatureOptOut = 4,
|
FeatureOptOut = 4,
|
||||||
|
/// Occurs when changing the phone number associated with the account
|
||||||
PhoneNumberChange = 5,
|
PhoneNumberChange = 5,
|
||||||
AccountRecovery = 6,
|
AccountRecovery = 6,
|
||||||
|
/// Occurs when a new web API key is created via https://steamcommunity.com/dev/apikey
|
||||||
|
ApiKeyCreation = 9,
|
||||||
|
#[num_enum(catch_all)]
|
||||||
Unknown(u32),
|
Unknown(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u32> for ConfirmationType {
|
|
||||||
fn from(text: u32) -> Self {
|
|
||||||
match text {
|
|
||||||
1 => ConfirmationType::Test,
|
|
||||||
2 => ConfirmationType::Trade,
|
|
||||||
3 => ConfirmationType::MarketSell,
|
|
||||||
4 => ConfirmationType::FeatureOptOut,
|
|
||||||
5 => ConfirmationType::PhoneNumberChange,
|
|
||||||
6 => ConfirmationType::AccountRecovery,
|
|
||||||
v => ConfirmationType::Unknown(v),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ConfirmationListResponse {
|
pub struct ConfirmationListResponse {
|
||||||
pub success: bool,
|
pub success: bool,
|
||||||
|
|
Loading…
Add table
Reference in a new issue