parent
6d4915a39c
commit
94a3210f09
5 changed files with 110 additions and 18 deletions
|
@ -16,6 +16,7 @@ mod legacy;
|
|||
pub mod manifest;
|
||||
pub mod migrate;
|
||||
mod steamv2;
|
||||
mod winauth;
|
||||
|
||||
pub use manifest::*;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{fs::File, io::Read, path::Path};
|
||||
|
||||
use log::debug;
|
||||
use log::*;
|
||||
use secrecy::SecretString;
|
||||
use serde::{de::Error, Deserialize};
|
||||
use steamguard::SteamGuardAccount;
|
||||
|
@ -12,6 +12,7 @@ use super::{
|
|||
legacy::{SdaAccount, SdaManifest},
|
||||
manifest::ManifestV1,
|
||||
steamv2::SteamMobileV2,
|
||||
winauth::parse_winauth_exports,
|
||||
EntryLoader, Manifest,
|
||||
};
|
||||
|
||||
|
@ -261,23 +262,48 @@ impl From<MigratingAccount> for SteamGuardAccount {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_and_upgrade_external_account(path: &Path) -> anyhow::Result<SteamGuardAccount> {
|
||||
let file = File::open(path)?;
|
||||
let mut deser = serde_json::Deserializer::from_reader(&file);
|
||||
let account: ExternalAccount = serde_path_to_error::deserialize(&mut deser)
|
||||
.map_err(|err| anyhow::anyhow!("Failed to deserialize account: {}", err))?;
|
||||
let mut account = MigratingAccount::External(account);
|
||||
pub fn load_and_upgrade_external_accounts(path: &Path) -> anyhow::Result<Vec<SteamGuardAccount>> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut buf = vec![];
|
||||
file.read_to_end(&mut buf)?;
|
||||
let mut deser = serde_json::Deserializer::from_slice(&buf);
|
||||
let accounts = match serde_path_to_error::deserialize(&mut deser) {
|
||||
Ok(account) => {
|
||||
vec![MigratingAccount::External(account)]
|
||||
}
|
||||
Err(json_err) => {
|
||||
// the file is not JSON, so it's probably a winauth export
|
||||
match parse_winauth_exports(buf) {
|
||||
Ok(accounts) => accounts
|
||||
.into_iter()
|
||||
.map(MigratingAccount::External)
|
||||
.collect(),
|
||||
Err(winauth_err) => {
|
||||
bail!(
|
||||
"Failed to parse as JSON: {}\nFailed to parse as Winauth export: {}",
|
||||
json_err,
|
||||
winauth_err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(accounts
|
||||
.into_iter()
|
||||
.map(|mut account| {
|
||||
while !account.is_latest() {
|
||||
account = account.upgrade();
|
||||
}
|
||||
|
||||
Ok(account.into())
|
||||
account.into()
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum ExternalAccount {
|
||||
pub(crate) enum ExternalAccount {
|
||||
Sda(SdaAccount),
|
||||
SteamMobileV2(SteamMobileV2),
|
||||
}
|
||||
|
@ -390,10 +416,16 @@ mod tests {
|
|||
account_name: "afarihm",
|
||||
steam_id: 76561199441992970,
|
||||
},
|
||||
Test {
|
||||
mafile: "src/fixtures/maFiles/compat/winauth/exports.txt",
|
||||
account_name: "example",
|
||||
steam_id: 1234,
|
||||
},
|
||||
];
|
||||
for case in cases {
|
||||
eprintln!("testing: {:?}", case);
|
||||
let account = load_and_upgrade_external_account(Path::new(case.mafile))?;
|
||||
let accounts = load_and_upgrade_external_accounts(Path::new(case.mafile))?;
|
||||
let account = accounts[0].clone();
|
||||
assert_eq!(account.account_name, case.account_name);
|
||||
assert_eq!(account.steam_id, case.steam_id);
|
||||
}
|
||||
|
|
50
src/accountmanager/winauth.rs
Normal file
50
src/accountmanager/winauth.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
//! Accounts exported from Winauth are in the following format:
|
||||
//!
|
||||
//! One account per line, with each account represented as a URL.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! otpauth://totp/Steam:<steamaccountname>?secret=<ABCDEFG1234_secret_dunno_what_for>&digits=5&issuer=Steam&deviceid=<URL_Escaped_device_name>&data=<url_encoded_data_json>
|
||||
//! ```
|
||||
//!
|
||||
//! The `data` field is a URL encoded JSON object with the following fields:
|
||||
//!
|
||||
//! ```json
|
||||
//! {"steamid":"<steam_id>","status":1,"shared_secret":"<shared_secret>","serial_number":"<serial_number>","revocation_code":"<revocation_code>","uri":"<uri>","server_time":"<server_time>","account_name":"<steam_login_name>","token_gid":"<token_gid>","identity_secret":"<identity_secret>","secret_1":"<secret_1>","steamguard_scheme":"2"}
|
||||
//! ```
|
||||
|
||||
use anyhow::Context;
|
||||
use log::*;
|
||||
use reqwest::Url;
|
||||
|
||||
use super::migrate::ExternalAccount;
|
||||
|
||||
pub(crate) fn parse_winauth_exports(buf: Vec<u8>) -> anyhow::Result<Vec<ExternalAccount>> {
|
||||
let buf = String::from_utf8(buf)?;
|
||||
let mut accounts = Vec::new();
|
||||
for line in buf.split('\n') {
|
||||
if line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let url = Url::parse(line).context("parsing as winauth export URL")?;
|
||||
let mut query = url.query_pairs();
|
||||
let issuer = query
|
||||
.find(|(key, _)| key == "issuer")
|
||||
.context("missing issuer field")?
|
||||
.1;
|
||||
if issuer != "Steam" {
|
||||
debug!("skipping non-Steam account: {}", issuer);
|
||||
continue;
|
||||
}
|
||||
let data = query
|
||||
.find(|(key, _)| key == "data")
|
||||
.context("missing data field")?
|
||||
.1;
|
||||
|
||||
trace!("data: {}", data);
|
||||
|
||||
let mut deser = serde_json::Deserializer::from_str(&data);
|
||||
let account = serde_path_to_error::deserialize(&mut deser)?;
|
||||
accounts.push(account);
|
||||
}
|
||||
Ok(accounts)
|
||||
}
|
|
@ -25,6 +25,7 @@ where
|
|||
manager: &mut AccountManager,
|
||||
_args: &GlobalArgs,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut accounts_added = 0;
|
||||
for file_path in self.files.iter() {
|
||||
debug!("loading entry: {:?}", file_path);
|
||||
match manager.import_account(file_path) {
|
||||
|
@ -38,25 +39,31 @@ where
|
|||
debug!("Falling back to external account import",);
|
||||
|
||||
let path = Path::new(&file_path);
|
||||
let account =
|
||||
match crate::accountmanager::migrate::load_and_upgrade_external_account(
|
||||
let accounts =
|
||||
match crate::accountmanager::migrate::load_and_upgrade_external_accounts(
|
||||
path,
|
||||
) {
|
||||
Ok(account) => account,
|
||||
Ok(accounts) => accounts,
|
||||
Err(err) => {
|
||||
error!("Failed to import account: {} {}", &file_path, err);
|
||||
error!("The original error was: {}", orig_err);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
for account in accounts {
|
||||
manager.add_account(account);
|
||||
info!("Imported account: {}", &file_path);
|
||||
accounts_added += 1;
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
bail!("Failed to import account: {} {}", &file_path, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
if accounts_added > 0 {
|
||||
info!("Imported {} accounts", accounts_added);
|
||||
}
|
||||
|
||||
manager.save()?;
|
||||
Ok(())
|
||||
|
|
2
src/fixtures/maFiles/compat/winauth/exports.txt
Normal file
2
src/fixtures/maFiles/compat/winauth/exports.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
otpauth://totp/Steam:example?secret=ASDF&issuer=Steam&data=%7B%22shared%5Fsecret%22%3A%22zvIayp3JPvtvX%2FQGHqsqKBk%2F44s%3D%22%2C%22serial%5Fnumber%22%3A%22kljasfhds%22%2C%22revocation%5Fcode%22%3A%22R12345%22%2C%22uri%22%3A%22otpauth%3A%2F%2Ftotp%2FSteam%3Aexample%3Fsecret%3DASDF%26issuer%3DSteam%22%2C%22server%5Ftime%22%3A1602522478%2C%22account%5Fname%22%3A%22example%22%2C%22token%5Fgid%22%3A%22jkkjlhkhjgf%22%2C%22identity%5Fsecret%22%3A%22kjsdlwowiqe%3D%22%2C%22secret%5F1%22%3A%22sklduhfgsdlkjhf%3D%22%2C%22status%22%3A1%2C%22device%5Fid%22%3A%22android%3A99d2ad0e%2D4bad%2D4247%2Db111%2D26393aae0be3%22%2C%22fully%5Fenrolled%22%3Atrue%2C%22Session%22%3A%7B%22SessionID%22%3A%22a%3Blskdjf%22%2C%22SteamLogin%22%3A%22983498437543%22%2C%22SteamLoginSecure%22%3A%22dlkjdsl%3Bj%257C%2532984730298%22%2C%22WebCookie%22%3A%22%3Blkjsed%3Bklfjas98093%22%2C%22OAuthToken%22%3A%22asdk%3Blf%3Bdsjlkfd%22%2C%22SteamID%22%3A1234%7D%7D
|
||||
otpauth://totp/Steam:example2?secret=ASDF&issuer=Steam&data=%7B%22shared%5Fsecret%22%3A%22zvIayp3JPvtvX%2FQGHqsqKBk%2F44s%3D%22%2C%22serial%5Fnumber%22%3A%22kljasfhds%22%2C%22revocation%5Fcode%22%3A%22R56789%22%2C%22uri%22%3A%22otpauth%3A%2F%2Ftotp%2FSteam%3Aexample%3Fsecret%3DASDF%26issuer%3DSteam%22%2C%22server%5Ftime%22%3A1602522478%2C%22account%5Fname%22%3A%22example2%22%2C%22token%5Fgid%22%3A%22jkkjlhkhjgf%22%2C%22identity%5Fsecret%22%3A%22kjsdlwowiqe%3D%22%2C%22secret%5F1%22%3A%22sklduhfgsdlkjhf%3D%22%2C%22status%22%3A1%2C%22device%5Fid%22%3A%22android%3A99d2ad0e%2D4bad%2D4247%2Db111%2D26393aae0be3%22%2C%22fully%5Fenrolled%22%3Atrue%2C%22Session%22%3A%7B%22SessionID%22%3A%22a%3Blskdjf%22%2C%22SteamLogin%22%3A%22983498437543%22%2C%22SteamLoginSecure%22%3A%22dlkjdsl%3Bj%257C%2532984730298%22%2C%22WebCookie%22%3A%22%3Blkjsed%3Bklfjas98093%22%2C%22OAuthToken%22%3A%22asdk%3Blf%3Bdsjlkfd%22%2C%22SteamID%22%3A5678%7D%7D
|
Loading…
Reference in a new issue