cli_support/
fxa_creds.rs
1use std::{
7 collections::HashMap,
8 fs,
9 io::{Read, Write},
10};
11
12use anyhow::Result;
13use url::Url;
14
15use fxa_client::{AccessTokenInfo, FirefoxAccount, FxaConfig, FxaError};
18use sync15::client::Sync15StorageClientInit;
19use sync15::KeyBundle;
20
21use crate::prompt::prompt_string;
22
23const CLIENT_ID: &str = "3c49430b43dfba77";
26const REDIRECT_URI: &str = "https://accounts.firefox.com/oauth/success/3c49430b43dfba77";
27pub const SYNC_SCOPE: &str = "https://identity.mozilla.com/apps/oldsync";
28pub const SESSION_SCOPE: &str = "https://identity.mozilla.com/tokens/session";
29
30fn load_fxa_creds(path: &str) -> Result<FirefoxAccount> {
31 let mut file = fs::File::open(path)?;
32 let mut s = String::new();
33 file.read_to_string(&mut s)?;
34 Ok(FirefoxAccount::from_json(&s)?)
35}
36
37fn load_or_create_fxa_creds(path: &str, cfg: FxaConfig, scopes: &[&str]) -> Result<FirefoxAccount> {
38 load_fxa_creds(path).or_else(|e| {
39 log::info!(
40 "Failed to load existing FxA credentials from {:?} (error: {}), launching OAuth flow",
41 path,
42 e
43 );
44 create_fxa_creds(path, cfg, scopes)
45 })
46}
47
48fn create_fxa_creds(path: &str, cfg: FxaConfig, scopes: &[&str]) -> Result<FirefoxAccount> {
49 let acct = FirefoxAccount::new(cfg);
50 handle_oauth_flow(path, &acct, scopes)?;
51 Ok(acct)
52}
53
54fn handle_oauth_flow(path: &str, acct: &FirefoxAccount, scopes: &[&str]) -> Result<()> {
55 let oauth_uri = acct.begin_oauth_flow(scopes, "fxa_creds")?;
56
57 if webbrowser::open(oauth_uri.as_ref()).is_err() {
58 log::warn!("Failed to open a web browser D:");
59 println!("Please visit this URL, sign in, and then copy-paste the final URL below.");
60 println!("\n {}\n", oauth_uri);
61 } else {
62 println!("Please paste the final URL below:\n");
63 }
64
65 let final_url = url::Url::parse(&prompt_string("Final URL").unwrap_or_default())?;
66 let query_params = final_url
67 .query_pairs()
68 .into_owned()
69 .collect::<HashMap<String, String>>();
70
71 acct.complete_oauth_flow(&query_params["code"], &query_params["state"])?;
72 acct.initialize_device("CLI Device", sync15::DeviceType::Desktop, vec![])?;
74 let mut file = fs::File::create(path)?;
75 write!(file, "{}", acct.to_json()?)?;
76 file.flush()?;
77 Ok(())
78}
79
80pub fn get_default_fxa_config() -> FxaConfig {
84 FxaConfig::release(CLIENT_ID, REDIRECT_URI)
85}
86
87pub fn get_account_and_token(
88 config: FxaConfig,
89 cred_file: &str,
90 scopes: &[&str],
91) -> Result<(FirefoxAccount, AccessTokenInfo)> {
92 let acct = load_or_create_fxa_creds(cred_file, config.clone(), scopes)?;
94 match acct.get_access_token(SYNC_SCOPE, None) {
96 Ok(t) => Ok((acct, t)),
97 Err(e) => {
98 match e {
99 FxaError::Authentication => {
101 println!("Saw an auth error using stored credentials - attempting to re-authenticate");
102 println!("If fails, consider deleting {cred_file} to start from scratch");
103 handle_oauth_flow(cred_file, &acct, scopes)?;
104 let token = acct.get_access_token(SYNC_SCOPE, None)?;
105 Ok((acct, token))
106 }
107 _ => Err(e.into()),
108 }
109 }
110 }
111}
112
113pub fn get_cli_fxa(config: FxaConfig, cred_file: &str, scopes: &[&str]) -> Result<CliFxa> {
114 let (account, token_info) = match get_account_and_token(config, cred_file, scopes) {
115 Ok(v) => v,
116 Err(e) => anyhow::bail!("Failed to use saved credentials. {}", e),
117 };
118 let tokenserver_url = Url::parse(&account.get_token_server_endpoint_url()?)?;
119
120 let client_init = Sync15StorageClientInit {
121 key_id: token_info.key.as_ref().unwrap().kid.clone(),
122 access_token: token_info.token.clone(),
123 tokenserver_url: tokenserver_url.clone(),
124 };
125
126 Ok(CliFxa {
127 account,
128 client_init,
129 tokenserver_url,
130 token_info,
131 })
132}
133
134pub struct CliFxa {
135 pub account: FirefoxAccount,
136 pub client_init: Sync15StorageClientInit,
137 pub tokenserver_url: Url,
138 pub token_info: AccessTokenInfo,
139}
140
141impl CliFxa {
142 pub fn as_auth_info(&self) -> sync_manager::SyncAuthInfo {
144 let scoped_key = self.token_info.key.as_ref().unwrap();
145 sync_manager::SyncAuthInfo {
146 kid: scoped_key.kid.clone(),
147 sync_key: scoped_key.k.clone(),
148 fxa_access_token: self.token_info.token.clone(),
149 tokenserver_url: self.tokenserver_url.to_string(),
150 }
151 }
152
153 pub fn as_key_bundle(&self) -> Result<KeyBundle> {
155 let scoped_key = self.token_info.key.as_ref().unwrap();
156 Ok(KeyBundle::from_ksync_bytes(&scoped_key.key_bytes()?)?)
157 }
158}