1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use crate::prompt::prompt_string;
use fxa_client::{error, AccessTokenInfo, Config, FirefoxAccount};
use std::collections::HashMap;
use std::{
fs,
io::{Read, Write},
};
use sync15::{KeyBundle, Sync15StorageClientInit};
use url::Url;
use anyhow::Result;
const CLIENT_ID: &str = "e7ce535d93522896";
const REDIRECT_URI: &str = "https://lockbox.firefox.com/fxa/android-redirect.html";
const SYNC_SCOPE: &str = "https://identity.mozilla.com/apps/oldsync";
fn load_fxa_creds(path: &str) -> Result<FirefoxAccount> {
let mut file = fs::File::open(path)?;
let mut s = String::new();
file.read_to_string(&mut s)?;
Ok(FirefoxAccount::from_json(&s)?)
}
fn load_or_create_fxa_creds(path: &str, cfg: Config) -> Result<FirefoxAccount> {
load_fxa_creds(path).or_else(|e| {
log::info!(
"Failed to load existing FxA credentials from {:?} (error: {}), launching OAuth flow",
path,
e
);
create_fxa_creds(path, cfg)
})
}
fn create_fxa_creds(path: &str, cfg: Config) -> Result<FirefoxAccount> {
let mut acct = FirefoxAccount::with_config(cfg);
let oauth_uri = acct.begin_oauth_flow(&[SYNC_SCOPE], "fxa_creds", None)?;
if webbrowser::open(&oauth_uri.as_ref()).is_err() {
log::warn!("Failed to open a web browser D:");
println!("Please visit this URL, sign in, and then copy-paste the final URL below.");
println!("\n {}\n", oauth_uri);
} else {
println!("Please paste the final URL below:\n");
}
let final_url = url::Url::parse(&prompt_string("Final URL").unwrap_or_default())?;
let query_params = final_url
.query_pairs()
.into_owned()
.collect::<HashMap<String, String>>();
acct.complete_oauth_flow(&query_params["code"], &query_params["state"])?;
acct.initialize_device("CLI Device", fxa_client::device::Type::Desktop, &[])?;
let mut file = fs::File::create(path)?;
write!(file, "{}", acct.to_json()?)?;
file.flush()?;
Ok(acct)
}
pub fn get_default_fxa_config() -> Config {
Config::release(CLIENT_ID, REDIRECT_URI)
}
fn get_account_and_token(
config: Config,
cred_file: &str,
) -> Result<(FirefoxAccount, AccessTokenInfo)> {
let mut acct = load_or_create_fxa_creds(cred_file, config.clone())?;
match acct.get_access_token(SYNC_SCOPE, None) {
Ok(t) => Ok((acct, t)),
Err(e) => {
match e.kind() {
error::ErrorKind::RemoteError { code: 401, .. } => {
println!("Saw an auth error using stored credentials - recreating them...");
acct = create_fxa_creds(cred_file, config)?;
let token = acct.get_access_token(SYNC_SCOPE, None)?;
Ok((acct, token))
}
_ => Err(e.into()),
}
}
}
}
pub fn get_cli_fxa(config: Config, cred_file: &str) -> Result<CliFxa> {
let tokenserver_url = config.token_server_endpoint_url()?;
let (acct, token_info) = match get_account_and_token(config, cred_file) {
Ok(v) => v,
Err(e) => anyhow::bail!("Failed to use saved credentials. {}", e),
};
let key = token_info.key.unwrap();
let client_init = Sync15StorageClientInit {
key_id: key.kid.clone(),
access_token: token_info.token,
tokenserver_url: tokenserver_url.clone(),
};
let root_sync_key = KeyBundle::from_ksync_bytes(&key.key_bytes()?)?;
Ok(CliFxa {
account: acct,
client_init,
tokenserver_url,
root_sync_key,
})
}
pub struct CliFxa {
pub account: FirefoxAccount,
pub client_init: Sync15StorageClientInit,
pub tokenserver_url: Url,
pub root_sync_key: KeyBundle,
}