chiark / gitweb /
Support saving login details. We can now log in!
authorSimon Tatham <anakin@pobox.com>
Wed, 3 Jan 2024 10:55:39 +0000 (10:55 +0000)
committerSimon Tatham <anakin@pobox.com>
Wed, 3 Jan 2024 11:06:02 +0000 (11:06 +0000)
src/config.rs
src/login.rs
src/main.rs

index 9d047aa94f004c915a65f7a2d01f0192b9d2432b..bd26ad6bdc8de3d420d7369ea8c36ee4033b700b 100644 (file)
@@ -1,4 +1,10 @@
 use std::path::PathBuf;
+use std::io::Write;
+
+use std::fs::OpenOptions;
+
+#[cfg(unix)]
+use std::os::unix::fs::OpenOptionsExt;
 
 #[cfg(windows)]
 use std::str::FromStr;
@@ -86,4 +92,26 @@ impl ConfigLocation {
     pub fn get_path(&self, leaf: &str) -> PathBuf {
         self.dir.join(leaf)
     }
+
+    pub fn create_file(&self, leaf: &str, contents: &str) ->
+        Result<(), std::io::Error>
+    {
+        std::fs::create_dir_all(&self.dir)?;
+
+        // FIXME: we could do better here, by creating a file under a
+        // separate name and then atomically renaming it
+
+        let mut opts = OpenOptions::new();
+        opts.write(true);
+        opts.create_new(true);
+
+        // On Windows files are not-world-readable by default, but on
+        // Unix we must be careful about this
+        #[cfg(unix)]
+        opts.mode(0o600);
+
+        let path = self.get_path(leaf);
+        opts.open(path)?
+            .write_all(contents.as_bytes())
+    }
 }
index 66c730eb0feca34d26305bd851e0053d4f04b583..67a00bf897e974f4d90c07766ca0adae74ef20d4 100644 (file)
@@ -1,7 +1,9 @@
 use reqwest::Url;
 use std::io::Write;
 
-use super::auth::AuthConfig;
+use super::TopLevelError;
+use super::auth::{AuthConfig, AuthError};
+use super::config::ConfigLocation;
 use super::client::{reqwest_client, Req, ClientError};
 use super::types::{Account, Application, Instance, Token};
 
@@ -121,7 +123,19 @@ impl Login {
     }
 }
 
-pub fn login(instance_url: &str) -> Result<(), ClientError> {
+pub fn login(cfgloc: &ConfigLocation, instance_url: &str) ->
+    Result<(), TopLevelError>
+{
+    // First, check we aren't logged in already, and give some
+    // marginally useful advice on what to do if we are.
+    match AuthConfig::load(cfgloc) {
+        Err(AuthError::Nonexistent(..)) => Ok(()),
+        Ok(auth) => Err(TopLevelError::new("", &format!(
+            "you are already logged in as {0}@{1}! Use --config to specify a separate configuration directory for another login, or delete {2} to remove the existing login details",
+            auth.username, auth.instance_domain, cfgloc.get_path("auth").display()))),
+        Err(e) => Err(TopLevelError::from(e)),
+    }?;
+
     // Ergonomics: parse the URL, adding a default https:// scheme if
     // it's just a bare hostname
     let urlstr = match instance_url.find('/') {
@@ -172,7 +186,8 @@ pub fn login(instance_url: &str) -> Result<(), ClientError> {
                                        &user_token.access_token)?;
 
     println!("");
-    println!("Successfully logged in as {}@{}", account.id, instance.domain);
+    println!("Successfully logged in as {}@{}", account.username, instance.domain);
+    println!("Now run 'mastodonochrome' without arguments to read and post!");
 
     // Save everything!
     let auth = AuthConfig {
@@ -184,7 +199,8 @@ pub fn login(instance_url: &str) -> Result<(), ClientError> {
         client_secret: app.client_secret.unwrap(),
         user_token: user_token.access_token,
     };
-    println!("JSON: {}", serde_json::to_string_pretty(&auth).unwrap());
+
+    cfgloc.create_file("auth", &serde_json::to_string_pretty(&auth).unwrap())?;
 
     Ok(())
 }
index b07d56c2b905b4c292571615f6dd32c9a8f52734..2545bb7fd95f22fdb54e0a8d5f391514e28835b8 100644 (file)
@@ -30,7 +30,7 @@ fn main_inner() -> Result<(), TopLevelError> {
     };
     match cli.login {
         None => Tui::run(&cfgloc, cli.readonly)?,
-        Some(ref server) => login(server)?,
+        Some(ref server) => login(&cfgloc, server)?,
     }
     Ok(())
 }